[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 5913 - (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 5913 # $CVSHeader: pg/macros/parserFormulaUpToConstant.pl,v 1.16 2008/09/15 15:35:34 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 : dpvc 5913 $context->{diagnostics}{formulas}{showGraphs} = 0;
122 : dpvc 5393 #
123 :     # Create a formula from the user's input.
124 :     #
125 :     my $f = main::Formula($context,@_);
126 :     #
127 :     # If it doesn't have a constant already, add one.
128 :     # (should check that C isn't already in use, and look
129 :     # up the first free name, but we'll cross our fingers
130 :     # for now. Could look through the defined variables
131 :     # to see if there is already an arbitraryConstant
132 :     # and use that.)
133 :     #
134 :     unless ($f->{constant}) {$f = $f + "C", $f->{constant} = "C"}
135 :     #
136 :     # Check that the formula is linear in C.
137 :     #
138 :     my $n = $f->D($f->{constant});
139 :     Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant})
140 :     unless $n->isConstant;
141 :     #
142 : dpvc 5460 # Make a version with adaptive parameters for use in the
143 : dpvc 5393 # comparison later on. We could like n0*C, but already have $n
144 :     # copies of C, so remove them. That way, n0 will be 0 when there
145 :     # are no C's in the student answer during the adaptive comparison.
146 :     # (Again, should really check that n0 is not in use already)
147 :     #
148 : dpvc 5460 my $n00 = $context->variables->get("n00");
149 :     $context->variables->add(n00=>'Parameter') unless $n00 and $n00->{parameter};
150 :     my $n01 = $context->variables->get("n01");
151 :     $context->variables->add(n01=>'Parameter') unless $n01 and $n01->{parameter};
152 :     $f->{adapt} = $f + "(n00-$n)$f->{constant} + n01";
153 : dpvc 5907
154 : dpvc 5393 return bless $f, $class;
155 :     }
156 :    
157 :     ##################################################
158 :     #
159 :     # Remember that compare implements the overloaded perl <=> operator,
160 :     # and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b.
161 :     # In our case, we only care about equality, so we will return 0 when
162 :     # equal and other numbers to indicate the reason they are not equal
163 :     # (this can be used by the answer checker to print helpful messages)
164 :     #
165 :     sub compare {
166 :     my ($l,$r) = @_; my $self = $l; my $context = $self->context;
167 :     $r = Value::makeValue($r,context=>$context);
168 :     #
169 :     # Not equal if the student value is constant or has no + C
170 :     #
171 :     return 2 if !Value::isFormula($r);
172 :     return 3 if !defined($r->{constant});
173 :     #
174 :     # If constants aren't the same, substitute the professor's in the student answer.
175 :     #
176 :     $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
177 :     #
178 :     # Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
179 :     #
180 : dpvc 5913 my $adapt = $l->adapt;
181 :     $main::{_cmp_} = sub {return $adapt == $r}; # a closure to access local variables
182 : dpvc 5912 my $equal = main::PG_restricted_eval('&{$main::{_cmp_}}'); # prevents errors with large adaptive parameters
183 :     delete $main::{_cmp_}; # remove temprary function
184 : dpvc 5913 $self->{adapt} = $self->{adapt}->inherit($adapt); # save the adapted value's flags
185 : dpvc 5673 return -1 unless $equal;
186 : dpvc 5393 #
187 :     # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
188 :     # (remember: return value of 0 is equal, and non-zero is unequal)
189 :     #
190 : dpvc 5460 return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
191 : dpvc 5393 }
192 :    
193 : dpvc 5912 #
194 : dpvc 5913 # Provide diagnostics based on the adapted function used to check
195 :     # the student's answer
196 :     #
197 :     sub cmp_diagnostics {
198 :     my $self = shift;
199 :     $self->inherit($self->{adapt})->SUPER::cmp_diagnostics(@_);
200 :     }
201 :    
202 :     #
203 : dpvc 5912 # Return the {adapt} formula with test points adjusted
204 :     #
205 :     sub adapt {
206 :     my $self = shift;
207 :     my $adapt = $self->{adapt}->inherit($self); delete $adapt->{adapt};
208 :     return $self->adjustInherit($self->{adapt});
209 :     }
210 :    
211 :     #
212 :     # Inherit from the main FormulaUpToConstant, but
213 :     # adjust the test points to include the constants
214 :     #
215 :     sub adjustInherit {
216 :     my $self = shift;
217 :     my $f = shift->inherit($self);
218 :     delete $f->{adapt}; delete $f->{constant};
219 :     foreach my $id ('test_points','test_at') {
220 :     if (defined $f->{$id}) {
221 :     $f->{$id} = $f->{$id}->value if Value::isValue($f->{$id});
222 :     $f->{$id} = [$f->{$id}] unless ref($f->{$id}) eq 'ARRAY';
223 :     $f->{$id} = [map {[$_]} @{$f->{$id}}] unless ref($f->{$id}[0]) eq 'ARRAY';
224 :     $f->{$id} = $self->addConstants($f->{$id});
225 :     }
226 :     }
227 :     return $f;
228 :     }
229 :    
230 :     #
231 :     # Insert dummy values for the constants for the test points
232 :     # (These are supposed to be +C, so the value shouldn't matter?)
233 :     #
234 :     sub addConstants {
235 :     my $self = shift; my $points = shift;
236 :     my @names = $self->context->variables->variables;
237 :     my $variables = $self->context->{variables};
238 :     my $Points = [];
239 :     foreach my $p (@{$points}) {
240 :     my @P = (.1) x scalar(@names); my $j = 0;
241 :     foreach my $i (0..scalar(@names)-1) {
242 :     if (!$variables->{$names[$i]}{arbitraryConstant}) {
243 :     $P[$i] = $p->[$j] if defined $p->[$j]; $j++;
244 :     }
245 :     }
246 :     push (@{$Points}, \@P);
247 :     }
248 :     return $Points;
249 :     }
250 :    
251 : dpvc 5440 ##################################################
252 : dpvc 5393 #
253 : dpvc 5440 # Here we override part of the answer comparison
254 :     # routines in order to be able to generate
255 :     # helpful error messages for students when
256 :     # they leave off the + C.
257 :     #
258 :    
259 :     #
260 : dpvc 5393 # Show hints by default
261 :     #
262 : dpvc 5440 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
263 : dpvc 5393
264 :     #
265 :     # Add useful messages, if the author requested them
266 :     #
267 :     sub cmp_postprocess {
268 :     my $self = shift; my $ans = shift;
269 :     $self->SUPER::cmp_postprocess($ans);
270 :     return unless $ans->{score} == 0 && !$ans->{isPreview};
271 :     return if $ans->{ans_message} || !$self->getFlag("showHints");
272 : dpvc 5440 my $student = $ans->{student_value};
273 :     my $result = $ans->{correct_value} <=> $student; # compare encodes the reason in the result
274 : dpvc 5393 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
275 : dpvc 5461 if ($result == 3) {
276 : dpvc 5463 my $context = $self->context;
277 :     $context->flags->set(no_parameters=>0);
278 :     $context->variables->add(x00=>'Real');
279 : dpvc 5905 my $correct = $self->removeConstant+"n01+n00x00"; # must use both parameters
280 :     $main::{_cmp_} = sub {return $correct == $student+"x00"}; # a closure to access local variables
281 :     $result = 1 if main::PG_restricted_eval('&{$main::{_cmp_}}'); # prevents domain errors (and other errors)
282 :     delete $main::{_cmp_}; # remove temprary function
283 : dpvc 5463 $context->variables->remove('x00');
284 :     $context->flags->set(no_parameters=>1);
285 : dpvc 5461 }
286 :     $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
287 : dpvc 5440 $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
288 :     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
289 : dpvc 5393 }
290 :    
291 : dpvc 5440 ##################################################
292 : dpvc 5393 #
293 :     # Get the name of the constant
294 :     #
295 :     sub constant {(shift)->{constant}}
296 :    
297 :     #
298 :     # Remove the constant and return a Formula object
299 :     #
300 :     sub removeConstant {
301 :     my $self = shift;
302 : dpvc 5912 return $self->adjustInherit(main::Formula($self->substitute($self->{constant}=>0))->reduce);
303 : dpvc 5393 }
304 :    
305 :     #
306 :     # Override the differentiation so that we always return
307 :     # a Formula, not a FormulaUpToConstant (we don't want to
308 :     # add the C in again).
309 :     #
310 :     sub D {
311 :     my $self = shift;
312 :     $self->removeConstant->D(@_);
313 :     }
314 :    
315 :     ######################################################################
316 :     #
317 :     # This class repalces the Parser::Variable class, and its job
318 :     # is to look for new constants that aren't in the context,
319 :     # and add them in. This allows students to use ANY constant
320 :     # they want, and a different one from the professor. We check
321 :     # that the student only used ONE arbitrary constant, however.
322 :     #
323 :     package FormulaUpToConstant::Variable;
324 :     our @ISA = ('Parser::Variable');
325 :    
326 :     sub new {
327 :     my $self = shift; my $class = ref($self) || $self;
328 :     my $equation = shift; my $variables = $equation->{context}{variables};
329 :     my ($name,$ref) = @_; my $def = $variables->{$name};
330 :     #
331 :     # If the variable is not already in the context, add it
332 :     # and mark it as an arbitrary constant (for later reference)
333 :     #
334 :     if (!defined($def) && length($name) eq 1) {
335 :     $equation->{context}->variables->add($name => 'Real');
336 :     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
337 :     $def = $variables->{$name};
338 :     }
339 :     #
340 :     # If the variable is an arbitrary constant
341 :     # Error if we already have a constant and it's not this one.
342 :     # Save the constant so we can check with it later.
343 :     #
344 :     if ($def && $def->{arbitraryConstant}) {
345 :     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
346 :     if $equation->{constant} and $name ne $equation->{constant};
347 :     $equation->{constant} = $name;
348 :     }
349 :     #
350 :     # Do the usual Variable stuff.
351 :     #
352 :     $self->SUPER::new($equation,$name,$ref);
353 :     }
354 :    
355 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9