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

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

Parent Directory Parent Directory | Revision Log Revision Log


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