Parent Directory
|
Revision Log
Revision 3383 - (view) (download) (as text)
| 1 : | dpvc | 3294 | 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 : | jj | 3383 | return if LimitedPolynomial::isConstant($op); |
| 181 : | dpvc | 3371 | $self->Error("You can only use '%s' with monomials",$self->{def}{string}) |
| 182 : | dpvc | 3294 | 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 : | dpvc | 3371 | $self->Error("Function '%s' can only be used with numbers",$self->{name}); |
| 230 : | dpvc | 3294 | } |
| 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 |