Parent Directory
|
Revision Log
Revision 5518 - (view) (download) (as text)
| 1 : | gage | 4997 | |
| 2 : | dpvc | 5371 | loadMacros("MathObjects.pl"); |
| 3 : | dpvc | 3294 | |
| 4 : | dpvc | 5392 | sub _contextLimitedPolynomial_init {LimitedPolynomial::Init()}; # don't load it again |
| 5 : | dpvc | 3294 | |
| 6 : | gage | 4997 | =head3 Context("LimitedPolynomial") |
| 7 : | |||
| 8 : | dpvc | 5373 | ########################################################## |
| 9 : | # | ||
| 10 : | dpvc | 5518 | # Implements a context in which students can only enter (expanded) |
| 11 : | # polynomials (i.e., sums of multiples of powers of x). | ||
| 12 : | dpvc | 5373 | # |
| 13 : | # Select the context using: | ||
| 14 : | # | ||
| 15 : | # Context("LimitedPolynomial"); | ||
| 16 : | # | ||
| 17 : | dpvc | 5518 | # If you set the "singlePowers" flag, then only one monomial of each |
| 18 : | # degree can be included in the polynomial: | ||
| 19 : | dpvc | 5373 | # |
| 20 : | # Context("LimitedPolynomial")->flags->set(singlePowers=>1); | ||
| 21 : | # | ||
| 22 : | dpvc | 5518 | # There is also a strict limited context that does not allow |
| 23 : | # operations even within the coefficients. Select it using: | ||
| 24 : | # | ||
| 25 : | # Context("LimitedPolynomial-Strict"); | ||
| 26 : | # | ||
| 27 : | # In addition to disallowing operations within the coefficients, | ||
| 28 : | # this context does not reduce constant operations (since they are | ||
| 29 : | # not allowed), and sets the singlePowers flag automatically. In | ||
| 30 : | # addition, it disables all the functions, though they can be | ||
| 31 : | # re-enabled, if needed. | ||
| 32 : | # | ||
| 33 : | dpvc | 3294 | |
| 34 : | dpvc | 5051 | =cut |
| 35 : | |||
| 36 : | dpvc | 5392 | ################################################## |
| 37 : | dpvc | 3294 | # |
| 38 : | # Handle common checking for BOPs | ||
| 39 : | # | ||
| 40 : | package LimitedPolynomial::BOP; | ||
| 41 : | |||
| 42 : | # | ||
| 43 : | # Do original check and then if the operands are numbers, its OK. | ||
| 44 : | # Otherwise, do an operator-specific check for if the polynomial is OK. | ||
| 45 : | # Otherwise report an error. | ||
| 46 : | # | ||
| 47 : | sub _check { | ||
| 48 : | my $self = shift; | ||
| 49 : | my $super = ref($self); $super =~ s/LimitedPolynomial/Parser/; | ||
| 50 : | &{$super."::_check"}($self); | ||
| 51 : | dpvc | 5518 | if (LimitedPolynomial::isConstant($self->{lop}) && |
| 52 : | LimitedPolynomial::isConstant($self->{rop})) { | ||
| 53 : | $self->checkStrict if $self->context->flag("strictCoefficients"); | ||
| 54 : | return; | ||
| 55 : | } | ||
| 56 : | dpvc | 3294 | return if $self->checkPolynomial; |
| 57 : | $self->Error("Your answer doesn't look like a polynomial"); | ||
| 58 : | } | ||
| 59 : | |||
| 60 : | # | ||
| 61 : | # filled in by subclasses | ||
| 62 : | # | ||
| 63 : | sub checkPolynomial {return 0} | ||
| 64 : | |||
| 65 : | # | ||
| 66 : | dpvc | 4298 | # Check that the exponents of a monomial are OK |
| 67 : | # and record the new exponent array | ||
| 68 : | # | ||
| 69 : | sub checkExponents { | ||
| 70 : | my $self = shift; | ||
| 71 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 72 : | LimitedPolynomial::markPowers($l); | ||
| 73 : | LimitedPolynomial::markPowers($r); | ||
| 74 : | my $exponents = $self->{exponents} = $r->{exponents}; | ||
| 75 : | delete $r->{exponents}; delete $r->{powers}; | ||
| 76 : | if ($l->{exponents}) { | ||
| 77 : | dpvc | 5051 | my $single = $self->context->flag('singlePowers'); |
| 78 : | dpvc | 4298 | foreach my $i (0..scalar(@{$exponents})-1) { |
| 79 : | $self->Error("A variable can appear only once in each term of a polynomial") | ||
| 80 : | if $exponents->[$i] && $l->{exponents}[$i] && $single; | ||
| 81 : | $exponents->[$i] += $l->{exponents}[$i]; | ||
| 82 : | } | ||
| 83 : | } | ||
| 84 : | delete $l->{exponents}; delete $l->{powers}; | ||
| 85 : | $self->{isPower} = 1; $self->{isPoly} = $l->{isPoly}; | ||
| 86 : | return 1; | ||
| 87 : | } | ||
| 88 : | |||
| 89 : | # | ||
| 90 : | dpvc | 3294 | # Check that the powers of combined monomials are OK |
| 91 : | # and record the new power list | ||
| 92 : | # | ||
| 93 : | sub checkPowers { | ||
| 94 : | my $self = shift; | ||
| 95 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 96 : | dpvc | 5051 | my $single = $self->context->flag('singlePowers'); |
| 97 : | dpvc | 4298 | LimitedPolynomial::markPowers($l); |
| 98 : | LimitedPolynomial::markPowers($r); | ||
| 99 : | dpvc | 3294 | $self->{isPoly} = 1; |
| 100 : | dpvc | 4298 | $self->{powers} = $l->{powers} || {}; delete $l->{powers}; |
| 101 : | dpvc | 3294 | return 1 unless $r->{powers}; |
| 102 : | foreach my $n (keys(%{$r->{powers}})) { | ||
| 103 : | dpvc | 5392 | $self->Error("Simplified polynomials can have at most one term of each degree") |
| 104 : | dpvc | 3294 | if $self->{powers}{$n} && $single; |
| 105 : | $self->{powers}{$n} = 1; | ||
| 106 : | } | ||
| 107 : | dpvc | 4298 | delete $r->{powers}; |
| 108 : | dpvc | 3294 | return 1; |
| 109 : | } | ||
| 110 : | |||
| 111 : | dpvc | 5518 | # |
| 112 : | # Report an error when both operands are constants | ||
| 113 : | # and strictCoefficients is in effect. | ||
| 114 : | # | ||
| 115 : | sub checkStrict { | ||
| 116 : | my $self = shift; | ||
| 117 : | $self->Error("Can't use '%s' between constants",$self->{bop}); | ||
| 118 : | } | ||
| 119 : | |||
| 120 : | dpvc | 5392 | ################################################## |
| 121 : | |||
| 122 : | dpvc | 3294 | package LimitedPolynomial; |
| 123 : | |||
| 124 : | # | ||
| 125 : | dpvc | 4298 | # Mark a variable as having power 1 |
| 126 : | # Mark a monomial as having its given powers | ||
| 127 : | # | ||
| 128 : | sub markPowers { | ||
| 129 : | my $self = shift; | ||
| 130 : | if ($self->class eq 'Variable') { | ||
| 131 : | my $vIndex = LimitedPolynomial::getVarIndex($self); | ||
| 132 : | $self->{index} = $vIndex->{$self->{name}}; | ||
| 133 : | $self->{exponents} = [(0) x scalar(keys %{$vIndex})]; | ||
| 134 : | $self->{exponents}[$self->{index}] = 1; | ||
| 135 : | } | ||
| 136 : | if ($self->{exponents}) { | ||
| 137 : | my $power = join(',',@{$self->{exponents}}); | ||
| 138 : | $self->{powers}{$power} = 1; | ||
| 139 : | } | ||
| 140 : | } | ||
| 141 : | |||
| 142 : | # | ||
| 143 : | # Get a hash of variable names that point to indices | ||
| 144 : | # within the array of powers for a monomial | ||
| 145 : | # | ||
| 146 : | sub getVarIndex { | ||
| 147 : | my $self = shift; | ||
| 148 : | my $equation = $self->{equation}; | ||
| 149 : | if (!$equation->{varIndex}) { | ||
| 150 : | $equation->{varIndex} = {}; my $i = 0; | ||
| 151 : | foreach my $v ($equation->{context}->variables->names) | ||
| 152 : | {$equation->{varIndex}{$v} = $i++} | ||
| 153 : | } | ||
| 154 : | return $equation->{varIndex}; | ||
| 155 : | } | ||
| 156 : | |||
| 157 : | # | ||
| 158 : | dpvc | 3294 | # Check for a constant expression |
| 159 : | # | ||
| 160 : | sub isConstant { | ||
| 161 : | my $self = shift; | ||
| 162 : | return 1 if $self->{isConstant} || $self->class eq 'Constant'; | ||
| 163 : | return scalar(keys(%{$self->getVariables})) == 0; | ||
| 164 : | } | ||
| 165 : | |||
| 166 : | ############################################## | ||
| 167 : | # | ||
| 168 : | # Now we get the individual replacements for the operators | ||
| 169 : | # that we don't want to allow. We inherit everything from | ||
| 170 : | # the original Parser::BOP class, and just add the | ||
| 171 : | # polynomial checks here. Note that checkpolynomial | ||
| 172 : | # only gets called if at least one of the terms is not | ||
| 173 : | # a number. | ||
| 174 : | # | ||
| 175 : | |||
| 176 : | package LimitedPolynomial::BOP::add; | ||
| 177 : | our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::add); | ||
| 178 : | |||
| 179 : | sub checkPolynomial { | ||
| 180 : | my $self = shift; | ||
| 181 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 182 : | dpvc | 5518 | $self->Error("Addition is allowed only between monomials") if $r->{isPoly}; |
| 183 : | dpvc | 3294 | $self->checkPowers; |
| 184 : | } | ||
| 185 : | |||
| 186 : | dpvc | 5518 | sub checkStrict { |
| 187 : | my $self = shift; | ||
| 188 : | $self->Error("You can only use addition for the terms of a polynomial",$self->{bop}); | ||
| 189 : | } | ||
| 190 : | |||
| 191 : | dpvc | 3294 | ############################################## |
| 192 : | |||
| 193 : | package LimitedPolynomial::BOP::subtract; | ||
| 194 : | our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::subtract); | ||
| 195 : | |||
| 196 : | sub checkPolynomial { | ||
| 197 : | my $self = shift; | ||
| 198 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 199 : | dpvc | 5518 | $self->Error("Subtraction is allowed only between monomials") if $r->{isPoly}; |
| 200 : | dpvc | 3294 | $self->checkPowers; |
| 201 : | } | ||
| 202 : | |||
| 203 : | dpvc | 5518 | sub checkStrict { |
| 204 : | my $self = shift; | ||
| 205 : | $self->Error("You can only use subtraction between the terms of a polynomial",$self->{bop}); | ||
| 206 : | } | ||
| 207 : | |||
| 208 : | dpvc | 3294 | ############################################## |
| 209 : | |||
| 210 : | package LimitedPolynomial::BOP::multiply; | ||
| 211 : | our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::multiply); | ||
| 212 : | |||
| 213 : | sub checkPolynomial { | ||
| 214 : | my $self = shift; | ||
| 215 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 216 : | dpvc | 4298 | my $lOK = (LimitedPolynomial::isConstant($l) || $l->{isPower} || |
| 217 : | $l->class eq 'Variable' || ($l->{isPoly} && $l->{isPoly} == 2)); | ||
| 218 : | my $rOK = ($r->{isPower} || $r->class eq 'Variable'); | ||
| 219 : | return $self->checkExponents if $lOK and $rOK; | ||
| 220 : | dpvc | 3294 | $self->Error("Coefficients must come before variables in a polynomial") |
| 221 : | if LimitedPolynomial::isConstant($r) && ($l->{isPower} || $l->class eq 'Variable'); | ||
| 222 : | $self->Error("Multiplication can only be used between coefficients and variables"); | ||
| 223 : | } | ||
| 224 : | |||
| 225 : | dpvc | 5518 | sub checkStrict { |
| 226 : | my $self = shift; | ||
| 227 : | $self->Error("You can only use '%s' between a coefficent and a variable in a polynomial",$self->{bop}); | ||
| 228 : | } | ||
| 229 : | |||
| 230 : | dpvc | 3294 | ############################################## |
| 231 : | |||
| 232 : | package LimitedPolynomial::BOP::divide; | ||
| 233 : | our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::divide); | ||
| 234 : | |||
| 235 : | sub checkPolynomial { | ||
| 236 : | my $self = shift; | ||
| 237 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 238 : | dpvc | 4298 | $self->Error("In a polynomial, you can only divide by numbers") |
| 239 : | dpvc | 3294 | unless LimitedPolynomial::isConstant($r); |
| 240 : | dpvc | 4298 | $self->Error("You can only divide a single term by a number") |
| 241 : | dpvc | 3294 | if $l->{isPoly} && $l->{isPoly} == 1; |
| 242 : | $self->{isPoly} = $l->{isPoly}; | ||
| 243 : | dpvc | 4298 | $self->{powers} = $l->{powers}; delete $l->{powers}; |
| 244 : | $self->{exponents} = $l->{exponents}; delete $l->{exponents}; | ||
| 245 : | dpvc | 3294 | return 1; |
| 246 : | } | ||
| 247 : | |||
| 248 : | dpvc | 5518 | sub checkStrict { |
| 249 : | my $self = shift; | ||
| 250 : | $self->Error("You can only use '%s' to form fractions",$self->{bop}) if $self->{lop}->class eq 'BOP'; | ||
| 251 : | } | ||
| 252 : | |||
| 253 : | dpvc | 3294 | ############################################## |
| 254 : | |||
| 255 : | package LimitedPolynomial::BOP::power; | ||
| 256 : | our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::power); | ||
| 257 : | |||
| 258 : | sub checkPolynomial { | ||
| 259 : | my $self = shift; | ||
| 260 : | my ($l,$r) = ($self->{lop},$self->{rop}); | ||
| 261 : | $self->Error("You can only raise a variable to a power in a polynomial") | ||
| 262 : | unless $l->class eq 'Variable'; | ||
| 263 : | $self->Error("Exponents must be constant in a polynomial") | ||
| 264 : | unless LimitedPolynomial::isConstant($r); | ||
| 265 : | my $n = Parser::Evaluate($r); | ||
| 266 : | $r->Error($$Value::context->{error}{message}) if $$Value::context->{error}{flag}; | ||
| 267 : | dpvc | 4298 | $n = $n->value; |
| 268 : | dpvc | 3294 | $self->Error("Exponents must be positive integers in a polynomial") |
| 269 : | unless $n > 0 && $n == int($n); | ||
| 270 : | dpvc | 4298 | LimitedPolynomial::markPowers($l); |
| 271 : | $self->{exponents} = $l->{exponents}; delete $l->{exponents}; | ||
| 272 : | foreach my $i (@{$self->{exponents}}) {$i = $n if $i} | ||
| 273 : | $self->{isPower} = 1; | ||
| 274 : | dpvc | 3294 | return 1; |
| 275 : | } | ||
| 276 : | |||
| 277 : | dpvc | 5518 | sub checkStrict { |
| 278 : | my $self = shift; | ||
| 279 : | $self->Error("You can only use powers of a variable in a polynomial"); | ||
| 280 : | } | ||
| 281 : | |||
| 282 : | dpvc | 3294 | ############################################## |
| 283 : | ############################################## | ||
| 284 : | # | ||
| 285 : | # Now we do the same for the unary operators | ||
| 286 : | # | ||
| 287 : | |||
| 288 : | package LimitedPolynomial::UOP; | ||
| 289 : | |||
| 290 : | sub _check { | ||
| 291 : | my $self = shift; | ||
| 292 : | my $super = ref($self); $super =~ s/LimitedPolynomial/Parser/; | ||
| 293 : | &{$super."::_check"}($self); | ||
| 294 : | my $op = $self->{op}; | ||
| 295 : | jj | 3383 | return if LimitedPolynomial::isConstant($op); |
| 296 : | dpvc | 3371 | $self->Error("You can only use '%s' with monomials",$self->{def}{string}) |
| 297 : | dpvc | 3294 | if $op->{isPoly}; |
| 298 : | $self->{isPoly} = 2; | ||
| 299 : | dpvc | 4298 | $self->{powers} = $op->{powers}; delete $op->{powers}; |
| 300 : | $self->{exponents} = $op->{exponents}; delete $op->{exponents}; | ||
| 301 : | dpvc | 3294 | } |
| 302 : | |||
| 303 : | sub checkPolynomial {return 0} | ||
| 304 : | |||
| 305 : | ############################################## | ||
| 306 : | |||
| 307 : | package LimitedPolynomial::UOP::plus; | ||
| 308 : | our @ISA = qw(LimitedPolynomial::UOP Parser::UOP::plus); | ||
| 309 : | |||
| 310 : | ############################################## | ||
| 311 : | |||
| 312 : | package LimitedPolynomial::UOP::minus; | ||
| 313 : | our @ISA = qw(LimitedPolynomial::UOP Parser::UOP::minus); | ||
| 314 : | |||
| 315 : | ############################################## | ||
| 316 : | ############################################## | ||
| 317 : | # | ||
| 318 : | # Don't allow absolute values | ||
| 319 : | # | ||
| 320 : | |||
| 321 : | package LimitedPolynomial::List::AbsoluteValue; | ||
| 322 : | our @ISA = qw(Parser::List::AbsoluteValue); | ||
| 323 : | |||
| 324 : | sub _check { | ||
| 325 : | my $self = shift; | ||
| 326 : | $self->SUPER::_check; | ||
| 327 : | return if LimitedPolynomial::isConstant($self->{coords}[0]); | ||
| 328 : | $self->Error("Can't use absolute values in polynomials"); | ||
| 329 : | } | ||
| 330 : | |||
| 331 : | ############################################## | ||
| 332 : | ############################################## | ||
| 333 : | # | ||
| 334 : | # Only allow numeric function calls | ||
| 335 : | # | ||
| 336 : | |||
| 337 : | package LimitedPolynomial::Function; | ||
| 338 : | |||
| 339 : | sub _check { | ||
| 340 : | my $self = shift; | ||
| 341 : | my $super = ref($self); $super =~ s/LimitedPolynomial/Parser/; | ||
| 342 : | &{$super."::_check"}($self); | ||
| 343 : | my $arg = $self->{params}->[0]; | ||
| 344 : | return if LimitedPolynomial::isConstant($arg); | ||
| 345 : | dpvc | 3371 | $self->Error("Function '%s' can only be used with numbers",$self->{name}); |
| 346 : | dpvc | 3294 | } |
| 347 : | |||
| 348 : | |||
| 349 : | package LimitedPolynomial::Function::numeric; | ||
| 350 : | our @ISA = qw(LimitedPolynomial::Function Parser::Function::numeric); | ||
| 351 : | |||
| 352 : | package LimitedPolynomial::Function::trig; | ||
| 353 : | our @ISA = qw(LimitedPolynomial::Function Parser::Function::trig); | ||
| 354 : | |||
| 355 : | dpvc | 4298 | package LimitedPolynomial::Function::hyperbolic; |
| 356 : | our @ISA = qw(LimitedPolynomial::Function Parser::Function::hyperbolic); | ||
| 357 : | |||
| 358 : | dpvc | 3294 | ############################################## |
| 359 : | ############################################## | ||
| 360 : | |||
| 361 : | dpvc | 5392 | package LimitedPolynomial; |
| 362 : | dpvc | 3294 | |
| 363 : | dpvc | 5392 | sub Init { |
| 364 : | # | ||
| 365 : | # Build the new context that calls the | ||
| 366 : | # above classes rather than the usual ones | ||
| 367 : | # | ||
| 368 : | dpvc | 3294 | |
| 369 : | dpvc | 5392 | my $context = $main::context{LimitedPolynomial} = Parser::Context->getCopy("Numeric"); |
| 370 : | dpvc | 5441 | $context->{name} = "LimitedPolynomial"; |
| 371 : | dpvc | 5392 | $context->operators->set( |
| 372 : | '+' => {class => 'LimitedPolynomial::BOP::add'}, | ||
| 373 : | '-' => {class => 'LimitedPolynomial::BOP::subtract'}, | ||
| 374 : | '*' => {class => 'LimitedPolynomial::BOP::multiply'}, | ||
| 375 : | '* ' => {class => 'LimitedPolynomial::BOP::multiply'}, | ||
| 376 : | ' *' => {class => 'LimitedPolynomial::BOP::multiply'}, | ||
| 377 : | ' ' => {class => 'LimitedPolynomial::BOP::multiply'}, | ||
| 378 : | '/' => {class => 'LimitedPolynomial::BOP::divide'}, | ||
| 379 : | ' /' => {class => 'LimitedPolynomial::BOP::divide'}, | ||
| 380 : | '/ ' => {class => 'LimitedPolynomial::BOP::divide'}, | ||
| 381 : | '^' => {class => 'LimitedPolynomial::BOP::power'}, | ||
| 382 : | '**' => {class => 'LimitedPolynomial::BOP::power'}, | ||
| 383 : | 'u+' => {class => 'LimitedPolynomial::UOP::plus'}, | ||
| 384 : | 'u-' => {class => 'LimitedPolynomial::UOP::minus'}, | ||
| 385 : | dpvc | 3294 | ); |
| 386 : | dpvc | 5392 | # |
| 387 : | # Remove these operators and functions | ||
| 388 : | # | ||
| 389 : | $context->lists->set( | ||
| 390 : | AbsoluteValue => {class => 'LimitedPolynomial::List::AbsoluteValue'}, | ||
| 391 : | dpvc | 3294 | ); |
| 392 : | dpvc | 5392 | $context->operators->undefine('_','!','U'); |
| 393 : | $context->functions->disable("atan2"); | ||
| 394 : | # | ||
| 395 : | # Hook into the numeric, trig, and hyperbolic functions | ||
| 396 : | # | ||
| 397 : | foreach ('ln','log','log10','exp','sqrt','abs','int','sgn') { | ||
| 398 : | $context->functions->set( | ||
| 399 : | "$_"=>{class => 'LimitedPolynomial::Function::numeric'} | ||
| 400 : | ); | ||
| 401 : | } | ||
| 402 : | foreach ('sin','cos','tan','sec','csc','cot', | ||
| 403 : | 'asin','acos','atan','asec','acsc','acot') { | ||
| 404 : | $context->functions->set( | ||
| 405 : | "$_"=>{class => 'LimitedPolynomial::Function::trig'}, | ||
| 406 : | "${_}h"=>{class => 'LimitedPolynomial::Function::hyperbolic'} | ||
| 407 : | ); | ||
| 408 : | } | ||
| 409 : | dpvc | 5484 | # |
| 410 : | # Don't convert -ax-b to -(ax+b), etc. | ||
| 411 : | # | ||
| 412 : | $context->reduction->set("(-x)-y"=>0); | ||
| 413 : | dpvc | 5392 | |
| 414 : | dpvc | 5518 | # |
| 415 : | # A context where coefficients can't include operations | ||
| 416 : | # | ||
| 417 : | $context = $main::context{"LimitedPolynomial-Strict"} = $context->copy; | ||
| 418 : | $context->flags->set(strictCoefficients=>1, singelPowers=>1, reduceConstants=>0); | ||
| 419 : | $context->functions->disable("All"); # can be re-enabled if needed | ||
| 420 : | |||
| 421 : | dpvc | 5392 | main::Context("LimitedPolynomial"); ### FIXME: probably should require author to set this explicitly |
| 422 : | dpvc | 3294 | } |
| 423 : | |||
| 424 : | dpvc | 5373 | 1; |
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |