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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9