[system] / trunk / pg / macros / contextLimitedPolynomial.pl Repository: Repository Listing bbplugincoursesdistsnplrochestersystemwww

# View of /trunk/pg/macros/contextLimitedPolynomial.pl

Tue Aug 28 22:40:15 2007 UTC (12 years, 5 months ago) by dpvc
File size: 11324 byte(s)
```Add context names for the context(s) created here.
```

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