[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 4121 - (download) (as text) (annotate)
Mon Jun 5 20:43:22 2006 UTC (13 years, 7 months ago) by dpvc
File size: 8803 byte(s)
Fixed a problem where things like x+4x would be accepted even in
single-power mode (the initial x was not being identified as a single
power of x correctly).

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9