| 1 | #!/usr/bin/perl |
1 | #!/usr/local/bin/perl -w |
| 2 | |
2 | |
| 3 | # This file is PGanswermacros.pl |
3 | # This file is PGanswermacros.pl |
| 4 | # This includes the subroutines for the ANS macros, that |
4 | # This includes the subroutines for the ANS macros, that |
| 5 | # is, macros allowing a more flexible answer checking |
5 | # is, macros allowing a more flexible answer checking |
| 6 | #################################################################### |
6 | #################################################################### |
| … | |
… | |
| 106 | =cut |
106 | =cut |
| 107 | |
107 | |
| 108 | BEGIN { |
108 | BEGIN { |
| 109 | be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix. |
109 | be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix. |
| 110 | } |
110 | } |
|
|
111 | my ($BR , # convenient localizations. |
|
|
112 | $PAR , |
|
|
113 | $numRelPercentTolDefault , |
|
|
114 | $numZeroLevelDefault , |
|
|
115 | $numZeroLevelTolDefault , |
|
|
116 | $numAbsTolDefault , |
|
|
117 | $numFormatDefault , |
|
|
118 | $functRelPercentTolDefault , |
|
|
119 | $functZeroLevelDefault , |
|
|
120 | $functZeroLevelTolDefault , |
|
|
121 | $functAbsTolDefault , |
|
|
122 | $functNumOfPoints , |
|
|
123 | $functVarDefault , |
|
|
124 | $functLLimitDefault , |
|
|
125 | $functULimitDefault , |
|
|
126 | $functMaxConstantOfIntegration |
|
|
127 | ); |
| 111 | |
128 | |
|
|
129 | sub _PGanswermacros_init { |
|
|
130 | |
| 112 | my $BR = $main::BR; # convenient localizations. |
131 | $BR = $main::BR; # convenient localizations. |
| 113 | my $PAR = $main::PAR; |
132 | $PAR = $main::PAR; |
| 114 | |
133 | |
| 115 | # import defaults |
134 | # import defaults |
| 116 | # these are now imported from the %envir variable |
135 | # these are now imported from the %envir variable |
| 117 | my $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
136 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
| 118 | my $numZeroLevelDefault = $main::numZeroLevelDefault; |
137 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
| 119 | my $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
138 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
| 120 | my $numAbsTolDefault = $main::numAbsTolDefault; |
139 | $numAbsTolDefault = $main::numAbsTolDefault; |
| 121 | my $numFormatDefault = $main::numFormatDefault; |
140 | $numFormatDefault = $main::numFormatDefault; |
| 122 | |
141 | |
| 123 | my $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
142 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
| 124 | my $functZeroLevelDefault = $main::functZeroLevelDefault; |
143 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
| 125 | my $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
144 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
| 126 | my $functAbsTolDefault = $main::functAbsTolDefault; |
145 | $functAbsTolDefault = $main::functAbsTolDefault; |
| 127 | my $functNumOfPoints = $main::functNumOfPoints; |
146 | $functNumOfPoints = $main::functNumOfPoints; |
| 128 | my $functVarDefault = $main::functVarDefault; |
147 | $functVarDefault = $main::functVarDefault; |
| 129 | my $functLLimitDefault = $main::functLLimitDefault; |
148 | $functLLimitDefault = $main::functLLimitDefault; |
| 130 | my $functULimitDefault = $main::functULimitDefault; |
149 | $functULimitDefault = $main::functULimitDefault; |
| 131 | my $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
150 | $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
| 132 | |
151 | |
| 133 | |
152 | } |
|
|
153 | _PGanswermacros_init(); |
| 134 | |
154 | |
| 135 | ########################################################################## |
155 | ########################################################################## |
| 136 | ########################################################################## |
156 | ########################################################################## |
| 137 | ## Number answer evaluators |
157 | ## Number answer evaluators |
| 138 | |
158 | |
| … | |
… | |
| 1004 | $out_options{'format'}, |
1024 | $out_options{'format'}, |
| 1005 | $out_options{'zeroLevel'}, |
1025 | $out_options{'zeroLevel'}, |
| 1006 | $out_options{'zeroLevelTol'} |
1026 | $out_options{'zeroLevelTol'} |
| 1007 | ) |
1027 | ) |
| 1008 | ); |
1028 | ); |
| 1009 | } |
|
|
| 1010 | else { |
1029 | } else { |
| 1011 | push(@output_list, |
1030 | push(@output_list, |
| 1012 | NUM_CMP( 'correctAnswer' => $ans, |
1031 | NUM_CMP( 'correctAnswer' => $ans, |
| 1013 | 'tolerance' => $tol, |
1032 | 'tolerance' => $tol, |
| 1014 | 'tolType' => $tolType, |
1033 | 'tolType' => $tolType, |
| 1015 | 'format' => $out_options{'format'}, |
1034 | 'format' => $out_options{'format'}, |
| … | |
… | |
| 1052 | my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
1071 | my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
| 1053 | |
1072 | |
| 1054 | if( $tolType eq 'relative' ) { |
1073 | if( $tolType eq 'relative' ) { |
| 1055 | $tol = $numRelPercentTolDefault unless defined $tol; |
1074 | $tol = $numRelPercentTolDefault unless defined $tol; |
| 1056 | $tol *= .01; |
1075 | $tol *= .01; |
| 1057 | } |
1076 | |
| 1058 | else { |
1077 | } else { |
| 1059 | $tol = $numAbsTolDefault unless defined $tol; |
1078 | $tol = $numAbsTolDefault unless defined $tol; |
| 1060 | } |
1079 | } |
| 1061 | $format = $numFormatDefault unless defined $format; |
1080 | $format = $numFormatDefault unless defined $format; |
| 1062 | $mode = 'std' unless defined $mode; |
1081 | $mode = 'std' unless defined $mode; |
| 1063 | $zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
1082 | $zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
| … | |
… | |
| 1876 | $func_params{'numPoints'}= $numPoints; |
1895 | $func_params{'numPoints'}= $numPoints; |
| 1877 | $func_params{'mode'} = $mode; |
1896 | $func_params{'mode'} = $mode; |
| 1878 | $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration; |
1897 | $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration; |
| 1879 | $func_params{'zeroLevel'} = $zeroLevel; |
1898 | $func_params{'zeroLevel'} = $zeroLevel; |
| 1880 | $func_params{'zeroLevelTol'} = $zeroLevelTol; |
1899 | $func_params{'zeroLevelTol'} = $zeroLevelTol; |
| 1881 | |
1900 | ######################################################## |
|
|
1901 | # End of cleanup of calling parameters |
|
|
1902 | ######################################################## |
| 1882 | my $i; #for use with loops |
1903 | my $i; #for use with loops |
| 1883 | my $PGanswerMessage = ""; |
1904 | my $PGanswerMessage = ""; |
| 1884 | my $originalCorrEqn = $correctEqn; |
1905 | my $originalCorrEqn = $correctEqn; |
| 1885 | |
1906 | |
| 1886 | #prepare the correct answer and check it's syntax |
1907 | #prepare the correct answer and check it's syntax |
| … | |
… | |
| 2179 | my $error = "WeBWorK was unable evaluate your function. Please check that your |
2200 | my $error = "WeBWorK was unable evaluate your function. Please check that your |
| 2180 | expression doesn't take roots of negative numbers, or divide by zero."; |
2201 | expression doesn't take roots of negative numbers, or divide by zero."; |
| 2181 | $rh_ans->throw_error('EVAL',$error); |
2202 | $rh_ans->throw_error('EVAL',$error); |
| 2182 | } else { |
2203 | } else { |
| 2183 | my $tol = $options{tol} if defined($options{tol}); |
2204 | my $tol = $options{tol} if defined($options{tol}); |
| 2184 | $tol = 0.01*$options{reltol} if defined($options{reltol}); |
2205 | #$tol = 0.01*$options{reltol} if defined($options{reltol}); |
| 2185 | $tol = .000001 unless defined($tol); |
2206 | $tol = .000001 unless defined($tol); |
| 2186 | |
2207 | |
| 2187 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
2208 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
| 2188 | } |
2209 | } |
| 2189 | $rh_ans; |
2210 | $rh_ans; |
| … | |
… | |
| 2273 | my $rf_correct_fun = $rh_ans->{rf_correct_ans}; |
2294 | my $rf_correct_fun = $rh_ans->{rf_correct_ans}; |
| 2274 | my $ra_vars_matrix = $rh_ans->{evaluation_points}; |
2295 | my $ra_vars_matrix = $rh_ans->{evaluation_points}; |
| 2275 | my $dim_of_param_space = @{$options{param_vars}}; |
2296 | my $dim_of_param_space = @{$options{param_vars}}; |
| 2276 | # Short cut. Bail if there are no param_vars |
2297 | # Short cut. Bail if there are no param_vars |
| 2277 | unless ($dim_of_param_space >0) { |
2298 | unless ($dim_of_param_space >0) { |
| 2278 | $rh_ans ->{ra_paramters} = []; |
2299 | $rh_ans ->{ra_parameters} = []; |
| 2279 | return $rh_ans; |
2300 | return $rh_ans; |
| 2280 | } |
2301 | } |
| 2281 | # inputs are row arrays in this case. |
2302 | # inputs are row arrays in this case. |
| 2282 | my @zero_params=(); |
2303 | my @zero_params=(); |
| 2283 | |
2304 | |
| … | |
… | |
| 2315 | while(@coeff) { |
2336 | while(@coeff) { |
| 2316 | $matrix->assign($row_num,$col_num, shift(@coeff) ); |
2337 | $matrix->assign($row_num,$col_num, shift(@coeff) ); |
| 2317 | $col_num++; |
2338 | $col_num++; |
| 2318 | } |
2339 | } |
| 2319 | } |
2340 | } |
| 2320 | # which might be useful for functions that are not defined at some points. |
2341 | |
| 2321 | } |
2342 | } |
| 2322 | $row_num++; |
2343 | $row_num++; |
| 2323 | last if $errors; # break if there are any errors. |
2344 | last if $errors; # break if there are any errors. |
| 2324 | # This cuts down on the size of error messages. |
2345 | # This cuts down on the size of error messages. |
| 2325 | # However it impossible to check for equivalence at 95% of points |
2346 | # However it impossible to check for equivalence at 95% of points |
| 2326 | |
2347 | # which might be useful for functions that are not defined at some points. |
| 2327 | } |
2348 | } |
| 2328 | warn "<br> best_approx_parameters: matrix1 <br> ", " $matrix " if $options{debug}; |
2349 | warn "<br> best_approx_parameters: matrix1 <br> ", " $matrix " if $options{debug}; |
| 2329 | warn "<br> best_approx_parameters: vector <br> ", " $rhs_vec " if $options{debug}; |
2350 | warn "<br> best_approx_parameters: vector <br> ", " $rhs_vec " if $options{debug}; |
| 2330 | |
2351 | |
| 2331 | # we have Matrix * parameter = data_vec + perpendicular vector |
2352 | # we have Matrix * parameter = data_vec + perpendicular vector |
| … | |
… | |
| 2390 | my $ra_parameters = $rh_ans ->{ra_parameters}; |
2411 | my $ra_parameters = $rh_ans ->{ra_parameters}; |
| 2391 | my @evaluation_points = @{$rh_ans->{evaluation_points} }; |
2412 | my @evaluation_points = @{$rh_ans->{evaluation_points} }; |
| 2392 | my @parameters = (); |
2413 | my @parameters = (); |
| 2393 | @parameters = @$ra_parameters if defined($ra_parameters) and ref($ra_parameters) eq 'ARRAY'; |
2414 | @parameters = @$ra_parameters if defined($ra_parameters) and ref($ra_parameters) eq 'ARRAY'; |
| 2394 | my $errors = undef; |
2415 | my $errors = undef; |
|
|
2416 | my @zero_params=(); |
|
|
2417 | for(my $i=1;$i<=@{$ra_parameters};$i++){push(@zero_params,0); } |
| 2395 | my @differences = (); |
2418 | my @differences = (); |
| 2396 | my $diff; |
2419 | my @student_values; |
|
|
2420 | my @correct_values; |
|
|
2421 | my @tol_values; |
|
|
2422 | my ($diff,$tol_val); |
| 2397 | # calculate the vector of differences between the test function and the comparison function. |
2423 | # calculate the vector of differences between the test function and the comparison function. |
| 2398 | while (@evaluation_points) { |
2424 | while (@evaluation_points) { |
| 2399 | my ($err1, $err2); |
2425 | my ($err1, $err2,$err3); |
| 2400 | my @vars = @{ shift(@evaluation_points) }; |
2426 | my @vars = @{ shift(@evaluation_points) }; |
| 2401 | my @inputs = (@vars, @parameters); |
2427 | my @inputs = (@vars, @parameters); |
| 2402 | my ($inVal, $correctVal); |
2428 | my ($inVal, $correctVal); |
| 2403 | ($inVal, $err1) = &{$rf_fun}(@vars); |
2429 | ($inVal, $err1) = &{$rf_fun}(@vars); |
| 2404 | $errors .= " $err1 " if defined($err1); |
2430 | $errors .= " $err1 " if defined($err1); |
| 2405 | $errors .= " Error detected evaluating student input at @vars " if defined($options{debug}) and $options{debug}=1 and defined($err1); |
2431 | $errors .= " Error detected evaluating student input at (".join(' , ',@vars) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err1); |
| 2406 | ($correctVal, $err2) =&{$rf_correct_fun}(@inputs); |
2432 | ($correctVal, $err2) =&{$rf_correct_fun}(@inputs); |
| 2407 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err2 " if defined($err2); |
2433 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err2 " if defined($err2); |
| 2408 | $errors .= " Error detected evaluating correct answer at @inputs " if defined($options{debug}) and $options{debug}=1 and defined($err2); |
2434 | $errors .= " Error detected evaluating correct answer at (".join(' , ',@inputs) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err2); |
|
|
2435 | ($tol_val,$err3)= &$rf_correct_fun(@vars, @zero_params); |
|
|
2436 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err3 " if defined($err3); |
|
|
2437 | $errors .= " Error detected evaluating correct answer at (".join(' , ',@vars, @zero_params) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err3); |
| 2409 | unless (defined($err1) or defined($err2) ) { |
2438 | unless (defined($err1) or defined($err2) or defined($err3) ) { |
| 2410 | $diff = $inVal - $correctVal; |
2439 | $diff = ( $inVal - ($correctVal -$tol_val ) ) - $tol_val; #prevents entering too high a number? |
| 2411 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
2440 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
| 2412 | |
2441 | |
| 2413 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
2442 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
| 2414 | $diff = $diff/abs( &$rf_correct_fun(@inputs) ) if $correctVal > $options{zeroLevel}; |
2443 | #warn "diff = $diff"; |
|
|
2444 | |
|
|
2445 | $diff = abs(( $inVal - ($correctVal-$tol_val ) )/$tol_val -1 ) if abs($tol_val) > $options{zeroLevel}; |
|
|
2446 | #$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel}; |
|
|
2447 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
| 2415 | } |
2448 | } |
| 2416 | } |
2449 | } |
| 2417 | last if $errors; # break if there are any errors. |
2450 | last if $errors; # break if there are any errors. |
| 2418 | # This cuts down on the size of error messages. |
2451 | # This cuts down on the size of error messages. |
| 2419 | # However it impossible to check for equivalence at 95% of points |
2452 | # However it impossible to check for equivalence at 95% of points |
| 2420 | # which might be useful for functions that are not defined at some points. |
2453 | # which might be useful for functions that are not defined at some points. |
|
|
2454 | push(@student_values,$inVal); |
|
|
2455 | push(@correct_values,( $inVal - ($correctVal-$tol_val ) )); |
| 2421 | push(@differences, $diff); |
2456 | push(@differences, $diff); |
|
|
2457 | push(@tol_values,$tol_val); |
| 2422 | } |
2458 | } |
| 2423 | $rh_ans ->{ra_differences} = \@differences; |
2459 | $rh_ans ->{ra_differences} = \@differences; |
|
|
2460 | $rh_ans ->{ra_student_values} = \@student_values; # values from student function |
|
|
2461 | $rh_ans ->{ra_adjusted_instructor_values} = \@correct_values; #values |
|
|
2462 | $rh_ans->{ra_instructor_values}=\@tol_values; |
| 2424 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
2463 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
| 2425 | $rh_ans; |
2464 | $rh_ans; |
| 2426 | } |
2465 | } |
| 2427 | |
2466 | |
| 2428 | |
2467 | |
| … | |
… | |
| 3152 | $problem_result{score} = $allAnswersCorrectQ; |
3191 | $problem_result{score} = $allAnswersCorrectQ; |
| 3153 | |
3192 | |
| 3154 | # I don't like to put in this bit of code. |
3193 | # I don't like to put in this bit of code. |
| 3155 | # It makes it hard to construct error free problem graders |
3194 | # It makes it hard to construct error free problem graders |
| 3156 | # I would prefer to know that the problem score was numeric. |
3195 | # I would prefer to know that the problem score was numeric. |
| 3157 | unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
3196 | unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
| 3158 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
3197 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
| 3159 | } |
3198 | } |
| 3160 | # |
3199 | # |
| 3161 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
3200 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
| 3162 | $problem_state{recorded_score} = 1; |
3201 | $problem_state{recorded_score} = 1; |
| … | |
… | |
| 3535 | |
3574 | |
| 3536 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
3575 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
| 3537 | } |
3576 | } |
| 3538 | else { |
3577 | else { |
| 3539 | $out = $number; |
3578 | $out = $number; |
|
|
3579 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
| 3540 | } |
3580 | } |
| 3541 | |
3581 | |
| 3542 | return $out; |
3582 | return $out; |
| 3543 | } |
3583 | } |
| 3544 | |
3584 | |
| … | |
… | |
| 3579 | # Use this to set default options |
3619 | # Use this to set default options |
| 3580 | sub set_default_options { |
3620 | sub set_default_options { |
| 3581 | my $rh_options = shift; |
3621 | my $rh_options = shift; |
| 3582 | warn "The first entry to set_default_options must be a reference to the option hash" unless ref($rh_options) eq 'HASH'; |
3622 | warn "The first entry to set_default_options must be a reference to the option hash" unless ref($rh_options) eq 'HASH'; |
| 3583 | my %default_options = @_; |
3623 | my %default_options = @_; |
|
|
3624 | unless ( defined($default_options{allow_unknown_options}) and $default_options{allow_unknown_options} == 1 ) { |
| 3584 | foreach my $key (keys %$rh_options) { |
3625 | foreach my $key1 (keys %$rh_options) { |
| 3585 | warn "This option |$key| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key}); |
3626 | warn "This option |$key1| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key1}); |
|
|
3627 | } |
| 3586 | } |
3628 | } |
| 3587 | foreach my $key (keys %default_options) { |
3629 | foreach my $key (keys %default_options) { |
| 3588 | if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) { |
3630 | if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) { |
| 3589 | $rh_options->{$key} = $default_options{$key}; #this allows tol => undef to allow the tol option, but doesn't define |
3631 | $rh_options->{$key} = $default_options{$key}; # using 'defined' instead of 'exists' allows |
|
|
3632 | # tol => undef to allow the tol option, but doesn't define |
| 3590 | # this key unless tol is explicitly defined. |
3633 | # this key unless tol is explicitly defined. |
| 3591 | } |
3634 | } |
| 3592 | } |
3635 | } |
| 3593 | } |
3636 | } |
| 3594 | # Use this to assign aliases for the standard options |
3637 | # Use this to assign aliases for the standard options |