[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 5441 - (download) (as text) (annotate)
Tue Aug 28 22:40:15 2007 UTC (12 years, 5 months ago) by dpvc
File size: 11324 byte(s)
Add context names for the context(s) created here.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9