Parent Directory
|
Revision Log
Fix up comments
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: pg/macros/parserFormulaUpToConstant.pl,v 1.22 2009/10/07 14:39:19 dpvc 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 =head1 NAME 18 19 parserFormulaUpToConstant.pl - implements formulas "plus a constant". 20 21 =head1 DESCRIPTION 22 23 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 29 To use FormulaWithConstat objects, load this macro file at the 30 top of your problem: 31 32 loadMacros("parserFormulaUpToConstant.pl"); 33 34 then create a formula with constant as follows: 35 36 $f = FormulaUpToConstant("sin(x)+C"); 37 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 ANS($f->cmp); 48 49 Note that the FormulaUpToConstant object creates its own 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 Context($f->{context}); 56 57 would make the current context the one being used by the 58 FormulaUpToConstant, while 59 60 $f->{context}->variables->names 61 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 $f->constant. 68 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 $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 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 ANS($f->cmp(showHints => 0)); 87 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 ANS($f->cmp(showLinearityHints => 0)); 95 96 =cut 97 98 loadMacros("MathObjects.pl"); 99 100 sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()} 101 102 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 # Make a version with adaptive parameters for use in the 142 # comparison later on. We could like n00*C, but already have $n 143 # copies of C, so remove them. That way, n00 will be 0 when there 144 # are no C's in the student answer during the adaptive comparison. 145 # (Again, should really check that n00 is not in use already) 146 # 147 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 153 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 # 178 # Compare with adaptive parameters to see if $l + n00 C = $r for some n0. 179 # 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; 187 # 188 # Check that n00 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 return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol"); 192 } 193 194 # 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 $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 # 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 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; 247 } 248 249 ################################################## 250 # 251 # 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 # Show hints by default 259 # 260 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)}; 261 262 # 263 # 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 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 # 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 # Add useful messages, if the author requested them 291 # 292 sub cmp_postprocess { 293 my $self = shift; my $ans = shift; 294 $self->SUPER::cmp_postprocess($ans,@_); 295 return unless $ans->{score} == 0 && !$ans->{isPreview}; 296 return if $ans->{ans_message} || !$self->getFlag("showHints"); 297 my $student = $ans->{student_value}; 298 my $result = Parser::Eval(sub {return $ans->{correct_value} <=> $student}); # compare encodes the reason in the result 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 } 309 $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1; 310 $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 } 313 314 ################################################## 315 # 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 return $self->adjustInherit(main::Formula($self->substitute($self->{constant}=>0))->reduce); 326 } 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 |