[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 3462 - (view) (download) (as text)

1 : jj 3455 loadMacros('Parser.pl');
2 : gage 1064
3 :     # This is extraAnswerEvaluators.pl
4 :    
5 :     # Most of the work is done in special namespaces
6 :     # At the end, we provide one global function, the interval answer evaluator
7 :    
8 :     # To do:
9 :     # Convert these to AnswerEvaluator objects
10 :     # Better error checking/messages
11 :     # Simplify checks so we don't make so much use of num_cmp and cplx_cmp.
12 :     # When they change, these functions may have to change.
13 :    
14 :     =head1 NAME
15 :    
16 :     extraAnswerEvaluators.pl -- located in the courseScripts directory
17 :    
18 :     =head1 SYNPOSIS
19 :    
20 :     Answer Evaluators for intervals, lists of numbers, lists of points,
21 :     and equations.
22 :    
23 :     interval_cmp() -- checks answers which are unions of intervals.
24 :     It can also be used for checking an ordered pair or
25 :     list of ordered pairs.
26 : apizer 1080
27 : gage 1064 number_list_cmp() -- checks a comma separated list of numbers. By use of
28 :     optional arguments, you can request that order be
29 :     important, that complex numbers be allowed, and
30 :     specify extra arguments to be sent to num_cmp (or
31 :     cplx_cmp) for checking individual entries.
32 : apizer 1080
33 : gage 1064 equation_cmp() -- provides a limited facility for checking equations.
34 :     It makes no pretense of checking to see if the real locus
35 :     of the student's equation matches the real locus of the
36 :     instructor's equation. The student's equation must be
37 :     of the same general type as the instructors to get credit.
38 :    
39 :    
40 :     =cut
41 :    
42 :     =head1 DESCRIPTION
43 :    
44 :     This file adds subroutines which create "answer evaluators" for checking student
45 :     answers of various "exotic" types.
46 :    
47 :     =cut
48 :    
49 :    
50 :     {
51 :     package Intervals;
52 : apizer 1080
53 : gage 1064 # We accept any of the following as infinity (case insensitive)
54 :     @infinitywords = ("i", "inf", "infty", "infinity");
55 :     $infinityre = join '|', @infinitywords;
56 :     $infinityre = "^([-+m]?)($infinityre)\$";
57 :    
58 :     sub new {
59 :     my $class = shift;
60 :     my $base_string = shift;
61 :     my $self = {};
62 :     $self->{'original'} = $base_string;
63 :     return bless $self, $class;
64 :     }
65 :    
66 :     # Not object oriented. It just returns the structure
67 :     sub new_interval { # must call with 4 arguments
68 :     my($l,$r,$lec, $rec) = @_;
69 :     return [[$l,$r],[$lec,$rec]];
70 :     }
71 :    
72 :     # error routine copied from AlgParser
73 :     sub error {
74 :     my($self, @args) = @_;
75 :     # we cheat to use error from algparser
76 :     my($ap) = new AlgParser();
77 :     $ap->inittokenizer($self->{'original'});
78 :     $ap->error(@args);
79 :     $self->{htmlerror} = $ap->{htmlerror};
80 :     $self->{error_msg} = $ap->{error_msg};
81 :     }
82 :    
83 :     # Determine if num_cmp detected a parsing/syntax type error
84 :    
85 :     sub has_errors {
86 :     my($ah) = shift;
87 :    
88 :     if($ah->{'student_ans'} =~ /error/) {
89 :     return 1;
90 :     }
91 :     my($am) = $ah->{'ans_message'};
92 :     if($am =~ /error/) {
93 :     return 2;
94 :     }
95 :     if($am =~ /must enter/) {
96 :     return 3;
97 :     }
98 :     if($am =~ /does not evaluate/) {
99 :     return 4;
100 :     }
101 :     return 0;
102 :     }
103 :    
104 :    
105 :     ## Parse a string into a bunch of intervals
106 :     ## We do it by hand to avoid problems of nested parentheses
107 :     ## This also builds a normalized version of the string, one with values,
108 :     ## and a latex version.
109 :     ##
110 :     ## Return value simply says whether or not this was successful
111 :     sub parse_intervals {
112 :     my($self) = shift;
113 :     my(%opts) = @_;
114 :     my($str) = $self->{'original'};
115 :     my(@ans_list) = ();
116 :     delete($opts{'sloppy'});
117 :     delete($opts{'ordered'});
118 :     my($unions) = 1;
119 :     if (defined($opts{'unions'}) and ($opts{'unions'} eq 'no')) {
120 :     $unions = 0;
121 :     }
122 :     # Sometimes we use this for lists of points
123 :     delete($opts{'unions'});
124 :     my($b1str,$b2str) = (', ', ', ');
125 :     if($unions) {
126 :     ($b1str,$b2str) = (' U ', ' \cup ');
127 :     }
128 : apizer 1080
129 : gage 1064 my($tmp_ae) = main::num_cmp(1, %opts);
130 :     $self->{'normalized'} = '';
131 :     $self->{'value'} = '';
132 :     $self->{'latex'} = '';
133 :     $self->{'htmlerror'} = '';
134 :     $self->{'error_msg'} = '';
135 :     my($pmi) = 0;
136 :     my(@cur) = ("","");
137 :     my($lb,$rb) = (0,0);
138 :     my($level,$spot,$hold,$char,$lr) = (0,0,0,"a",0);
139 :    
140 :     while ($spot < length($str)) {
141 :     $char = substr($str,$spot,1);
142 :     if ($char=~ /[\[(,)\]]/) { # Its a special character
143 :     if ($char eq ",") {
144 :     if ($level == 1) { # Level 1 comma
145 :     if ($lr == 1) {
146 :     $self->error("Not a valid interval; too many commas.",[$spot]);
147 :     return 0;
148 :     } else {
149 :     $lr=1;
150 :     $cur[0] = substr($str,$hold, $spot-$hold);
151 :     if($pmi = pminf($cur[0])) {
152 :     if($pmi<0) {
153 :     $self->{'value'} .= '-';
154 :     $self->{'normalized'} .= '-';
155 :     $self->{'latex'} .= '-';
156 :     }
157 :     $self->{'value'} .= 'Infinity, ';
158 :     $self->{'normalized'} .= 'Infinity, ';
159 :     $self->{'latex'} .= '\infty, ';
160 :     } else {
161 :     my($tmp_ah) = $tmp_ae->evaluate($cur[0]);
162 :     if(has_errors($tmp_ah)) {
163 :     $self->error("I could not parse your input correctly",[$hold, $spot]);
164 :     return 0;
165 :     }
166 :     $self->{'normalized'} .= $tmp_ah->{'preview_text_string'}.", ";
167 :     $self->{'value'} .= $tmp_ah->{'student_ans'}.", ";
168 :     $self->{'latex'} .= $tmp_ah->{'preview_latex_string'}.", ";
169 :     }
170 :     $hold = $spot+1;
171 :     }
172 :     }
173 :     } # end of comma
174 :     elsif ($char eq "[" or $char eq "(") { #opening
175 :     if ($level==0) {
176 :     $lr = 0;
177 :     if(scalar(@ans_list)) { # this is not the first interval
178 :     $self->{'normalized'} .= $b1str;
179 :     $self->{'value'} .= $b1str;
180 :     $self->{'latex'} .= $b2str;
181 :     }
182 :     $self->{'normalized'} .= "$char";
183 :     $self->{'value'} .= "$char";
184 :     $self->{'latex'} .= "$char";
185 :     $hold=$spot+1;
186 :     if ($char eq "[") {
187 :     $lb = 1;
188 :     } else {
189 :     $lb = 0;
190 :     }
191 :     }
192 :     $level++;
193 :     } # end of open paren
194 :     else { # must be closed paren
195 :     if ($level == 0) {
196 :     $self->error("Not a valid interval; extra $char when I expected a new interval to open.",[$spot]);
197 :     return 0;
198 :     } elsif ($level == 1) {
199 :     if ($lr != 1) {
200 :     $self->error("Not a valid interval; closing an interval without a right component.", [$spot]);
201 :     return 0;
202 :     } else {
203 :     $cur[1] = substr($str, $hold, $spot-$hold);
204 :     if($pmi = pminf($cur[1])) {
205 :     if($pmi<0) {
206 :     $self->{'value'} .= '-';
207 :     $self->{'normalized'} .= '-';
208 :     $self->{'latex'} .= '-';
209 :     }
210 :     $self->{'value'} .= "Infinity$char";
211 :     $self->{'normalized'} .= "Infinity$char";
212 :     $self->{'latex'} .= '\infty'."$char";
213 :     } else {
214 :     my($tmp_ah) = $tmp_ae->evaluate($cur[1]);
215 :     if(has_errors($tmp_ah)) {
216 :     $self->error("I could not parse your input correctly",[$hold, $spot]);
217 :     return 0;
218 :     }
219 :     $self->{'normalized'} .= $tmp_ah->{'preview_text_string'}."$char";
220 :     $self->{'value'} .= $tmp_ah->{'student_ans'}."$char";
221 :     $self->{'latex'} .= $tmp_ah->{'preview_latex_string'}."$char";
222 :     }
223 :     if ($char eq "]") {
224 :     $rb = 1;
225 :     } else {
226 :     $rb = 0;
227 :     }
228 :     push @ans_list, new_interval($cur[0], $cur[1], $lb, $rb);
229 :     }
230 :     }
231 :     $level--;
232 :     }
233 :     }
234 :     $spot++;
235 :     }
236 : apizer 1080
237 : gage 1064 if($level>0) {
238 :     $self->error("Your expression ended in the middle of an interval.",
239 :     [$hold, $spot]);
240 :     return 0;
241 :     }
242 :     $self->{'parsed'} = \@ans_list;
243 :     return 1;
244 :     }
245 :    
246 :     # Is the argument an exceptable +/- infinity
247 :     # Its sort of multiplies the input by 0 using 0 * oo = 1, 0 * (-oo) = -1.
248 :     sub pminf {
249 :     my($val) = shift;
250 :     $val = "\L$val"; # lowercase
251 :     $val =~ s/ //g; # remove space
252 :     if ($val =~ /$infinityre/) {
253 :     if (($1 eq '-') or ($1 eq 'm')) {
254 :     return -1;
255 :     } else {
256 :     return 1;
257 :     }
258 :     }
259 :     return 0;
260 :     }
261 :    
262 :     # inputs are now of type Intervals, and then options
263 :    
264 :     sub cmp_intervals {
265 :     my($in1) = shift;
266 :     my($in2) = shift;
267 :     my(%opts) = @_;
268 :     my($strict_ordering) = 0;
269 :     if (defined($opts{'ordering'}) && $opts{'ordering'} eq 'strict') {
270 :     $strict_ordering = 1;
271 :     }
272 :     delete($opts{'ordering'});
273 :    
274 :     my($issloppy) = 0;
275 :     if (defined($opts{'sloppy'}) && $opts{'sloppy'} eq 'yes') {
276 :     $issloppy = 1;
277 :     }
278 :     delete($opts{'sloppy'});
279 :    
280 :     delete($opts{'unions'});
281 :    
282 :    
283 :     my(@i1) = @{$in1->{'parsed'}};
284 :     my(@i2) = @{$in2->{'parsed'}};
285 :    
286 :     my($j,$pm10,$pm11,$pm20,$pm21);
287 :     # Same number of intervals?
288 :     if (scalar(@i1) != scalar(@i2)) {
289 :     return 0;
290 :     }
291 :     for ($j=0; $j<scalar(@i1);$j++) {
292 :     my($lbound) = 0;
293 :     my($ubound) = scalar(@i1)-1;
294 :     my($lookformatch) = 1;
295 :     if ($strict_ordering) {
296 :     $lbound = $j;
297 :     $ubound = $j;
298 :     }
299 :     for ($k=$lbound; $lookformatch && $k<=$ubound; $k++) {
300 :     # Do they all have correct inclusions ()[]?
301 :     if (! $issloppy and ($i1[$j]->[1][0] != $i2[$k]->[1][0] or
302 :     $i1[$j]->[1][1] != $i2[$k]->[1][1])) {
303 :     next;
304 :     }
305 :     $pm10 = pminf($i1[$j]->[0][0]);
306 :     $pm11 = pminf($i1[$j]->[0][1]);
307 :     $pm20 = pminf($i2[$k]->[0][0]);
308 :     $pm21 = pminf($i2[$k]->[0][1]);
309 :     if ($pm10 != $pm20) {
310 :     next;
311 :     }
312 :     if ($pm11 != $pm21) {
313 :     next;
314 :     }
315 :     # Now we deal with only numbers, no infinities
316 :     if ($pm10 == 0) {
317 :     # $opts{'correctAnswer'} = $i1[$j]->[0][0];
318 :     my $ae = main::num_cmp($i1[$j]->[0][0], %opts);
319 :     my $result = $ae->evaluate($i2[$k]->[0][0]);
320 :     if ($result->{score} == 0) {
321 :     next;
322 :     }
323 :     }
324 :     if ($pm11 == 0) {
325 :     # $opts{'correctAnswer'} = $i1[$j]->[0][1];
326 :     my $ae = main::num_cmp($i1[$j]->[0][1], %opts);
327 :     my $result = $ae->evaluate($i2[$k]->[0][1]);
328 :     if ($result->{score} == 0) {
329 :     next;
330 :     }
331 :     }
332 :     $lookformatch=0;
333 :     }
334 :     if ($lookformatch) { # still looking ...
335 :     return 0;
336 :     }
337 :     }
338 :     return 1;
339 :     }
340 :    
341 :     sub show_int {
342 :     my($intt) = shift;
343 :     my($intstring) = "";
344 :     return "|$intt->[0]->[0]%%$intt->[0]->[1]|";
345 :     }
346 :    
347 :    
348 :    
349 :     } # End of package Intervals
350 :    
351 :     {
352 :     package Interval_evaluator;
353 :    
354 :     sub nicify_string {
355 :     my $str = shift;
356 :    
357 :     $str = uc($str);
358 :     $str =~ s/\s//g; # remove white space
359 :     $str;
360 :     }
361 : apizer 1080
362 : gage 1064 ##### The answer evaluator
363 :    
364 :     sub interval_cmp {
365 :    
366 :     my $right_ans = shift;
367 :     my %opts = @_;
368 :    
369 :     $opts{'mode'} = 'std' unless defined($opts{'mode'});
370 :     $opts{'tolType'} = 'relative' unless defined($opts{'tolType'});
371 : apizer 1080
372 : gage 1064 my $ans_eval = sub {
373 :     my $student = shift;
374 : apizer 1080
375 : gage 1064 my $ans_hash = new AnswerHash(
376 : gage 3320 'score'=>0,
377 :     'correct_ans'=>$right_ans,
378 :     'student_ans'=>$student,
379 :     'original_student_ans' => $student,
380 :     # 'type' => undef,
381 :     'ans_message'=>'',
382 :     'preview_text_string'=>'',
383 :     'preview_latex_string'=>'',
384 :     );
385 : gage 1064 # Handle string matches separately
386 :     my($studentisstring, $correctisstring, $tststr) = (0,0,"");
387 :     my($nicestud, $nicecorrect) = (nicify_string($student),
388 :     nicify_string($right_ans));
389 :     if(defined($opts{'strings'})) {
390 :     for $tststr (@{$opts{'strings'}}) {
391 :     $tststr = nicify_string($tststr);
392 :     if(($tststr eq $nicestud)) {$studentisstring=1;}
393 :     if(($tststr eq $nicecorrect)) {$correctisstring=1;}
394 :     }
395 :     if($studentisstring) {
396 :     $ans_hash->{'preview_text_string'} = $student;
397 :     $ans_hash->{'preview_latex_string'} = $student;
398 :     }
399 :     }
400 :     my($student_int, $correct_int);
401 :     if(!$studentisstring) {
402 :     $student_int = new Intervals($student);
403 :     if(! $student_int->parse_intervals(%opts)) {
404 :     # Error in student input
405 :     $ans_hash->{'student_ans'} = "error: $student_int->{htmlerror}";
406 :     $ans_hash->{'ans_message'} = "$student_int->{error_msg}";
407 :     return $ans_hash;
408 :     }
409 : apizer 1080
410 : gage 1064 $ans_hash->{'student_ans'} = $student_int->{'value'};
411 :     $ans_hash->{'preview_text_string'} = $student_int->{'normalized'};
412 :     $ans_hash->{'preview_latex_string'} = $student_int->{'latex'};
413 :     }
414 :    
415 :     if(!$correctisstring) {
416 :     $correct_int = new Intervals($right_ans);
417 :     if(! $correct_int->parse_intervals(%opts)) {
418 :     # Cannot parse instuctor's answer!
419 :     $ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
420 :     return $ans_hash;
421 :     }
422 :     }
423 :     if($correctisstring || $studentisstring) {
424 :     if($nicestud eq $nicecorrect) {
425 :     $ans_hash -> setKeys('score' => 1);
426 :     }
427 :     } else {
428 :     if (Intervals::cmp_intervals($correct_int, $student_int, %opts)) {
429 :     $ans_hash -> setKeys('score' => 1);
430 :     }
431 :     }
432 :    
433 :     return $ans_hash;
434 :     };
435 :    
436 :     return $ans_eval;
437 :     }
438 :    
439 :     }
440 :    
441 :     {
442 :     package Equation_eval;
443 :    
444 :     sub split_eqn {
445 :     my $instring = shift;
446 :    
447 :     split /=/, $instring;
448 :     }
449 : apizer 1080
450 :    
451 : gage 1064 sub equation_cmp {
452 :     my $right_ans = shift;
453 :     my %opts = @_;
454 :     my $vars = ['x','y'];
455 :    
456 : apizer 1080
457 : gage 1064 $vars = $opts{'vars'} if defined($opts{'vars'});
458 :    
459 :     my $ans_eval = sub {
460 :     my $student = shift;
461 : apizer 1080
462 : gage 1064 my $ans_hash = new AnswerHash(
463 :     'score'=>0,
464 :     'correct_ans'=>$right_ans,
465 :     'student_ans'=>$student,
466 :     'original_student_ans' => $student,
467 :     # 'type' => undef,
468 :     'ans_message'=>'',
469 :     'preview_text_string'=>'',
470 :     'preview_latex_string'=>'',
471 :     );
472 :    
473 :     if(! ($student =~ /\S/)) { return $ans_hash; }
474 : apizer 1080
475 : gage 1064 my @right= split_eqn($right_ans);
476 :     if(scalar(@right) != 2) {
477 :     $ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
478 :     return $ans_hash;
479 :     }
480 :     my @studsplit = split_eqn($student);
481 :     if(scalar(@studsplit) != 2) {
482 :     $ans_hash->{'ans_message'} = "You did not enter an equation (with an equals sign and two sides).";
483 :     return $ans_hash;
484 :     }
485 :    
486 :     # Next we should do syntax checks on everyone
487 :    
488 :     my $ah = new AnswerHash;
489 :     $ah->input($right[0]);
490 :     $ah=main::check_syntax($ah);
491 :     if($ah->{error_flag}) {
492 :     $ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
493 :     return $ans_hash;
494 :     }
495 : apizer 1080
496 : gage 1064 $ah->input($right[1]);
497 :     $ah=main::check_syntax($ah);
498 :     if($ah->{error_flag}) {
499 :     $ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
500 :     return $ans_hash;
501 :     }
502 :    
503 :     # Correct answer checks out, now check student's syntax
504 :    
505 :     my @prevs = ("","");
506 :     my @prevtxt = ("","");
507 :     $ah->input($studsplit[0]);
508 :     $ah=main::check_syntax($ah);
509 :     if($ah->{error_flag}) {
510 :     $ans_hash->{'ans_message'} = "Syntax error on the left side of your equation.";
511 :     return $ans_hash;
512 :     }
513 :     $prevs[0] = $ah->{'preview_latex_string'};
514 :     $prevstxt[0] = $ah->{'preview_text_string'};
515 : apizer 1080
516 :    
517 : gage 1064 $ah->input($studsplit[1]);
518 :     $ah=main::check_syntax($ah);
519 :     if($ah->{error_flag}) {
520 :     $ans_hash->{'ans_message'} = "Syntax error on the right side of your equation.";
521 :     return $ans_hash;
522 :     }
523 :     $prevs[1] = $ah->{'preview_latex_string'};
524 :     $prevstxt[1] = $ah->{'preview_text_string'};
525 :    
526 :     $ans_hash->{'preview_latex_string'} = "$prevs[0] = $prevs[1]";
527 :     $ans_hash->{'preview_text_string'} = "$prevstxt[0] = $prevstxt[1]";
528 : apizer 1080
529 :    
530 : gage 1064 # Check for answer equivalent to 0=0
531 :     # Could be false positive below because of parameter
532 :     my $ae = main::fun_cmp("0", %opts);
533 :     my $res = $ae->evaluate("$studsplit[0]-($studsplit[1])");
534 :     if($res->{'score'}==1) {
535 :     # Student is 0=0, is correct answer also like this?
536 :     $res = $ae->evaluate("$right[0]-($right[1])");
537 :     if($res->{'score'}==1) {
538 :     $ans_hash-> setKeys('score' => $res->{'score'});
539 :     }
540 :     return $ans_hash;
541 :     }
542 :    
543 :     # Maybe answer really is 0=0, and student got it wrong, so check that
544 :     $res = $ae->evaluate("$right[0]-($right[1])");
545 :     if($res->{'score'}==1) {
546 :     return $ans_hash;
547 :     }
548 :    
549 :     # Finally, use fun_cmp to check the answers
550 : apizer 1080
551 : gage 1064 $ae = main::fun_cmp("o*($right[0]-($right[1]))", vars=>$vars, params=>['o'], %opts);
552 :     $res= $ae->evaluate("$studsplit[0]-($studsplit[1])");
553 :     $ans_hash-> setKeys('score' => $res->{'score'});
554 : apizer 1080
555 : gage 1064 return $ans_hash;
556 :     };
557 :    
558 :     return $ans_eval;
559 :     }
560 :     }
561 :    
562 :     =head3 interval_cmp ()
563 :    
564 :     Compares an interval or union of intervals. Typical invocations are
565 :    
566 :     interval_cmp("(2, 3] U(7, 11)")
567 :    
568 :     The U is used for union symbol. In fact, any garbage (or nothing at all)
569 :     can go between intervals. It makes sure open/closed parts of intervals
570 :     are correct, unless you don't like that. To have it ignore the difference
571 :     between open and closed endpoints, use
572 :    
573 :     interval_cmp("(2, 3] U(7, 11)", sloppy=>'yes')
574 :    
575 :     interval_cmp uses num_cmp on the endpoints. You can pass optional
576 :     arguments for num_cmp, so to change the tolerance, you can use
577 :    
578 :     interval_cmp("(2, 3] U(3+4, 11)", relTol=>3)
579 :    
580 :     The intervals can be listed in any order, unless you want to force a
581 :     particular order, which is signaled as
582 :    
583 :     interval_cmp("(2, 3] U(3+4, 11)", ordered=>'strict')
584 :    
585 :     You can specify infinity as an endpoint. It will do a case-insensitive
586 :     string match looking for I, Infinity, Infty, or Inf. You can prepend a +
587 :     or -, as in
588 :    
589 :     interval_cmp("(-inf, 3] U [e^10, infinity)")
590 :     or
591 :     interval_cmp("(-INF, 3] U [e^10, +I)")
592 :    
593 :     If the question might have an empty set as the answer, you can use
594 :     the strings option to allow for it. So
595 :    
596 :     interval_cmp("$ans", strings=>['empty'])
597 :    
598 :     will not generate an error message if the student enters the string
599 :     empty. Better still, it will mark a student answer of "empty" as correct
600 :     iff this matches $ans.
601 :    
602 :     You can use interval_cmp for ordered pairs, or lists of ordered pairs.
603 :     Internally, this is just a distinction of whether to put nice union symbols
604 :     between intervals, or commas. To get commas, use
605 :    
606 :     interval_cmp("(1,2), (2,3), (4,-1)", unions=>'no')
607 :    
608 :     Note that interval_cmp makes no attempt at simplifying overlapping intervals.
609 :     This becomes an important feature when you are really checking lists of
610 :     ordered pairs.
611 :    
612 :     =cut
613 :    
614 : jj 3462 sub interval_cmp2 {
615 :     my $correct_ans = shift;
616 :    
617 :     my %opts = @_;
618 :    
619 :     my $mode = $num_params{mode} || 'std';
620 :     my %options = (debug => $opts{debug});
621 :     my $ans_type = ''; # set to List, Union, or Interval below
622 :    
623 :     #
624 :     # Get an apppropriate context based on the mode
625 :     #
626 :     my $oldContext = Context();
627 :     my ($context, $ans_eval);
628 :     if(defined($opts{unions}) and $opts{unions} eq 'no' ) {
629 :     # This is really a list of points
630 :     $context = Context("Vector")->copy;
631 :     $ans_type = 'List';
632 :     $options{showCoordinateHints} = 0;
633 :     $options{showHints} = 0;
634 :     $options{partialCredit}=0;
635 :     $options{showLengthHints} = 0;
636 :     } else {
637 :     $context = Context("Numeric")->copy;
638 :     $correct_ans =~ tr/u/U/;
639 :     if($correct_ans =~ /U/) {
640 :     $context->operators->add('u'=> {precedence => 0.5, associativity => 'left',
641 :     type => 'bin', isUnion => 1, string => ' U ', TeX => '\cup ',
642 :     class => 'Parser::BOP::union'});
643 :     # $context->operators->add('u'=> {alias => 'U'});
644 :     $ans_type = 'Union';
645 :     $options{showHints} = 0;
646 :     $options{showLengthHints} = 0;
647 :     $options{showEndpointHints}=0;
648 :     $options{partialCredit}=0;
649 :     } else {
650 :     $ans_type = 'Interval';
651 :     $options{showEndpointHints}=0;
652 :     }
653 :     }
654 :     $opts{tolType} = $opts{tolType} || 'relative';
655 :     $opts{tolerance} = $opts{tolerance} || $opts{tol} ||
656 :     $opts{reltol} || $opts{relTol} || $opts{abstol} || 1;
657 :     $opts{zeroLevel} = $opts{zeroLevel} || $opts{zeroLevelTol} ||
658 :     $main::numZeroLevelTolDefault;
659 :     if ($opts{tolType} eq 'absolute' or defined($opts{tol})
660 :     or defined($opts{abstol})) {
661 :     $context->flags->set(
662 :     tolerance => $opts{tolerance},
663 :     tolType => 'absolute',
664 :     );
665 :     } else {
666 :     $context->flags->set(
667 :     tolerance => .01*$opts{tolerance},
668 :     tolType => 'relative',
669 :     );
670 :     }
671 :     $context->flags->set(
672 :     zeroLevel => $opts{zeroLevel},
673 :     zeroLevelTol => $opts{zeroLevelTol},
674 :     );
675 :     $options{ordered} = 1 if(defined($opts{ordered}) and $opts{ordered});
676 :     if (defined($opts{'sloppy'}) && $opts{'sloppy'} eq 'yes') {
677 :     $options{requireParenMatch} = 0;
678 :     }
679 :     Context($context);
680 :     if($ans_type eq 'List') {
681 :     $ans_eval = List($correct_ans)->cmp(%options);
682 :     } elsif($ans_type eq 'Union') {
683 :     $ans_eval = Union($correct_ans)->cmp(%options);
684 :     warn "Union with options ".join(',', %options);
685 :     } elsif($ans_type eq 'Interval') {
686 :     $ans_eval = Interval($correct_ans)->cmp(%options);
687 :     } else {
688 :     warn "Bug -- should not be here";
689 :     }
690 :    
691 :     Context($oldContext);
692 :     return($ans_eval);
693 :    
694 :    
695 :     # ToDo: tolerances
696 :     # modes?
697 :     # strings
698 :     # infinities
699 :     #@infinitywords = ("i", "inf", "infty", "infinity");
700 :     #$infinityre = join '|', @infinitywords;
701 :     #$infinityre = "^([-+m]?)($infinityre)\$";
702 :    
703 :    
704 :     }
705 :    
706 : gage 1064 sub interval_cmp {
707 :     Interval_evaluator::interval_cmp(@_);
708 :     }
709 :    
710 :     =head3 number_list_cmp ()
711 :    
712 :     Checks an answer which is a comma-separated list of numbers. The actual
713 :     numbers are fed to num_cmp, so all of the flexibilty of num_cmp carries
714 :     over (values can be expressions to be evaluated). For example,
715 :    
716 :     number_list_cmp("1, -2")
717 :    
718 :     will accept "1, -2", "-2, 1", or "-1-1,sqrt(1)".
719 :    
720 :     number_list_cmp("1^2 + 1, 2^2 + 1, 3^2 + 1", ordered=>'strict')
721 :    
722 :     will accept "2, 5, 10", but not "5, 2, 10".
723 :    
724 :     If you want to allow complex number entries, complex=>'ok' will cause it
725 :     to use cplx_cmp instead:
726 :    
727 :     number_list_cmp("2, -2, 2i, -2i", complex=>'ok')
728 :    
729 :     In cases where you set complex=>'ok', be sure the problem file loads
730 :     PGcomplexmacros.pl.
731 :    
732 :     Optional arguements for num_cmp (resp. cplx_cmp) can be used as well,
733 :     such as
734 :    
735 :     number_list_cmp("cos(3), sqrt(111)", relTol => 3)
736 :    
737 :     The strings=>['hello'] argument is treated specially. It can be used to
738 :     replace the entire answer. So
739 :    
740 :     number_list_cmp("cos(3), sqrt(111)", strings=>['none'])
741 :    
742 :     will mark "none" wrong, but not generate an error. On the other hand,
743 :    
744 :     number_list_cmp("none", strings=>['none'])
745 :    
746 : jj 3455 will mark "none" as correct.
747 : gage 1064
748 :     =cut
749 :    
750 :     sub number_list_cmp {
751 : jj 3455 my $list = shift;
752 : jj 3462
753 : jj 3455 my %num_params = @_;
754 : jj 3462
755 :     my $mode = $num_params{mode} || 'std';
756 :     my %options = (debug => $num_params{debug});
757 :    
758 :     #
759 :     # Get an apppropriate context based on the mode
760 :     #
761 : jj 3455 my $oldContext = Context();
762 : jj 3462 my $context;
763 :     for ($mode) {
764 :     /^strict$/i and do {
765 :     $context = $Parser::Context::Default::context{LimitedNumeric}->copy;
766 :     $context->operators->set(',' => {class=> 'Parser::BOP::comma'});
767 :     last;
768 :     };
769 :     /^arith$/i and do {
770 :     $context = $Parser::Context::Default::context{LegacyNumeric}->copy;
771 :     $context->functions->disable('All');
772 :     last;
773 :     };
774 :     /^frac$/i and do {
775 :     $context = $Parser::Context::Default::context{'LimitedNumeric-Fraction'}->copy;
776 :     $context->operators->set(',' => {class=> 'Parser::BOP::comma'});
777 :     last;
778 :     };
779 :     if(defined($num_params{'complex'}) &&
780 :     ($num_params{'complex'} =~ /(yes|ok)/i)) {
781 :     $context = $Parser::Context::Default::context{Complex}->copy;
782 :     last;
783 :     }
784 :    
785 :     # default
786 :     $context = $Parser::Context::Default::context{LegacyNumeric}->copy;
787 : jj 3455 }
788 : jj 3462 $context->{format}{number} = $num_params{'format'} || $main::numFormatDefault;
789 :     $context->strings->clear;
790 : jj 3455 if (defined($num_params{strings}) && $num_params{strings}) {
791 : jj 3462 foreach my $string (@{$num_params{strings}}) {
792 :     my %tex = ($string =~ m/(-?)inf(inity)?/i)? (TeX => "$1\\infty"): ();
793 :     $context->strings->add(uc($string) => {%tex});
794 :     }
795 :     }
796 :    
797 : jj 3455 $num_params{tolType} = $num_params{tolType} || 'relative';
798 : jj 3462 $num_params{tolerance} = $num_params{tolerance} || $num_params{tol} ||
799 :     $num_params{reltol} || $num_params{relTol} || $num_params{abstol} || 1;
800 :     $num_params{zeroLevel} = $num_params{zeroLevel} || $num_params{zeroLevelTol} ||
801 :     $main::numZeroLevelTolDefault;
802 : jj 3455 if ($num_params{tolType} eq 'absolute' or defined($num_params{tol})
803 :     or defined($num_params{abstol})) {
804 : jj 3462 $context->flags->set(
805 :     tolerance => $num_params{tolerance},
806 :     tolType => 'absolute',
807 :     );
808 :     } else {
809 :     $context->flags->set(
810 :     tolerance => .01*$num_params{tolerance},
811 :     tolType => 'relative',
812 :     );
813 :     }
814 :     $context->flags->set(
815 :     zeroLevel => $num_params{zeroLevel},
816 :     zeroLevelTol => $num_params{zeroLevelTol},
817 :     );
818 :     $options{ordered} = 1 if(defined($num_params{ordered}) and $opts{ordered});
819 :    
820 : jj 3455 Context($context);
821 : jj 3462 my $ans_eval = List($list)->cmp(%options);
822 : jj 3455 Context($oldContext);
823 :     return($ans_eval);
824 : gage 1064 }
825 :    
826 : jj 3455
827 : gage 1064 =head3 equation_cmp ()
828 :    
829 :     Compares an equation. This really piggy-backs off of fun_cmp. It looks
830 :     at LHS-RHS of the equations to see if they agree up to constant multiple.
831 :     It also guards against an answer of 0=0 (which technically gives a constant
832 :     multiple of any equation). It is best suited to situations such as checking
833 :     the equation of a line which might be vertical and you don't want to give
834 :     that away, or checking equations of ellipses where the students answer should
835 :     be quadratic.
836 :    
837 :     Typical invocation would be:
838 :    
839 :     equation_com("x^2+(y-1)^2 = 11", vars=>['x','y'])
840 :    
841 :     =cut
842 :    
843 :     sub equation_cmp {
844 :     Equation_eval::equation_cmp(@_);
845 :     }
846 :    

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9