[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 5556 - (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 :     # $CVSHeader: webwork2/lib/WeBWorK.pm,v 1.100 2007/08/13 22:59:53 sh002i Exp $
5 :     #
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 5393 return bless $f, $class;
153 :     }
154 :    
155 :     ##################################################
156 :     #
157 :     # Remember that compare implements the overloaded perl <=> operator,
158 :     # and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b.
159 :     # In our case, we only care about equality, so we will return 0 when
160 :     # equal and other numbers to indicate the reason they are not equal
161 :     # (this can be used by the answer checker to print helpful messages)
162 :     #
163 :     sub compare {
164 :     my ($l,$r) = @_; my $self = $l; my $context = $self->context;
165 :     $r = Value::makeValue($r,context=>$context);
166 :     #
167 :     # Not equal if the student value is constant or has no + C
168 :     #
169 :     return 2 if !Value::isFormula($r);
170 :     return 3 if !defined($r->{constant});
171 :     #
172 :     # If constants aren't the same, substitute the professor's in the student answer.
173 :     #
174 :     $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
175 :     #
176 :     # Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
177 :     #
178 :     return -1 unless $l->{adapt} == $r;
179 :     #
180 :     # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
181 :     # (remember: return value of 0 is equal, and non-zero is unequal)
182 :     #
183 : dpvc 5460 return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
184 : dpvc 5393 }
185 :    
186 : dpvc 5440 ##################################################
187 : dpvc 5393 #
188 : dpvc 5440 # Here we override part of the answer comparison
189 :     # routines in order to be able to generate
190 :     # helpful error messages for students when
191 :     # they leave off the + C.
192 :     #
193 :    
194 :     #
195 : dpvc 5393 # Show hints by default
196 :     #
197 : dpvc 5440 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
198 : dpvc 5393
199 :     #
200 :     # Add useful messages, if the author requested them
201 :     #
202 :     sub cmp_postprocess {
203 :     my $self = shift; my $ans = shift;
204 :     $self->SUPER::cmp_postprocess($ans);
205 :     return unless $ans->{score} == 0 && !$ans->{isPreview};
206 :     return if $ans->{ans_message} || !$self->getFlag("showHints");
207 : dpvc 5440 my $student = $ans->{student_value};
208 :     my $result = $ans->{correct_value} <=> $student; # compare encodes the reason in the result
209 : dpvc 5393 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
210 : dpvc 5461 if ($result == 3) {
211 : dpvc 5463 my $context = $self->context;
212 :     $context->flags->set(no_parameters=>0);
213 :     $context->variables->add(x00=>'Real');
214 :     $result = 1 if $self->removeConstant+"n01+n00x00" == $student+"x00"; # must use both parameters
215 :     $context->variables->remove('x00');
216 :     $context->flags->set(no_parameters=>1);
217 : dpvc 5461 }
218 :     $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
219 : dpvc 5440 $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
220 :     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
221 : dpvc 5393 }
222 :    
223 : dpvc 5440 ##################################################
224 : dpvc 5393 #
225 :     # Get the name of the constant
226 :     #
227 :     sub constant {(shift)->{constant}}
228 :    
229 :     #
230 :     # Remove the constant and return a Formula object
231 :     #
232 :     sub removeConstant {
233 :     my $self = shift;
234 :     main::Formula($self->substitute($self->{constant}=>0))->reduce;
235 :     }
236 :    
237 :     #
238 :     # Override the differentiation so that we always return
239 :     # a Formula, not a FormulaUpToConstant (we don't want to
240 :     # add the C in again).
241 :     #
242 :     sub D {
243 :     my $self = shift;
244 :     $self->removeConstant->D(@_);
245 :     }
246 :    
247 :     ######################################################################
248 :     #
249 :     # This class repalces the Parser::Variable class, and its job
250 :     # is to look for new constants that aren't in the context,
251 :     # and add them in. This allows students to use ANY constant
252 :     # they want, and a different one from the professor. We check
253 :     # that the student only used ONE arbitrary constant, however.
254 :     #
255 :     package FormulaUpToConstant::Variable;
256 :     our @ISA = ('Parser::Variable');
257 :    
258 :     sub new {
259 :     my $self = shift; my $class = ref($self) || $self;
260 :     my $equation = shift; my $variables = $equation->{context}{variables};
261 :     my ($name,$ref) = @_; my $def = $variables->{$name};
262 :     #
263 :     # If the variable is not already in the context, add it
264 :     # and mark it as an arbitrary constant (for later reference)
265 :     #
266 :     if (!defined($def) && length($name) eq 1) {
267 :     $equation->{context}->variables->add($name => 'Real');
268 :     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
269 :     $def = $variables->{$name};
270 :     }
271 :     #
272 :     # If the variable is an arbitrary constant
273 :     # Error if we already have a constant and it's not this one.
274 :     # Save the constant so we can check with it later.
275 :     #
276 :     if ($def && $def->{arbitraryConstant}) {
277 :     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
278 :     if $equation->{constant} and $name ne $equation->{constant};
279 :     $equation->{constant} = $name;
280 :     }
281 :     #
282 :     # Do the usual Variable stuff.
283 :     #
284 :     $self->SUPER::new($equation,$name,$ref);
285 :     }
286 :    
287 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9