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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5988 - (view) (download) (as text)

1 : sh002i 5556 ################################################################################
2 :     # WeBWorK Online Homework Delivery System
3 :     # Copyright 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
4 : dpvc 5988 # $CVSHeader: pg/macros/parserFormulaUpToConstant.pl,v 1.19 2008/09/21 18:06:19 dpvc Exp $
5 : sh002i 5556 #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 :     ################################################################################
16 :    
17 : sh002i 5551 =head1 NAME
18 : dpvc 5393
19 : sh002i 5551 parserFormulaUpToConstant.pl - implements formulas "plus a constant".
20 : dpvc 5393
21 : sh002i 5551 =head1 DESCRIPTION
22 : dpvc 5393
23 : sh002i 5551 This file implements the FormulaUpToConstant object, which is
24 :     a formula that is only unique up to a constant (i.e., this is
25 :     an anti-derivative). Students must include the "+C" as part of
26 :     their answers, but they can use any (single-letter) constant that
27 :     they want, and it doesn't have to be the one the professor used.
28 : dpvc 5393
29 : sh002i 5551 To use FormulaWithConstat objects, load this macro file at the
30 :     top of your problem:
31 :    
32 : sh002i 5555 loadMacros("parserFormulaUpToConstant.pl");
33 : sh002i 5551
34 :     then create a formula with constant as follows:
35 :    
36 : sh002i 5555 $f = FormulaUpToConstant("sin(x)+C");
37 : sh002i 5551
38 :     Note that the C should NOT already be a variable in the Context;
39 :     the FormulaUpToConstant object will handle adding it in for
40 :     you. If you don't include a constant in your formula (i.e., if
41 :     all the variables that you used are already in your Context,
42 :     then the FormulaUpToConstant object will add "+C" for you.
43 :    
44 :     The FormulaUpToConstant should work like any normal Formula,
45 :     and in particular, you use $f->cmp to get its answer checker.
46 :    
47 : sh002i 5555 ANS($f->cmp);
48 : sh002i 5551
49 :     Note that the FormulaUpToConstant object creates its only private
50 :     copy of the current Context (so that it can add variables without
51 :     affecting the rest of the problem). You should not notice this
52 :     in general, but if you need to access that context, use $f->{context}.
53 :     E.g.
54 :    
55 : sh002i 5555 Context($f->{context});
56 : sh002i 5551
57 :     would make the current context the one being used by the
58 :     FormulaUpToConstant, while
59 :    
60 : sh002i 5555 $f->{context}->variables->names
61 : sh002i 5551
62 :     would return a list of the variables in the private context.
63 :    
64 :     To get the name of the constant in use in the formula,
65 :     use
66 :    
67 : sh002i 5555 $f->constant.
68 : sh002i 5551
69 :     If you combine a FormulaUpToConstant with other formulas,
70 :     the result will be a new FormulaUpToConstant object, with
71 :     a new Context, and potentially a new + C added to it. This
72 :     is likely not what you want. Instead, you should convert
73 :     back to a Formula first, then combine with other objects,
74 :     then convert back to a FormulaUpToConstant, if necessary.
75 :     To do this, use the removeConstant() method:
76 :    
77 : sh002i 5555 $f = FormulaUpToConstant("sin(x)+C");
78 :     $g = Formula("cos(x)");
79 :     $h = $f->removeConstant + $g; # $h will be "sin(x)+cos(x)"
80 :     $h = FormulaUpToConstant($h); # $h will be "sin(x)+cos(x)+C"
81 : sh002i 5551
82 :     The answer evaluator by default will give "helpful" messages
83 :     to the student when the "+ C" is left out. You can turn off
84 :     these messages using the showHints option to the cmp() method:
85 :    
86 : sh002i 5555 ANS($f->cmp(showHints => 0));
87 : sh002i 5551
88 :     One of the hints is about whether the student's answer is linear
89 :     in the arbitrary constant. This test requires differentiating
90 :     the student answer. Since there are times when that could be
91 :     problematic, you can disable that test via the showLinearityHints
92 :     flag. (Note: setting showHints to 0 also disables these hints.)
93 :    
94 : sh002i 5555 ANS($f->cmp(showLinearityHints => 0));
95 : sh002i 5551
96 : dpvc 5393 =cut
97 :    
98 : sh002i 5551 loadMacros("MathObjects.pl");
99 :    
100 :     sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()}
101 :    
102 : dpvc 5393 package FormulaUpToConstant;
103 :     @ISA = ('Value::Formula');
104 :    
105 :     sub Init {
106 :     main::PG_restricted_eval('sub FormulaUpToConstant {FormulaUpToConstant->new(@_)}');
107 :     }
108 :    
109 :     #
110 :     # Create an instance of a FormulaUpToConstant. If no constant
111 :     # is supplied, we add C ourselves.
112 :     #
113 :     sub new {
114 :     my $self = shift; my $class = ref($self) || $self;
115 :     #
116 :     # Copy the context (so we can modify it) and
117 :     # replace the usual Variable object with our own.
118 :     #
119 :     my $context = (Value::isContext($_[0]) ? shift : $self->context)->copy;
120 :     $context->{parser}{Variable} = 'FormulaUpToConstant::Variable';
121 :     #
122 :     # Create a formula from the user's input.
123 :     #
124 :     my $f = main::Formula($context,@_);
125 :     #
126 :     # If it doesn't have a constant already, add one.
127 :     # (should check that C isn't already in use, and look
128 :     # up the first free name, but we'll cross our fingers
129 :     # for now. Could look through the defined variables
130 :     # to see if there is already an arbitraryConstant
131 :     # and use that.)
132 :     #
133 :     unless ($f->{constant}) {$f = $f + "C", $f->{constant} = "C"}
134 :     #
135 :     # Check that the formula is linear in C.
136 :     #
137 :     my $n = $f->D($f->{constant});
138 :     Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant})
139 :     unless $n->isConstant;
140 :     #
141 : dpvc 5460 # Make a version with adaptive parameters for use in the
142 : dpvc 5393 # comparison later on. We could like n0*C, but already have $n
143 :     # copies of C, so remove them. That way, n0 will be 0 when there
144 :     # are no C's in the student answer during the adaptive comparison.
145 :     # (Again, should really check that n0 is not in use already)
146 :     #
147 : dpvc 5460 my $n00 = $context->variables->get("n00");
148 :     $context->variables->add(n00=>'Parameter') unless $n00 and $n00->{parameter};
149 :     my $n01 = $context->variables->get("n01");
150 :     $context->variables->add(n01=>'Parameter') unless $n01 and $n01->{parameter};
151 :     $f->{adapt} = $f + "(n00-$n)$f->{constant} + n01";
152 : dpvc 5907
153 : dpvc 5393 return bless $f, $class;
154 :     }
155 :    
156 :     ##################################################
157 :     #
158 :     # Remember that compare implements the overloaded perl <=> operator,
159 :     # and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b.
160 :     # In our case, we only care about equality, so we will return 0 when
161 :     # equal and other numbers to indicate the reason they are not equal
162 :     # (this can be used by the answer checker to print helpful messages)
163 :     #
164 :     sub compare {
165 :     my ($l,$r) = @_; my $self = $l; my $context = $self->context;
166 :     $r = Value::makeValue($r,context=>$context);
167 :     #
168 :     # Not equal if the student value is constant or has no + C
169 :     #
170 :     return 2 if !Value::isFormula($r);
171 :     return 3 if !defined($r->{constant});
172 :     #
173 :     # If constants aren't the same, substitute the professor's in the student answer.
174 :     #
175 :     $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
176 : dpvc 5988
177 : dpvc 5393 #
178 :     # Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
179 :     #
180 : dpvc 5913 my $adapt = $l->adapt;
181 : dpvc 5988 my $equal = Parser::Eval(sub {$adapt == $r});
182 : dpvc 5913 $self->{adapt} = $self->{adapt}->inherit($adapt); # save the adapted value's flags
183 : dpvc 5988 $self->{adapt}{test_values} = $adapt->{test_values}; # (these two are removed by inherit)
184 :     $self->{adapt}{test_adapt} = $adapt->{test_adapt};
185 :     $_[1]->{test_values} = $r->{test_values}; # save these in student answer for diagnostics
186 : dpvc 5673 return -1 unless $equal;
187 : dpvc 5393 #
188 :     # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
189 :     # (remember: return value of 0 is equal, and non-zero is unequal)
190 :     #
191 : dpvc 5460 return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
192 : dpvc 5393 }
193 :    
194 : dpvc 5912 #
195 :     # Return the {adapt} formula with test points adjusted
196 :     #
197 :     sub adapt {
198 :     my $self = shift;
199 :     return $self->adjustInherit($self->{adapt});
200 :     }
201 :    
202 :     #
203 :     # Inherit from the main FormulaUpToConstant, but
204 :     # adjust the test points to include the constants
205 :     #
206 :     sub adjustInherit {
207 :     my $self = shift;
208 :     my $f = shift->inherit($self);
209 :     delete $f->{adapt}; delete $f->{constant};
210 :     foreach my $id ('test_points','test_at') {
211 :     if (defined $f->{$id}) {
212 : dpvc 5917 $f->{$id} = [$f->{$id}->value] if Value::isValue($f->{$id});
213 : dpvc 5912 $f->{$id} = [$f->{$id}] unless ref($f->{$id}) eq 'ARRAY';
214 : dpvc 5917 $f->{$id} = [map {
215 :     (Value::isValue($_) ? [$_->value] :
216 :     (ref($_) eq 'ARRAY'? $_ : [$_]))
217 :     } @{$f->{$id}}];
218 : dpvc 5912 $f->{$id} = $self->addConstants($f->{$id});
219 :     }
220 :     }
221 :     return $f;
222 :     }
223 :    
224 :     #
225 :     # Insert dummy values for the constants for the test points
226 :     # (These are supposed to be +C, so the value shouldn't matter?)
227 :     #
228 :     sub addConstants {
229 :     my $self = shift; my $points = shift;
230 :     my @names = $self->context->variables->variables;
231 :     my $variables = $self->context->{variables};
232 :     my $Points = [];
233 :     foreach my $p (@{$points}) {
234 : dpvc 5917 if (scalar(@{$p}) == scalar(@names)) {
235 :     push (@{$Points},$p);
236 :     } else {
237 :     my @P = (.1) x scalar(@names); my $j = 0;
238 :     foreach my $i (0..scalar(@names)-1) {
239 :     if (!$variables->{$names[$i]}{arbitraryConstant}) {
240 :     $P[$i] = $p->[$j] if defined $p->[$j]; $j++;
241 :     }
242 : dpvc 5912 }
243 : dpvc 5917 push (@{$Points}, \@P);
244 : dpvc 5912 }
245 :     }
246 :     return $Points;
247 :     }
248 :    
249 : dpvc 5440 ##################################################
250 : dpvc 5393 #
251 : dpvc 5440 # Here we override part of the answer comparison
252 :     # routines in order to be able to generate
253 :     # helpful error messages for students when
254 :     # they leave off the + C.
255 :     #
256 :    
257 :     #
258 : dpvc 5393 # Show hints by default
259 :     #
260 : dpvc 5440 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
261 : dpvc 5393
262 :     #
263 : dpvc 5914 # Provide diagnostics based on the adapted function used to check
264 :     # the student's answer
265 :     #
266 :     sub cmp_diagnostics {
267 :     my $self = shift;
268 : dpvc 5988 my $adapt = $self->inherit($self->{adapt});
269 :     $adapt->{test_values} = $self->{adapt}{test_values}; # these aren't copied by inherit
270 :     $adapt->{test_adapt} = $self->{adapt}{test_adapt};
271 :     $adapt->SUPER::cmp_diagnostics(@_);
272 : dpvc 5914 }
273 :    
274 :     #
275 :     # Make it possible to graph single-variable formulas by setting
276 :     # the arbitrary constants to 0 first.
277 :     #
278 :     sub cmp_graph {
279 :     my $self = shift; my $diagnostics = shift;
280 :     my $F1 = shift; my $F2; ($F1,$F2) = @{$F1} if (ref($F1) eq 'ARRAY');
281 :     my %subs; my $context = $self->context;
282 :     foreach my $v ($context->variables->variables)
283 :     {$subs{$v} = 0 if ($context->variables->get($v)->{arbitraryConstant})}
284 :     $F1 = $F1->inherit($F1->{adapt})->substitute(%subs)->reduce;
285 :     $F2 = $F2->inherit($F2->{adapt})->substitute(%subs)->reduce;
286 :     $self->SUPER::cmp_graph($diagnostics,[$F1,$F2]);
287 :     }
288 :    
289 :     #
290 : dpvc 5393 # Add useful messages, if the author requested them
291 :     #
292 :     sub cmp_postprocess {
293 :     my $self = shift; my $ans = shift;
294 : dpvc 5917 $self->SUPER::cmp_postprocess($ans,@_);
295 : dpvc 5393 return unless $ans->{score} == 0 && !$ans->{isPreview};
296 :     return if $ans->{ans_message} || !$self->getFlag("showHints");
297 : dpvc 5440 my $student = $ans->{student_value};
298 : dpvc 5988 my $result = Parser::Eval(sub {return $ans->{correct_value} <=> $student}); # compare encodes the reason in the result
299 : dpvc 5393 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
300 : dpvc 5461 if ($result == 3) {
301 : dpvc 5463 my $context = $self->context;
302 :     $context->flags->set(no_parameters=>0);
303 :     $context->variables->add(x00=>'Real');
304 : dpvc 5905 my $correct = $self->removeConstant+"n01+n00x00"; # must use both parameters
305 : dpvc 5917 $result = 1 if $correct->cmp_compare($student+"x00",{});
306 : dpvc 5463 $context->variables->remove('x00');
307 :     $context->flags->set(no_parameters=>1);
308 : dpvc 5461 }
309 :     $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
310 : dpvc 5440 $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
311 :     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
312 : dpvc 5393 }
313 :    
314 : dpvc 5440 ##################################################
315 : dpvc 5393 #
316 :     # Get the name of the constant
317 :     #
318 :     sub constant {(shift)->{constant}}
319 :    
320 :     #
321 :     # Remove the constant and return a Formula object
322 :     #
323 :     sub removeConstant {
324 :     my $self = shift;
325 : dpvc 5912 return $self->adjustInherit(main::Formula($self->substitute($self->{constant}=>0))->reduce);
326 : dpvc 5393 }
327 :    
328 :     #
329 :     # Override the differentiation so that we always return
330 :     # a Formula, not a FormulaUpToConstant (we don't want to
331 :     # add the C in again).
332 :     #
333 :     sub D {
334 :     my $self = shift;
335 :     $self->removeConstant->D(@_);
336 :     }
337 :    
338 :     ######################################################################
339 :     #
340 :     # This class repalces the Parser::Variable class, and its job
341 :     # is to look for new constants that aren't in the context,
342 :     # and add them in. This allows students to use ANY constant
343 :     # they want, and a different one from the professor. We check
344 :     # that the student only used ONE arbitrary constant, however.
345 :     #
346 :     package FormulaUpToConstant::Variable;
347 :     our @ISA = ('Parser::Variable');
348 :    
349 :     sub new {
350 :     my $self = shift; my $class = ref($self) || $self;
351 :     my $equation = shift; my $variables = $equation->{context}{variables};
352 :     my ($name,$ref) = @_; my $def = $variables->{$name};
353 :     #
354 :     # If the variable is not already in the context, add it
355 :     # and mark it as an arbitrary constant (for later reference)
356 :     #
357 :     if (!defined($def) && length($name) eq 1) {
358 :     $equation->{context}->variables->add($name => 'Real');
359 :     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
360 :     $def = $variables->{$name};
361 :     }
362 :     #
363 :     # If the variable is an arbitrary constant
364 :     # Error if we already have a constant and it's not this one.
365 :     # Save the constant so we can check with it later.
366 :     #
367 :     if ($def && $def->{arbitraryConstant}) {
368 :     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
369 :     if $equation->{constant} and $name ne $equation->{constant};
370 :     $equation->{constant} = $name;
371 :     }
372 :     #
373 :     # Do the usual Variable stuff.
374 :     #
375 :     $self->SUPER::new($equation,$name,$ref);
376 :     }
377 :    
378 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9