[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 4997 - (view) (download) (as text)

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9