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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 5440 Revision 6058
1################################################################################
2# WeBWorK Online Homework Delivery System
3# Copyright 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader$
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=head1 NAME
18
19parserFormulaUpToConstant.pl - implements formulas "plus a constant".
20
21=head1 DESCRIPTION
22
23This file implements the FormulaUpToConstant object, which is
24a formula that is only unique up to a constant (i.e., this is
25an anti-derivative). Students must include the "+C" as part of
26their answers, but they can use any (single-letter) constant that
27they want, and it doesn't have to be the one the professor used.
28
29To use FormulaWithConstat objects, load this macro file at the
30top of your problem:
31
32 loadMacros("parserFormulaUpToConstant.pl");
33
34then create a formula with constant as follows:
35
36 $f = FormulaUpToConstant("sin(x)+C");
37
38Note that the C should NOT already be a variable in the Context;
39the FormulaUpToConstant object will handle adding it in for
40you. If you don't include a constant in your formula (i.e., if
41all the variables that you used are already in your Context,
42then the FormulaUpToConstant object will add "+C" for you.
43
44The FormulaUpToConstant should work like any normal Formula,
45and in particular, you use $f->cmp to get its answer checker.
46
47 ANS($f->cmp);
48
49Note that the FormulaUpToConstant object creates its only private
50copy of the current Context (so that it can add variables without
51affecting the rest of the problem). You should not notice this
52in general, but if you need to access that context, use $f->{context}.
53E.g.
54
55 Context($f->{context});
56
57would make the current context the one being used by the
58FormulaUpToConstant, while
59
60 $f->{context}->variables->names
61
62would return a list of the variables in the private context.
63
64To get the name of the constant in use in the formula,
65use
66
67 $f->constant.
68
69If you combine a FormulaUpToConstant with other formulas,
70the result will be a new FormulaUpToConstant object, with
71a new Context, and potentially a new + C added to it. This
72is likely not what you want. Instead, you should convert
73back to a Formula first, then combine with other objects,
74then convert back to a FormulaUpToConstant, if necessary.
75To do this, use the removeConstant() method:
76
77 $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
82The answer evaluator by default will give "helpful" messages
83to the student when the "+ C" is left out. You can turn off
84these messages using the showHints option to the cmp() method:
85
86 ANS($f->cmp(showHints => 0));
87
88One of the hints is about whether the student's answer is linear
89in the arbitrary constant. This test requires differentiating
90the student answer. Since there are times when that could be
91problematic, you can disable that test via the showLinearityHints
92flag. (Note: setting showHints to 0 also disables these hints.)
93
94 ANS($f->cmp(showLinearityHints => 0));
95
96=cut
97
1loadMacros("MathObjects.pl"); 98loadMacros("MathObjects.pl");
2 99
3sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()} 100sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()}
4
5=head1 FormulaUpToConstant();
6
7 ######################################################################
8 #
9 # This file implements the FormulaUpToConstant object, which is
10 # a formula that is only unique up to a constant (i.e., this is
11 # an anti-derivative). Students must include the "+C" as part of
12 # their answers, but they can use any (single-letter) constant that
13 # they want, and it doesn't have to be the one the professor used.
14 #
15 # To use FormulaWithConstat objects, load this macro file at the
16 # top of your problem:
17 #
18 # loadMacros("parserFormulaUpToConstant.pl");
19 #
20 # then create a formula with constant as follows:
21 #
22 # $f = FormulaUpToConstant("sin(x)+C");
23 #
24 # Note that the C should NOT already be a variable in the Context;
25 # the FormulaUpToConstant object will handle adding it in for
26 # you. If you don't include a constant in your formula (i.e., if
27 # all the variables that you used are already in your Context,
28 # then the FormulaUpToConstant object will add "+C" for you.
29 #
30 # The FormulaUpToConstant should work like any normal Formula,
31 # and in particular, you use $f->cmp to get its answer checker.
32 #
33 # ANS($f->cmp);
34 #
35 # Note that the FormulaUpToConstant object creates its only private
36 # copy of the current Context (so that it can add variables without
37 # affecting the rest of the problem). You should not notice this
38 # in general, but if you need to access that context, use $f->{context}.
39 # E.g.
40 #
41 # Context($f->{context});
42 #
43 # would make the current context the one being used by the
44 # FormulaUpToConstant, while
45 #
46 # $f->{context}->variables->names
47 #
48 # would return a list of the variables in the private context.
49 #
50 # To get the name of the constant in use in the formula,
51 # use
52 #
53 # $f->constant.
54 #
55 # If you combine a FormulaUpToConstant with other formulas,
56 # the result will be a new FormulaUpToConstant object, with
57 # a new Context, and potentially a new + C added to it. This
58 # is likely not what you want. Instead, you should convert
59 # back to a Formula first, then combine with other objects,
60 # then convert back to a FormulaUpToConstant, if necessary.
61 # To do this, use the removeConstant() method:
62 #
63 # $f = FormulaUpToConstant("sin(x)+C");
64 # $g = Formula("cos(x)");
65 # $h = $f->removeConstant + $g; # $h will be "sin(x)+cos(x)"
66 # $h = FormulaUpToConstant($h); # $h will be "sin(x)+cos(x)+C"
67 #
68 # The answer evaluator by default will give "helpful" messages
69 # to the student when the "+ C" is left out. You can turn off
70 # these messages using the showHints option to the cmp() method:
71 #
72 # ANS($f->cmp(showHints => 0));
73 #
74 # One of the hints is about whether the student's answer is linear
75 # in the arbitrary constant. This test requires differentiating
76 # the student answer. Since there are times when that could be
77 # problematic, you can disable that test via the showLinearityHints
78 # flag. (Note: setting showHints to 0 also disables these hints.)
79 #
80 # ANS($f->cmp(showLinearityHints => 0));
81 #
82 ######################################################################
83
84=cut
85 101
86package FormulaUpToConstant; 102package FormulaUpToConstant;
87@ISA = ('Value::Formula'); 103@ISA = ('Value::Formula');
88 104
89sub Init { 105sub Init {
120 # 136 #
121 my $n = $f->D($f->{constant}); 137 my $n = $f->D($f->{constant});
122 Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant}) 138 Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant})
123 unless $n->isConstant; 139 unless $n->isConstant;
124 # 140 #
125 # Make a version with an adaptive parameter for use in the 141 # Make a version with adaptive parameters for use in the
126 # comparison later on. We could like n0*C, but already have $n 142 # comparison later on. We could like n0*C, but already have $n
127 # copies of C, so remove them. That way, n0 will be 0 when there 143 # copies of C, so remove them. That way, n0 will be 0 when there
128 # are no C's in the student answer during the adaptive comparison. 144 # are no C's in the student answer during the adaptive comparison.
129 # (Again, should really check that n0 is not in use already) 145 # (Again, should really check that n0 is not in use already)
130 # 146 #
131 my $n0 = $context->variables->get("n0"); 147 my $n00 = $context->variables->get("n00");
132 $context->variables->add(n0=>'Parameter') unless $n0 and $n0->{parameter}; 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};
133 $f->{adapt} = $f + "(n0-$n)$f->{constant}"; 151 $f->{adapt} = $f + "(n00-$n)$f->{constant} + n01";
152
134 return bless $f, $class; 153 return bless $f, $class;
135} 154}
136 155
137################################################## 156##################################################
138# 157#
152 return 3 if !defined($r->{constant}); 171 return 3 if !defined($r->{constant});
153 # 172 #
154 # If constants aren't the same, substitute the professor's in the student answer. 173 # If constants aren't the same, substitute the professor's in the student answer.
155 # 174 #
156 $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant}; 175 $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
176
157 # 177 #
158 # Compare with adaptive parameters to see if $l + n0 C = $r for some n0. 178 # Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
159 # 179 #
160 return -1 unless $l->{adapt} == $r; 180 my $adapt = $l->adapt;
181 my $equal = Parser::Eval(sub {$adapt == $r});
182 $self->{adapt} = $self->{adapt}->inherit($adapt); # save the adapted value's flags
183 $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 return -1 unless $equal;
161 # 187 #
162 # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer) 188 # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
163 # (remember: return value of 0 is equal, and non-zero is unequal) 189 # (remember: return value of 0 is equal, and non-zero is unequal)
164 # 190 #
165 return abs($context->variables->get("n0")->{value}) < $context->flag("zeroLevelTol"); 191 return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
192}
193
194#
195# Return the {adapt} formula with test points adjusted
196#
197sub 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#
206sub 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 $f->{$id} = [$f->{$id}->value] if Value::isValue($f->{$id});
213 $f->{$id} = [$f->{$id}] unless ref($f->{$id}) eq 'ARRAY';
214 $f->{$id} = [map {
215 (Value::isValue($_) ? [$_->value] :
216 (ref($_) eq 'ARRAY'? $_ : [$_]))
217 } @{$f->{$id}}];
218 $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#
228sub 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 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 }
243 push (@{$Points}, \@P);
244 }
245 }
246 return $Points;
166} 247}
167 248
168################################################## 249##################################################
169# 250#
170# Here we override part of the answer comparison 251# Here we override part of the answer comparison
177# Show hints by default 258# Show hints by default
178# 259#
179sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)}; 260sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
180 261
181# 262#
263# Provide diagnostics based on the adapted function used to check
264# the student's answer
265#
266sub cmp_diagnostics {
267 my $self = shift;
268 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}
273
274#
275# Make it possible to graph single-variable formulas by setting
276# the arbitrary constants to 0 first.
277#
278sub 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#
182# Add useful messages, if the author requested them 290# Add useful messages, if the author requested them
183# 291#
184sub cmp_postprocess { 292sub cmp_postprocess {
185 my $self = shift; my $ans = shift; 293 my $self = shift; my $ans = shift;
186 $self->SUPER::cmp_postprocess($ans); 294 $self->SUPER::cmp_postprocess($ans,@_);
187 return unless $ans->{score} == 0 && !$ans->{isPreview}; 295 return unless $ans->{score} == 0 && !$ans->{isPreview};
188 return if $ans->{ans_message} || !$self->getFlag("showHints"); 296 return if $ans->{ans_message} || !$self->getFlag("showHints");
189 my $student = $ans->{student_value}; 297 my $student = $ans->{student_value};
190 my $result = $ans->{correct_value} <=> $student; # compare encodes the reason in the result 298 my $result = Parser::Eval(sub {return $ans->{correct_value} <=> $student}); # compare encodes the reason in the result
191 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3; 299 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
300 if ($result == 3) {
301 my $context = $self->context;
302 $context->flags->set(no_parameters=>0);
303 $context->variables->add(x00=>'Real');
304 my $correct = $self->removeConstant+"n01+n00x00"; # must use both parameters
305 $result = 1 if $correct->cmp_compare($student+"x00",{});
306 $context->variables->remove('x00');
307 $context->flags->set(no_parameters=>1);
308 }
192 $self->cmp_Error($ans,"Your answer is not the most general solution") 309 $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
193 if $result == 1 || ($result == 3 && $self->removeConstant == $student);
194 $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'") 310 $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
195 if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant; 311 if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
196} 312}
197 313
198################################################## 314##################################################
204# 320#
205# Remove the constant and return a Formula object 321# Remove the constant and return a Formula object
206# 322#
207sub removeConstant { 323sub removeConstant {
208 my $self = shift; 324 my $self = shift;
209 main::Formula($self->substitute($self->{constant}=>0))->reduce; 325 return $self->adjustInherit(main::Formula($self->substitute($self->{constant}=>0))->reduce);
210} 326}
211 327
212# 328#
213# Override the differentiation so that we always return 329# Override the differentiation so that we always return
214# a Formula, not a FormulaUpToConstant (we don't want to 330# a Formula, not a FormulaUpToConstant (we don't want to

Legend:
Removed from v.5440  
changed lines
  Added in v.6058

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9