[system] / trunk / webwork / system / courseScripts / PGcomplexmacros.pl Repository:
ViewVC logotype

Diff of /trunk/webwork/system/courseScripts/PGcomplexmacros.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.386  
changed lines
  Added in v.387

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9