[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 5373 - (download) (as text) (annotate)
Sun Aug 19 02:01:57 2007 UTC (12 years, 3 months ago) by dpvc
File size: 11054 byte(s)
Normalized comments and headers to that they will format their POD
documentation properly.  (I know that the POD processing was supposed
to strip off the initial #, but that doesn't seem to happen, so I've
added a space throughout.)

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9