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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

1 : lr003k 1102 #!/usr/local/bin/perl
2 : sh002i 1050 # This file is PGcomplexmacros.pl
3 :     # This includes the subroutines for the ANS macros, that
4 :     # is, macros allowing a more flexible answer checking
5 :     ####################################################################
6 :     # Copyright @ 1995-2002 The WeBWorK Team
7 :     # All Rights Reserved
8 :     ####################################################################
9 :     #$Id$
10 :    
11 :    
12 :     =head1 NAME
13 :    
14 :     Macros for complex numbers for the PG language
15 :    
16 :     =head1 SYNPOSIS
17 :    
18 :    
19 :    
20 :     =head1 DESCRIPTION
21 :    
22 :     =cut
23 :    
24 :    
25 :     BEGIN{
26 :     be_strict();
27 : lr003k 1102
28 : sh002i 1050 }
29 :    
30 :    
31 :    
32 :     sub _PGcomplexmacros_init {
33 :     }
34 :     # export functions from Complex1.
35 :    
36 :     foreach my $f (@Complex1::EXPORT) {
37 : lr003k 1102 # #PG_restricted_eval("\*$f = \*Complex1::$f"); # this is too clever --
38 : sh002i 1050 # the original subroutines are destroyed
39 : lr003k 1102 next if $f eq 'sqrt'; #exporting the square root caused conflicts with the standard version
40 :     # You can still use Complex1::sqrt to take square root of complex numbers
41 :     next if $f eq 'log'; #exporting loq caused conflicts with the standard version
42 : sh002i 1050 # You can still use Complex1::log to take square root of complex numbers
43 :    
44 :     my $string = qq{
45 :     sub main::$f {
46 :     &Complex1::$f;
47 :     }
48 :     };
49 :     PG_restricted_eval($string);
50 :     }
51 :    
52 : gage 1072
53 : lr003k 1102 # You need to add
54 : sh002i 1050 # sub i(); # to your problem or else to dangerousMacros.pl
55 :     # in order to use expressions such as 1 +3*i;
56 :     # Without this prototype you would have to write 1+3*i();
57 :     # The prototype has to be defined at compile time, but dangerousMacros.pl is complied first.
58 :     #Complex1::display_format('cartesian');
59 :    
60 :     # number format used frequently in strict prefilters
61 :     my $number = '([+-]?)(?=\d|\.\d)\d*(\.\d*)?(E([+-]?\d+))?';
62 :    
63 :    
64 :     sub polar{
65 :     my $z = shift;
66 :     my %options = @_;
67 :     my $r = rho($z);
68 :     my $theta = $z->theta;
69 :     my $r_format = ':%0.3f';
70 :     my $theta_format = ':%0.3f';
71 :     $r_format=":" . $options{r_format} if defined($options{r_format});
72 :     $theta_format = ":" . $options{theta_format} if defined($options{theta_format});
73 :     "{$r$r_format} e^{i {$theta$theta_format}}";
74 :    
75 :     }
76 :    
77 :     =head4 cplx_cmp
78 : lr003k 1102
79 : sh002i 1050 This subroutine compares complex numbers.
80 :     Available prefilters include:
81 :     each of these are called by cplx_cmp( answer, mode => '(prefilter name)' )
82 :     'std' The standard comparison method for complex numbers. This option it the default
83 :     and works with any combination of cartesian numbers, polar numbers, and
84 :     functions. The default display method is cartesian, for all methods, but if
85 :     the student answer is polar, even in part, then their answer will be displayed
86 :     that way.
87 :     'strict_polar' This is still under developement. The idea is to check to make sure that there
88 :     only a single term in front of the e and after it... but the method does not
89 :     check to make sure that the i is in the exponent, nor does it handle cases
90 :     where the polar has e** coefficients.
91 :     'strict_num_cartesian' This prefilter allows only complex numbers of the form "a+bi" where a and b
92 :     are strictly numbers.
93 :     'strict_num_polar' This prefilter allows only complex numbers of the form "ae^(bi)" where a and b
94 :     are strictly numbers.
95 :     'strict' This is a combination of strict_num_cartesian and strict_num_polar, so it
96 :     allows complex numbers of either the form "a+bi" or "ae^(bi)" where a and b
97 :     are strictly numbers.
98 :    
99 :    
100 :     =cut
101 :    
102 :     sub cplx_cmp {
103 :     my $correctAnswer = shift;
104 :     my %cplx_params = @_;
105 : lr003k 1102
106 : sh002i 1050 assign_option_aliases( \%cplx_params,
107 :     'reltol' => 'relTol',
108 :     );
109 :     set_default_options(\%cplx_params,
110 : lr003k 1102 'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative',
111 :     # default mode should be relative, to obtain this tol must not be defined
112 :     'tolerance' => $main::numAbsTolDefault,
113 :     'relTol' => $main::numRelPercentTolDefault,
114 : sh002i 1050 'zeroLevel' => $main::numZeroLevelDefault,
115 : lr003k 1102 'zeroLevelTol' => $main::numZeroLevelTolDefault,
116 : sh002i 1050 'format' => $main::numFormatDefault,
117 : lr003k 1102 'debug' => 0,
118 :     'mode' => 'std',
119 :     'strings' => undef,
120 : sh002i 1050
121 :     );
122 :     my $format = $cplx_params{'format'};
123 :     my $mode = $cplx_params{'mode'};
124 : lr003k 1102
125 : sh002i 1050 if( $cplx_params{tolType} eq 'relative' ) {
126 :     $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'};
127 :     }
128 : lr003k 1102
129 : sh002i 1050 my $formattedCorrectAnswer;
130 :     my $correct_num_answer;
131 :     my $corrAnswerIsString = 0;
132 : lr003k 1102
133 : sh002i 1050
134 :     if (defined($cplx_params{strings}) && $cplx_params{strings}) {
135 :     my $legalString = '';
136 :     my @legalStrings = @{$cplx_params{strings}};
137 :     $correct_num_answer = $correctAnswer;
138 :     $formattedCorrectAnswer = $correctAnswer;
139 :     foreach $legalString (@legalStrings) {
140 :     if ( uc($correctAnswer) eq uc($legalString) ) {
141 :     $corrAnswerIsString = 1;
142 : lr003k 1102
143 : sh002i 1050 last;
144 :     }
145 :     } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
146 :     } else {
147 :     $correct_num_answer = $correctAnswer;
148 :     $formattedCorrectAnswer = prfmt( $correctAnswer, $cplx_params{'format'} );
149 :     }
150 :     $correct_num_answer = math_constants($correct_num_answer);
151 : lr003k 1102
152 : sh002i 1050 my $PGanswerMessage = '';
153 : lr003k 1102
154 : sh002i 1050 my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report);
155 : lr003k 1102
156 : sh002i 1050 if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) {
157 :     ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer);
158 :     } else { # case of a string answer
159 :     $PG_eval_errors = ' ';
160 :     $correctVal = $correctAnswer;
161 :     }
162 : lr003k 1102
163 :     #if ( ($PG_eval_errors && $corrAnswerIsString == 0) or (not (( ref($correctAnswer) =~ /^Complex?/) || is_a_number($correctVal)) && $corrAnswerIsString == 0)) {
164 :     ##error message from eval or above
165 : sh002i 1050 #warn "Error in 'correct' answer: $PG_eval_errors<br>
166 : lr003k 1102 # The answer $correctAnswer evaluates to $correctVal,
167 :     # which cannot be interpreted as a number. ";
168 :    
169 : sh002i 1050 #}
170 :     ########################################################################
171 : lr003k 1102 $correctVal = $correct_num_answer;
172 :     $correctVal = cplx( $correctVal, 0 ) unless ref($correctVal) =~/^Complex?/ || $corrAnswerIsString == 1;
173 :    
174 :     #construct the answer evaluator
175 :     my $answer_evaluator = new AnswerEvaluator;
176 :     $answer_evaluator->{debug} = $cplx_params{debug};
177 :     $answer_evaluator->ans_hash(
178 : sh002i 1050 correct_ans => $correctVal,
179 :     type => "${mode}_number",
180 :     tolerance => $cplx_params{tolerance},
181 :     tolType => 'absolute', # $cplx_params{tolType},
182 :     original_correct_ans => $formattedCorrectAnswer,
183 :     answerIsString => $corrAnswerIsString,
184 :     answer_form => 'cartesian',
185 :     );
186 :     my ($in, $formattedSubmittedAnswer);
187 : lr003k 1102 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift;
188 : sh002i 1050 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;}
189 :     );
190 :     if (defined($cplx_params{strings}) && $cplx_params{strings}) {
191 :     $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params);
192 :     }
193 :    
194 :     $answer_evaluator->install_pre_filter(\&check_syntax);
195 : lr003k 1102
196 : sh002i 1050 $answer_evaluator->install_pre_filter(\&math_constants);
197 :     $answer_evaluator->install_pre_filter(\&cplx_constants);
198 :     $answer_evaluator->install_pre_filter(\&check_for_polar);
199 :     if ($mode eq 'std') {
200 : lr003k 1102 # do nothing
201 : sh002i 1050 } elsif ($mode eq 'strict_polar') {
202 :     $answer_evaluator->install_pre_filter(\&is_a_polar);
203 :     } elsif ($mode eq 'strict_num_cartesian') {
204 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian);
205 :     } elsif ($mode eq 'strict_num_polar') {
206 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_polar);
207 :     } elsif ($mode eq 'strict') {
208 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_complex);
209 : lr003k 1102 } else {
210 :     $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
211 :     $formattedSubmittedAnswer = $in;
212 :     }
213 : sh002i 1050
214 : lr003k 1102 if ($corrAnswerIsString == 0 ){ # avoiding running compare_cplx when correct answer is a string.
215 : sh002i 1050 $answer_evaluator->install_evaluator(\&compare_cplx, %cplx_params);
216 :     }
217 : lr003k 1102
218 :    
219 : sh002i 1050 ###############################################################################
220 :     # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's
221 :     # can be displayed in the answer message. This may still cause a few anomolies when strings are used
222 :     #
223 :     ###############################################################################
224 :    
225 :     $answer_evaluator->install_post_filter(\&fix_answers_for_display);
226 :     $answer_evaluator->install_post_filter(\&fix_for_polar_display);
227 : lr003k 1102
228 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
229 : sh002i 1050 return $rh_ans unless $rh_ans->catch_error('EVAL');
230 :     $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message};
231 :     $rh_ans->clear_error('EVAL'); } );
232 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } );
233 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } );
234 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } );
235 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } );
236 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } );
237 :     $answer_evaluator;
238 :     }
239 :    
240 :    
241 :    
242 :     # compares two complex numbers by comparing their real and imaginary parts
243 : lr003k 1102 sub compare_cplx {
244 :     my ($rh_ans, %options) = @_;
245 :     my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans});
246 :    
247 :     if ($PG_eval_errors) {
248 :     $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
249 :     $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
250 :     # return $rh_ans;
251 :     } else {
252 :     $rh_ans->{student_ans} = prfmt($inVal,$options{format});
253 :     }
254 :    
255 :     $inVal = cplx($inVal,0) unless ref($inVal) =~/Complex/;
256 :     my $permitted_error_Re;
257 :     my $permitted_error_Im;
258 :     if ($rh_ans->{tolType} eq 'absolute') {
259 :     $permitted_error_Re = $rh_ans->{tolerance};
260 :     $permitted_error_Im = $rh_ans->{tolerance};
261 :     }
262 :     elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) {
263 :     $permitted_error_Re = $options{zeroLevelTol}; ## want $tol to be non zero
264 :     $permitted_error_Im = $options{zeroLevelTol}; ## want $tol to be non zero
265 :     }
266 :     else {
267 :     $permitted_error_Re = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Re);
268 :     $permitted_error_Im = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Im);
269 :    
270 :     }
271 :    
272 :     $rh_ans->{score} = 1 if ( abs( $rh_ans->{correct_ans}->Complex::Re - $inVal->Complex::Re) <=
273 :     $permitted_error_Re && abs($rh_ans->{correct_ans}->Complex::Im - $inVal->Complex::Im )<= $permitted_error_Im );
274 :    
275 :     $rh_ans;
276 :     }
277 : apizer 1080
278 : sh002i 1050 sub mult_cmp{
279 : lr003k 1102 my $answer = shift;
280 :     my @answers = @{$answer} if ref($answer) eq 'ARRAY';
281 : sh002i 1050 my %mult_params = @_;
282 :     my @keys = qw ( tolerance tolType format mode zeroLevel zeroLevelTol debug );
283 :     my @correctVal;
284 :     my $formattedCorrectAnswer;
285 :     my @correct_num_answer;
286 :     my ($PG_eval_errors,$PG_full_error_report);
287 :     assign_option_aliases( \%mult_params,
288 :     'reltol' => 'relTol',
289 :     );
290 :     set_default_options(\%mult_params,
291 :     'tolType' => (defined($mult_params{tol}) ) ? 'absolute' : 'relative',
292 :     # default mode should be relative, to obtain this tol must not be defined
293 : lr003k 1102 'tolerance' => $main::numAbsTolDefault,
294 : sh002i 1050 'relTol' => $main::numRelPercentTolDefault,
295 :     'zeroLevel' => $main::numZeroLevelDefault,
296 :     'zeroLevelTol' => $main::numZeroLevelTolDefault,
297 :     'format' => $main::numFormatDefault,
298 :     'debug' => 0,
299 :     'mode' => 'std',
300 :     'compare' => 'num',
301 :     );
302 :     my $format = $mult_params{'format'};
303 :     my $mode = $mult_params{'mode'};
304 : lr003k 1102
305 :    
306 : sh002i 1050 if( $mult_params{tolType} eq 'relative' ) {
307 :     $mult_params{'tolerance'} = .01*$mult_params{'tolerance'};
308 :     }
309 : lr003k 1102
310 : sh002i 1050 if( $mult_params{ 'compare' } eq 'cplx' ){
311 :     foreach( @answers )
312 :     {
313 :     $_ = cplx( $_, 0 ) unless ref($_) =~/Complex/;
314 :     }
315 :     }
316 : lr003k 1102
317 : sh002i 1050 my $corrAnswerIsString = 0;
318 : lr003k 1102
319 :     for( my $k = 0; $k < @answers; $k++ ){
320 : sh002i 1050 if (defined($mult_params{strings}) && $mult_params{strings}) {
321 :     my $legalString = '';
322 :     my @legalStrings = @{$mult_params{strings}};
323 :     $correct_num_answer[$k] = $answers[$k];
324 :     $formattedCorrectAnswer .= $answers[$k] . ",";
325 :     foreach $legalString (@legalStrings) {
326 :     if ( uc($answers[$k]) eq uc($legalString) ) {
327 :     $corrAnswerIsString = 1;
328 : lr003k 1102
329 : sh002i 1050 last;
330 :     }
331 :     } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
332 :     } else {
333 :     $correct_num_answer[$k] = $answers[$k];
334 :     $formattedCorrectAnswer .= prfmt( $answers[$k], $mult_params{'format'} ) . ", ";
335 :     }
336 :     $correct_num_answer[$k] = math_constants($correct_num_answer[$k]);
337 :     my $PGanswerMessage = '';
338 : lr003k 1102
339 :    
340 : sh002i 1050 if (defined($correct_num_answer[$k]) && $correct_num_answer[$k] =~ /\S/ && $corrAnswerIsString == 0 ) {
341 :     ($correctVal[$k], $PG_eval_errors,$PG_full_error_report) =
342 :     PG_answer_eval($correct_num_answer[$k]);
343 :     } else { # case of a string answer
344 :     $PG_eval_errors = ' ';
345 :     $correctVal[$k] = $answers[$k];
346 :     }
347 : lr003k 1102
348 : sh002i 1050 #if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal[$k])) && $corrAnswerIsString == 0)) {
349 :     ##error message from eval or above
350 :     #warn "Error in 'correct' answer: $PG_eval_errors<br>
351 : lr003k 1102 #The answer $answers[$k] evaluates to $correctVal[$k],
352 : sh002i 1050 #which cannot be interpreted as a number. ";
353 : lr003k 1102
354 : sh002i 1050 #}
355 :     ########################################################################
356 :     $correctVal[$k] = $correct_num_answer[$k];
357 :     }
358 : lr003k 1102 $formattedCorrectAnswer =~ s/, \Z//;
359 :    
360 :     #construct the answer evaluator
361 :    
362 :     my $answer_evaluator = new AnswerEvaluator;
363 : sh002i 1050
364 : lr003k 1102
365 : sh002i 1050 $answer_evaluator->{debug} = $mult_params{debug};
366 : lr003k 1102 $answer_evaluator->ans_hash(
367 : sh002i 1050 correct_ans => [@correctVal],
368 :     type => "${mode}_number",
369 :     tolerance => $mult_params{tolerance},
370 :     tolType => 'absolute', # $mult_params{tolType},
371 :     original_correct_ans => $formattedCorrectAnswer,
372 :     answerIsString => $corrAnswerIsString,
373 :     answer_form => 'cartesian',
374 :     );
375 :     my ($in, $formattedSubmittedAnswer);
376 : lr003k 1102 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift;
377 : sh002i 1050 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;}
378 :     );
379 :     if (defined($mult_params{strings}) && $mult_params{strings}) {
380 :     $answer_evaluator->install_pre_filter(\&check_strings, %mult_params);
381 :     }
382 : lr003k 1102
383 : sh002i 1050 $answer_evaluator -> install_pre_filter( \&mult_prefilters, %mult_params );
384 :     $answer_evaluator->install_pre_filter( sub{my $rh_ans = shift; $rh_ans->{original_student_ans} = $rh_ans->{student_ans};$rh_ans;});
385 : lr003k 1102
386 : sh002i 1050 if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string.
387 :     $answer_evaluator->install_evaluator(\&compare_mult, %mult_params);
388 :     }
389 : lr003k 1102
390 :    
391 : sh002i 1050 ###############################################################################
392 :     # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's
393 :     # can be displayed in the answer message. This may still cause a few anomolies when strings are used
394 :     #
395 :     ###############################################################################
396 :     $answer_evaluator->install_post_filter( sub{my $rh_ans = shift; $rh_ans->{student_ans} = $rh_ans->{original_student_ans};$rh_ans;});
397 :     $answer_evaluator->install_post_filter(\&fix_answers_for_display);
398 :     $answer_evaluator->install_post_filter(\&fix_for_polar_display);
399 : lr003k 1102
400 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
401 : sh002i 1050 return $rh_ans unless $rh_ans->catch_error('EVAL');
402 :     $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message};
403 :     $rh_ans->clear_error('EVAL'); } );
404 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } );
405 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } );
406 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } );
407 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } );
408 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } );
409 :     $answer_evaluator;
410 :     }
411 :    
412 :     sub mult_prefilters{
413 :     my ($rh_ans, %options) = @_;
414 :     my @student_answers = split/,/,$rh_ans->{student_ans};
415 :     foreach( @student_answers ){
416 :     $rh_ans->{student_ans} = $_;
417 :     $rh_ans = &check_syntax( $rh_ans );
418 :     $rh_ans = &math_constants( $rh_ans );
419 :     if( $options{compare} eq 'cplx' ){
420 :     $rh_ans = &cplx_constants( $rh_ans );
421 :     #$rh_ans = &check_for_polar( $rh_ans );
422 :     }
423 :     if ( $options{mode} eq 'std') {
424 : lr003k 1102 # do nothing
425 : sh002i 1050 } elsif ($options{mode} eq 'strict_polar') {
426 :     $rh_ans = &is_a_polar( $rh_ans );
427 :     } elsif ($options{mode} eq 'strict_num_cartesian') {
428 :     $rh_ans = &is_a_numeric_cartesian( $rh_ans );
429 :     } elsif ($options{mode} eq 'strict_num_polar') {
430 :     $rh_ans = &is_a_numeric_polar( $rh_ans );
431 :     } elsif ($options{mode} eq 'strict') {
432 :     $rh_ans = &is_a_numeric_complex( $rh_ans );
433 :     } elsif ($options{mode} eq 'arith') {
434 :     $rh_ans = &is_an_arithmetic_expression( $rh_ans );
435 :     } elsif ($options{mode} eq 'frac') {
436 :     $rh_ans = &is_a_fraction( $rh_ans );
437 :    
438 : lr003k 1102 } else {
439 : sh002i 1050 #$PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
440 :     #$formattedSubmittedAnswer = $in;
441 :     }
442 :     $_ = $rh_ans->{student_ans};
443 :     }
444 :     my $ans_string;
445 :     foreach( @student_answers )
446 :     {
447 :     $ans_string .= ", $_";
448 : lr003k 1102 }
449 : sh002i 1050 $ans_string =~ s/\A,//;
450 :     $rh_ans->{student_ans} = $ans_string;
451 :     $rh_ans;
452 :     }
453 :    
454 :     # compares two complex numbers by comparing their real and imaginary parts
455 :     sub compare_mult {
456 :     my ($rh_ans, %options) = @_;
457 :     my @student_answers = split/,/,$rh_ans->{student_ans};
458 :     my @correct_answers = @{$rh_ans->{correct_ans}};
459 :     my $one_correct = 1/@correct_answers;
460 :     my $temp_score = 0;
461 :     foreach( @correct_answers ){
462 :     $rh_ans->{correct_ans} = $_;
463 :     foreach( @student_answers ){
464 :     $rh_ans->{student_ans} = $_;
465 :     if( $options{compare} eq 'cplx' ){
466 :     $rh_ans = &compare_cplx( $rh_ans, %options);
467 :     }else{
468 :     $rh_ans = &compare_numbers( $rh_ans, %options);
469 :     }
470 :     if( $rh_ans->{score} == 1 )
471 :     {
472 :     $temp_score += $one_correct;
473 :     $rh_ans->{score} = 0;
474 :     last;
475 :     }
476 :     }
477 :     }
478 :     $rh_ans->{score} = $temp_score;
479 :     $rh_ans;
480 : lr003k 1102
481 :     }
482 : apizer 1080
483 : lr003k 1102 sub multi_cmp{
484 :     my $pointer = shift;
485 :     my %options = @_;
486 :     my @evals = @{$pointer};
487 :     my $answer_evaluator = new AnswerEvaluator;
488 :     $answer_evaluator->install_evaluator( sub {
489 :     my $rh_ans = shift;
490 :     $rh_ans->{score} = 1;#in order to use AND below, score must be 1
491 :     $rh_ans->{preview_text_string} = "";#needs to be initialized to prevent warnings
492 :     my @student_answers = split/,/,$rh_ans->{student_ans};
493 :     foreach my $eval ( @evals ){
494 :     my $temp_eval = new AnswerEvaluator;
495 :     my $temp_hash = $temp_eval->ans_hash;
496 :     $temp_hash->{preview_text_string} = "";#needs to be initialized to prevent warnings
497 :     foreach my $temp ( @student_answers ){
498 :     $eval->evaluate($temp);
499 :     $temp = $eval->ans_hash->{student_ans} unless $eval->ans_hash->{student_ans}=~ /you/i;
500 :     $temp_hash = $temp_hash->OR( $eval->ans_hash );
501 :     if( $eval->ans_hash->{score} == 1){ last; }
502 :     }
503 :     $rh_ans = $rh_ans->AND( $temp_hash );
504 :     }
505 :     $rh_ans->{correct_ans} =~ s/No correct answer specified (OR|AND) //g;
506 :     $rh_ans->{student_ans} = "";
507 :     foreach( @student_answers ){ $rh_ans->{student_ans} .= "$_, "; }
508 :     $rh_ans->{student_ans} =~ s/, \Z//;
509 :     if( scalar(@evals) != scalar(@student_answers) ){ $rh_ans->{score} = 0; }#wrong number of answers makes answer wrong
510 :     $rh_ans; });
511 :     $answer_evaluator;
512 : sh002i 1050 }
513 :    
514 : lr003k 1102 # Output is text displaying the complex numver in "e to the i theta" form. The
515 :     # formats for the argument theta is determined by the option C<theta_format> and the
516 :     # format for the modulus is determined by the C<r_format> option.
517 : sh002i 1050
518 :     #this basically just checks for "e^" which unfortunately will show something like (e^4)*i as a polar, this should be changed
519 :     sub check_for_polar{
520 :    
521 :     my($in,%options) = @_;
522 :     my $rh_ans;
523 :     my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ;
524 :     if ($process_ans_hash) {
525 :     $rh_ans = $in;
526 :     $in = $rh_ans->{student_ans};
527 : lr003k 1102 }
528 : sh002i 1050 # The code fragment above allows this filter to be used when the input is simply a string
529 :     # as well as when the input is an AnswerHash, and options.
530 :     if( $in =~ /2.71828182845905\*\*/ ){
531 :     $rh_ans->{answer_form} = 'polar';
532 :     }
533 :     $rh_ans;
534 :     }
535 :    
536 :    
537 : apizer 1080
538 : sh002i 1050 sub cplx_constants {
539 :     my($in,%options) = @_;
540 :     my $rh_ans;
541 :     my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ;
542 :     if ($process_ans_hash) {
543 :     $rh_ans = $in;
544 :     $in = $rh_ans->{student_ans};
545 : lr003k 1102 }
546 : sh002i 1050 # The code fragment above allows this filter to be used when the input is simply a string
547 :     # as well as when the input is an AnswerHash, and options.
548 :     $in =~ s/\bi\b/(i)/g; #try to keep -i being recognized as a file reference
549 :     # and recognized as a function whose output is an imaginary number
550 : lr003k 1102
551 : sh002i 1050 if ($process_ans_hash) {
552 :     $rh_ans->{student_ans}=$in;
553 :     return $rh_ans;
554 :     } else {
555 :     return $in;
556 :     }
557 :     }
558 :    
559 :     ## allows only for numbers of the form a+bi and ae^(bi), where a and b are strict numbers
560 :     sub is_a_numeric_complex {
561 :     my ($num,%options) = @_;
562 :     my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
563 :     my ($rh_ans);
564 :     if ($process_ans_hash) {
565 :     $rh_ans = $num;
566 :     $num = $rh_ans->{student_ans};
567 :     }
568 : lr003k 1102
569 : sh002i 1050 my $is_a_number = 0;
570 :     return $is_a_number unless defined($num);
571 :     $num =~ s/^\s*//; ## remove initial spaces
572 :     $num =~ s/\s*$//; ## remove trailing spaces
573 : lr003k 1102
574 : sh002i 1050 if ($num =~
575 :    
576 :     /^($number[+,-]?($number\*\(i\)|\(i\)|\(i\)\*$number)|($number\*\(i\)|-?\(i\)|-?\(i\)\*$number)([+,-]$number)?|($number\*)?2.71828182845905\*\*\(($number\*\(i\)|\(i\)\*$number|i|-\(i\))\)|$number)$/){
577 :     $is_a_number = 1;
578 :     }
579 : lr003k 1102
580 : sh002i 1050 if ($process_ans_hash) {
581 :     if ($is_a_number == 1 ) {
582 :     $rh_ans->{student_ans}=$num;
583 :     return $rh_ans;
584 :     } else {
585 :     $rh_ans->{student_ans} = "Incorrect number format: You must enter a numeric complex, e.g. a+bi
586 :     or a*e^(bi)";
587 :     $rh_ans->throw_error('COMPLEX', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
588 :     return $rh_ans;
589 :     }
590 :     } else {
591 :     return $is_a_number;
592 :     }
593 :     }
594 :    
595 :     ## allows only for the form a + bi, where a and b are strict numbers
596 :     sub is_a_numeric_cartesian {
597 :     my ($num,%options) = @_;
598 :     my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
599 :     my ($rh_ans);
600 :     if ($process_ans_hash) {
601 :     $rh_ans = $num;
602 :     $num = $rh_ans->{student_ans};
603 :     }
604 : lr003k 1102
605 : sh002i 1050 my $is_a_number = 0;
606 :     return $is_a_number unless defined($num);
607 :     $num =~ s/^\s*//; ## remove initial spaces
608 :     $num =~ s/\s*$//; ## remove trailing spaces
609 : lr003k 1102
610 : sh002i 1050 if ($num =~
611 :    
612 :     /^($number[+,-]?($number\*\(i\)|\(i\)|\(i\)\*$number)|($number\*\(i\)|-?\(i\)|-?\(i\)\*$number)([+,-]$number)?|$number)$/){
613 :     $is_a_number = 1;
614 :     }
615 : lr003k 1102
616 : sh002i 1050 if ($process_ans_hash) {
617 :     if ($is_a_number == 1 ) {
618 :     $rh_ans->{student_ans}=$num;
619 :     return $rh_ans;
620 :     } else {
621 :     $rh_ans->{student_ans} = "Incorrect number format: You must enter a numeric cartesian, e.g. a+bi";
622 :     $rh_ans->throw_error('CARTESIAN', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
623 :     return $rh_ans;
624 :     }
625 :     } else {
626 :     return $is_a_number;
627 :     }
628 :     }
629 :    
630 :     ## allows only for the form ae^(bi), where a and b are strict numbers
631 :     sub is_a_numeric_polar {
632 :     my ($num,%options) = @_;
633 :     my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
634 :     my ($rh_ans);
635 :     if ($process_ans_hash) {
636 :     $rh_ans = $num;
637 :     $num = $rh_ans->{student_ans};
638 :     }
639 : lr003k 1102
640 : sh002i 1050 my $is_a_number = 0;
641 :     return $is_a_number unless defined($num);
642 :     $num =~ s/^\s*//; ## remove initial spaces
643 :     $num =~ s/\s*$//; ## remove trailing spaces
644 :     if ($num =~
645 :     /^($number|($number\*)?2.71828182845905\*\*\(($number\*\(i\)|\(i\)\*$number|i|-\(i\))\))$/){
646 :     $is_a_number = 1;
647 :     }
648 : lr003k 1102
649 : sh002i 1050 if ($process_ans_hash) {
650 :     if ($is_a_number == 1 ) {
651 :     $rh_ans->{student_ans}=$num;
652 :     return $rh_ans;
653 :     } else {
654 :     $rh_ans->{student_ans} = "Incorrect number format: You must enter a numeric polar, e.g. a*e^(bi)";
655 :     $rh_ans->throw_error('POLAR', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
656 :     return $rh_ans;
657 :     }
658 :     } else {
659 :     return $is_a_number;
660 :     }
661 :     }
662 : lr003k 1102
663 : sh002i 1050 #this subroutine mearly captures what is before and after the "e**" it does not verify that the "i" is there, or in the
664 :     #exponent this must eventually be addresed
665 :     sub is_a_polar {
666 :     my ($num,%options) = @_;
667 :     my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
668 :     my ($rh_ans);
669 :     if ($process_ans_hash) {
670 :     $rh_ans = $num;
671 :     $num = $rh_ans->{student_ans};
672 :     }
673 : lr003k 1102
674 : sh002i 1050 my $is_a_number = 0;
675 :     return $is_a_number unless defined($num);
676 :     $num =~ s/^\s*//; ## remove initial spaces
677 :     $num =~ s/\s*$//; ## remove trailing spaces
678 :     $num =~ /^(.*)\*2.71828182845905\*\*(.*)/;
679 :     #warn "rho: ", $1;
680 :     #warn "theta: ", $2;
681 :     if( defined( $1 ) ){
682 :     if( &single_term( $1 ) && &single_term( $2 ) )
683 :     {
684 :     $is_a_number = 1;
685 :     }
686 :     }
687 :     if ($process_ans_hash) {
688 :     if ($is_a_number == 1 ) {
689 :     $rh_ans->{student_ans}=$num;
690 :     return $rh_ans;
691 :     } else {
692 :     $rh_ans->{student_ans} = "Incorrect number format: You must enter a polar, e.g. a*e^(bi)";
693 :     $rh_ans->throw_error('POLAR', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
694 :     return $rh_ans;
695 :     }
696 :     } else {
697 :     return $is_a_number;
698 :     }
699 :     }
700 :    
701 :     =head4 single_term()
702 :     This subroutine takes in a string, which is a mathematical expresion, and determines whether or not
703 :     it is a single term. This is accoplished using a stack. Open parenthesis pluses and minuses are all
704 :     added onto the stack, and when a closed parenthesis is reached, the stack is popped untill the open
705 :     parenthesis is found. If the original was a single term, the stack should be empty after
706 :     evaluation. If there is anything left ( + or - ) then false is returned.
707 :     Of course, the unary operator "-" must be handled... if it is a unary operator, and not a regular -
708 :     the only place it could occur unambiguously without being surrounded by parenthesis, is the very
709 :     first position. So that case is checked before the loop begins.
710 :     =cut
711 :    
712 :     sub single_term{
713 :     my $term = shift;
714 :     my @stack;
715 :     $term = reverse $term;
716 :     if( length $term >= 1 )
717 :     {
718 :     my $temp = chop $term;
719 :     if( $temp ne "-" ){ $term .= $temp; }
720 :     }
721 :     while( length $term >= 1 ){
722 :     my $character = chop $term;
723 :     if( $character eq "+" || $character eq "-" || $character eq "(" ){
724 :     push @stack, $character;
725 :     }elsif( $character eq ")" ){
726 :     while( pop @stack ne "(" ){}
727 :     }
728 : lr003k 1102
729 : sh002i 1050 }
730 :     if( scalar @stack == 0 ){ return 1;}else{ return 0;}
731 :     }
732 :    
733 :     # changes default to display as a polar
734 :     sub fix_for_polar_display{
735 :     my ($rh_ans, %options) = @_;
736 :     if( ref( $rh_ans->{student_ans} ) =~ /Complex/ && $rh_ans->{answer_form} eq 'polar' ){
737 :     $rh_ans->{student_ans}->display_format( 'polar');
738 :     ## these lines of code have the polar displayed as re^(theta) instead of [rho,theta]
739 :     $rh_ans->{student_ans} =~ s/,/*e^\(/;
740 :     $rh_ans->{student_ans} =~ s/\[//;
741 :     $rh_ans->{student_ans} =~ s/\]/i\)/;
742 :     }
743 :     $rh_ans;
744 :     }
745 :    
746 :     sub cplx_cmp2 {
747 :     my $correctAnswer = shift;
748 :     my %cplx_params = @_;
749 :     my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug );
750 :     assign_option_aliases( \%cplx_params,
751 :     'reltol' => 'relTol',
752 :     );
753 :     set_default_options(\%cplx_params,
754 :     'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative',
755 :     # default mode should be relative, to obtain this tol must not be defined
756 : lr003k 1102 'tolerance' => $main::numAbsTolDefault,
757 : sh002i 1050 'relTol' => $main::numRelPercentTolDefault,
758 :     'zeroLevel' => $main::numZeroLevelDefault,
759 :     'zeroLevelTol' => $main::numZeroLevelTolDefault,
760 :     'format' => $main::numFormatDefault,
761 :     'debug' => 0,
762 :     'mode' => 'std',
763 :    
764 :     );
765 :     $correctAnswer = cplx($correctAnswer,0) unless ref($correctAnswer) =~/Complex/;
766 :     my $format = $cplx_params{'format'};
767 :     my $mode = $cplx_params{'mode'};
768 : lr003k 1102
769 : sh002i 1050 if( $cplx_params{tolType} eq 'relative' ) {
770 :     $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'};
771 :     }
772 : lr003k 1102
773 : sh002i 1050 my $formattedCorrectAnswer;
774 :     my $correct_num_answer;
775 :     my $corrAnswerIsString = 0;
776 : lr003k 1102
777 : sh002i 1050
778 :     if (defined($cplx_params{strings}) && $cplx_params{strings}) {
779 :     my $legalString = '';
780 :     my @legalStrings = @{$cplx_params{strings}};
781 :     $correct_num_answer = $correctAnswer;
782 :     $formattedCorrectAnswer = $correctAnswer;
783 :     foreach $legalString (@legalStrings) {
784 :     if ( uc($correctAnswer) eq uc($legalString) ) {
785 :     $corrAnswerIsString = 1;
786 : lr003k 1102
787 : sh002i 1050 last;
788 :     }
789 :     } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
790 :     } else {
791 :     $correct_num_answer = $correctAnswer;
792 :     $formattedCorrectAnswer = prfmt( $correctAnswer, $cplx_params{'format'} );
793 :     }
794 :     $correct_num_answer = math_constants($correct_num_answer);
795 :     my $PGanswerMessage = '';
796 : lr003k 1102
797 : sh002i 1050 my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report);
798 : lr003k 1102
799 : sh002i 1050 if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) {
800 :     ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer);
801 :     } else { # case of a string answer
802 :     $PG_eval_errors = ' ';
803 :     $correctVal = $correctAnswer;
804 :     }
805 : lr003k 1102
806 : sh002i 1050 if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) {
807 :     ##error message from eval or above
808 :     warn "Error in 'correct' answer: $PG_eval_errors<br>
809 : lr003k 1102 The answer $correctAnswer evaluates to $correctVal,
810 : sh002i 1050 which cannot be interpreted as a number. ";
811 : lr003k 1102
812 : sh002i 1050 }
813 :     ########################################################################
814 :     $correctVal = $correct_num_answer;#it took me two and a half hours to figure out that correctVal wasn't
815 :     #getting the number properly
816 : lr003k 1102 #construct the answer evaluator
817 :     my $answer_evaluator = new AnswerEvaluator;
818 : sh002i 1050
819 : lr003k 1102
820 : sh002i 1050 $answer_evaluator->{debug} = $cplx_params{debug};
821 : lr003k 1102 $answer_evaluator->ans_hash(
822 : sh002i 1050 correct_ans => $correctVal,
823 :     type => "${mode}_number",
824 :     tolerance => $cplx_params{tolerance},
825 :     tolType => 'absolute', # $cplx_params{tolType},
826 :     original_correct_ans => $formattedCorrectAnswer,
827 :     answerIsString => $corrAnswerIsString,
828 :     answer_form => 'cartesian',
829 :     );
830 :     my ($in, $formattedSubmittedAnswer);
831 : lr003k 1102 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift;
832 : sh002i 1050 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;}
833 :     );
834 :     if (defined($cplx_params{strings}) && $cplx_params{strings}) {
835 :     $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params);
836 :     }
837 :     #$answer_evaluator->install_pre_filter(\&check_syntax);
838 : lr003k 1102
839 : sh002i 1050 $answer_evaluator->install_pre_filter(\&math_constants);
840 :     $answer_evaluator->install_pre_filter(\&cplx_constants);
841 :     $answer_evaluator->install_pre_filter(\&check_for_polar);
842 :     if ($mode eq 'std') {
843 : lr003k 1102 # do nothing
844 : sh002i 1050 } elsif ($mode eq 'strict_polar') {
845 :     $answer_evaluator->install_pre_filter(\&is_a_polar);
846 :     } elsif ($mode eq 'strict_num_cartesian') {
847 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian);
848 :     } elsif ($mode eq 'strict_num_polar') {
849 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_polar);
850 :     } elsif ($mode eq 'strict') {
851 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_complex);
852 :     } elsif ($mode eq 'arith') {
853 :     $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression);
854 :     } elsif ($mode eq 'frac') {
855 :     $answer_evaluator->install_pre_filter(\&is_a_fraction);
856 :    
857 : lr003k 1102 } else {
858 : sh002i 1050 $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
859 :     $formattedSubmittedAnswer = $in;
860 :     }
861 :     if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string.
862 :     $answer_evaluator->install_evaluator(\&compare_cplx2, %cplx_params);
863 :     }
864 : lr003k 1102
865 :    
866 : sh002i 1050 ###############################################################################
867 :     # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's
868 :     # can be displayed in the answer message. This may still cause a few anomolies when strings are used
869 :     #
870 :     ###############################################################################
871 :    
872 :     $answer_evaluator->install_post_filter(\&fix_answers_for_display);
873 :     $answer_evaluator->install_post_filter(\&fix_for_polar_display);
874 : lr003k 1102
875 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
876 : sh002i 1050 return $rh_ans unless $rh_ans->catch_error('EVAL');
877 :     $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message};
878 :     $rh_ans->clear_error('EVAL'); } );
879 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } );
880 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } );
881 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } );
882 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } );
883 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } );
884 :     $answer_evaluator;
885 :     }
886 :    
887 :    
888 :     # compares two complex numbers by comparing their real and imaginary parts
889 :     sub compare_cplx2 {
890 :     my ($rh_ans, %options) = @_;
891 :     my @answers = split/,/,$rh_ans->{student_ans};
892 :     foreach( @answers )
893 :     {
894 :     $rh_ans->{student_ans} = $_;
895 :     $rh_ans = &check_syntax( $rh_ans );
896 :     my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans});
897 : lr003k 1102
898 : sh002i 1050 if ($PG_eval_errors) {
899 :     $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
900 :     $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
901 :     # return $rh_ans;
902 :     } else {
903 :     $rh_ans->{student_ans} = prfmt($inVal,$options{format});
904 :     }
905 : lr003k 1102
906 : sh002i 1050 $inVal = cplx($inVal,0) unless ref($inVal) =~/Complex/;
907 :     my $permitted_error_Re;
908 :     my $permitted_error_Im;
909 :     if ($rh_ans->{tolType} eq 'absolute') {
910 :     $permitted_error_Re = $rh_ans->{tolerance};
911 :     $permitted_error_Im = $rh_ans->{tolerance};
912 :     }
913 :     elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) {
914 :     $permitted_error_Re = $options{zeroLevelTol}; ## want $tol to be non zero
915 : lr003k 1102 $permitted_error_Im = $options{zeroLevelTol}; ## want $tol to be non zero
916 :     }
917 : sh002i 1050 else {
918 :     $permitted_error_Re = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Re);
919 :     $permitted_error_Im = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Im);
920 : lr003k 1102
921 : sh002i 1050 }
922 : lr003k 1102
923 : sh002i 1050 $rh_ans->{score} = 1 if ( abs( $rh_ans->{correct_ans}->Complex::Re - $inVal->Complex::Re) <=
924 :     $permitted_error_Re && abs($rh_ans->{correct_ans}->Complex::Im - $inVal->Complex::Im )<= $permitted_error_Im );
925 :     if( $rh_ans->{score} == 1 ){ return $rh_ans; }
926 : lr003k 1102
927 :    
928 : sh002i 1050 }
929 :     $rh_ans;
930 : lr003k 1102
931 : sh002i 1050 }
932 :    
933 :    
934 :     sub cplx_cmp_mult {
935 :     my $correctAnswer = shift;
936 :     my %cplx_params = @_;
937 :     my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug );
938 :     assign_option_aliases( \%cplx_params,
939 :     'reltol' => 'relTol',
940 :     );
941 :     set_default_options(\%cplx_params,
942 :     'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative',
943 :     # default mode should be relative, to obtain this tol must not be defined
944 : lr003k 1102 'tolerance' => $main::numAbsTolDefault,
945 : sh002i 1050 'relTol' => $main::numRelPercentTolDefault,
946 :     'zeroLevel' => $main::numZeroLevelDefault,
947 :     'zeroLevelTol' => $main::numZeroLevelTolDefault,
948 :     'format' => $main::numFormatDefault,
949 :     'debug' => 0,
950 :     'mode' => 'std',
951 :    
952 :     );
953 :     $correctAnswer = cplx($correctAnswer,0) unless ref($correctAnswer) =~/Complex/;
954 :     my $format = $cplx_params{'format'};
955 :     my $mode = $cplx_params{'mode'};
956 : lr003k 1102
957 : sh002i 1050 if( $cplx_params{tolType} eq 'relative' ) {
958 :     $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'};
959 :     }
960 : lr003k 1102
961 : sh002i 1050 my $formattedCorrectAnswer;
962 :     my $correct_num_answer;
963 :     my $corrAnswerIsString = 0;
964 : lr003k 1102
965 : sh002i 1050
966 :     if (defined($cplx_params{strings}) && $cplx_params{strings}) {
967 :     my $legalString = '';
968 :     my @legalStrings = @{$cplx_params{strings}};
969 :     $correct_num_answer = $correctAnswer;
970 :     $formattedCorrectAnswer = $correctAnswer;
971 :     foreach $legalString (@legalStrings) {
972 :     if ( uc($correctAnswer) eq uc($legalString) ) {
973 :     $corrAnswerIsString = 1;
974 : lr003k 1102
975 : sh002i 1050 last;
976 :     }
977 :     } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
978 :     } else {
979 :     $correct_num_answer = $correctAnswer;
980 :     $formattedCorrectAnswer = prfmt( $correctAnswer, $cplx_params{'format'} );
981 :     }
982 :     $correct_num_answer = math_constants($correct_num_answer);
983 :     my $PGanswerMessage = '';
984 : lr003k 1102
985 : sh002i 1050 my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report);
986 : lr003k 1102
987 : sh002i 1050 if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) {
988 :     ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer);
989 :     } else { # case of a string answer
990 :     $PG_eval_errors = ' ';
991 :     $correctVal = $correctAnswer;
992 :     }
993 : lr003k 1102
994 : sh002i 1050 if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) {
995 :     ##error message from eval or above
996 :     warn "Error in 'correct' answer: $PG_eval_errors<br>
997 : lr003k 1102 The answer $correctAnswer evaluates to $correctVal,
998 : sh002i 1050 which cannot be interpreted as a number. ";
999 : lr003k 1102
1000 : sh002i 1050 }
1001 :     ########################################################################
1002 :     $correctVal = $correct_num_answer;#it took me two and a half hours to figure out that correctVal wasn't
1003 :     #getting the number properly
1004 : lr003k 1102 #construct the answer evaluator
1005 : sh002i 1050 my $counter = 0;
1006 :     my $answer_evaluator = new AnswerEvaluator;
1007 : lr003k 1102
1008 : sh002i 1050 my $number;
1009 :     $answer_evaluator->install_pre_filter( sub{ my $rh_ans = shift; my @temp =
1010 :     split/,/,$rh_ans->{student_ans}; $number = @temp; warn "this number ", $number; $rh_ans;});
1011 :     warn "number ", $number;
1012 :     while( $counter < 4 )
1013 :     {
1014 :     $answer_evaluator = &answer_mult( $correctVal, $mode, $formattedCorrectAnswer,
1015 :     $corrAnswerIsString, $counter, %cplx_params );
1016 :     warn "answer_evaluator ", $answer_evaluator;
1017 :     $answer_evaluator->install_evaluator( sub { my $rh_ans = shift; warn "score ", $rh_ans->{score};
1018 :     $rh_ans;});
1019 :     $counter += 1;
1020 :     }
1021 : lr003k 1102
1022 : sh002i 1050 $answer_evaluator;
1023 :    
1024 :     }
1025 :    
1026 :     sub answer_mult{
1027 :     my $correctVal = shift;
1028 :     my $mode = shift;
1029 :     my $formattedCorrectAnswer = shift;
1030 :     my $corrAnswerIsString = shift;
1031 :     my $counter = shift;
1032 :     warn "counter ", $counter;
1033 : lr003k 1102
1034 : sh002i 1050 my %cplx_params = @_;
1035 : lr003k 1102 my $answer_evaluator = new AnswerEvaluator;
1036 : sh002i 1050
1037 : lr003k 1102
1038 : sh002i 1050 $answer_evaluator->{debug} = $cplx_params{debug};
1039 : lr003k 1102 $answer_evaluator->ans_hash(
1040 : sh002i 1050 correct_ans => $correctVal,
1041 :     type => "${mode}_number",
1042 :     tolerance => $cplx_params{tolerance},
1043 :     tolType => 'absolute', # $cplx_params{tolType},
1044 :     original_correct_ans => $formattedCorrectAnswer,
1045 :     answerIsString => $corrAnswerIsString,
1046 :     answer_form => 'cartesian',
1047 :     );
1048 :     $answer_evaluator->install_pre_filter(sub {
1049 : lr003k 1102 my $rh_ans = shift;
1050 :     $rh_ans->{original_student_ans} = $rh_ans->{student_ans};
1051 : sh002i 1050 my @answers = split/,/,$rh_ans->{student_ans};
1052 :     $rh_ans -> {student_ans} = $answers[$counter];
1053 :     $rh_ans;
1054 :     }
1055 :     );
1056 :     if (defined($cplx_params{strings}) && $cplx_params{strings}) {
1057 :     $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params);
1058 :     }
1059 : lr003k 1102 $answer_evaluator->install_pre_filter(\&check_syntax);
1060 : sh002i 1050 $answer_evaluator->install_pre_filter(\&math_constants);
1061 :     $answer_evaluator->install_pre_filter(\&cplx_constants);
1062 :     $answer_evaluator->install_pre_filter(\&check_for_polar);
1063 :     if ($mode eq 'std') {
1064 : lr003k 1102 # do nothing
1065 : sh002i 1050 } elsif ($mode eq 'strict_polar') {
1066 :     $answer_evaluator->install_pre_filter(\&is_a_polar);
1067 :     } elsif ($mode eq 'strict_num_cartesian') {
1068 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian);
1069 :     } elsif ($mode eq 'strict_num_polar') {
1070 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_polar);
1071 :     } elsif ($mode eq 'strict') {
1072 :     $answer_evaluator->install_pre_filter(\&is_a_numeric_complex);
1073 :     } elsif ($mode eq 'arith') {
1074 :     $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression);
1075 :     } elsif ($mode eq 'frac') {
1076 :     $answer_evaluator->install_pre_filter(\&is_a_fraction);
1077 :    
1078 : lr003k 1102 } else {
1079 : sh002i 1050 #$PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
1080 :     }
1081 :     if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string.
1082 :     $answer_evaluator->install_evaluator(\&compare_cplx, %cplx_params);
1083 :     }
1084 : lr003k 1102
1085 :    
1086 : sh002i 1050 ###############################################################################
1087 :     # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's
1088 :     # can be displayed in the answer message. This may still cause a few anomolies when strings are used
1089 :     #
1090 :     ###############################################################################
1091 :    
1092 :     $answer_evaluator->install_post_filter(\&fix_answers_for_display);
1093 :     $answer_evaluator->install_post_filter(\&fix_for_polar_display);
1094 : lr003k 1102 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
1095 : sh002i 1050 return $rh_ans unless $rh_ans->catch_error('EVAL');
1096 :     $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message};
1097 :     $rh_ans->clear_error('EVAL'); } );
1098 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } );
1099 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } );
1100 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } );
1101 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } );
1102 :     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; warn "ans hash", $rh_ans->clear_error('STRING'); } );
1103 :     $answer_evaluator;
1104 :     }
1105 :    
1106 :    
1107 :    
1108 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9