loadMacros("Parser.pl"); sub _contextLimitedPolynomial_init {}; # don't load it again =head3 Context("LimitedPolynomial") ########################################################## # # Implements a context in which students can only # enter (expanded) polynomials (i.e., sums of multiples # of powers of x). # # Select the context using: # # Context("LimitedPolynomial"); # # If you set the "singlePowers" flag, then only one monomial of # each degree can be included in the polynomial: # # Context("LimitedPolynomial")->flags->set(singlePowers=>1); # =cut # # Handle common checking for BOPs # package LimitedPolynomial::BOP; # # Do original check and then if the operands are numbers, its OK. # Otherwise, do an operator-specific check for if the polynomial is OK. # Otherwise report an error. # sub _check { my \$self = shift; my \$super = ref(\$self); \$super =~ s/LimitedPolynomial/Parser/; &{\$super."::_check"}(\$self); return if LimitedPolynomial::isConstant(\$self->{lop}) && LimitedPolynomial::isConstant(\$self->{rop}); return if \$self->checkPolynomial; \$self->Error("Your answer doesn't look like a polynomial"); } # # filled in by subclasses # sub checkPolynomial {return 0} # # Check that the exponents of a monomial are OK # and record the new exponent array # sub checkExponents { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); LimitedPolynomial::markPowers(\$l); LimitedPolynomial::markPowers(\$r); my \$exponents = \$self->{exponents} = \$r->{exponents}; delete \$r->{exponents}; delete \$r->{powers}; if (\$l->{exponents}) { my \$single = \$self->context->flag('singlePowers'); foreach my \$i (0..scalar(@{\$exponents})-1) { \$self->Error("A variable can appear only once in each term of a polynomial") if \$exponents->[\$i] && \$l->{exponents}[\$i] && \$single; \$exponents->[\$i] += \$l->{exponents}[\$i]; } } delete \$l->{exponents}; delete \$l->{powers}; \$self->{isPower} = 1; \$self->{isPoly} = \$l->{isPoly}; return 1; } # # Check that the powers of combined monomials are OK # and record the new power list # sub checkPowers { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); my \$single = \$self->context->flag('singlePowers'); LimitedPolynomial::markPowers(\$l); LimitedPolynomial::markPowers(\$r); \$self->{isPoly} = 1; \$self->{powers} = \$l->{powers} || {}; delete \$l->{powers}; return 1 unless \$r->{powers}; foreach my \$n (keys(%{\$r->{powers}})) { \$self->Error("Polynomials can have at most one term of each degree") if \$self->{powers}{\$n} && \$single; \$self->{powers}{\$n} = 1; } delete \$r->{powers}; return 1; } package LimitedPolynomial; # # Mark a variable as having power 1 # Mark a monomial as having its given powers # sub markPowers { my \$self = shift; if (\$self->class eq 'Variable') { my \$vIndex = LimitedPolynomial::getVarIndex(\$self); \$self->{index} = \$vIndex->{\$self->{name}}; \$self->{exponents} = [(0) x scalar(keys %{\$vIndex})]; \$self->{exponents}[\$self->{index}] = 1; } if (\$self->{exponents}) { my \$power = join(',',@{\$self->{exponents}}); \$self->{powers}{\$power} = 1; } } # # Get a hash of variable names that point to indices # within the array of powers for a monomial # sub getVarIndex { my \$self = shift; my \$equation = \$self->{equation}; if (!\$equation->{varIndex}) { \$equation->{varIndex} = {}; my \$i = 0; foreach my \$v (\$equation->{context}->variables->names) {\$equation->{varIndex}{\$v} = \$i++} } return \$equation->{varIndex}; } # # Check for a constant expression # sub isConstant { my \$self = shift; return 1 if \$self->{isConstant} || \$self->class eq 'Constant'; return scalar(keys(%{\$self->getVariables})) == 0; } ############################################## # # Now we get the individual replacements for the operators # that we don't want to allow. We inherit everything from # the original Parser::BOP class, and just add the # polynomial checks here. Note that checkpolynomial # only gets called if at least one of the terms is not # a number. # package LimitedPolynomial::BOP::add; our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::add); sub checkPolynomial { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); \$self->Error("Addition is allowed only between monomials") if \$r->{isPoly}; \$self->checkPowers; } ############################################## package LimitedPolynomial::BOP::subtract; our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::subtract); sub checkPolynomial { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); \$self->Error("Subtraction is only allowed between monomials") if \$r->{isPoly}; \$self->checkPowers; } ############################################## package LimitedPolynomial::BOP::multiply; our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::multiply); sub checkPolynomial { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); my \$lOK = (LimitedPolynomial::isConstant(\$l) || \$l->{isPower} || \$l->class eq 'Variable' || (\$l->{isPoly} && \$l->{isPoly} == 2)); my \$rOK = (\$r->{isPower} || \$r->class eq 'Variable'); return \$self->checkExponents if \$lOK and \$rOK; \$self->Error("Coefficients must come before variables in a polynomial") if LimitedPolynomial::isConstant(\$r) && (\$l->{isPower} || \$l->class eq 'Variable'); \$self->Error("Multiplication can only be used between coefficients and variables"); } ############################################## package LimitedPolynomial::BOP::divide; our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::divide); sub checkPolynomial { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); \$self->Error("In a polynomial, you can only divide by numbers") unless LimitedPolynomial::isConstant(\$r); \$self->Error("You can only divide a single term by a number") if \$l->{isPoly} && \$l->{isPoly} == 1; \$self->{isPoly} = \$l->{isPoly}; \$self->{powers} = \$l->{powers}; delete \$l->{powers}; \$self->{exponents} = \$l->{exponents}; delete \$l->{exponents}; return 1; } ############################################## package LimitedPolynomial::BOP::power; our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::power); sub checkPolynomial { my \$self = shift; my (\$l,\$r) = (\$self->{lop},\$self->{rop}); \$self->Error("You can only raise a variable to a power in a polynomial") unless \$l->class eq 'Variable'; \$self->Error("Exponents must be constant in a polynomial") unless LimitedPolynomial::isConstant(\$r); my \$n = Parser::Evaluate(\$r); \$r->Error(\$\$Value::context->{error}{message}) if \$\$Value::context->{error}{flag}; \$n = \$n->value; \$self->Error("Exponents must be positive integers in a polynomial") unless \$n > 0 && \$n == int(\$n); LimitedPolynomial::markPowers(\$l); \$self->{exponents} = \$l->{exponents}; delete \$l->{exponents}; foreach my \$i (@{\$self->{exponents}}) {\$i = \$n if \$i} \$self->{isPower} = 1; return 1; } ############################################## ############################################## # # Now we do the same for the unary operators # package LimitedPolynomial::UOP; sub _check { my \$self = shift; my \$super = ref(\$self); \$super =~ s/LimitedPolynomial/Parser/; &{\$super."::_check"}(\$self); my \$op = \$self->{op}; return if LimitedPolynomial::isConstant(\$op); \$self->Error("You can only use '%s' with monomials",\$self->{def}{string}) if \$op->{isPoly}; \$self->{isPoly} = 2; \$self->{powers} = \$op->{powers}; delete \$op->{powers}; \$self->{exponents} = \$op->{exponents}; delete \$op->{exponents}; } sub checkPolynomial {return 0} ############################################## package LimitedPolynomial::UOP::plus; our @ISA = qw(LimitedPolynomial::UOP Parser::UOP::plus); ############################################## package LimitedPolynomial::UOP::minus; our @ISA = qw(LimitedPolynomial::UOP Parser::UOP::minus); ############################################## ############################################## # # Don't allow absolute values # package LimitedPolynomial::List::AbsoluteValue; our @ISA = qw(Parser::List::AbsoluteValue); sub _check { my \$self = shift; \$self->SUPER::_check; return if LimitedPolynomial::isConstant(\$self->{coords}[0]); \$self->Error("Can't use absolute values in polynomials"); } ############################################## ############################################## # # Only allow numeric function calls # package LimitedPolynomial::Function; sub _check { my \$self = shift; my \$super = ref(\$self); \$super =~ s/LimitedPolynomial/Parser/; &{\$super."::_check"}(\$self); my \$arg = \$self->{params}->[0]; return if LimitedPolynomial::isConstant(\$arg); \$self->Error("Function '%s' can only be used with numbers",\$self->{name}); } package LimitedPolynomial::Function::numeric; our @ISA = qw(LimitedPolynomial::Function Parser::Function::numeric); package LimitedPolynomial::Function::trig; our @ISA = qw(LimitedPolynomial::Function Parser::Function::trig); package LimitedPolynomial::Function::hyperbolic; our @ISA = qw(LimitedPolynomial::Function Parser::Function::hyperbolic); ############################################## ############################################## package main; # # Now build the new context that calls the # above classes rather than the usual ones # \$context{LimitedPolynomial} = Parser::Context->getCopy(undef,"Numeric"); \$context{LimitedPolynomial}->operators->set( '+' => {class => 'LimitedPolynomial::BOP::add'}, '-' => {class => 'LimitedPolynomial::BOP::subtract'}, '*' => {class => 'LimitedPolynomial::BOP::multiply'}, '* ' => {class => 'LimitedPolynomial::BOP::multiply'}, ' *' => {class => 'LimitedPolynomial::BOP::multiply'}, ' ' => {class => 'LimitedPolynomial::BOP::multiply'}, '/' => {class => 'LimitedPolynomial::BOP::divide'}, ' /' => {class => 'LimitedPolynomial::BOP::divide'}, '/ ' => {class => 'LimitedPolynomial::BOP::divide'}, '^' => {class => 'LimitedPolynomial::BOP::power'}, '**' => {class => 'LimitedPolynomial::BOP::power'}, 'u+' => {class => 'LimitedPolynomial::UOP::plus'}, 'u-' => {class => 'LimitedPolynomial::UOP::minus'}, ); # # Remove these operators and functions # \$context{LimitedPolynomial}->lists->set( AbsoluteValue => {class => 'LimitedPolynomial::List::AbsoluteValue'}, ); \$context{LimitedPolynomial}->operators->undefine('_','!','U'); \$context{LimitedPolynomial}->functions->disable("atan2"); # # Hook into the numeric, trig, and hyperbolic functions # foreach ('ln','log','log10','exp','sqrt','abs','int','sgn') { \$context{LimitedPolynomial}->functions->set( "\$_"=>{class => 'LimitedPolynomial::Function::numeric'} ); } foreach ('sin','cos','tan','sec','csc','cot', 'asin','acos','atan','asec','acsc','acot') { \$context{LimitedPolynomial}->functions->set( "\$_"=>{class => 'LimitedPolynomial::Function::trig'}, "\${_}h"=>{class => 'LimitedPolynomial::Function::hyperbolic'} ); } Context("LimitedPolynomial");