[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 5914 - (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 5914 # $CVSHeader: pg/macros/parserFormulaUpToConstant.pl,v 1.17 2008/09/16 03:01:17 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 :     #
177 :     # Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
178 :     #
179 : dpvc 5913 my $adapt = $l->adapt;
180 :     $main::{_cmp_} = sub {return $adapt == $r}; # a closure to access local variables
181 : dpvc 5912 my $equal = main::PG_restricted_eval('&{$main::{_cmp_}}'); # prevents errors with large adaptive parameters
182 :     delete $main::{_cmp_}; # remove temprary function
183 : dpvc 5913 $self->{adapt} = $self->{adapt}->inherit($adapt); # save the adapted value's flags
184 : dpvc 5673 return -1 unless $equal;
185 : dpvc 5393 #
186 :     # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
187 :     # (remember: return value of 0 is equal, and non-zero is unequal)
188 :     #
189 : dpvc 5460 return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
190 : dpvc 5393 }
191 :    
192 : dpvc 5912 #
193 :     # Return the {adapt} formula with test points adjusted
194 :     #
195 :     sub adapt {
196 :     my $self = shift;
197 :     my $adapt = $self->{adapt}->inherit($self); delete $adapt->{adapt};
198 :     return $self->adjustInherit($self->{adapt});
199 :     }
200 :    
201 :     #
202 :     # Inherit from the main FormulaUpToConstant, but
203 :     # adjust the test points to include the constants
204 :     #
205 :     sub adjustInherit {
206 :     my $self = shift;
207 :     my $f = shift->inherit($self);
208 :     delete $f->{adapt}; delete $f->{constant};
209 :     foreach my $id ('test_points','test_at') {
210 :     if (defined $f->{$id}) {
211 :     $f->{$id} = $f->{$id}->value if Value::isValue($f->{$id});
212 :     $f->{$id} = [$f->{$id}] unless ref($f->{$id}) eq 'ARRAY';
213 :     $f->{$id} = [map {[$_]} @{$f->{$id}}] unless ref($f->{$id}[0]) eq 'ARRAY';
214 :     $f->{$id} = $self->addConstants($f->{$id});
215 :     }
216 :     }
217 :     return $f;
218 :     }
219 :    
220 :     #
221 :     # Insert dummy values for the constants for the test points
222 :     # (These are supposed to be +C, so the value shouldn't matter?)
223 :     #
224 :     sub addConstants {
225 :     my $self = shift; my $points = shift;
226 :     my @names = $self->context->variables->variables;
227 :     my $variables = $self->context->{variables};
228 :     my $Points = [];
229 :     foreach my $p (@{$points}) {
230 :     my @P = (.1) x scalar(@names); my $j = 0;
231 :     foreach my $i (0..scalar(@names)-1) {
232 :     if (!$variables->{$names[$i]}{arbitraryConstant}) {
233 :     $P[$i] = $p->[$j] if defined $p->[$j]; $j++;
234 :     }
235 :     }
236 :     push (@{$Points}, \@P);
237 :     }
238 :     return $Points;
239 :     }
240 :    
241 : dpvc 5440 ##################################################
242 : dpvc 5393 #
243 : dpvc 5440 # Here we override part of the answer comparison
244 :     # routines in order to be able to generate
245 :     # helpful error messages for students when
246 :     # they leave off the + C.
247 :     #
248 :    
249 :     #
250 : dpvc 5393 # Show hints by default
251 :     #
252 : dpvc 5440 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
253 : dpvc 5393
254 :     #
255 : dpvc 5914 # Provide diagnostics based on the adapted function used to check
256 :     # the student's answer
257 :     #
258 :     sub cmp_diagnostics {
259 :     my $self = shift;
260 :     $self->inherit($self->{adapt})->SUPER::cmp_diagnostics(@_);
261 :     }
262 :    
263 :     #
264 :     # Make it possible to graph single-variable formulas by setting
265 :     # the arbitrary constants to 0 first.
266 :     #
267 :     sub cmp_graph {
268 :     my $self = shift; my $diagnostics = shift;
269 :     my $F1 = shift; my $F2; ($F1,$F2) = @{$F1} if (ref($F1) eq 'ARRAY');
270 :     my %subs; my $context = $self->context;
271 :     foreach my $v ($context->variables->variables)
272 :     {$subs{$v} = 0 if ($context->variables->get($v)->{arbitraryConstant})}
273 :     $F1 = $F1->inherit($F1->{adapt})->substitute(%subs)->reduce;
274 :     $F2 = $F2->inherit($F2->{adapt})->substitute(%subs)->reduce;
275 :     $self->SUPER::cmp_graph($diagnostics,[$F1,$F2]);
276 :     }
277 :    
278 :     #
279 : dpvc 5393 # Add useful messages, if the author requested them
280 :     #
281 :     sub cmp_postprocess {
282 :     my $self = shift; my $ans = shift;
283 :     $self->SUPER::cmp_postprocess($ans);
284 :     return unless $ans->{score} == 0 && !$ans->{isPreview};
285 :     return if $ans->{ans_message} || !$self->getFlag("showHints");
286 : dpvc 5440 my $student = $ans->{student_value};
287 :     my $result = $ans->{correct_value} <=> $student; # compare encodes the reason in the result
288 : dpvc 5393 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
289 : dpvc 5461 if ($result == 3) {
290 : dpvc 5463 my $context = $self->context;
291 :     $context->flags->set(no_parameters=>0);
292 :     $context->variables->add(x00=>'Real');
293 : dpvc 5905 my $correct = $self->removeConstant+"n01+n00x00"; # must use both parameters
294 :     $main::{_cmp_} = sub {return $correct == $student+"x00"}; # a closure to access local variables
295 :     $result = 1 if main::PG_restricted_eval('&{$main::{_cmp_}}'); # prevents domain errors (and other errors)
296 :     delete $main::{_cmp_}; # remove temprary function
297 : dpvc 5463 $context->variables->remove('x00');
298 :     $context->flags->set(no_parameters=>1);
299 : dpvc 5461 }
300 :     $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
301 : dpvc 5440 $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
302 :     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
303 : dpvc 5393 }
304 :    
305 : dpvc 5440 ##################################################
306 : dpvc 5393 #
307 :     # Get the name of the constant
308 :     #
309 :     sub constant {(shift)->{constant}}
310 :    
311 :     #
312 :     # Remove the constant and return a Formula object
313 :     #
314 :     sub removeConstant {
315 :     my $self = shift;
316 : dpvc 5912 return $self->adjustInherit(main::Formula($self->substitute($self->{constant}=>0))->reduce);
317 : dpvc 5393 }
318 :    
319 :     #
320 :     # Override the differentiation so that we always return
321 :     # a Formula, not a FormulaUpToConstant (we don't want to
322 :     # add the C in again).
323 :     #
324 :     sub D {
325 :     my $self = shift;
326 :     $self->removeConstant->D(@_);
327 :     }
328 :    
329 :     ######################################################################
330 :     #
331 :     # This class repalces the Parser::Variable class, and its job
332 :     # is to look for new constants that aren't in the context,
333 :     # and add them in. This allows students to use ANY constant
334 :     # they want, and a different one from the professor. We check
335 :     # that the student only used ONE arbitrary constant, however.
336 :     #
337 :     package FormulaUpToConstant::Variable;
338 :     our @ISA = ('Parser::Variable');
339 :    
340 :     sub new {
341 :     my $self = shift; my $class = ref($self) || $self;
342 :     my $equation = shift; my $variables = $equation->{context}{variables};
343 :     my ($name,$ref) = @_; my $def = $variables->{$name};
344 :     #
345 :     # If the variable is not already in the context, add it
346 :     # and mark it as an arbitrary constant (for later reference)
347 :     #
348 :     if (!defined($def) && length($name) eq 1) {
349 :     $equation->{context}->variables->add($name => 'Real');
350 :     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
351 :     $def = $variables->{$name};
352 :     }
353 :     #
354 :     # If the variable is an arbitrary constant
355 :     # Error if we already have a constant and it's not this one.
356 :     # Save the constant so we can check with it later.
357 :     #
358 :     if ($def && $def->{arbitraryConstant}) {
359 :     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
360 :     if $equation->{constant} and $name ne $equation->{constant};
361 :     $equation->{constant} = $name;
362 :     }
363 :     #
364 :     # Do the usual Variable stuff.
365 :     #
366 :     $self->SUPER::new($equation,$name,$ref);
367 :     }
368 :    
369 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9