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

Annotation of /trunk/pg/macros/contextLimitedPolynomial.pl

Parent Directory Parent Directory | Revision Log 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