[system] / trunk / pg / macros / contextPolynomialFactors.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/contextPolynomialFactors.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6218 - (download) (as text) (annotate)
Thu Apr 1 00:21:01 2010 UTC (9 years, 10 months ago) by dpvc
File size: 10299 byte(s)
Make it easier to subclass this class for RationalFunction context, and fix a bug with the stict division check

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright  2000-2010 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: pg/macros/contextPolynomialFactors.pl,v 1.2 2010/03/31 21:45:42 dpvc Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 =head1 NAME
   18 
   19 contextPolynomialFactors.pl - Allow only entry of polynomials, and
   20                               their products and powers
   21 
   22 =head1 DESCRIPTION
   23 
   24 Implements a context in which students can only enter products and
   25 powers of polynomials
   26 
   27 Select the context using:
   28 
   29   Context("PolynomialFactors");
   30 
   31 If you set the "singlePowers" flag, then only one monomial of each
   32 degree can be included in each factor polynomial:
   33 
   34   Context("PolynomialFactors")->flags->set(singlePowers=>1);
   35 
   36 If you set the "singleFactors" flag, then factors can not be repeated.
   37 For example,
   38 
   39   Context("PolynomialFactors")->flags->set(singleFactors=>1);
   40   Formula("(x+1)^2*(x+1)");
   41 
   42 will generate an error indicating that factors can appear only once.
   43 Note, however, that this only catches factors that appear exactly the
   44 same in both cases, so (x+1)*(1+x) would be allowed, as would
   45 -(x-1)*(1-x).  Note also that there is no check for whether the
   46 factors are irreducible, so (x^2-1)*(x+1)*(x-1) would be allowed.
   47 Also, there is no check for whether constants have been factored out,
   48 so 3*(x+1)*(3x+3) would be allowed.  This still needs more work to
   49 make it very useful.
   50 
   51 There are two additional flags that control whether division by a
   52 constant or raising to a power are allowed to be performed on a
   53 product or factors or only on a single factor at at time.  These are
   54 strictDivision and strictPowers.  By default, strictDivisions is 0, so
   55 (x*(x+1))/3 is allowed, while strictPowers is 1, so (x*(x+1))^3 is not
   56 (it must be written x^3*(x+1)^3).
   57 
   58 Finally, there is also a strict context that does not allow
   59 operations even within the coefficients.  Select it using:
   60 
   61   Context("PolynomialFactors-Strict");
   62 
   63 In addition to disallowing operations within the coefficients, this
   64 context does not reduce constant operations (since they are not
   65 allowed), and sets the singlePowers, singleFactors, strictDivision,
   66 and stricPowers flags automatically.  In addition, it disables all the
   67 functions, though they can be re-enabled, if needed.
   68 
   69 =cut
   70 
   71 ##################################################
   72 
   73 loadMacros(
   74   "MathObjects.pl",
   75   "contextLimitedPolynomial.pl"
   76 );
   77 
   78 sub _contextPolynomialFactors_init {PolynomialFactors::Init()}
   79 
   80 ##############################################
   81 
   82 package PolynomialFactors::BOP::add;
   83 our @ISA = qw(LimitedPolynomial::BOP::add);
   84 
   85 sub checkPolynomial {
   86   my $self = shift;
   87   my ($l,$r) = ($self->{lop},$self->{rop});
   88   $self->Error("Addition is allowed only between monomials")
   89     if $r->{isPoly} || ($l->{isPoly} && $l->{isPoly} > 2);
   90   $self->checkPowers;
   91 }
   92 
   93 ##############################################
   94 
   95 package PolynomialFactors::BOP::subtract;
   96 our @ISA = qw(LimitedPolynomial::BOP::subtract);
   97 
   98 sub checkPolynomial {
   99   my $self = shift;
  100   my ($l,$r) = ($self->{lop},$self->{rop});
  101   $self->Error("Subtraction is allowed only between monomials")
  102     if $r->{isPoly} || ($l->{isPoly} && $l->{isPoly} > 2);
  103   $self->checkPowers;
  104 }
  105 
  106 ##############################################
  107 
  108 package PolynomialFactors::BOP::multiply;
  109 our @ISA = qw(LimitedPolynomial::BOP::multiply);
  110 
  111 sub checkPolynomial {
  112   my $self = shift; my ($l,$r) = ($self->{lop},$self->{rop});
  113   my $lOK = (LimitedPolynomial::isConstant($l) || $l->{isPower} ||
  114        $l->class eq 'Variable' || ($l->{isPoly} && $l->{isPoly} == 2));
  115   my $rOK = ($r->{isPower} || $r->class eq 'Variable');
  116   return $self->checkExponents if $lOK and $rOK;
  117   $self->Error("Coefficients must come before variables or factors")
  118     if LimitedPolynomial::isConstant($r) && ($l->{isPower} || $l->class eq 'Variable');
  119   if ($l->{isPoly} || $r->{isPoly}) {
  120     PolynomialFactors::markFactor($l);
  121     PolynomialFactors::markFactor($r);
  122     return $self->checkFactors($l,$r);
  123   }
  124   return 1;
  125 }
  126 
  127 sub checkFactors {
  128   my $self = shift; my ($l,$r) = @_;
  129   my $single = $self->context->flag("singleFactors");
  130   $self->{factors} = $l->{factors}; delete $l->{factors};
  131   foreach my $factor (keys %{$r->{factors}}) {
  132     if ($single && $self->{factors}{$factor}) {
  133       $self->Error("Each factor can appear only once (combine like factors)") unless $factor eq "0";
  134       $self->Error("Only one constant coefficient or negation is allowed (combine them)");
  135     }
  136     $self->{factors}{$factor} = 1;
  137   }
  138   delete $r->{factors};
  139   $self->{isPoly} = 4; # product of factors
  140   return 1;
  141 }
  142 
  143 sub checkStrict {
  144   my $self = shift;
  145   $self->Error("You can only use '%s' between coefficents and variables or between factors",$self->{bop});
  146 }
  147 
  148 ##############################################
  149 
  150 package PolynomialFactors::BOP::divide;
  151 our @ISA = qw(LimitedPolynomial::BOP::divide);
  152 
  153 sub checkPolynomial {
  154   my $self = shift; my ($l,$r) = ($self->{lop},$self->{rop});
  155   $self->Error("In a polynomial, you can only divide by numbers")
  156     unless LimitedPolynomial::isConstant($r);
  157   if ($l->{isPoly} && $l->{isPoly} != 2) {
  158     $self->Error("You can only divide a single term or factor by a number")
  159       if $l->{isPoly} == 3 || ($self->context->flag("strictDivision") && $l->{isPoly} != 1);
  160     PolynomialFactors::markOpFactor($self,$l);
  161     $self->Error("Only one constant multiple or fraction is allowed (combine them)")
  162       if $self->{factors}{0} && $self->context->flag("singleFactors");
  163     $self->{factors}{0} = 1; # mark as constant multiple;
  164     $self->{isPoly} = 3;  # factor over a number
  165   } else {
  166     $self->{isPoly} = $l->{isPoly};
  167     $self->{powers} = $l->{powers}; delete $l->{powers};
  168     $self->{exponents} = $l->{exponents}; delete $l->{exponents};
  169   }
  170   return 1;
  171 }
  172 
  173 ##############################################
  174 
  175 package PolynomialFactors::BOP::power;
  176 our @ISA = qw(LimitedPolynomial::BOP::power);
  177 
  178 sub checkPolynomial {
  179   my $self = shift; my ($l,$r) = ($self->{lop},$self->{rop});
  180   $self->Error("Exponents must be constant in a polynomial")
  181     unless LimitedPolynomial::isConstant($r);
  182   my $n = Parser::Evaluate($r);
  183   $r->Error($$Value::context->{error}{message}) if $$Value::context->{error}{flag};
  184   $n = $n->value;
  185   $self->Error("Exponents must be positive integers in a polynomial")
  186     unless $n > 0 && $n == int($n);
  187   if ($l->{isPoly}) {
  188     $self->Error("You can only raise a single term or factor to a power")
  189       if $l->{isPoly} > 2 && $self->context->flag("strictPowers");
  190     PolynomialFactors::markOpFactor($self,$l);
  191     $self->{isPoly} = 5; # factor to a power
  192   } else {
  193     LimitedPolynomial::markPowers($l);
  194     $self->{exponents} = $l->{exponents}; delete $l->{exponents};
  195     foreach my $i (@{$self->{exponents}}) {$i = $n if $i}
  196     $self->{isPower} = 1;
  197   }
  198   return 1;
  199 }
  200 
  201 sub checkStrict {
  202   my $self = shift;
  203   $self->Error("You can only use powers of a variable or factor");
  204 }
  205 
  206 ##############################################
  207 
  208 package PolynomialFactors::UOP::minus;
  209 our @ISA = qw(LimitedPolynomial::UOP::minus);
  210 
  211 sub checkPolynomial {
  212   my $self = shift; my $op = $self->{op};
  213   if ($op->{isPoly} && $self->context->flag("singleFactors")) {
  214     $self->Error("Double negatives are not allowed") if $op->{isPoly} == 2;
  215     $self->Error("Only one factor or constant can be negated") if $op->{isPoly} == 4;
  216   }
  217   PolynomialFactors::markOpFactor($self,$op);
  218   $self->{factors}{0} = 1; # mark as constant multiple
  219   return 1;
  220 }
  221 
  222 ##############################################
  223 
  224 package PolynomialFactors;
  225 our @ISA = ('LimitedPolynomal');
  226 
  227 sub markFactor {
  228   my $self = shift;
  229   return if $self->{factors};
  230   $self->{factors} = {};
  231   if ($self->class eq 'Variable') {
  232     $self->{factors}{$self->{name}} = 1;
  233   } elsif ($self->class eq 'Number') {
  234     $self->{factors}{0} = 1;
  235   } elsif ($self->{isPoly} && $self->{isPoly} == 1) {
  236     $self->{factors}{$self->string} = 1;
  237   } elsif ($self->{isPower}) {
  238     $self->{factors}{$self->{lop}->string} = 1;
  239   }
  240 }
  241 
  242 sub markOpFactor {
  243   my $self = shift; my $op = shift;
  244   markFactor($op);
  245   $self->{factors} = $op->{factors};
  246   delete $op->{factors};
  247 }
  248 
  249 sub Init {
  250   #
  251   #  Build the new context that calls the
  252   #  above classes rather than the usual ones
  253   #
  254 
  255   my $context = $main::context{PolynomialFactors} = Parser::Context->getCopy("LimitedPolynomial");
  256   $context->{name} = "PolynomialFactors";
  257   $context->operators->set(
  258      '+' => {class => 'PolynomialFactors::BOP::add'},
  259      '-' => {class => 'PolynomialFactors::BOP::subtract'},
  260      '*' => {class => 'PolynomialFactors::BOP::multiply'},
  261     '* ' => {class => 'PolynomialFactors::BOP::multiply'},
  262     ' *' => {class => 'PolynomialFactors::BOP::multiply'},
  263      ' ' => {class => 'PolynomialFactors::BOP::multiply'},
  264      '/' => {class => 'PolynomialFactors::BOP::divide'},
  265     ' /' => {class => 'PolynomialFactors::BOP::divide'},
  266     '/ ' => {class => 'PolynomialFactors::BOP::divide'},
  267      '^' => {class => 'PolynomialFactors::BOP::power'},
  268     '**' => {class => 'PolynomialFactors::BOP::power'},
  269     'u-' => {class => 'PolynomialFactors::UOP::minus'},
  270   );
  271   $context->flags->set(strictPowers => 1);
  272 
  273   #
  274   #  A context where coefficients can't include operations
  275   #
  276   $context = $main::context{"PolynomialFactors-Strict"} = $context->copy;
  277   $context->flags->set(
  278     strictCoefficients => 1, strictDivision => 1,
  279     singlePowers => 1, singleFactors => 1,
  280     reduceConstants => 0,
  281   );
  282   $context->functions->disable("All");  # can be re-enabled if needed
  283 
  284   main::Context("PolynomialFactors");  ### FIXME:  probably should require author to set this explicitly
  285 }
  286 
  287 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9