[system] / trunk / pg / macros / extraAnswerEvaluators.pl Repository: Repository Listing bbplugincoursesdistsnplrochestersystemwww

# Annotation of /trunk/pg/macros/extraAnswerEvaluators.pl

Revision 3513 - (view) (download) (as text)

 1 : jj 3455 loadMacros('Parser.pl'); 2 : gage 1064 3 : =head1 NAME 4 : 5 : extraAnswerEvaluators.pl -- located in the courseScripts directory 6 : 7 : =head1 SYNPOSIS 8 : 9 : Answer Evaluators for intervals, lists of numbers, lists of points, 10 : and equations. 11 : 12 : interval_cmp() -- checks answers which are unions of intervals. 13 : It can also be used for checking an ordered pair or 14 : list of ordered pairs. 15 : apizer 1080 16 : gage 1064 number_list_cmp() -- checks a comma separated list of numbers. By use of 17 : optional arguments, you can request that order be 18 : important, that complex numbers be allowed, and 19 : specify extra arguments to be sent to num_cmp (or 20 : cplx_cmp) for checking individual entries. 21 : apizer 1080 22 : gage 1064 equation_cmp() -- provides a limited facility for checking equations. 23 : It makes no pretense of checking to see if the real locus 24 : of the student's equation matches the real locus of the 25 : instructor's equation. The student's equation must be 26 : of the same general type as the instructors to get credit. 27 : 28 : 29 : =cut 30 : 31 : =head1 DESCRIPTION 32 : 33 : This file adds subroutines which create "answer evaluators" for checking student 34 : answers of various "exotic" types. 35 : 36 : =cut 37 : 38 : { 39 : package Equation_eval; 40 : jj 3489 41 : gage 1064 sub split_eqn { 42 : my $instring = shift; 43 : jj 3489 44 : split /=/,$instring; 45 : gage 1064 } 46 : jj 3489 47 : apizer 1080 48 : gage 1064 sub equation_cmp { 49 : my $right_ans = shift; 50 : my %opts = @_; 51 : my$vars = ['x','y']; 52 : 53 : apizer 1080 54 : gage 1064 $vars =$opts{'vars'} if defined($opts{'vars'}); 55 : 56 : my$ans_eval = sub { 57 : my $student = shift; 58 : apizer 1080 59 : gage 1064 my$ans_hash = new AnswerHash( 60 : 'score'=>0, 61 : 'correct_ans'=>$right_ans, 62 : 'student_ans'=>$student, 63 : 'original_student_ans' => $student, 64 : # 'type' => undef, 65 : 'ans_message'=>'', 66 : 'preview_text_string'=>'', 67 : 'preview_latex_string'=>'', 68 : ); 69 : 70 : if(! ($student =~ /\S/)) { return $ans_hash; } 71 : apizer 1080 72 : gage 1064 my @right= split_eqn($right_ans); 73 : if(scalar(@right) != 2) { 74 : $ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 75 : return$ans_hash; 76 : } 77 : my @studsplit = split_eqn($student); 78 : if(scalar(@studsplit) != 2) { 79 :$ans_hash->{'ans_message'} = "You did not enter an equation (with an equals sign and two sides)."; 80 : return $ans_hash; 81 : } 82 : 83 : # Next we should do syntax checks on everyone 84 : 85 : my$ah = new AnswerHash; 86 : $ah->input($right[0]); 87 : $ah=main::check_syntax($ah); 88 : if($ah->{error_flag}) { 89 :$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 90 : return $ans_hash; 91 : } 92 : apizer 1080 93 : gage 1064$ah->input($right[1]); 94 :$ah=main::check_syntax($ah); 95 : if($ah->{error_flag}) { 96 : $ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 97 : return$ans_hash; 98 : } 99 : 100 : # Correct answer checks out, now check student's syntax 101 : 102 : my @prevs = ("",""); 103 : my @prevtxt = ("",""); 104 : $ah->input($studsplit[0]); 105 : $ah=main::check_syntax($ah); 106 : if($ah->{error_flag}) { 107 :$ans_hash->{'ans_message'} = "Syntax error on the left side of your equation."; 108 : return $ans_hash; 109 : } 110 :$prevs[0] = $ah->{'preview_latex_string'}; 111 :$prevstxt[0] = $ah->{'preview_text_string'}; 112 : apizer 1080 113 : 114 : gage 1064$ah->input($studsplit[1]); 115 :$ah=main::check_syntax($ah); 116 : if($ah->{error_flag}) { 117 : $ans_hash->{'ans_message'} = "Syntax error on the right side of your equation."; 118 : return$ans_hash; 119 : } 120 : $prevs[1] =$ah->{'preview_latex_string'}; 121 : $prevstxt[1] =$ah->{'preview_text_string'}; 122 : 123 : $ans_hash->{'preview_latex_string'} = "$prevs[0] = $prevs[1]"; 124 :$ans_hash->{'preview_text_string'} = "$prevstxt[0] =$prevstxt[1]"; 125 : apizer 1080 126 : 127 : gage 1064 # Check for answer equivalent to 0=0 128 : # Could be false positive below because of parameter 129 : my $ae = main::fun_cmp("0", %opts); 130 : my$res = $ae->evaluate("$studsplit[0]-($studsplit[1])"); 131 : if($res->{'score'}==1) { 132 : # Student is 0=0, is correct answer also like this? 133 : $res =$ae->evaluate("$right[0]-($right[1])"); 134 : if($res->{'score'}==1) { 135 :$ans_hash-> setKeys('score' => $res->{'score'}); 136 : } 137 : return$ans_hash; 138 : } 139 : 140 : # Maybe answer really is 0=0, and student got it wrong, so check that 141 : $res =$ae->evaluate("$right[0]-($right[1])"); 142 : if($res->{'score'}==1) { 143 : return$ans_hash; 144 : } 145 : 146 : # Finally, use fun_cmp to check the answers 147 : apizer 1080 148 : gage 1064 $ae = main::fun_cmp("o*($right[0]-($right[1]))", vars=>$vars, params=>['o'], %opts); 149 : $res=$ae->evaluate("$studsplit[0]-($studsplit[1])"); 150 : $ans_hash-> setKeys('score' =>$res->{'score'}); 151 : apizer 1080 152 : gage 1064 return $ans_hash; 153 : }; 154 : 155 : return$ans_eval; 156 : } 157 : } 158 : 159 : jj 3506 sub mode2context { 160 : my $mode = shift; 161 : jj 3513 my %options = @_; 162 : jj 3506 my$context; 163 : for ($mode) { 164 : /^strict$/i and do { 165 : $context =$Parser::Context::Default::context{LimitedNumeric}->copy; 166 : $context->operators->redefine(','); 167 : last; 168 : }; 169 : /^arith$/i and do { 170 : $context =$Parser::Context::Default::context{LegacyNumeric}->copy; 171 : $context->functions->disable('All'); 172 : last; 173 : }; 174 : /^frac$/i and do { 175 : $context =$Parser::Context::Default::context{'LimitedNumeric-Fraction'}->copy; 176 : $context->operators->redefine(','); 177 : last; 178 : }; 179 : 180 : # default 181 :$context = $Parser::Context::Default::context{LegacyNumeric}->copy; 182 : } 183 : jj 3513 # If we are using complex numbers, then we ignore the other mode parts 184 : if(defined($options{'complex'}) && 185 : ($options{'complex'} =~ /(yes|ok)/i)) { 186 : #$context->constants->redefine('i', from=>'Complex'); 187 : #$context->functions->redefine(['arg','mod','Re','Im','conj', 'sqrt', 'log'], from=>'Complex'); 188 : #$context->operators->redefine(['^', '**'], from=>'Complex'); 189 : $context =$Parser::Context::Default::context{'Complex'}; 190 : } 191 : jj 3506 $options{tolType} =$options{tolType} || 'relative'; 192 : $options{tolerance} =$options{tolerance} || $options{tol} || 193 :$options{reltol} || $options{relTol} ||$options{abstol} || 1; 194 : $options{zeroLevel} =$options{zeroLevel} || $options{zeroLevelTol} || 195 :$main::numZeroLevelTolDefault; 196 : if ($options{tolType} eq 'absolute' or defined($options{tol}) 197 : or defined($options{abstol})) { 198 :$context->flags->set( 199 : tolerance => $options{tolerance}, 200 : tolType => 'absolute', 201 : ); 202 : } else { 203 :$context->flags->set( 204 : tolerance => .01*$options{tolerance}, 205 : tolType => 'relative', 206 : ); 207 : } 208 :$context->flags->set( 209 : zeroLevel => $options{zeroLevel}, 210 : zeroLevelTol =>$options{zeroLevelTol}, 211 : ); 212 : $context->{format}{number} =$options{'format'} || $main::numFormatDefault; 213 : return($context); 214 : } 215 : 216 : gage 1064 =head3 interval_cmp () 217 : 218 : Compares an interval or union of intervals. Typical invocations are 219 : 220 : interval_cmp("(2, 3] U(7, 11)") 221 : 222 : The U is used for union symbol. In fact, any garbage (or nothing at all) 223 : can go between intervals. It makes sure open/closed parts of intervals 224 : are correct, unless you don't like that. To have it ignore the difference 225 : between open and closed endpoints, use 226 : 227 : interval_cmp("(2, 3] U(7, 11)", sloppy=>'yes') 228 : 229 : interval_cmp uses num_cmp on the endpoints. You can pass optional 230 : arguments for num_cmp, so to change the tolerance, you can use 231 : 232 : interval_cmp("(2, 3] U(3+4, 11)", relTol=>3) 233 : 234 : The intervals can be listed in any order, unless you want to force a 235 : particular order, which is signaled as 236 : 237 : interval_cmp("(2, 3] U(3+4, 11)", ordered=>'strict') 238 : 239 : You can specify infinity as an endpoint. It will do a case-insensitive 240 : string match looking for I, Infinity, Infty, or Inf. You can prepend a + 241 : or -, as in 242 : 243 : interval_cmp("(-inf, 3] U [e^10, infinity)") 244 : or 245 : interval_cmp("(-INF, 3] U [e^10, +I)") 246 : 247 : If the question might have an empty set as the answer, you can use 248 : the strings option to allow for it. So 249 : 250 : interval_cmp("$ans", strings=>['empty']) 251 : 252 : will not generate an error message if the student enters the string 253 : empty. Better still, it will mark a student answer of "empty" as correct 254 : iff this matches$ans. 255 : 256 : You can use interval_cmp for ordered pairs, or lists of ordered pairs. 257 : Internally, this is just a distinction of whether to put nice union symbols 258 : between intervals, or commas. To get commas, use 259 : 260 : interval_cmp("(1,2), (2,3), (4,-1)", unions=>'no') 261 : 262 : Note that interval_cmp makes no attempt at simplifying overlapping intervals. 263 : This becomes an important feature when you are really checking lists of 264 : ordered pairs. 265 : 266 : jj 3489 Now we use the Parser package for checking intervals (or lists of 267 : points if unions=>'no'). So, one can specify the Parser options 268 : showCoordinateHints, showHints, partialCredit, and/or showLengthHints 269 : as optional arguments: 270 : 271 : interval_cmp("(1,2), (2,3), (4,-1)", unions=>'no', partialCredit=>1) 272 : 273 : Also, set differences and 'R' for all real numbers now work too since they work 274 : for Parser Intervals and Unions. 275 : 276 : gage 1064 =cut 277 : 278 : jj 3489 sub interval_cmp { 279 : jj 3462 my $correct_ans = shift; 280 : 281 : my %opts = @_; 282 : 283 : jj 3489 my$mode = $opts{mode} || 'std'; 284 : jj 3462 my %options = (debug =>$opts{debug}); 285 : jj 3506 my $ans_type = ''; # set to List, Union, or String below 286 : jj 3462 287 : # 288 : # Get an apppropriate context based on the mode 289 : # 290 : my$oldContext = Context(); 291 : jj 3506 my $context = mode2context($mode, %opts); 292 : jj 3489 293 : jj 3462 if(defined($opts{unions}) and$opts{unions} eq 'no' ) { 294 : jj 3463 # This is really a list of points, not intervals at all 295 : jj 3462 $ans_type = 'List'; 296 : jj 3489$context->parens->redefine('('); 297 : $context->parens->redefine('['); 298 : jj 3506$context->parens->redefine('{'); 299 : $context->operators->redefine('u',using=>','); 300 :$context->operators->set(u=>{string=>", ", TeX=>',\,'}); 301 : jj 3462 } else { 302 : jj 3489 $context->parens->redefine('(', from=>'Interval'); 303 :$context->parens->redefine('[', from=>'Interval'); 304 : $context->parens->redefine('{', from=>'Interval'); 305 : jj 3506 306 : jj 3513$context->constants->redefine('R',from=>'Interval'); 307 : jj 3489 $context->operators->redefine('U',from=>"Interval"); 308 :$context->operators->redefine('u',from=>"Interval",using=>"U"); 309 : jj 3506 $ans_type = 'Union'; 310 : jj 3462 } 311 : jj 3506 # Take optional arguments intended for List, or Union 312 : jj 3489 for my$o qw( showCoordinateHints showHints partialCredit showLengthHints ) { 313 : $options{$o} = $opts{$o} || 0; 314 : } 315 : jj 3513 $options{showUnionReduceWarnings} =$opts{showUnionReduceWarnings}; 316 : $options{studentsMustReduceUnions} =$opts{studentsMustReduceUnions}; 317 : if(defined($opts{ordered}) and$opts{ordered}) { 318 : $options{ordered} = 1; 319 : # Force this option if the the union must be ordered 320 :$options{studentsMustReduceUnions} = 1; 321 : } 322 : jj 3462 if (defined($opts{'sloppy'}) &&$opts{'sloppy'} eq 'yes') { 323 : $options{requireParenMatch} = 0; 324 : } 325 : jj 3489 # historically we allow more infinities 326 : jj 3463$context->strings->add( 327 : 'i' => {alias=>'infinity'}, 328 : 'infty' => {alias=>'infinity'}, 329 : 'minfinity' => {infinite=>1, negative=>1}, 330 : 'minfty' => {alias=>'minfinity'}, 331 : 'minf' => {alias=>'minfinity'}, 332 : 'mi' => {alias=>'minfinity'}, 333 : jj 3513 ); 334 : jj 3489 # Add any strings 335 : if ($opts{strings}) { 336 : foreach my$string (@{$opts{strings}}) { 337 :$string = uc($string); 338 :$context->strings->add($string) unless 339 : defined($context->strings->get($string)); 340 :$ans_type = 'String' if $string eq uc($correct_ans); 341 : } 342 : } 343 : jj 3506 my $ans_eval; 344 : jj 3462 Context($context); 345 : if($ans_type eq 'List') { 346 :$ans_eval = List($correct_ans)->cmp(%options); 347 : } elsif($ans_type eq 'Union') { 348 : $ans_eval = Union($correct_ans)->cmp(%options); 349 : jj 3489 } elsif($ans_type eq 'String') { 350 :$ans_eval = List($correct_ans)->cmp(%options); 351 : jj 3462 } else { 352 : jj 3463 warn "Bug -- should not be here in interval_cmp"; 353 : jj 3462 } 354 : 355 : Context($oldContext); 356 : return($ans_eval); 357 : } 358 : 359 : gage 1064 =head3 number_list_cmp () 360 : 361 : Checks an answer which is a comma-separated list of numbers. The actual 362 : numbers are fed to num_cmp, so all of the flexibilty of num_cmp carries 363 : over (values can be expressions to be evaluated). For example, 364 : 365 : number_list_cmp("1, -2") 366 : 367 : will accept "1, -2", "-2, 1", or "-1-1,sqrt(1)". 368 : 369 : number_list_cmp("1^2 + 1, 2^2 + 1, 3^2 + 1", ordered=>'strict') 370 : 371 : will accept "2, 5, 10", but not "5, 2, 10". 372 : 373 : If you want to allow complex number entries, complex=>'ok' will cause it 374 : to use cplx_cmp instead: 375 : 376 : number_list_cmp("2, -2, 2i, -2i", complex=>'ok') 377 : 378 : In cases where you set complex=>'ok', be sure the problem file loads 379 : PGcomplexmacros.pl. 380 : 381 : Optional arguements for num_cmp (resp. cplx_cmp) can be used as well, 382 : such as 383 : 384 : number_list_cmp("cos(3), sqrt(111)", relTol => 3) 385 : 386 : The strings=>['hello'] argument is treated specially. It can be used to 387 : replace the entire answer. So 388 : 389 : number_list_cmp("cos(3), sqrt(111)", strings=>['none']) 390 : 391 : will mark "none" wrong, but not generate an error. On the other hand, 392 : 393 : number_list_cmp("none", strings=>['none']) 394 : 395 : jj 3455 will mark "none" as correct. 396 : gage 1064 397 : jj 3489 One can also specify optionnal arguments for Parser's List checker: showHints, 398 : partialCredit, and showLengthHints, as in: 399 : 400 : number_list_cmp("cos(3), sqrt(111)", partialCredit=>1) 401 : 402 : gage 1064 =cut 403 : 404 : sub number_list_cmp { 405 : jj 3455 my$list = shift; 406 : jj 3462 407 : jj 3455 my %num_params = @_; 408 : jj 3462 409 : my $mode =$num_params{mode} || 'std'; 410 : my %options = (debug => $num_params{debug}); 411 : 412 : # 413 : # Get an apppropriate context based on the mode 414 : # 415 : jj 3455 my$oldContext = Context(); 416 : jj 3506 my $context = mode2context($mode, %num_params); 417 : jj 3462 418 : jj 3489 #$context->strings->clear; 419 : jj 3463 if ($num_params{strings}) { 420 : jj 3462 foreach my $string (@{$num_params{strings}}) { 421 : my %tex = ($string =~ m/(-?)inf(inity)?/i)? (TeX => "$1\\infty"): (); 422 : jj 3489 $string = uc($string); 423 : $context->strings->add($string => {%tex}) unless 424 : defined($context->strings->get($string)); 425 : jj 3462 } 426 : } 427 : 428 : $options{ordered} = 1 if(defined($num_params{ordered}) and $opts{ordered}); 429 : jj 3463 # These didn't exist before in number_list_cmp so they behaved like 430 : # in List()->cmp. Now they can be optionally set 431 : jj 3489 for my$o qw( showHints partialCredit showLengthHints ) { 432 : $options{$o} = $num_params{$o} || 0; 433 : } 434 : jj 3462 435 : jj 3455 Context($context); 436 : jj 3462 my$ans_eval = List($list)->cmp(%options); 437 : jj 3455 Context($oldContext); 438 : return(\$ans_eval); 439 : gage 1064 } 440 : 441 : jj 3455 442 : gage 1064 =head3 equation_cmp () 443 : 444 : Compares an equation. This really piggy-backs off of fun_cmp. It looks 445 : at LHS-RHS of the equations to see if they agree up to constant multiple. 446 : It also guards against an answer of 0=0 (which technically gives a constant 447 : multiple of any equation). It is best suited to situations such as checking 448 : the equation of a line which might be vertical and you don't want to give 449 : that away, or checking equations of ellipses where the students answer should 450 : be quadratic. 451 : 452 : Typical invocation would be: 453 : 454 : equation_com("x^2+(y-1)^2 = 11", vars=>['x','y']) 455 : 456 : =cut 457 : 458 : sub equation_cmp { 459 : Equation_eval::equation_cmp(@_); 460 : } 461 :

 aubreyja at gmail dot com ViewVC Help Powered by ViewVC 1.0.9