--- trunk/webwork/system/courseScripts/PGanswermacros.pl 2001/06/14 23:45:41 5
+++ trunk/webwork/system/courseScripts/PGanswermacros.pl 2001/06/20 19:04:08 35
@@ -1,4 +1,4 @@
-#!/usr/local/bin/perl -w
+#!/usr/local/bin/webwork-perl
# This file is PGanswermacros.pl
# This includes the subroutines for the ANS macros, that
@@ -7,6 +7,7 @@
# Copyright @ 1995-2000 University of Rochester
# All Rights Reserved
####################################################################
+#$Id$
=head1 NAME
@@ -108,49 +109,76 @@
BEGIN {
be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix.
}
-my ($BR , # convenient localizations.
- $PAR ,
- $numRelPercentTolDefault ,
- $numZeroLevelDefault ,
- $numZeroLevelTolDefault ,
- $numAbsTolDefault ,
- $numFormatDefault ,
- $functRelPercentTolDefault ,
- $functZeroLevelDefault ,
- $functZeroLevelTolDefault ,
- $functAbsTolDefault ,
- $functNumOfPoints ,
- $functVarDefault ,
- $functLLimitDefault ,
- $functULimitDefault ,
- $functMaxConstantOfIntegration
-);
-sub _PGanswermacros_init {
- $BR = $main::BR; # convenient localizations.
- $PAR = $main::PAR;
-
- # import defaults
- # these are now imported from the %envir variable
- $numRelPercentTolDefault = $main::numRelPercentTolDefault;
- $numZeroLevelDefault = $main::numZeroLevelDefault;
- $numZeroLevelTolDefault = $main::numZeroLevelTolDefault;
- $numAbsTolDefault = $main::numAbsTolDefault;
- $numFormatDefault = $main::numFormatDefault;
-
- $functRelPercentTolDefault = $main::functRelPercentTolDefault;
- $functZeroLevelDefault = $main::functZeroLevelDefault;
- $functZeroLevelTolDefault = $main::functZeroLevelTolDefault;
- $functAbsTolDefault = $main::functAbsTolDefault;
- $functNumOfPoints = $main::functNumOfPoints;
- $functVarDefault = $main::functVarDefault;
- $functLLimitDefault = $main::functLLimitDefault;
- $functULimitDefault = $main::functULimitDefault;
- $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration;
+sub _PGanswermacros_export {
+ my @EXPORT = (
+ '&std_num_cmp', '&std_num_cmp_list', '&std_num_cmp_abs',
+ '&std_num_cmp_abs_list', '&frac_num_cmp', '&frac_num_cmp_list',
+ '&frac_num_cmp_abs', '&frac_num_cmp_abs_list', '&arith_num_cmp',
+ '&arith_num_cmp_list', '&arith_num_cmp_abs', '&arith_num_cmp_abs_list',
+ '&strict_num_cmp', '&strict_num_cmp_list', '&strict_num_cmp_abs',
+ '&strict_num_cmp_abs_list', '&numerical_compare_with_units',
+ '&std_num_str_cmp', '&num_cmp', '&num_rel_cmp', '&NUM_CMP',
+ '&NUM_CMP_LIST', '&adaptive_function_cmp', '&function_cmp',
+ '&function_cmp_up_to_constant', '&function_cmp_abs',
+ '&function_cmp_up_to_constant_abs', '&multivar_function_cmp',
+ '&fun_cmp', '&FUNCTION_CMP', '&is_array', '&check_syntax',
+ '&std_num_filter', '&std_num_array_filter', '&function_from_string2',
+ '&is_zero_array', '&best_approx_parameters',
+ '&calculate_difference_vector', '&str_filters', '&remove_whitespace',
+ '&compress_whitespace', '&trim_whitespace', '&ignore_case',
+ '&ignore_order', '&std_str_cmp', '&std_str_cmp_list', '&std_cs_str_cmp',
+ '&std_cs_str_cmp_list', '&strict_str_cmp', '&strict_str_cmp_list',
+ '&unordered_str_cmp', '&unordered_str_cmp_list',
+ '&unordered_cs_str_cmp', '&unordered_cs_str_cmp_list',
+ '&ordered_str_cmp', '&ordered_str_cmp_list', '&ordered_cs_str_cmp',
+ '&ordered_cs_str_cmp_list', '&str_cmp', '&STR_CMP', '&checkbox_cmp',
+ '&radio_cmp', '&store_ans_at', '&DUMMY_ANSWER', '&escapeHTML',
+ '&anstext', '&ansradio', '&mail_answers_to', '&mail_answers_to2',
+ '&install_problem_grader', '&std_problem_grader',
+ '&std_problem_grader2', '&avg_problem_grader', '&get_var_array',
+ '&get_limits_array', '&check_option_list', '&function_invalid_params',
+ '&is_a_number', '&is_a_fraction', '&is_an_arithmetic_expression',
+ '&math_constants', '&clean_up_error_msg', '&prfmt', '&pretty_print',
+ '&set_default_options', '&assign_option_aliases',
+ );
+ @EXPORT;
+}
+
+my ($BR, $PAR,$numRelPercentTolDefault,$numZeroLevelDefault,$numZeroLevelTolDefault,
+ $numAbsTolDefault,$numFormatDefault,$functRelPercentTolDefault,$functZeroLevelDefault,
+ $functZeroLevelTolDefault,$functAbsTolDefault,$functNumOfPoints,$functVarDefault,
+ $functLLimitDefault, $functULimitDefault, $functMaxConstantOfIntegration,
+ );
+
+sub _PGanswermacros_init {
+
+ $BR = $main::BR; # convenient localizations.
+ $PAR = $main::PAR;
+
+ # import defaults
+ # these are now imported from the %envir variable
+ $numRelPercentTolDefault = PG_restricted_eval(q{$main::numRelPercentTolDefault});
+ $numZeroLevelDefault = PG_restricted_eval(q{$main::numZeroLevelDefault});
+ $numZeroLevelTolDefault = PG_restricted_eval(q{$main::numZeroLevelTolDefault});
+ $numAbsTolDefault = PG_restricted_eval(q{$main::numAbsTolDefault});
+ $numFormatDefault = PG_restricted_eval(q{$main::numFormatDefault});
+
+ $functRelPercentTolDefault = PG_restricted_eval(q{$main::functRelPercentTolDefault});
+ $functZeroLevelDefault = PG_restricted_eval(q{$main::functZeroLevelDefault});
+ $functZeroLevelTolDefault = PG_restricted_eval(q{$main::functZeroLevelTolDefault});
+ $functAbsTolDefault = PG_restricted_eval(q{$main::functAbsTolDefault});
+ $functNumOfPoints = PG_restricted_eval(q{$main::functNumOfPoints});
+ $functVarDefault = PG_restricted_eval(q{$main::functVarDefault});
+ $functLLimitDefault = PG_restricted_eval(q{$main::functLLimitDefault});
+ $functULimitDefault = PG_restricted_eval(q{$main::functULimitDefault});
+ $functMaxConstantOfIntegration = PG_restricted_eval(q{$main::functMaxConstantOfIntegration});
+
+
+
}
-_PGanswermacros_init();
##########################################################################
##########################################################################
@@ -351,16 +379,26 @@
=cut
sub std_num_cmp { # compare numbers allowing use of elementary functions
- my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+ my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'std',
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol
- );
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $numRelPercentTolDefault,
+ 'mode' => 'std',
+ 'format' => $numFormatDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'debug' => 0,
+ );
+
+ num_cmp([$correctAnswer], %options);
}
## Similar to std_num_cmp but accepts a list of numbers in the form
@@ -370,205 +408,337 @@
sub std_num_cmp_list {
my ( $relPercentTol, $format, @answerList) = @_;
- NUM_CMP_LIST( 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'std',
- 'zeroLevel' => $numZeroLevelDefault,
- 'zeroLevelTol' => $numZeroLevelTolDefault,
- 'answerList' => \@answerList
- );
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $numRelPercentTolDefault,
+ 'mode' => 'std',
+ 'format' => $numFormatDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'debug' => 0,
+ );
+
+ num_cmp(\@answerList, %options);
+
}
-sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance
+sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance
my ( $correctAnswer, $absTol, $format) = @_;
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format);
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'std',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'std',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0
- );
+ num_cmp([$correctAnswer], %options);
}
## See std_num_cmp_list for usage
+
sub std_num_cmp_abs_list {
my ( $absTol, $format, @answerList ) = @_;
- NUM_CMP_LIST( 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'std',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0,
- 'answerList' => \@answerList
- );
-}
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format,
+ );
+ set_default_options( \%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'std',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+
+ num_cmp(\@answerList, %options);
+
+}
sub frac_num_cmp { # only allow fractions and numbers as submitted answer
- my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'frac',
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol
- );
+ my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $relPercentTol,
+ 'mode' => 'frac',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'debug' => 0,
+ );
+
+ num_cmp([$correctAnswer], %options);
}
## See std_num_cmp_list for usage
sub frac_num_cmp_list {
- my ( $relPercentTol, $format, @answerList ) = @_;
-
- NUM_CMP_LIST( 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'frac',
- 'zeroLevel' => $numZeroLevelDefault,
- 'zeroLevelTol' => $numZeroLevelTolDefault,
- 'answerList' => \@answerList
- );
+ my ( $relPercentTol, $format, @answerList ) = @_;
+
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $relPercentTol,
+ 'mode' => 'frac',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'debug' => 0,
+ );
+
+ num_cmp(\@answerList, %options);
+
}
-sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance
- my ( $correctAnswer, $absTol, $format ) = @_;
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'frac',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0
- );
-}
+sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance
+ my ( $correctAnswer, $absTol, $format ) = @_;
+
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format
+ );
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'frac',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+ num_cmp([$correctAnswer], %options);
+
+}
+
## See std_num_cmp_list for usage
sub frac_num_cmp_abs_list {
- my ( $absTol, $format, @answerList ) = @_;
-
- NUM_CMP_LIST( 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'frac',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0,
- 'answerList' => \@answerList
- );
+ my ( $absTol, $format, @answerList ) = @_;
+
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format
+ );
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'frac',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+
+ num_cmp(\@answerList, %options);
}
sub arith_num_cmp { # only allow arithmetic expressions as submitted answer
- my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+
+ my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $relPercentTol,
+ 'mode' => 'arith',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'debug' => 0,
+ );
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'arith',
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol
- );
+ num_cmp([$correctAnswer], %options);
}
## See std_num_cmp_list for usage
sub arith_num_cmp_list {
- my ( $relPercentTol, $format, @answerList ) = @_;
+ my ( $relPercentTol, $format, @answerList ) = @_;
- NUM_CMP_LIST( 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'arith',
- 'zeroLevel' => $numZeroLevelDefault,
- 'zeroLevelTol' => $numZeroLevelTolDefault,
- 'answerList' => \@answerList
- );
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $relPercentTol,
+ 'mode' => 'arith',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'debug' => 0,
+ );
+ num_cmp(\@answerList, %options);
}
-sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance
- my ( $correctAnswer, $absTol, $format ) = @_;
+sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance
+ my ( $correctAnswer, $absTol, $format ) = @_;
+
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format
+ );
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'arith',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+ num_cmp([$correctAnswer], %options);
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'arith',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0
- );
+
}
## See std_num_cmp_list for usage
sub arith_num_cmp_abs_list {
- my ( $absTol, $format, @answerList ) = @_;
-
- NUM_CMP_LIST( 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'arith',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0,
- 'answerList' => \@answerList
- );
+ my ( $absTol, $format, @answerList ) = @_;
+
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format
+ );
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'arith',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+ num_cmp(\@answerList, %options);
+
}
sub strict_num_cmp { # only allow numbers as submitted answer
- my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+
+ my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $relPercentTol,
+ 'mode' => 'strict',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'debug' => 0,
+ );
+
+ num_cmp([$correctAnswer], %options);
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'strict',
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol
- );
}
## See std_num_cmp_list for usage
sub strict_num_cmp_list { # compare numbers
my ( $relPercentTol, $format, @answerList ) = @_;
+
+ my %options = ( 'tolerance' => $relPercentTol,
+ 'format' => $format,
+ );
+
+ set_default_options( \%options,
+ 'tolType' => 'relative',
+ 'tolerance' => $relPercentTol,
+ 'mode' => 'strict',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'debug' => 0,
+ );
+
+ num_cmp(\@answerList, %options);
+ }
- NUM_CMP_LIST( 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'format' => $format,
- 'mode' => 'strict',
- 'zeroLevel' => $numZeroLevelDefault,
- 'zeroLevelTol' => $numZeroLevelTolDefault,
- 'answerList' => \@answerList
- );
-}
sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance
- my ( $correctAnswer, $absTol, $format ) = @_;
- NUM_CMP( 'correctAnswer' => $correctAnswer,
- 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'strict',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0
- );
+ my ( $correctAnswer, $absTol, $format ) = @_;
+
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format
+ );
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'strict',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+
+ num_cmp([$correctAnswer], %options);
+
}
## See std_num_cmp_list for usage
sub strict_num_cmp_abs_list { # compare numbers
- my ( $absTol, $format, @answerList ) = @_;
+ my ( $absTol, $format, @answerList ) = @_;
+
+
+ my %options = ( 'tolerance' => $absTol,
+ 'format' => $format
+ );
+
+ set_default_options (\%options,
+ 'tolType' => 'absolute',
+ 'tolerance' => $absTol,
+ 'mode' => 'strict',
+ 'format' => $numFormatDefault,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0,
+ 'debug' => 0,
+ );
+
+ num_cmp(\@answerList, %options);
+
+
- NUM_CMP_LIST( 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'format' => $format,
- 'mode' => 'strict',
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0,
- 'answerList' => \@answerList
- );
}
@@ -583,153 +753,150 @@
## relTol -- a relative tolerance
## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies
## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero
-sub numerical_compare_with_units {
- my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units.
- my %options = @_; # all of the other inputs are (key value) pairs
- # handle the defaults
- $options{'mode'} = 'std' unless defined( $options{'mode'} );
- $options{'format'} = $numFormatDefault unless defined( $options{'format'} );
- $options{'zeroLevel'} = $numZeroLevelDefault unless defined( $options{'zeroLevel'} );
- $options{'zeroLevelTol'} = $numZeroLevelTolDefault unless defined( $options{'zeroLevelTol'} );
-
- # both spellings are maintained for backward compatibility
- # relTol is preferred
- if( defined $options{'reltol'} ) {
- $options{'relTol'} = $options{'reltol'};
- delete $options{'reltol'};
- }
-
- my ($tol, $tolerance_mode);
- if ( defined $options{'tol'} ) {
- $tol = $options{'tol'};
- $tolerance_mode = 'absolute';
- }
- elsif( defined $options{'relTol'} ) {
- $tol = $options{'relTol'};
- $tolerance_mode = 'relative';
- }
- else { #the default is a relative tolerance
- $tol = $numRelPercentTolDefault;
- $tolerance_mode = 'relative';
+
+sub check_strings {
+ my ($rh_ans, %options) = @_;
+
+ # if the student's answer is a number, simply return the answer hash (unchanged).
+
+ if ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) {
+ if ( $rh_ans->{answerIsString} == 1) {
+ $rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number
+ }
+ return $rh_ans;
+ }
+ # the student's answer is recognized as a string
+ my $ans = $rh_ans->{student_ans};
+
+# OVERVIEW of remindar of function:
+# if answer is correct, return correct. (adjust score to 1)
+# if answer is incorect:
+# 1) determine if the answer is sensible. if it is, return incorrect.
+# 2) if the answer is not sensible (and incorrect), then return an error message indicating so.
+# no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators)
+# last: 'STRING' post_filter will clear the error (avoiding pink screen.)
+
+ my $sensibleAnswer = 0;
+ $ans = str_filters( $ans, 'compress_whitespace' ); # remove trailing, leading, and double spaces.
+ my ($ans_eval) = str_cmp($rh_ans->{correct_ans});
+ my $temp_ans_hash = &$ans_eval($ans);
+ $rh_ans->{test} = $temp_ans_hash;
+ if ($temp_ans_hash->{score} ==1 ) { # students answer matches the correct answer.
+ $rh_ans->{score} = 1;
+ $sensibleAnswer = 1;
+ } else { # students answer does not match the correct answer.
+ ## find out if string makes sense
+ my $legalString = '';
+ my @legalStrings = @{$options{strings}};
+ foreach $legalString (@legalStrings) {
+ if ( uc($ans) eq uc($legalString) ) {
+ $sensibleAnswer = 1;
+ last;
+ }
+ }
+ $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible
+ $rh_ans->throw_error('EVAL', "$BR Your answer is not a recognized answer") unless ($sensibleAnswer);
+ # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer);
+ # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) );
+ }
+ $rh_ans->{student_ans} = $ans;
+ if ($sensibleAnswer) {
+ $rh_ans->throw_error('STRING', "The student's answer $rh_ans->{student_ans} is interpreted as a string.");
}
+ # warn ("\$rh_ans->{answerIsString} = $rh_ans->{answerIsString}");
+
+ $rh_ans;
+
+}
- # Prepare the correct answer
- $correct_answer = str_filters( $correct_answer, 'trim_whitespace' );
+
+sub check_units {
+ my ($rh_ans, %options) = @_;
+
+ my %correct_units = %{$rh_ans-> {rh_correct_units}};
+
+ my $ans = $rh_ans->{student_ans};
+ # $ans = '' unless defined ($ans);
+ $ans = str_filters ($ans, 'trim_whitespace');
+ my $original_student_ans = $ans;
+
+ $rh_ans->{original_student_ans} = $original_student_ans;
+
# it surprises me that the match below works since the first .* is greedy.
- my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/;
+ my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/;
+
+ unless ( defined($num_answer) && $units ) {
+ # there is an error reading the input
+ if ( $ans =~ /\S/ ) { # the answer is not blank
+ $rh_ans -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " .
+ "as a number or an arithmetic expression followed by a unit specification. " .
+ "Your answer must contain units." );
+ $rh_ans->throw_error('UNITS', "The answer \"$ans\" could not be interpreted " .
+ "as a number or an arithmetic expression followed by a unit specification. " .
+ "Your answer must contain units." );
+ }
- my %correct_units = Units::evaluate_units($correct_units);
- if ( defined( $correct_units{'ERROR'} ) ) {
- die "ERROR: The answer \"$correct_answer\" in the problem definition cannot be parsed:\n" .
- "$correct_units{'ERROR'}\n";
+ return $rh_ans;
}
- my $ans_evaluator = sub {
+ # we have been able to parse the answer into a numerical part and a unit part
- my $ans = shift;
- $ans = '' unless defined($ans);
- my $original_student_ans = $ans;
-
- $ans = str_filters( $ans, 'trim_whitespace' );
-
- my $ans_hash = new AnswerHash(
- 'score' => 0,
- 'correct_ans' => spf($correct_num_answer,$options{'format'}) . " $correct_units",
- 'student_ans' => $ans,
- 'ans_message' => '',
- 'type' => 'num_cmp_with_units',
- 'preview_text_string' => '',
- 'original_student_ans' => $original_student_ans
- );
+ # $num_answer = $1; #$1 and $2 from the regular expression above
+ # $units = $2;
- # it surprises me that the match below works since the first .* is greedy.
- my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/;
+ my %units = Units::evaluate_units($units);
+ if ( defined( $units{'ERROR'} ) ) {
+ # handle error condition
+ $units{'ERROR'} = clean_up_error_msg($units{'ERROR'});
+ $rh_ans -> setKeys( 'ans_message' => "$units{'ERROR'}" );
+ return $rh_ans;
+ }
- unless ( defined($num_answer) && $units ) {
- # there is an error reading the input
- if ( $ans =~ /\S/ ) { # the answer is not blank
- $ans_hash -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " .
- "as a number or an arithmetic expression followed by a unit specification. " .
- "Your answer must contain units." );
- }
+ my $units_match = 1;
+ my $fund_unit;
+ foreach $fund_unit (keys %correct_units) {
+ next if $fund_unit eq 'factor';
+ $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit};
+ }
+
+ if ( $units_match ) {
+ # units are ok. Evaluate the numerical part of the answer
+ $rh_ans->{'tolerance'} = $rh_ans->{'tolerance'}* $correct_units{'factor'}/$units{'factor'} if
+ $rh_ans->{'tolType'} eq 'absolute'; # the tolerance is in the units specified by the instructor.
+ $rh_ans->{correct_ans} = prfmt($rh_ans->{correct_ans}*$correct_units{'factor'}/$units{'factor'});
+ $rh_ans->{student_ans} = $num_answer;
- return $ans_hash;
+ } else {
+ $rh_ans -> setKeys( ans_message => 'There is an error in the units for this answer.' );
+ $rh_ans -> throw_error ( 'UNITS', 'There is an error in the units for this answer.' );
}
+
+ return $rh_ans;
+ }
- # we have been able to parse the answer into a numerical part and a unit part
-
- $num_answer = $1; #$1 and $2 from the regular expression above
- $units = $2;
-
- my %units = Units::evaluate_units($units);
- if ( defined( $units{'ERROR'} ) ) {
- # handle error condition
- $units{'ERROR'} = clean_up_error_msg($units{'ERROR'});
-
- $ans_hash -> setKeys( 'ans_message' => "$units{'ERROR'}" );
- return $ans_hash;
- }
+# This mode is depricated. send input through num_cmp -- it can handle units.
+sub numerical_compare_with_units {
+ my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units.
+ my %options = @_; # all of the other inputs are (key value) pairs
- my $units_match = 1;
- my $fund_unit;
- foreach $fund_unit (keys %correct_units) {
- next if $fund_unit eq 'factor';
- $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit};
- }
-
- if ( $units_match ) {
-
- # units are ok. Evaluate the numerical part of the answer
- $tol = $tol * $correct_units{'factor'}/$units{'factor'} if
- $tolerance_mode eq 'absolute'; # the tolerance is in the units specified by the instructor.
-
- my $numerical_answer_evaluator = NUM_CMP( 'correctAnswer' => $correct_num_answer*$correct_units{'factor'}/$units{'factor'},
- 'tolerance' => $tol,
- 'tolType' => $tolerance_mode,
- 'format' => $options{'format'},
- 'mode' => $options{'mode'},
- 'zeroLevel' => $options{'zeroLevel'},
- 'zeroLevelTol' => $options{'zeroLevelTol'} );
-
- # because num_answer may contain an arithmetic expression rather than
- # a number we can't multiply it by the $units{'factor'}
- # instead we divide the correct answer by this amount;
- # this is also why the numerical_answer_evaluator is not defined outside this subroutine.
-
- $ans_hash = &$numerical_answer_evaluator($num_answer);
-
- # now we need to doctor the correct answer in order to add units
- # to it and correct for the division we did before
- $ans_hash -> {correct_ans} =
- prfmt( ( $ans_hash->{'correct_ans'} )*$units{'factor'}/$correct_units{'factor'},
- $options{'format'} ) . " $correct_units";
- # we also need to doctor the submitted answer to get it back in its original format.
-
- # we don't add the units on if there is an error message from numerical_answer_evaluator
- if ( ( $ans_hash -> {ans_message} ) =~ /^\s*$/ ) {
- $ans_hash -> {student_ans} = $ans_hash -> {student_ans} . " $units";
- $ans_hash -> setKeys( original_student_ans => $ans );
- }
- else {
- # error message from numerical_answer_evaluator doesn't have units tacked on
- $ans_hash -> setKeys( original_student_ans => $ans );
- }
- }
- else {
- $ans_hash -> setKeys( ans_message => 'There is an error in the units for this answer.' );
- }
+ # Prepare the correct answer
+ $correct_answer = str_filters( $correct_answer, 'trim_whitespace' );
- return $ans_hash;
- };
+ # it surprises me that the match below works since the first .* is greedy.
+ my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/;
- $ans_evaluator;
+ $options{units} = $correct_units;
+
+
+ num_cmp($correct_num_answer, %options);
}
+
=head3 std_num_str_cmp()
-
+
NOTE: This function is maintained for compatibility. num_cmp() with the
'strings' parameter is slightly preferred.
@@ -760,111 +927,121 @@
=cut
-sub std_num_str_cmp {
+sub std_num_str_cmp {
my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
-
- $ra_legalStrings = [''] unless defined $ra_legalStrings;
- my @legalStrings = @{$ra_legalStrings};
-
- my $ans_evaluator = sub {
-
- my $ans = shift;
- my $ans_hash;
- my $corrAnswerIsString = 0;
-# my $studAnswerIsString = 0; ## uses new incorrect logic
- my $studAnswerIsString = 1;
-
- my $legalString = '';
- foreach $legalString (@legalStrings) {
- if ( uc($correctAnswer) eq uc($legalString) ) {
- $corrAnswerIsString = 1;
- last;
- }
- } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
-
- # Neither of these is perfect; the first is more general, but
- # has problems with certain special strings like "ee", while the
- # second doesn't support arithmetic expressions.
- #
-# if( $ans !~ m/^\s*([\+\-\*\/\^\(\)\[\]\{\}\s\d\.Ee]*|e|pi)\s*$/ ) {
-# $studAnswerIsString = 1;
+ # warn ('This method is depreciated. Use num_cmp instead.');
+ return num_cmp ($correctAnswer, strings=>$ra_legalStrings, relTol=>$relpercentTol, format=>$format,
+ zeroLevel=>$zeroLevel, zeroLevelTol=>$zeroLevelTol);
+}
+
+#sub old_std_num_str_cmp {
+# my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
+#
+# $ra_legalStrings = [''] unless defined $ra_legalStrings;
+# my @legalStrings = @{$ra_legalStrings};
+#
+# my $ans_evaluator = sub {
+#
+# my $ans = shift;
+# my $ans_hash;
+# my $corrAnswerIsString = 0;
+## my $studAnswerIsString = 0; ## uses new incorrect logic
+# my $studAnswerIsString = 1;
+#
+# my $legalString = '';
+# foreach $legalString (@legalStrings) {
+# if ( uc($correctAnswer) eq uc($legalString) ) {
+# $corrAnswerIsString = 1;
+# last;
+# }
+# } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
+#
+# # Neither of these is perfect; the first is more general, but
+# # has problems with certain special strings like "ee", while the
+# # second doesn't support arithmetic expressions.
+# #
+## if( $ans !~ m/^\s*([\+\-\*\/\^\(\)\[\]\{\}\s\d\.Ee]*|e|pi)\s*$/ ) {
+## $studAnswerIsString = 1;
+## }
+# #if( $ans !~ m/^\s*([\d+\-*\/^()]|e|pi)\s*$/ ) {
+# # $studAnswerIsString = 1;
+# #}
+#
+# ## Both the above new versions are incorrect. We replace this by the original logic namely that
+# ## an answer that contain any of the symbols
+# ## a digit(0-9), +, -, *, /, ^, (, ), {, }, [, ]
+# ## or an answer that consists of "pi" or "e" alone
+# ## will be considered an arithmetic expression rather than a string answer.
+#
+# if ($ans =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) {$studAnswerIsString = 0;}
+#
+#
+# ## at this point $studAnswerIsString = 0 iff correct answer is numeric
+#
+# if( $studAnswerIsString ) {
+# $ans = str_filters( $ans, 'compress_whitespace' )
# }
- #if( $ans !~ m/^\s*([\d+\-*\/^()]|e|pi)\s*$/ ) {
- # $studAnswerIsString = 1;
- #}
-
- ## Both the above new versions are incorrect. We replace this by the original logic namely that
- ## an answer that contain any of the symbols
- ## a digit(0-9), +, -, *, /, ^, (, ), {, }, [, ]
- ## or an answer that consists of "pi" or "e" alone
- ## will be considered an arithmetic expression rather than a string answer.
-
- if ($ans =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) {$studAnswerIsString = 0;}
-
-
- ## at this point $studAnswerIsString = 0 iff correct answer is numeric
-
- if( $studAnswerIsString ) {
- $ans = str_filters( $ans, 'compress_whitespace' )
- }
-
- if ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 1) ) {
- my $string_answer_evaluator = std_str_cmp( $correctAnswer );
- $ans_hash = &$string_answer_evaluator( $ans );
-
- if( ($ans_hash -> {score}) != 1 ) { ## find out if string makes sense
- my $sensibleAnswer = 0;
- foreach $legalString (@legalStrings) {
- if ( uc($ans) eq uc($legalString) ) {
- $sensibleAnswer = 1;
- last;
- }
- }
- $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible
-
- $ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' )
- unless ($sensibleAnswer);
- $ans_hash -> setKeys( 'student_ans' => uc($ans) );
- }
- }
- elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 0) ) {
- my $numeric_answer_evaluator = std_num_cmp($correctAnswer,$relpercentTol,$format,$zeroLevel,$zeroLevelTol);
- $ans_hash = &$numeric_answer_evaluator($ans);
- }
- elsif ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 0) ) {
- my $numeric_answer_evaluator = std_num_cmp(1);
- $ans_hash = &$numeric_answer_evaluator($ans);
- $ans_hash -> setKeys( 'score' => 0,
- 'correct_ans' => $correctAnswer
- );
- }
- elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 1) ) {
- my $string_answer_evaluator = std_str_cmp('bad');
- $ans_hash = &$string_answer_evaluator($ans);
-
- $ans_hash -> setKeys( 'score' => 0,
- 'correct_ans' => $correctAnswer
- );
-
- ## find out if string makes sense
- my $sensibleAnswer = 0;
- foreach $legalString (@legalStrings) {
- if ( uc($ans) eq uc($legalString) ) {
- $sensibleAnswer = 1;
- last;
- }
- }
- $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible
-
- $ans_hash -> setKeys( 'ans_message' => "Your answer is not a recognized answer" )
- unless $sensibleAnswer;
- }
-
- return $ans_hash;
- };
-
- return $ans_evaluator;
-}
+#
+#
+#
+#
+# if ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 1) ) {
+# my $string_answer_evaluator = std_str_cmp( $correctAnswer );
+# $ans_hash = &$string_answer_evaluator( $ans );
+#
+# if( ($ans_hash -> {score}) != 1 ) { ## find out if string makes sense
+# my $sensibleAnswer = 0;
+# foreach $legalString (@legalStrings) {
+# if ( uc($ans) eq uc($legalString) ) {
+# $sensibleAnswer = 1;
+# last;
+# }
+# }
+# $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible
+#
+# $ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' )
+# unless ($sensibleAnswer);
+# $ans_hash -> setKeys( 'student_ans' => uc($ans) );
+# }
+# }
+# elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 0) ) {
+# my $numeric_answer_evaluator = std_num_cmp($correctAnswer,$relpercentTol,$format,$zeroLevel,$zeroLevelTol);
+# $ans_hash = &$numeric_answer_evaluator($ans);
+# }
+# elsif ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 0) ) {
+# my $numeric_answer_evaluator = std_num_cmp(1);
+# $ans_hash = &$numeric_answer_evaluator($ans);
+# $ans_hash -> setKeys( 'score' => 0,
+# 'correct_ans' => $correctAnswer
+# );
+# }
+# elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 1) ) {
+# my $string_answer_evaluator = std_str_cmp('bad');
+# $ans_hash = &$string_answer_evaluator($ans);
+#
+# $ans_hash -> setKeys( 'score' => 0,
+# 'correct_ans' => $correctAnswer
+# );
+#
+# ## find out if string makes sense
+# my $sensibleAnswer = 0;
+# foreach $legalString (@legalStrings) {
+# if ( uc($ans) eq uc($legalString) ) {
+# $sensibleAnswer = 1;
+# last;
+# }
+# }
+# $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible
+#
+# $ans_hash -> setKeys( 'ans_message' => "Your answer is not a recognized answer" )
+# unless $sensibleAnswer;
+# }
+#
+# return $ans_hash;
+# };
+#
+# return $ans_evaluator;
+#}
=head3 num_cmp()
@@ -909,23 +1086,29 @@
sub num_cmp {
my $correctAnswer = shift @_;
my @opt = @_;
+ my %out_options;
+#########################################################################
+# Retain this first check for backword compatibility. Allows input of the form
+# num_cmp($ans, 1, '%0.5f') but warns against it
+#########################################################################
+
my %known_options = ( 'mode' => 'std',
- 'format' => $numFormatDefault,
- 'tol' => $numAbsTolDefault,
- 'relTol' => $numRelPercentTolDefault,
- 'units' => undef,
- 'strings' => undef,
- 'zeroLevel' => $numZeroLevelDefault,
- 'zeroLevelTol' => $numZeroLevelTolDefault,
-
- 'reltol' => undef, #alternate spelling
- 'unit' => undef #alternate spelling
- );
- my %in_options;
+ 'format' => $numFormatDefault,
+ 'tol' => $numAbsTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'units' => undef,
+ 'strings' => undef,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'tolType' => 'relative',
+ 'tolerance' => 1,
+ 'reltol' => undef, #alternate spelling
+ 'unit' => undef); #alternate spelling
+
my @output_list;
- my %out_options;
-
+ my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt;
+
unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 ||
( defined($opt[0]) and exists $known_options{$opt[0]} ) ) {
# unless the first parameter is a list of arrays
@@ -936,7 +1119,7 @@
warn "This method of using num_cmp() is deprecated. Please rewrite this" .
" problem using the options style of parameter passing (or" .
" check that your first option is spelled correctly).";
- my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt;
+
%out_options = ( 'relTol' => $relPercentTol,
'format' => $format,
@@ -945,56 +1128,72 @@
'mode' => 'std'
);
}
- else {
- # handle options
+# else {
+# # handle options
+#
+#
+# @opt = ( 'relTol' => $relPercentTol,
+# 'format' => $format,
+# 'zeroLevel' => $numZeroLevelDefault,
+# 'zeroLevelTol' => $numZeroLevelTolDefault,
+# 'mode' => 'std'
+# );
+# }
+#########################################################################
+# Now handle the options assuming they are entered in the form
+# num_cmp($ans, relTol=>1, format=>'%0.5f')
+#########################################################################
+ %out_options = @opt;
+ assign_option_aliases( \%out_options,
+ 'reltol' => 'relTol',
+ 'unit' => 'units',
+ );
- check_option_list( @opt );
- %in_options = @opt;
- # both spellings maintained for compatibility
- # relTol is preferred
- if( defined( $in_options{'reltol'} ) ) {
- $in_options{'relTol'} = $in_options{'reltol'};
- delete $in_options{'reltol'};
- }
- # both spellings maintained for compatibility
- # units is preferred
- if( defined( $in_options{'unit'} ) ) {
- $in_options{'units'} = $in_options{'unit'};
- delete $in_options{'unit'};
- }
- # can't use both units and strings
- if( defined( $in_options{'units'} ) && defined( $in_options{'strings'} ) ) {
- warn "Can't use both 'units' and 'strings' in the same problem " .
- "(check your parameters to num_cmp() )";
- }
+ set_default_options( \%out_options,
+ 'tolType' => (defined($out_options{tol}) ) ? 'absolute' : 'relative',
+ 'tolerance' => (defined($out_options{tol}) ) ? $numAbsTolDefault : $numRelPercentTolDefault,
+ 'mode' => 'std',
+ 'format' => $numFormatDefault,
+ 'tol' => $numAbsTolDefault,
+ 'relTol' => $numRelPercentTolDefault,
+ 'units' => undef,
+ 'strings' => undef,
+ 'zeroLevel' => $numZeroLevelDefault,
+ 'zeroLevelTol' => $numZeroLevelTolDefault,
+ 'debug' => 0,
+
+ );
- #%out_options = %known_options;
- foreach my $opt_name (keys %in_options) {
- if( exists( $known_options{$opt_name} ) ) {
- $out_options{$opt_name} = $in_options{$opt_name};
- }
- else {
- die "Option $opt_name is not defined for num_cmp. Answer is $correctAnswer; " .
- "Default options are:
", pretty_print(\%known_options);
- }
- }
- }
- # set tolerance flags -- note that the order of testing means that
- # relative tolerance is the default
- my ($tolType, $tol);
- if ( defined( $out_options{'tol'} ) ) {
- $tolType = 'absolute';
- $tol = $out_options{'tol'};
+
+
+ # can't use both units and strings
+ if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) {
+ warn "Can't use both 'units' and 'strings' in the same problem " .
+ "(check your parameters to num_cmp() )";
+
}
- else {
- $tolType = 'relative';
- $tol = $out_options{'relTol'};
+
+
+ # my ($tolType, $tol);
+ if ($out_options{tolType} eq 'absolute') {
+ # $tolType = 'absolute';
+ # $out_options{tolType} = 'absolute';
+ # $tol = $out_options{'tol'};
+ $out_options{'tolerance'}=$out_options{'tol'};
+ delete($out_options{'relTol'}) if exists( $out_options{'relTol'} );
+ } else {
+ # $tolType = 'relative';
+ # $out_options{tolType} = 'relative';
+ # $tol = $out_options{'relTol'};
+ # $out_options{'tolType'} = $out_options{'relative'};
+ $out_options{'tolerance'}=$out_options{'relTol'};
+ # delete($out_options{'tol'}) if exists( $out_options{'tol'} );
}
# thread over lists
@@ -1006,46 +1205,67 @@
else {
push( @ans_list, $correctAnswer );
}
+
# produce answer evaluators
foreach my $ans (@ans_list) {
- if( defined( $out_options{'units'} ) ) {
- $ans = "$ans $out_options{'units'}";
- push( @output_list, numerical_compare_with_units($ans, %out_options) );
- }
- elsif( defined( $out_options{'strings'} ) ) {
- if( defined $out_options{'tol'} ) {
- warn "You are using 'tol' (for absolute tolerance) with a num/str " .
- "compare, which currently only uses relative tolerance. The default " .
- "tolerance will be used.";
- }
-
- push( @output_list, std_num_str_cmp( $ans, $out_options{'strings'},
- $out_options{'relTol'},
- $out_options{'format'},
- $out_options{'zeroLevel'},
- $out_options{'zeroLevelTol'}
- )
- );
- } else {
+ if( defined( $out_options{'units'} ) ) {
+ $ans = "$ans $out_options{'units'}";
+
+ push( @output_list, NUM_CMP( 'correctAnswer' => $ans,
+ 'tolerance' => $out_options{tolerance},
+ 'tolType' => $out_options{tolType},
+ 'format' => $out_options{'format'},
+ 'mode' => $out_options{'mode'},
+ 'zeroLevel' => $out_options{'zeroLevel'},
+ 'zeroLevelTol' => $out_options{'zeroLevelTol'},
+ 'debug' => $out_options{'debug'},
+ 'units' => $out_options{'units'},
+ )
+ );
+ }
+ elsif( defined( $out_options{'strings'} ) ) {
+ #if( defined $out_options{'tol'} ) {
+ # warn "You are using 'tol' (for absolute tolerance) with a num/str " .
+ # "compare, which currently only uses relative tolerance. The default " .
+ # "tolerance will be used.";
+ #}
+
+ push( @output_list, NUM_CMP( 'correctAnswer' => $ans,
+ 'tolerance' => $out_options{tolerance},
+ 'tolType' => $out_options{tolType},
+ 'format' => $out_options{'format'},
+ 'mode' => $out_options{'mode'},
+ 'zeroLevel' => $out_options{'zeroLevel'},
+ 'zeroLevelTol' => $out_options{'zeroLevelTol'},
+ 'debug' => $out_options{'debug'},
+ 'strings' => $out_options{'strings'},
+
+ )
+ );
+ }
+ else {
+
push(@output_list,
- NUM_CMP( 'correctAnswer' => $ans,
- 'tolerance' => $tol,
- 'tolType' => $tolType,
- 'format' => $out_options{'format'},
- 'mode' => $out_options{'mode'},
- 'zeroLevel' => $out_options{'zeroLevel'},
- 'zeroLevelTol' => $out_options{'zeroLevelTol'},
- ),
- );
- }
+ NUM_CMP( 'correctAnswer' => $ans,
+ 'tolerance' => $out_options{tolerance},
+ 'tolType' => $out_options{tolType},
+ 'format' => $out_options{'format'},
+ 'mode' => $out_options{'mode'},
+ 'zeroLevel' => $out_options{'zeroLevel'},
+ 'zeroLevelTol' => $out_options{'zeroLevelTol'},
+ 'debug' => $out_options{'debug'},
+
+ ),
+ );
+ }
}
-
+
return @output_list;
-}
+ }
#legacy code for compatability purposes
sub num_rel_cmp { # compare numbers
- std_num_cmp( @_ );
+ std_num_cmp( @_ );
}
## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION
@@ -1059,243 +1279,198 @@
## determines allowable formats for the input
## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies
## zeroLevelTol -- absolute tolerance to allow when answer is close to zero
-sub NUM_CMP { # low level numeric compare
- my %num_params = @_;
-
- my $correctAnswer = $num_params{'correctAnswer'};
- my $tol = $num_params{'tolerance'};
- my $tolType = $num_params{'tolType'};
- my $format = $num_params{'format'};
- my $mode = $num_params{'mode'};
- my $zeroLevel = $num_params{'zeroLevel'};
- my $zeroLevelTol = $num_params{'zeroLevelTol'};
-
- if( $tolType eq 'relative' ) {
- $tol = $numRelPercentTolDefault unless defined $tol;
- $tol *= .01;
+sub compare_numbers {
+ my ($rh_ans, %options) = @_;
+ my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans});
+ if ($PG_eval_errors) {
+ $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
+ $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
+
+
} else {
- $tol = $numAbsTolDefault unless defined $tol;
+ $rh_ans->{student_ans} = prfmt($inVal,$options{format});
}
- $format = $numFormatDefault unless defined $format;
- $mode = 'std' unless defined $mode;
- $zeroLevel = $numZeroLevelDefault unless defined $zeroLevel;
- $zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol;
-
- my $formattedCorrectAnswer = prfmt( $correctAnswer, $format );
-
- my $answer_evaluator = sub {
- my $in = shift @_;
- $in = '' unless defined $in;
- my $score = 0;
- my $original_student_answer = $in;
- my $parser = new AlgParserWithImplicitExpand;
- my $ret = $parser -> parse($in);
- my $preview_text_string = '';
- my $preview_latex_string = '';
-
- if ( ref($ret) ) { ## parsed successfully
- $parser -> tostring();
- $parser -> normalize();
- $in = $parser -> tostring();
- $preview_text_string = $in;
- $preview_latex_string = $parser -> tolatex();
-
- }
- else { ## error in parsing
- my $ans_hash = new AnswerHash(
- 'score' => $score,
- 'correct_ans' => $formattedCorrectAnswer,
- 'student_ans' => "error: $parser->{htmlerror}",
- 'ans_message' => $parser -> {error_msg},
- 'type' => "${mode}_number",
- 'preview_text_string' => $preview_text_string,
- 'preview_latex_string' => $preview_latex_string,
- 'original_student_ans' => $original_student_answer
- );
-
- return $ans_hash;
- }
-
- my $PGanswerMessage = '';
-
- my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report);
-
- $inVal = '';
- $correctAnswer = math_constants($correctAnswer);
- my $formattedSubmittedAnswer = '';
+
+ my $permitted_error;
+
+ if ($rh_ans->{tolType} eq 'absolute') {
+ $permitted_error = $rh_ans->{tolerance};
+
+ }
+ elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) {
+ $permitted_error = $options{zeroLevelTol}; ## want $tol to be non zero
+ }
+ else {
+ $permitted_error = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans});
+ }
+
+ my $is_a_number = is_a_number($inVal);
+ $rh_ans->{score} = 1 if ( ($is_a_number) and
+ (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) );
+ if (not $is_a_number) {
+ $rh_ans->throw_error('EVAL','Your answer does not evaluate to a number');
+ }
+
+ $rh_ans;
+}
- #special variable $@ holds the last error from a Perl eval statement
- $@='';
+sub NUM_CMP { # low level numeric compare
+ my %num_params = @_;
+
+ my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug );
+ foreach my $key (@keys) {
+ warn "$key must be defined in options when calling NUM_CMP" unless defined ($num_params{$key});
+ }
- if ($correctAnswer =~ /\S/) {
- ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correctAnswer);
- }
- else {
- $PG_eval_errors = ' ';
- }
+ my $correctAnswer = $num_params{'correctAnswer'};
+ my $format = $num_params{'format'};
+ my $mode = $num_params{'mode'};
+
+ # my $tol = $num_params{'tolerance'};
+ # my $tolType = $num_params{'tolType'};
+ # my $zeroLevel = $num_params{'zeroLevel'};
+ # my $zeroLevelTol = $num_params{'zeroLevelTol'};
+
+ if( $num_params{tolType} eq 'relative' ) {
+ $num_params{'tolerance'} = .01*$num_params{'tolerance'};
+ }
- if ( $PG_eval_errors or not is_a_number($correctVal) ) { ##error message from eval or above
- $formattedSubmittedAnswer = $PG_eval_errors;
- $formattedSubmittedAnswer = clean_up_error_msg($formattedSubmittedAnswer);
- $PGanswerMessage = 'Tell your professor that there is an error in this problem';
- my $ans_hash = new AnswerHash(
- 'score' => $score,
- 'correct_ans' => $formattedCorrectAnswer,
- 'student_ans' => $formattedSubmittedAnswer,
- 'ans_message' => $PGanswerMessage,
- 'type' => 'number',
- 'preview_text_string' => $preview_text_string,
- 'preview_latex_string' => $preview_latex_string,
- 'original_student_ans' => $original_student_answer
- );
+ #$format = $numFormatDefault unless defined $format;
+ #$mode = 'std' unless defined $mode;
+ #$zeroLevel = $numZeroLevelDefault unless defined $zeroLevel;
+ #$zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol;
+
+ my $formattedCorrectAnswer;
+ my $correct_units;
+ my $correct_num_answer;
+ my %correct_units;
+ my $corrAnswerIsString = 0;
+
- return $ans_hash;
+ if (defined($num_params{units}) && $num_params{units}) {
+ $correctAnswer = str_filters( $correctAnswer, 'trim_whitespace' );
+ # units are in form stuff space units where units contains no spaces.
+
+ ($correct_num_answer, $correct_units) = $correctAnswer =~ /^(.*)\s+([^\s]*)$/;
+ %correct_units = Units::evaluate_units($correct_units);
+ if ( defined( $correct_units{'ERROR'} ) ) {
+ warn ("ERROR: The answer \"$correctAnswer\" in the problem definition cannot be parsed:\n" .
+ "$correct_units{'ERROR'}\n");
}
-
- $in = &math_constants($in);
-
- MODE_CASE: { ## bare block for "case" statement
- if ($mode eq 'std') {
- last MODE_CASE;
- }
- elsif ($mode eq 'strict') {
- unless (is_a_number($in)) {
- $PGanswerMessage = 'You must enter a number, e.g. -6, 5.3, or 6.12E-3';
- $formattedSubmittedAnswer = 'Incorrect number format';
- }
- else {
- last MODE_CASE;
- }
- }
- elsif ($mode eq 'arith') {
- unless (is_an_arithmetic_expression($in)) {
- $PGanswerMessage = 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2';
- $formattedSubmittedAnswer = 'Not an arithmetic expression';
- }
- else {
- last MODE_CASE;
- }
- }
- elsif ($mode eq 'frac') {
- unless (is_a_fraction($in)) {
- $PGanswerMessage = 'You must enter a number or fraction , e.g. -6 or 7/13';
- $formattedSubmittedAnswer = 'Not a number or fraction';
- }
- else {
- last MODE_CASE;
- }
- }
- else {
- $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
- $formattedSubmittedAnswer = $in;
+ # $formattedCorrectAnswer = spf($correct_num_answer,$num_params{'format'}) . " $correct_units";
+ $formattedCorrectAnswer = prfmt($correct_num_answer,$num_params{'format'}) . " $correct_units";
+
+ } elsif (defined($num_params{strings}) && $num_params{strings}) {
+
+ my $legalString = '';
+ my @legalStrings = @{$num_params{strings}};
+ $correct_num_answer = $correctAnswer;
+ $formattedCorrectAnswer = $correctAnswer;
+ foreach $legalString (@legalStrings) {
+ if ( uc($correctAnswer) eq uc($legalString) ) {
+ $corrAnswerIsString = 1;
+ last;
}
+ } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
+
- my $ans_hash = new AnswerHash(
- score => $score,
- correct_ans => $formattedCorrectAnswer,
- student_ans => $formattedSubmittedAnswer,
- ans_message => $PGanswerMessage,
- type => "${mode}_number",
- preview_text_string => $preview_text_string,
- preview_latex_string => $preview_latex_string,
- original_student_ans => $original_student_answer
- );
+ } else {
+ $correct_num_answer = $correctAnswer;
+ $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} );
+ }
- return $ans_hash;
- } # end of MODE_CASES bare block
+ $correct_num_answer = math_constants($correct_num_answer);
+
+ my $PGanswerMessage = '';
+
+ my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report);
+
+ if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) {
+ ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer);
+ }
+ else {
+ $PG_eval_errors = ' ';
+ }
- $@ = '';
- if ($in =~ /\S/) {
+ if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) {
+ ##error message from eval or above
+ warn "Error in 'correct' answer: $PG_eval_errors
+ The answer $correctAnswer evaluates to $correctVal,
+ which cannot be interpreted as a number. ";
+
+ }
+ #########################################################################
- ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in);
- }
- else {
- $PG_eval_errors = ' ';
- }
+ #construct the answer evaluator
+ my $answer_evaluator = new AnswerEvaluator;
+ $answer_evaluator->{debug} = $num_params{debug};
+ $answer_evaluator->ans_hash( correct_ans => $correct_num_answer,
+ type => "${mode}_number",
+ tolerance => $num_params{tolerance},
+ tolType => $num_params{tolType},
+ units => $correct_units,
+ original_correct_ans => $formattedCorrectAnswer,
+ rh_correct_units => \%correct_units,
+ answerIsString => $corrAnswerIsString,
+ );
+ my ($in, $formattedSubmittedAnswer);
+ $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift;
+ $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;}
+ );
+ if (defined($num_params{units}) && $num_params{units}) {
+ $answer_evaluator->install_pre_filter(\&check_units);
+ }
+ if (defined($num_params{strings}) && $num_params{strings}) {
+ $answer_evaluator->install_pre_filter(\&check_strings, %num_params);
+ }
- if ($PG_eval_errors) { ##error message from eval or above
- $formattedSubmittedAnswer = $PG_eval_errors;
- $formattedSubmittedAnswer =clean_up_error_msg($formattedSubmittedAnswer);
- $PGanswerMessage = 'There is a syntax error in your answer';
- $PGanswerMessage = '' if $PG_eval_errors eq ' ';
- my $ans_hash = new AnswerHash(
- 'score' => $score,
- 'correct_ans' => $formattedCorrectAnswer,
- 'student_ans' => $formattedSubmittedAnswer,
- 'ans_message' => $PGanswerMessage,
- 'type' => "${mode}_number",
- 'preview_text_string' => $preview_text_string,
- 'preview_latex_string' => $preview_latex_string,
- 'original_student_ans' => $original_student_answer
- );
+
+ $answer_evaluator->install_pre_filter(\&check_syntax);
+
+ $answer_evaluator->install_pre_filter(\&math_constants);
- return $ans_hash;
- }
- else {
- $formattedSubmittedAnswer = prfmt($inVal,$format);
- }
- my $permitted_error;
- if (defined($tolType) && $tolType eq 'absolute') {
- $permitted_error = $tol;
- }
- elsif ( abs($correctVal) <= $zeroLevel) {
- $permitted_error = $zeroLevelTol; ## want $tol to be non zero
- }
- else {
- $permitted_error = abs($tol*$correctVal);
- }
- my $is_a_number = is_a_number($inVal);
- $score = 1 if ( ($is_a_number) and
- (abs( $inVal - $correctVal ) <= $permitted_error) );
- if ($PG_eval_errors) {
- $PGanswerMessage = 'There is a syntax error in your answer';
- }
- elsif (not $is_a_number) {
- $PGanswerMessage = 'Your answer does not evaluate to a number';
- }
+ if ($mode eq 'std') {
+ # do nothing
+ } elsif ($mode eq 'strict') {
+ $answer_evaluator->install_pre_filter(\&is_a_number);
+ } elsif ($mode eq 'arith') {
+ $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression);
+ } elsif ($mode eq 'frac') {
+ $answer_evaluator->install_pre_filter(\&is_a_fraction);
- my $ans_hash = new AnswerHash(
- 'score' => $score,
- 'correct_ans' => $formattedCorrectAnswer,
- 'student_ans' => $formattedSubmittedAnswer,
- 'ans_message' => $PGanswerMessage,
- 'type' => "${mode}_number",
- 'preview_text_string' => $preview_text_string,
- 'preview_latex_string' => $preview_latex_string,
- 'original_student_ans' => $original_student_answer
- );
+ } else {
+ $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
+ $formattedSubmittedAnswer = $in;
+ }
+
+ if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string.
+ $answer_evaluator->install_evaluator(\&compare_numbers, %num_params);
+ }
- return $ans_hash;
- };
+ $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
+
+ $rh_ans->{student_ans} = $rh_ans->{original_student_ans};
+ $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans};
+ $rh_ans;}
+ );
- return $answer_evaluator;
+ $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
+ return $rh_ans unless $rh_ans->catch_error('EVAL');
+ $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message};
+ $rh_ans->clear_error('EVAL'); } );
+ $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } );
+ $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('UNITS'); } );
+ $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('NUMBER'); } );
+ $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } );
+
+
+ $answer_evaluator;
}
-## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION
-sub NUM_CMP_LIST { # low level numeric list compare
- my %num_params = @_;
-
- my @outputList;
- my $ans;
-
- while ( @{$num_params{'answerList'}} ) {
- $ans = shift @{$num_params{'answerList'}};
- push( @outputList, NUM_CMP( 'correctAnswer' => $ans,
- 'tolerance' => $num_params{'tolerance'},
- 'tolType' => $num_params{'tolType'},
- 'format' => $num_params{'format'},
- 'mode' => $num_params{'mode'},
- 'zeroLevel' => $num_params{'zeroLevel'},
- 'zeroLevelTol' => $num_params{'zeroLevelTol'}
- )
- );
- }
- return @outputList;
-}
@@ -1489,7 +1664,7 @@
'reltol' => $main::functRelPercentTolDefault,
'numPoints' => $main::functNumOfPoints,
'zeroLevel' => $main::functZeroLevelDefault,
- 'zeroLevelTol' => $main::functZeroLevelTolDefault,
+ 'zeroLevelTol' => $main::functZeroLevelTolDefault,
'debug' => 0,
);
@@ -1501,19 +1676,19 @@
my $zeroLevel = $options{'zeroLevel'};
my $zeroLevelTol = $options{'zeroLevelTol'};
- FUNCTION_CMP( 'correctEqn' => $correctEqn,
- 'var' => $var_ref,
- 'limits' => $limit_ref,
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'numPoints' => $numPoints,
- 'mode' => 'std',
- 'maxConstantOfIntegration' => 10**100,
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol,
- 'scale_norm' => 1,
- 'params' => $ra_params,
- 'debug' => $options{debug} ,
+ FUNCTION_CMP( 'correctEqn' => $correctEqn,
+ 'var' => $var_ref,
+ 'limits' => $limit_ref,
+ 'tolerance' => $relPercentTol,
+ 'tolType' => 'relative',
+ 'numPoints' => $numPoints,
+ 'mode' => 'std',
+ 'maxConstantOfIntegration' => 10**100,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol,
+ 'scale_norm' => 1,
+ 'params' => $ra_params,
+ 'debug' => $options{debug} ,
);
}
@@ -1525,16 +1700,16 @@
function_invalid_params( $correctEqn );
}
else {
- FUNCTION_CMP( 'correctEqn' => $correctEqn,
- 'var' => $var,
- 'limits' => [$llimit, $ulimit],
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'numPoints' => $numPoints,
- 'mode' => 'std',
- 'maxConstantOfIntegration' => 0,
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol
+ FUNCTION_CMP( 'correctEqn' => $correctEqn,
+ 'var' => $var,
+ 'limits' => [$llimit, $ulimit],
+ 'tolerance' => $relPercentTol,
+ 'tolType' => 'relative',
+ 'numPoints' => $numPoints,
+ 'mode' => 'std',
+ 'maxConstantOfIntegration' => 0,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol
);
}
}
@@ -1546,16 +1721,16 @@
function_invalid_params( $correctEqn );
}
else {
- FUNCTION_CMP( 'correctEqn' => $correctEqn,
- 'var' => $var,
- 'limits' => [$llimit, $ulimit],
- 'tolerance' => $relPercentTol,
- 'tolType' => 'relative',
- 'numPoints' => $numPoints,
- 'mode' => 'antider',
- 'maxConstantOfIntegration' => $maxConstantOfIntegration,
- 'zeroLevel' => $zeroLevel,
- 'zeroLevelTol' => $zeroLevelTol
+ FUNCTION_CMP( 'correctEqn' => $correctEqn,
+ 'var' => $var,
+ 'limits' => [$llimit, $ulimit],
+ 'tolerance' => $relPercentTol,
+ 'tolType' => 'relative',
+ 'numPoints' => $numPoints,
+ 'mode' => 'antider',
+ 'maxConstantOfIntegration' => $maxConstantOfIntegration,
+ 'zeroLevel' => $zeroLevel,
+ 'zeroLevelTol' => $zeroLevelTol
);
}
}
@@ -1592,16 +1767,16 @@
}
else {
- FUNCTION_CMP( 'correctEqn' => $correctEqn,
- 'var' => $var,
- 'limits' => [$llimit, $ulimit],
- 'tolerance' => $absTol,
- 'tolType' => 'absolute',
- 'numPoints' => $numPoints,
- 'mode' => 'antider',
- 'maxConstantOfIntegration' => $maxConstantOfIntegration,
- 'zeroLevel' => 0,
- 'zeroLevelTol' => 0
+ FUNCTION_CMP( 'correctEqn' => $correctEqn,
+ 'var' => $var,
+ 'limits' => [$llimit, $ulimit],
+ 'tolerance' => $absTol,
+ 'tolType' => 'absolute',
+ 'numPoints' => $numPoints,
+ 'mode' => 'antider',
+ 'maxConstantOfIntegration' => $maxConstantOfIntegration,
+ 'zeroLevel' => 0,
+ 'zeroLevelTol' => 0
);
}
}
@@ -1737,25 +1912,25 @@
my %opt = @_;
assign_option_aliases( \%opt,
- 'vars' => 'var', # set the standard option 'var' to the one specified as vars
- 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain
- 'reltol' => 'relTol',
+ 'vars' => 'var', # set the standard option 'var' to the one specified as vars
+ 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain
+ 'reltol' => 'relTol',
'param' => 'params',
);
set_default_options( \%opt,
- 'var' => $functVarDefault,
- 'params' => [],
- 'limits' => [[$functLLimitDefault, $functULimitDefault]],
- 'mode' => 'std',
- 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative',
- 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined
- 'relTol' => $functRelPercentTolDefault,
- 'numPoints' => $functNumOfPoints,
+ 'var' => $functVarDefault,
+ 'params' => [],
+ 'limits' => [[$functLLimitDefault, $functULimitDefault]],
+ 'mode' => 'std',
+ 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative',
+ 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined
+ 'relTol' => $functRelPercentTolDefault,
+ 'numPoints' => $functNumOfPoints,
'maxConstantOfIntegration' => $functMaxConstantOfIntegration,
- 'zeroLevel' => $functZeroLevelDefault,
- 'zeroLevelTol' => $functZeroLevelTolDefault,
- 'debug' => 0,
+ 'zeroLevel' => $functZeroLevelDefault,
+ 'zeroLevelTol' => $functZeroLevelTolDefault,
+ 'debug' => 0,
);
@@ -1780,8 +1955,6 @@
delete($out_options{'tol'}) if exists( $out_options{'tol'} );
}
-
-
my @output_list = ();
# thread over lists
my @ans_list = ();
@@ -1793,23 +1966,21 @@
push( @ans_list, $correctAnswer );
}
-
-
# produce answer evaluators
foreach my $ans (@ans_list) {
push(@output_list,
- FUNCTION_CMP( 'correctEqn' => $ans,
- 'var' => $out_options{'var'},
- 'limits' => $out_options{'limits'},
- 'tolerance' => $tol,
- 'tolType' => $tolType,
- 'numPoints' => $out_options{'numPoints'},
- 'mode' => $out_options{'mode'},
- 'maxConstantOfIntegration' => $out_options{'maxConstantOfIntegration'},
- 'zeroLevel' => $out_options{'zeroLevel'},
- 'zeroLevelTol' => $out_options{'zeroLevelTol'},
- 'params' => $out_options{'params'},
- 'debug' => $out_options{'debug'},
+ FUNCTION_CMP( 'correctEqn' => $ans,
+ 'var' => $out_options{'var'},
+ 'limits' => $out_options{'limits'},
+ 'tolerance' => $tol,
+ 'tolType' => $tolType,
+ 'numPoints' => $out_options{'numPoints'},
+ 'mode' => $out_options{'mode'},
+ 'maxConstantOfIntegration' => $out_options{'maxConstantOfIntegration'},
+ 'zeroLevel' => $out_options{'zeroLevel'},
+ 'zeroLevelTol' => $out_options{'zeroLevelTol'},
+ 'params' => $out_options{'params'},
+ 'debug' => $out_options{'debug'},
),
);
}
@@ -1841,13 +2012,13 @@
my %func_params = @_;
my $correctEqn = $func_params{'correctEqn'};
- my $var = $func_params{'var'};
+ my $var = $func_params{'var'};
my $ra_limits = $func_params{'limits'};
- my $tol = $func_params{'tolerance'};
- my $tolType = $func_params{'tolType'};
+ my $tol = $func_params{'tolerance'};
+ my $tolType = $func_params{'tolType'};
my $numPoints = $func_params{'numPoints'};
- my $mode = $func_params{'mode'};
- my $maxConstantOfIntegration = $func_params{'maxConstantOfIntegration'};
+ my $mode = $func_params{'mode'};
+ my $maxConstantOfIntegration = $func_params{'maxConstantOfIntegration'};
my $zeroLevel = $func_params{'zeroLevel'};
my $zeroLevelTol = $func_params{'zeroLevelTol'};
@@ -1888,15 +2059,16 @@
$zeroLevel = $functZeroLevelDefault unless defined $zeroLevel;
$zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol;
- $func_params{'var'} = $var;
- $func_params{'limits'} = \@limits;
- $func_params{'tolerance'}= $tol;
- $func_params{'tolType'} = $tolType;
- $func_params{'numPoints'}= $numPoints;
- $func_params{'mode'} = $mode;
- $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration;
- $func_params{'zeroLevel'} = $zeroLevel;
- $func_params{'zeroLevelTol'} = $zeroLevelTol;
+ $func_params{'var'} = $var;
+ $func_params{'limits'} = \@limits;
+ $func_params{'tolerance'} = $tol;
+ $func_params{'tolType'} = $tolType;
+ $func_params{'numPoints'} = $numPoints;
+ $func_params{'mode'} = $mode;
+ $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration;
+ $func_params{'zeroLevel'} = $zeroLevel;
+ $func_params{'zeroLevelTol'} = $zeroLevelTol;
+
########################################################
# End of cleanup of calling parameters
########################################################
@@ -1905,7 +2077,7 @@
my $originalCorrEqn = $correctEqn;
#prepare the correct answer and check it's syntax
- my $rh_correct_ans = new AnswerHash;
+ my $rh_correct_ans = new AnswerHash;
$rh_correct_ans->input($correctEqn);
$rh_correct_ans = check_syntax($rh_correct_ans);
warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
@@ -1918,7 +2090,7 @@
#create the evaluation points
my $random_for_answers = new PGrandom($main::PG_original_problemSeed);
- my $NUMBER_OF_STEPS_IN_RANDOM = 1000; # determines the granularity of the random_for_answers number generator
+ my $NUMBER_OF_STEPS_IN_RANDOM = 1000; # determines the granularity of the random_for_answers number generator
my (@evaluation_points);
for( my $count = 0; $count < @PARAMS+1+$numPoints; $count++ ) {
my (@vars,$iteration_limit);
@@ -1937,7 +2109,7 @@
my $evaluation_points = Matrix->new_from_array_ref(\@evaluation_points);
#my $COEFFS = determine_param_coeffs($correct_eqn_sub,$evaluation_points[0],$numOfParameters);
- #warn "coeff", join(" | ", @{$COEFFS});
+ #warn "coeff", join(" | ", @{$COEFFS});
#construct the answer evaluator
my $answer_evaluator = new AnswerEvaluator;
@@ -2100,7 +2272,7 @@
}
if ($PGanswerMessage) {
$rh_ans->input( "( " . join(", ", @out ) . " )" );
- $rh_ans->throw_error('SYTNAX', 'There is a syntax error in your answer.');
+ $rh_ans->throw_error('SYNTAX', 'There is a syntax error in your answer.');
} else {
$rh_ans->input( [@out] );
}
@@ -2442,7 +2614,7 @@
if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance
#warn "diff = $diff";
- $diff = abs(( $inVal - ($correctVal-$tol_val ) )/$tol_val -1 ) if abs($tol_val) > $options{zeroLevel};
+ $diff = ( $inVal - ($correctVal-$tol_val ) )/abs($tol_val) -1 if abs($tol_val) > $options{zeroLevel};
#$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel};
#warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal";
}
@@ -2457,9 +2629,9 @@
push(@tol_values,$tol_val);
}
$rh_ans ->{ra_differences} = \@differences;
- $rh_ans ->{ra_student_values} = \@student_values; # values from student function
- $rh_ans ->{ra_adjusted_instructor_values} = \@correct_values; #values
- $rh_ans->{ra_instructor_values}=\@tol_values;
+ $rh_ans ->{ra_student_values} = \@student_values;
+ $rh_ans ->{ra_adjusted_student_values} = \@correct_values;
+ $rh_ans->{ra_tol_values}=\@tol_values;
$rh_ans->throw_error('EVAL', $errors) if defined($errors);
$rh_ans;
}
@@ -2525,10 +2697,10 @@
my $stringToFilter = shift @_;
my @filters_to_use = @_;
my %known_filters = ( 'remove_whitespace' => undef,
- 'compress_whitespace' => undef,
- 'trim_whitespace' => undef,
- 'ignore_case' => undef,
- 'ignore_order' => undef
+ 'compress_whitespace' => undef,
+ 'trim_whitespace' => undef,
+ 'ignore_case' => undef,
+ 'ignore_order' => undef
);
#test for unknown filters
@@ -2660,8 +2832,8 @@
my @filters = ( 'compress_whitespace', 'ignore_case' );
my $type = 'std_str_cmp';
STR_CMP( 'correctAnswer' => $correctAnswer,
- 'filters' => \@filters,
- 'type' => $type
+ 'filters' => \@filters,
+ 'type' => $type
);
}
@@ -2874,38 +3046,30 @@
## filters -- reference to an array containing the filters to be applied
## type -- a string containing the type of answer evaluator in use
## OUT: a reference to an answer evaluator subroutine
+
sub STR_CMP {
my %str_params = @_;
-
$str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} );
-
my $answer_evaluator = sub {
my $in = shift @_;
$in = '' unless defined $in;
my $original_student_ans = $in;
-
$in = str_filters( $in, @{$str_params{'filters'}} );
-
my $correctQ = ( $in eq $str_params{'correctAnswer'} ) ? 1: 0;
- my $ans_hash = new AnswerHash(
- 'score' => $correctQ,
+ my $ans_hash = new AnswerHash( 'score' => $correctQ,
'correct_ans' => $str_params{'correctAnswer'},
'student_ans' => $in,
'ans_message' => '',
- 'type' => $str_params{'type'},
- 'preview_text_string' => $in,
- 'preview_latex_string' => $in,
- 'original_student_ans' => $original_student_ans
+ 'type' => $str_params{'type'},
+ 'preview_text_string' => $in,
+ 'preview_latex_string' => $in,
+ 'original_student_ans' => $original_student_ans
);
-
return $ans_hash;
};
-
return $answer_evaluator;
}
-
-
##########################################################################
##########################################################################
## Miscellaneous answer evaluators
@@ -3193,7 +3357,7 @@
# I don't like to put in this bit of code.
# It makes it hard to construct error free problem graders
# I would prefer to know that the problem score was numeric.
- unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
+ unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
$problem_state{recorded_score} = 0; # This gets rid of non-numeric scores
}
#
@@ -3426,6 +3590,7 @@
## an array of array references -- ([llim,ulim], [llim,ulim])
## an array of limits -- (llim,ulim)
## OUT: an array of array references -- ([llim,ulim], [llim,ulim]) or ([llim,ulim])
+
sub get_limits_array {
my $in = shift @_;
my @out;
@@ -3481,18 +3646,21 @@
return $error_response;
}
-# outputs a hash to the screen
-# sub display_options {
-# my %options = @_;
-# my $out_string = "";
-# foreach my $key (keys %options) {
-# $out_string .= " $key => $options{$key},
";
-# }
-# return $out_string;
-# }
+
+#########################################################################
+# Filters for answer evaluators
+#########################################################################
+
sub is_a_number {
- my ($num) = @_;
+ my ($num,%options) = @_;
+ my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
+ my ($rh_ans);
+ if ($process_ans_hash) {
+ $rh_ans = $num;
+ $num = $rh_ans->{student_ans};
+ }
+
my $is_a_number = 0;
return $is_a_number unless defined($num);
$num =~ s/^\s*//; ## remove initial spaces
@@ -3502,43 +3670,109 @@
if ($num =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){
$is_a_number = 1;
}
-
- return $is_a_number;
+
+ if ($process_ans_hash) {
+ if ($is_a_number == 1 ) {
+ $rh_ans->{student_ans}=$num;
+ return $rh_ans;
+ } else {
+ $rh_ans->{student_ans} = "Incorrect number format: You must enter a number, e.g. -6, 5.3, or 6.12E-3";
+ $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
+ return $rh_ans;
+ }
+ } else {
+ return $is_a_number;
+ }
}
sub is_a_fraction {
-
- ## does not test for validity, just for allowed characters
- ## note that an integer will qualify as a fraction
- my ($exp) = @_;
- my $is_a_fraction = 0;
- return $is_a_fraction unless defined($exp);
- if ($exp =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) {
+ my ($num,%options) = @_;
+ my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
+ my ($rh_ans);
+ if ($process_ans_hash) {
+ $rh_ans = $num;
+ $num = $rh_ans->{student_ans};
+ }
+
+ my $is_a_fraction = 0;
+ return $is_a_fraction unless defined($num);
+ $num =~ s/^\s*//; ## remove initial spaces
+ $num =~ s/\s*$//; ## remove trailing spaces
+
+ if ($num =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) {
$is_a_fraction = 1;
}
-
- return $is_a_fraction;
+
+ if ($process_ans_hash) {
+ if ($is_a_fraction == 1 ) {
+ $rh_ans->{student_ans}=$num;
+ return $rh_ans;
+ } else {
+ $rh_ans->{student_ans} = "Not a number of fraction: You must enter a number or fraction, e.g. -6 or 7/13";
+ $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
+ return $rh_ans;
+ }
+
+ } else {
+ return $is_a_fraction;
+ }
}
-sub is_an_arithmetic_expression {
- ## does not test for validity, just for allowed characters
- my ($exp) = @_;
- my $is_an_arithmetic_expression = 0;
- if ($exp =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) {
+
+sub is_an_arithmetic_expression {
+ my ($num,%options) = @_;
+ my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
+ my ($rh_ans);
+ if ($process_ans_hash) {
+ $rh_ans = $num;
+ $num = $rh_ans->{student_ans};
+ }
+
+ my $is_an_arithmetic_expression = 0;
+ return $is_an_arithmetic_expression unless defined($num);
+ $num =~ s/^\s*//; ## remove initial spaces
+ $num =~ s/\s*$//; ## remove trailing spaces
+
+ if ($num =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) {
$is_an_arithmetic_expression = 1;
}
-
- return $is_an_arithmetic_expression;
+
+ if ($process_ans_hash) {
+ if ($is_an_arithmetic_expression == 1 ) {
+ $rh_ans->{student_ans}=$num;
+ return $rh_ans;
+ } else {
+
+ $rh_ans->{student_ans} = "Not an arithmetic expression: You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2";
+ $rh_ans->throw_error('NUMBER', 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2');
+ return $rh_ans;
+ }
+
+ } else {
+ return $is_an_arithmetic_expression;
+ }
}
#replaces pi, e, and ^ with their Perl equivalents
sub math_constants {
- my($in) = @_;
+ my($in,%options) = @_;
+ my $rh_ans;
+ my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ;
+ if ($process_ans_hash) {
+ $rh_ans = $in;
+ $in = $rh_ans->{student_ans};
+ }
+
$in =~s/\bpi\b/(4*atan2(1,1))/ge;
$in =~s/\be\b/(exp(1))/ge;
$in =~s/\^/**/g;
-
- return $in;
+
+ if ($process_ans_hash) {
+ $rh_ans->{student_ans}=$in;
+ return $rh_ans;
+ } else {
+ return $in;
+ }
}
sub clean_up_error_msg {
@@ -3576,7 +3810,6 @@
}
else {
$out = $number;
- $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828...
}
return $out;
@@ -3628,8 +3861,7 @@
}
foreach my $key (keys %default_options) {
if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) {
- $rh_options->{$key} = $default_options{$key}; # using 'defined' instead of 'exists' allows
- # tol => undef to allow the tol option, but doesn't define
+ $rh_options->{$key} = $default_options{$key}; #this allows tol => undef to allow the tol option, but doesn't define
# this key unless tol is explicitly defined.
}
}