Parent Directory | Revision Log

Revision **5393** -
(**download**)
(**as text**)
(**annotate**)

*Mon Aug 20 04:23:02 2007 UTC*
(12 years, 6 months ago)
by *dpvc*

File size: 8337 byte(s)

File size: 8337 byte(s)

Implements a general antiderivative formula object where the student must include a "+ C" (using any variable he or she chooses) and the correct answer also shows a "+ C". See the comments in the file for details.

1 loadMacros("MathObjects.pl"); 2 3 sub _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"); 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 ###################################################################### 69 70 =cut 71 72 package FormulaUpToConstant; 73 @ISA = ('Value::Formula'); 74 75 sub Init { 76 main::PG_restricted_eval('sub FormulaUpToConstant {FormulaUpToConstant->new(@_)}'); 77 } 78 79 # 80 # Create an instance of a FormulaUpToConstant. If no constant 81 # is supplied, we add C ourselves. 82 # 83 sub new { 84 my $self = shift; my $class = ref($self) || $self; 85 # 86 # Copy the context (so we can modify it) and 87 # replace the usual Variable object with our own. 88 # 89 my $context = (Value::isContext($_[0]) ? shift : $self->context)->copy; 90 $context->{parser}{Variable} = 'FormulaUpToConstant::Variable'; 91 # 92 # Create a formula from the user's input. 93 # 94 my $f = main::Formula($context,@_); 95 # 96 # If it doesn't have a constant already, add one. 97 # (should check that C isn't already in use, and look 98 # up the first free name, but we'll cross our fingers 99 # for now. Could look through the defined variables 100 # to see if there is already an arbitraryConstant 101 # and use that.) 102 # 103 unless ($f->{constant}) {$f = $f + "C", $f->{constant} = "C"} 104 # 105 # Check that the formula is linear in C. 106 # 107 my $n = $f->D($f->{constant}); 108 Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant}) 109 unless $n->isConstant; 110 # 111 # Make a version with an adaptive parameter for use in the 112 # comparison later on. We could like n0*C, but already have $n 113 # copies of C, so remove them. That way, n0 will be 0 when there 114 # are no C's in the student answer during the adaptive comparison. 115 # (Again, should really check that n0 is not in use already) 116 # 117 my $n0 = $context->variables->get("n0"); 118 $context->variables->add(n0=>'Parameter') unless $n0 and $n0->{parameter}; 119 $f->{adapt} = $f + "(n0-$n)$f->{constant}"; 120 return bless $f, $class; 121 } 122 123 ################################################## 124 # 125 # Remember that compare implements the overloaded perl <=> operator, 126 # and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b. 127 # In our case, we only care about equality, so we will return 0 when 128 # equal and other numbers to indicate the reason they are not equal 129 # (this can be used by the answer checker to print helpful messages) 130 # 131 sub compare { 132 my ($l,$r) = @_; my $self = $l; my $context = $self->context; 133 $r = Value::makeValue($r,context=>$context); 134 # 135 # Not equal if the student value is constant or has no + C 136 # 137 return 2 if !Value::isFormula($r); 138 return 3 if !defined($r->{constant}); 139 # 140 # If constants aren't the same, substitute the professor's in the student answer. 141 # 142 $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant}; 143 # 144 # Compare with adaptive parameters to see if $l + n0 C = $r for some n0. 145 # 146 return -1 unless $l->{adapt} == $r; 147 # 148 # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer) 149 # (remember: return value of 0 is equal, and non-zero is unequal) 150 # 151 return abs($context->variables->get("n0")->{value}) < $context->flag("zeroLevelTol"); 152 } 153 154 # 155 # Show hints by default 156 # 157 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1)}; 158 159 # 160 # Add useful messages, if the author requested them 161 # 162 sub cmp_postprocess { 163 my $self = shift; my $ans = shift; 164 $self->SUPER::cmp_postprocess($ans); 165 return unless $ans->{score} == 0 && !$ans->{isPreview}; 166 return if $ans->{ans_message} || !$self->getFlag("showHints"); 167 my $result = $ans->{correct_value} <=> $ans->{student_value}; # compare encodes the reason in the result 168 $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3; 169 $self->cmp_Error($ans,"Your answer is not the most general solution") 170 if $result == 1 || ($result == 3 && $self->removeConstant == $ans->{student_value}); 171 } 172 173 # 174 # Get the name of the constant 175 # 176 sub constant {(shift)->{constant}} 177 178 # 179 # Remove the constant and return a Formula object 180 # 181 sub removeConstant { 182 my $self = shift; 183 main::Formula($self->substitute($self->{constant}=>0))->reduce; 184 } 185 186 # 187 # Override the differentiation so that we always return 188 # a Formula, not a FormulaUpToConstant (we don't want to 189 # add the C in again). 190 # 191 sub D { 192 my $self = shift; 193 $self->removeConstant->D(@_); 194 } 195 196 ###################################################################### 197 # 198 # This class repalces the Parser::Variable class, and its job 199 # is to look for new constants that aren't in the context, 200 # and add them in. This allows students to use ANY constant 201 # they want, and a different one from the professor. We check 202 # that the student only used ONE arbitrary constant, however. 203 # 204 package FormulaUpToConstant::Variable; 205 our @ISA = ('Parser::Variable'); 206 207 sub new { 208 my $self = shift; my $class = ref($self) || $self; 209 my $equation = shift; my $variables = $equation->{context}{variables}; 210 my ($name,$ref) = @_; my $def = $variables->{$name}; 211 # 212 # If the variable is not already in the context, add it 213 # and mark it as an arbitrary constant (for later reference) 214 # 215 if (!defined($def) && length($name) eq 1) { 216 $equation->{context}->variables->add($name => 'Real'); 217 $equation->{context}->variables->set($name => {arbitraryConstant => 1}); 218 $def = $variables->{$name}; 219 } 220 # 221 # If the variable is an arbitrary constant 222 # Error if we already have a constant and it's not this one. 223 # Save the constant so we can check with it later. 224 # 225 if ($def && $def->{arbitraryConstant}) { 226 $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref) 227 if $equation->{constant} and $name ne $equation->{constant}; 228 $equation->{constant} = $name; 229 } 230 # 231 # Do the usual Variable stuff. 232 # 233 $self->SUPER::new($equation,$name,$ref); 234 } 235 236 237 1;

aubreyja at gmail dot com | ViewVC Help |

Powered by ViewVC 1.0.9 |