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

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

Wed Jun 15 23:55:17 2005 UTC (14 years, 7 months ago) by dpvc
File size: 8747 byte(s)
```Implements a Parser context in which only polynomials (of a single
variable) can be entered.  Only sums of multiples of powers of the
variable are allowed to be entered (though the coefficients can
contain mathematical operations).  An optional flag lets you specify
that only one term of each degree is allowed, so the student would
have to combine 1+x+x+x^2 to get 1+2x+x^2 in that case.
```

```    1 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;
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
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   return if LimitedPolynomail::isConstant(\$op);
181   \$self->Error("You can only use '\$self->{def}{string}' with monomials")
182     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   \$self->Error("Function '\$self->{name}' can only be used with numbers");
230 }
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");
```