[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 4997 - (download) (as text) (annotate)
Mon Jun 11 18:16:40 2007 UTC (12 years, 7 months ago) by gage
File size: 11038 byte(s)
Fixing docementation so that it can be read from the web.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9