[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 3294 - (download) (as text) (annotate)
Wed Jun 15 23:55:17 2005 UTC (14 years, 7 months ago) by dpvc
File size: 8747 byte(s)
Implements a Parser context in which only polynomials (of a single
variable) can be entered.  Only sums of multiples of powers of the
variable are allowed to be entered (though the coefficients can
contain mathematical operations).  An optional flag lets you specify
that only one term of each degree is allowed, so the student would
have to combine 1+x+x+x^2 to get 1+2x+x^2 in that case.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9