[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 5556 - (download) (as text) (annotate)
Thu Oct 4 16:40:49 2007 UTC (12 years, 4 months ago) by sh002i
File size: 13973 byte(s)
added standard copyright/license header

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright  2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK.pm,v 1.100 2007/08/13 22:59:53 sh002i 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 monomial as having its given powers
  143 #
  144 sub markPowers {
  145   my $self = shift;
  146   if ($self->class eq 'Variable') {
  147     my $vIndex = LimitedPolynomial::getVarIndex($self);
  148     $self->{index} = $vIndex->{$self->{name}};
  149     $self->{exponents} = [(0) x scalar(keys %{$vIndex})];
  150     $self->{exponents}[$self->{index}] = 1;
  151   }
  152   if ($self->{exponents}) {
  153     my $power = join(',',@{$self->{exponents}});
  154     $self->{powers}{$power} = 1;
  155   }
  156 }
  157 
  158 #
  159 #  Get a hash of variable names that point to indices
  160 #  within the array of powers for a monomial
  161 #
  162 sub getVarIndex {
  163   my $self = shift;
  164   my $equation = $self->{equation};
  165   if (!$equation->{varIndex}) {
  166     $equation->{varIndex} = {}; my $i = 0;
  167     foreach my $v ($equation->{context}->variables->names)
  168       {$equation->{varIndex}{$v} = $i++}
  169   }
  170   return $equation->{varIndex};
  171 }
  172 
  173 #
  174 #  Check for a constant expression
  175 #
  176 sub isConstant {
  177   my $self = shift;
  178   return 1 if $self->{isConstant} || $self->class eq 'Constant';
  179   return scalar(keys(%{$self->getVariables})) == 0;
  180 }
  181 
  182 ##############################################
  183 #
  184 #  Now we get the individual replacements for the operators
  185 #  that we don't want to allow.  We inherit everything from
  186 #  the original Parser::BOP class, and just add the
  187 #  polynomial checks here.  Note that checkpolynomial
  188 #  only gets called if at least one of the terms is not
  189 #  a number.
  190 #
  191 
  192 package LimitedPolynomial::BOP::add;
  193 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::add);
  194 
  195 sub checkPolynomial {
  196   my $self = shift;
  197   my ($l,$r) = ($self->{lop},$self->{rop});
  198   $self->Error("Addition is allowed only between monomials") if $r->{isPoly};
  199   $self->checkPowers;
  200 }
  201 
  202 sub checkStrict {
  203   my $self = shift;
  204   $self->Error("You can only use addition for the terms of a polynomial",$self->{bop});
  205 }
  206 
  207 ##############################################
  208 
  209 package LimitedPolynomial::BOP::subtract;
  210 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::subtract);
  211 
  212 sub checkPolynomial {
  213   my $self = shift;
  214   my ($l,$r) = ($self->{lop},$self->{rop});
  215   $self->Error("Subtraction is allowed only between monomials") if $r->{isPoly};
  216   $self->checkPowers;
  217 }
  218 
  219 sub checkStrict {
  220   my $self = shift;
  221   $self->Error("You can only use subtraction between the terms of a polynomial",$self->{bop});
  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 a coefficent and a variable 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} == 1;
  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 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/LimitedPolynomial/Parser/;
  309   &{$super."::_check"}($self);
  310   my $op = $self->{op};
  311   return if LimitedPolynomial::isConstant($op);
  312   $self->Error("You can only use '%s' with monomials",$self->{def}{string})
  313     if $op->{isPoly};
  314   $self->{isPoly} = 2;
  315   $self->{powers} = $op->{powers}; delete $op->{powers};
  316   $self->{exponents} = $op->{exponents}; delete $op->{exponents};
  317 }
  318 
  319 sub checkPolynomial {return 0}
  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), etc.
  427   #
  428   $context->reduction->set("(-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, singelPowers=>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