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

View of /trunk/webwork/system/courseScripts/PGanswermacros.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 206 - (download) (as text) (annotate)
Fri Sep 14 20:21:52 2001 UTC (11 years, 8 months ago) by apizer
File size: 144099 byte(s)
added anstext_non_anonymous, a non anonymous version of anstext

    1 #!/usr/local/bin/webwork-perl
    2 
    3 # This file is PGanswermacros.pl
    4 # This includes the subroutines for the ANS macros, that
    5 # is, macros allowing a more flexible answer checking
    6 ####################################################################
    7 # Copyright @ 1995-2000 University of Rochester
    8 # All Rights Reserved
    9 ####################################################################
   10 #$Id$
   11 
   12 =head1 NAME
   13 
   14   PGanswermacros.pl -- located in the courseScripts directory
   15 
   16 =head1 SYNPOSIS
   17 
   18   Number Answer Evaluators:
   19     num_cmp() --  uses an input hash to determine parameters
   20 
   21     std_num_cmp(), std_num_cmp_list(), std_num_cmp_abs, std_num_cmp_abs_list()
   22     frac_num_cmp(), frac_num_cmp_list(), frac_num_cmp_abs, frac_num_cmp_abs_list()
   23     arith_num_cmp(), arith_num_cmp_list(), arith_num_cmp_abs, arith_num_cmp_abs_list()
   24     strict_num_cmp(), strict_num_cmp_list(), strict_num_cmp_abs, strict_num_cmp_abs_list()
   25     numerical_compare_with_units()  --  requires units as part of the answer
   26     std_num_str_cmp() --  also accepts a set of strings as possible answers
   27 
   28   Function Answer Evaluators:
   29     fun_cmp() --  uses an input hash to determine parameters
   30 
   31     function_cmp(), function_cmp_abs()
   32     function_cmp_up_to_constant(), function_cmp_up_to_constant_abs()
   33     multivar_function_cmp()
   34 
   35   String Answer Evaluators:
   36     str_cmp() --  uses an input hash to determine parameters
   37 
   38     std_str_cmp(), std_str_cmp_list(), std_cs_str_cmp(), std_cs_str_cmp_list()
   39     strict_str_cmp(), strict_str_cmp_list()
   40     ordered_str_cmp(), ordered_str_cmp_list(), ordered_cs_str_cmp(), ordered_cs_str_cmp_list()
   41     unordered_str_cmp(), unordered_str_cmp_list(), unordered_cs_str_cmp(), unordered_cs_str_cmp_list()
   42 
   43   Miscellaneous Answer Evaluators:
   44     checkbox_cmp()
   45     radio_cmp()
   46 
   47 =cut
   48 
   49 =head1 DESCRIPTION
   50 
   51 This file adds subroutines which create "answer evaluators" for checking
   52 answers. Each answer evaluator accepts a single input from a student answer,
   53 checks it and creates an output hash %ans_hash with seven or eight entries
   54 (the preview_latex_string is optional). The output hash is now being created
   55 with the AnswerHash package "class", which is located at the end of this file.
   56 This class is currently just a wrapper for the hash, but this might change in
   57 the future as new capabilities are added.
   58 
   59           score     =>  $correctQ,
   60           correct_ans   =>  $originalCorrEqn,
   61           student_ans   =>  $modified_student_ans
   62           original_student_ans  =>  $original_student_answer,
   63           ans_message   =>  $PGanswerMessage,
   64           type      =>  'typeString',
   65           preview_text_string =>  $preview_text_string,
   66           preview_latex_string  =>  $preview_latex_string
   67 
   68 
   69   $ans_hash{score}      --  a number between 0 and 1 indicating
   70                     whether the answer is correct. Fractions
   71                     allow the implementation of partial
   72                     credit for incorrect answers.
   73   $ans_hash{correct_ans}      --  The correct answer, as supplied by the
   74                     instructor and then formatted. This can
   75                     be viewed by the student after the answer date.
   76   $ans_hash{student_ans}      --  This is the student answer, after reformatting;
   77                     for example the answer might be forced
   78                     to capital letters for comparison with
   79                     the instructors answer. For a numerical
   80                     answer, it gives the evaluated answer.
   81                     This is displayed in the section reporting
   82                     the results of checking the student answers.
   83   $ans_hash{original_student_ans}   --  This is the original student answer. This is displayed
   84                     on the preview page and may be used for sticky answers.
   85   $ans_hash{ans_message}      --  Any error message, or hint provided by the answer evaluator.
   86                     This is also displayed in the section reporting
   87                     the results of checking the student answers.
   88   $ans_hash{type}       --  A string indicating the type of answer evaluator. This
   89                     helps in preprocessing the student answer for errors.
   90                     Some examples:
   91                       'number_with_units'
   92                       'function'
   93                       'frac_number'
   94                       'arith_number'
   95   $ans_hash{preview_text_string}    --  This typically shows how the student answer was parsed. It is
   96                     displayed on the preview page. For a student answer of 2sin(3x)
   97                     this would be 2*sin(3*x). For string answers it is typically the
   98                     same as $ans_hash{student_ans}.
   99   $ans_hash{preview_latex_string}   --  THIS IS OPTIONAL. This is latex version of the student answer
  100                     which is used to show a typeset view on the answer on the preview
  101                     page. For a student answer of 2/3, this would be \frac{2}{3}.
  102 
  103 Technical note: the routines in this file are not actually answer evaluators. Instead, they create
  104 answer evaluators. An answer evaluator is an anonymous subroutine, referenced by a named scalar. The
  105 routines in this file build the subroutine and return a reference to it. Later, when the student
  106 actually enters an answer, the problem processor feeds that answer to the referenced subroutine, which
  107 evaluates it and returns a score (usually 0 or 1). For most users, this distinction is unimportant, but
  108 if you plan on writing your own answer evaluators, you should understand this point.
  109 
  110 =cut
  111 
  112 BEGIN {
  113   be_strict(); # an alias for use strict.  This means that all global variable must contain main:: as a prefix.
  114 }
  115 
  116 
  117 my ($BR           ,   # convenient localizations.
  118   $PAR          ,
  119   $numRelPercentTolDefault    ,
  120   $numZeroLevelDefault      ,
  121   $numZeroLevelTolDefault     ,
  122   $numAbsTolDefault     ,
  123   $numFormatDefault     ,
  124   $functRelPercentTolDefault    ,
  125   $functZeroLevelDefault      ,
  126   $functZeroLevelTolDefault   ,
  127   $functAbsTolDefault     ,
  128   $functNumOfPoints     ,
  129   $functVarDefault      ,
  130   $functLLimitDefault     ,
  131   $functULimitDefault     ,
  132   $functMaxConstantOfIntegration    ,
  133   $CA
  134 );
  135 
  136 
  137 
  138 
  139 sub _PGanswermacros_init {
  140 
  141      $BR  = $main::BR;    # convenient localizations.
  142      $PAR = $main::PAR;
  143 
  144     # import defaults
  145     # these are now imported from the %envir variable
  146      $numRelPercentTolDefault     = $main::numRelPercentTolDefault;
  147      $numZeroLevelDefault       = $main::numZeroLevelDefault;
  148      $numZeroLevelTolDefault      = $main::numZeroLevelTolDefault;
  149      $numAbsTolDefault        = $main::numAbsTolDefault;
  150      $numFormatDefault        = $main::numFormatDefault;
  151      $functRelPercentTolDefault     = $main::functRelPercentTolDefault;
  152      $functZeroLevelDefault       = $main::functZeroLevelDefault;
  153      $functZeroLevelTolDefault      = $main::functZeroLevelTolDefault;
  154      $functAbsTolDefault        = $main::functAbsTolDefault;
  155      $functNumOfPoints        = $main::functNumOfPoints;
  156      $functVarDefault       = $main::functVarDefault;
  157      $functLLimitDefault        = $main::functLLimitDefault;
  158      $functULimitDefault        = $main::functULimitDefault;
  159      $functMaxConstantOfIntegration     = $main::functMaxConstantOfIntegration;
  160 
  161 
  162 
  163 }
  164 
  165 ##########################################################################
  166 ##########################################################################
  167 ## Number answer evaluators
  168 
  169 =head2 Number Answer Evaluators
  170 
  171 Number answer evaluators take in a numerical answer, compare it to the correct answer,
  172 and return a score. In addition, they can choose to accept or reject an answer based on
  173 its format, closeness to the correct answer, and other criteria. There are two types
  174 of numerical answer evaluators: num_cmp(), which takes a hash of named options as parameters,
  175 and the "mode"_num_cmp() variety, which use different functions to access different sets of
  176 options. In addition, there is the special case of std_num_str_cmp(), which can evaluate
  177 both numbers and strings.
  178 
  179 Numerical Comparison Options
  180 
  181   correctAnswer   --  This is the correct answer that the student answer will
  182             be compared to. However, this does not mean that the
  183             student answer must match this exactly. How close the
  184             student answer must be is determined by the other
  185             options, especially tolerance and format.
  186 
  187   tolerance   --  These options determine how close the student answer
  188             must be to the correct answer to qualify. There are two
  189             types of tolerance: relative and absolute. Relative
  190             tolerances are given in percentages. A relative
  191             tolerance of 1 indicates that the student answer must
  192             be within 1% of the correct answer to qualify as correct.
  193             In other words, a student answer is correct when
  194               abs(studentAnswer - correctAnswer) <= abs(.01*relpercentTol*correctAnswer)
  195             Using absolute tolerance, the student answer must be a
  196             fixed distance from the correct answer to qualify.
  197             For example, an absolute tolerance of 5 means that any
  198             number which is +-5 of the correct answer qualifies as correct.
  199               Final (rarely used) tolerance options are zeroLevel
  200             and zeroLevelTol, used in conjunction with relative
  201             tolerance. if correctAnswer has absolute value less than
  202             or equal to zeroLevel, then the student answer must be,
  203             in absolute terms, within zeroLevelTol of correctAnswer, i.e.,
  204               abs(studentAnswer - correctAnswer) <= zeroLevelTol.
  205             In other words, if the correct answer is very near zero,
  206             an absolute tolerance will be used. One must do this to
  207             handle floating point answers very near zero, because of
  208             the inaccuracy of floating point arithmetic. However, the
  209             default values are almost always adequate.
  210 
  211   mode      --  This determines the allowable methods for entering an
  212             answer. Answers which do not meet this requirement will
  213             be graded as incorrect, regardless of their numerical
  214             value. The recognized modes are:
  215               'std' (default) --  allows any expression which evaluates
  216                         to a number, including those using
  217                         elementary functions like sin() and
  218                         exp(), as well as the operations of
  219                         arithmetic (+, -, *, /, ^)
  220               'strict'  --  only decimal numbers are allowed
  221               'frac'    --  whole numbers and fractions are allowed
  222               'arith'   --  arithmetic expressions are allowed, but
  223                         no functions
  224             Note that all modes allow the use of "pi" and "e" as
  225             constants, and also the use of "E" to represent scientific
  226             notation.
  227 
  228   format      --  The format to use when displaying the correct and
  229             submitted answers. This has no effect on how answers are
  230             evaluated; it is only for cosmetic purposes. The
  231             formatting syntax is the same as Perl uses for the sprintf()
  232             function. Format strings are of the form '%m.nx' or '%m.nx#',
  233             where m and n are described below, and x is a formatter.
  234               Esentially, m is the minimum length of the field
  235             (make this negative to left-justify). Note that the decimal
  236             point counts as a character when determining the field width.
  237             If m begins with a zero, the number will be padded with zeros
  238             instead of spaces to fit the field.
  239               The precision specifier (n) works differently, depending
  240             on which formatter you are using. For d, i, o, u, x and X
  241             formatters (non-floating point formatters), n is the minimum
  242             number of digits to display. For e and f, it is the number of
  243             digits that appear after the decimal point (extra digits will
  244             be rounded; insufficient digits will be padded with spaces--see
  245             '#' below). For g, it is the number of significant digits to
  246             display.
  247               The full list of formatters can be found in the manpage
  248             for printf(3), or by typing "perldoc -f sprintf" at a
  249             terminal prompt. The following is a brief summary of the
  250             most frequent formatters:
  251               d --  decimal number
  252               ld  --  long decimal number
  253               u --  unsigned decimal number
  254               lu  --  long unsigned decimal number
  255               x --  hexadecimal number
  256               o --  octal number
  257               e --  floating point number in scientific notation
  258               f --  floating point number
  259               g --  either e or f, whichever takes less space
  260             Technically, g will use e if the exponent is less than -4 or
  261             greater than or equal to the precision. Trailing zeros are
  262             removed in this mode.
  263               If the format string ends in '#', trailing zeros will be
  264             removed in the decimal part. Note that this is not a standard
  265             syntax; it is handled internally by WeBWorK and not by Perl
  266             (although this should not be a concern to end users).
  267             The default format is '%0.5f#', which displays as a floating
  268             point number with 5 digits of precision and no trailing zeros.
  269             Other useful format strings might be '%0.2f' for displaying
  270             dollar amounts, or '%010d' to display an integer with leading
  271             zeros. Setting format to an empty string ( '' ) means no
  272             formatting will be used; this will show 'arbitrary' precision
  273             floating points.
  274 
  275 Default Values (As of 7/24/2000) (Option -- Variable Name -- Value)
  276 
  277   Format          --  $numFormatDefault   --  "%0.5f#"
  278   Relative Tolerance    --  $numRelPercentTolDefault  --  .1
  279   Absolute Tolerance    --  $numAbsTolDefault   --  .001
  280   Zero Level        --  $numZeroLevelDefault    --  1E-14
  281   Zero Level Tolerance  --  $numZeroLevelTolDefault   --  1E-12
  282 
  283 =cut
  284 
  285 
  286 =head3 num_cmp()
  287 
  288 Compares a number or a list of numbers, using a named hash of options to set
  289 parameters. This can make for more readable code than using the "mode"_num_cmp()
  290 style, but some people find one or the other easier to remember.
  291 
  292 ANS( num_cmp( answer or answer_array_ref, options_hash ) );
  293 
  294   1. the correct answer, or a reference to an array of correct answers
  295   2. a hash with the following keys (all optional):
  296     mode      --  'std' (default) (allows any expression evaluating to
  297                 a number)
  298               'strict' (only numbers are allowed)
  299               'frac' (fractions are allowed)
  300               'arith' (arithmetic expressions allowed)
  301     format      --  '%0.5f#' (default); defines formatting for the
  302                 correct answer
  303     tol       --  an absolute tolerance, or
  304     relTol      --  a relative tolerance
  305     units     --  the units to use for the answer(s)
  306     strings     --  a reference to an array of strings which are valid
  307                 answers (works like std_num_str_cmp() )
  308     zeroLevel   --  if the correct answer is this close to zero,
  309                  then zeroLevelTol applies
  310     zeroLevelTol  --  absolute tolerance to allow when answer is close
  311                  to zero
  312 
  313     debug     --  if set to 1, provides verbose listing of
  314                 hash entries throughout fliters.
  315 
  316   Returns an answer evaluator, or (if given a reference to an array of
  317   answers), a list of answer evaluators. Note that a reference to an array of
  318   answers results is just a shortcut for writing a separate <code>num_cmp()</code> for each
  319   answer.
  320 
  321 EXAMPLES:
  322 
  323   num_cmp( 5 )          --  correct answer is 5, using defaults
  324                   for all options
  325   num_cmp( [5,6,7] )        --  correct answers are 5, 6, and 7,
  326                   using defaults for all options
  327   num_cmp( 5, mode => 'strict' )  --  correct answer is 5, mode is strict
  328   num_cmp( [5,6], relTol => 5 ) --  correct answers are 5 and 6,
  329                     both with 5% relative tolerance
  330   num_cmp( 6, strings => ["Inf", "Minf", "NaN"] )
  331                   --  correct answer is 6, "Inf", "Minf",
  332                    and "NaN" recognized as valid, but
  333                    incorrect answers.
  334 
  335 =cut
  336 
  337 sub num_cmp {
  338   my $correctAnswer = shift @_;
  339   $CA = $correctAnswer;
  340   my @opt = @_;
  341   my %out_options;
  342 
  343 #########################################################################
  344 # Retain this first check for backword compatibility.  Allows input of the form
  345 # num_cmp($ans, 1, '%0.5f') but warns against it
  346 #########################################################################
  347   my %known_options = (
  348           'mode'      =>  'std',
  349           'format'    =>  $numFormatDefault,
  350           'tol'     =>  $numAbsTolDefault,
  351           'relTol'    =>  $numRelPercentTolDefault,
  352           'units'     =>  undef,
  353           'strings'   =>  undef,
  354           'zeroLevel'   =>  $numZeroLevelDefault,
  355           'zeroLevelTol'  =>  $numZeroLevelTolDefault,
  356           'tolType'       =>      'relative',
  357           'tolerance'     =>      1,
  358           'reltol'    =>  undef,      #alternate spelling
  359           'unit'      =>  undef,      #alternate spelling
  360           'debug'     =>  0
  361         );
  362 
  363   my @output_list;
  364   my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt;
  365 
  366   unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 ||
  367         ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) {
  368     # unless the first parameter is a list of arrays
  369     # or the second parameter is a known option or
  370     # no options were used,
  371     # use the old num_cmp which does not use options, but has inputs
  372     # $relPercentTol,$format,$zeroLevel,$zeroLevelTol
  373     warn "This method of using num_cmp() is deprecated. Please rewrite this" .
  374           " problem using the options style of parameter passing (or" .
  375           " check that your first option is spelled correctly).";
  376 
  377     %out_options = (  'relTol'    => $relPercentTol,
  378           'format'    => $format,
  379           'zeroLevel'   => $zeroLevel,
  380           'zeroLevelTol'    => $zeroLevelTol,
  381           'mode'      => 'std'
  382     );
  383   }
  384 
  385 #########################################################################
  386 # Now handle the options assuming they are entered in the form
  387 # num_cmp($ans, relTol=>1, format=>'%0.5f')
  388 #########################################################################
  389   %out_options = @opt;
  390   assign_option_aliases( \%out_options,
  391         'reltol'    =>      'relTol',
  392         'unit'      =>      'units',
  393         );
  394 
  395   set_default_options( \%out_options,
  396            'tolType'    =>  (defined($out_options{tol}) ) ? 'absolute' : 'relative',
  397            'tolerance'        =>  (defined($out_options{tol}) ) ? $numAbsTolDefault : $numRelPercentTolDefault,
  398            'mode'   =>  'std',
  399            'format'   =>  $numFormatDefault,
  400            'tol'    =>  $numAbsTolDefault,
  401            'relTol'   =>  $numRelPercentTolDefault,
  402            'units'    =>  undef,
  403            'strings'    =>  undef,
  404            'zeroLevel'  =>  $numZeroLevelDefault,
  405            'zeroLevelTol' =>  $numZeroLevelTolDefault,
  406            'debug'    =>  0,
  407   );
  408 
  409   # can't use both units and strings
  410   if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) {
  411     warn "Can't use both 'units' and 'strings' in the same problem " .
  412     "(check your parameters to num_cmp() )";
  413   }
  414 
  415   # my ($tolType, $tol);
  416     if ($out_options{tolType} eq 'absolute')   {
  417     $out_options{'tolerance'}=$out_options{'tol'};
  418     delete($out_options{'relTol'}) if exists( $out_options{'relTol'} );
  419   } else {
  420     $out_options{'tolerance'}=$out_options{'relTol'};
  421     # delete($out_options{'tol'}) if exists( $out_options{'tol'} );
  422   }
  423 
  424   # thread over lists
  425   my @ans_list = ();
  426 
  427   if ( ref($correctAnswer) eq 'ARRAY' ) {
  428     @ans_list = @{$correctAnswer};
  429   }
  430   else { push( @ans_list, $correctAnswer );
  431   }
  432 
  433   # produce answer evaluators
  434   foreach my $ans (@ans_list) {
  435     if( defined( $out_options{'units'} ) ) {
  436       $ans = "$ans $out_options{'units'}";
  437 
  438       push( @output_list, NUM_CMP(  'correctAnswer'       =>  $ans,
  439               'tolerance'   =>  $out_options{tolerance},
  440               'tolType'   =>  $out_options{tolType},
  441               'format'    =>  $out_options{'format'},
  442               'mode'      =>  $out_options{'mode'},
  443               'zeroLevel'   =>  $out_options{'zeroLevel'},
  444               'zeroLevelTol'        =>  $out_options{'zeroLevelTol'},
  445               'debug'     =>  $out_options{'debug'},
  446               'units'     =>  $out_options{'units'},
  447       )
  448       );
  449     } elsif( defined( $out_options{'strings'} ) ) {
  450       #if( defined $out_options{'tol'} ) {
  451       #    warn "You are using 'tol' (for absolute tolerance) with a num/str " .
  452       # "compare, which currently only uses relative tolerance. The default " .
  453       #     "tolerance will be used.";
  454       #}
  455 
  456       push( @output_list, NUM_CMP(  'correctAnswer' =>  $ans,
  457                 'tolerance' =>  $out_options{tolerance},
  458               'tolType' =>  $out_options{tolType},
  459               'format'  =>  $out_options{'format'},
  460               'mode'    =>  $out_options{'mode'},
  461               'zeroLevel' =>  $out_options{'zeroLevel'},
  462               'zeroLevelTol'  =>  $out_options{'zeroLevelTol'},
  463               'debug'   =>  $out_options{'debug'},
  464               'strings' =>  $out_options{'strings'},
  465          )
  466          );
  467     } else {
  468       push(@output_list,
  469         NUM_CMP(  'correctAnswer'       =>  $ans,
  470           'tolerance'   =>  $out_options{tolerance},
  471           'tolType'   =>  $out_options{tolType},
  472           'format'    =>  $out_options{'format'},
  473           'mode'      =>  $out_options{'mode'},
  474           'zeroLevel'   =>  $out_options{'zeroLevel'},
  475           'zeroLevelTol'        =>  $out_options{'zeroLevelTol'},
  476           'debug'     =>  $out_options{'debug'},
  477         ),
  478       );
  479       }
  480   }
  481 
  482   return (wantarray) ? @output_list : $output_list[0];
  483 }
  484 
  485 #legacy code for compatability purposes
  486 sub num_rel_cmp {   # compare numbers
  487     std_num_cmp( @_ );
  488 }
  489 
  490 
  491 =head3 "mode"_num_cmp() functions
  492 
  493 There are 16 functions total, 4 for each mode (std, frac, strict, arith). Each mode has
  494 one "normal" function, one which accepts a list of answers, one which uses absolute
  495 rather than relative tolerance, and one which uses absolute tolerance and accepts a list.
  496 The "std" family is documented below; all others work precisely the same.
  497 
  498  std_num_cmp($correctAnswer) OR
  499  std_num_cmp($correctAnswer, $relPercentTol) OR
  500  std_num_cmp($correctAnswer, $relPercentTol, $format) OR
  501  std_num_cmp($correctAnswer, $relPercentTol, $format, $zeroLevel) OR
  502  std_num_cmp($correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol)
  503 
  504   $correctAnswer  --  the correct answer
  505   $relPercentTol  --  the tolerance, as a percentage (optional)
  506   $format   --  the format of the displayed answer (optional)
  507   $zeroLevel  --  if the correct answer is this close to zero, then zeroLevelTol applies (optional)
  508   $zeroLevelTol --  absolute tolerance to allow when correct answer is close to zero (optional)
  509 
  510   std_num_cmp() uses standard mode (arithmetic operations and elementary
  511   functions allowed) and relative tolerance. Options are specified by
  512   one or more parameters. Note that if you wish to set an option which
  513   is later in the parameter list, you must set all previous options.
  514 
  515  std_num_cmp_abs($correctAnswer) OR
  516  std_num_cmp_abs($correctAnswer, $absTol) OR
  517  std_num_cmp_abs($correctAnswer, $absTol, $format)
  518 
  519   $correctAnswer    --  the correct answer
  520   $absTol     --  an absolute tolerance (optional)
  521   $format     --  the format of the displayed answer (optional)
  522 
  523   std_num_cmp_abs() uses standard mode and absolute tolerance. Options
  524   are set as with std_num_cmp(). Note that $zeroLevel and $zeroLevelTol
  525   do not apply with absolute tolerance.
  526 
  527  std_num_cmp_list($relPercentTol, $format, @answerList)
  528 
  529   $relPercentTol    --  the tolerance, as a percentage
  530   $format     --  the format of the displayed answer(s)
  531   @answerList   --  a list of one or more correct answers
  532 
  533   std_num_cmp_list() uses standard mode and relative tolerance. There
  534   is no way to set $zeroLevel or $zeroLevelTol. Note that no
  535   parameters are optional. All answers in the list will be
  536   evaluated with the same set of parameters.
  537 
  538  std_num_cmp_abs_list($absTol, $format, @answerList)
  539 
  540   $absTol   --  an absolute tolerance
  541   $format   --  the format of the displayed answer(s)
  542   @answerList --  a list of one or more correct answers
  543 
  544   std_num_cmp_abs_list() uses standard mode and absolute tolerance.
  545   Note that no parameters are optional. All answers in the list will be
  546   evaluated with the same set of parameters.
  547 
  548  arith_num_cmp(), arith_num_cmp_list(), arith_num_cmp_abs(), arith_num_cmp_abs_list()
  549  strict_num_cmp(), strict_num_cmp_list(), strict_num_cmp_abs(), strict_num_cmp_abs_list()
  550  frac_num_cmp(), frac_num_cmp_list(), frac_num_cmp_abs(), frac_num_cmp_abs_list()
  551 
  552 Examples:
  553 
  554   ANS( strict_num_cmp( 3.14159 ) )  --  The student answer must be a number
  555     in decimal or scientific notation which is within .1 percent of 3.14159.
  556     This assumes $numRelPercentTolDefault has been set to .1.
  557   ANS( strict_num_cmp( $answer, .01 ) ) --  The student answer must be a
  558     number within .01 percent of $answer (e.g. 3.14159 if $answer is 3.14159
  559     or $answer is "pi" or $answer is 4*atan(1)).
  560   ANS( frac_num_cmp( $answer) ) or ANS( frac_num_cmp( $answer,.01 ))  --
  561     The student answer can be a number or fraction, e.g. 2/3.
  562   ANS( arith_num_cmp( $answer) ) or ANS( arith_num_cmp( $answer,.01 ))  --
  563     The student answer can be an arithmetic expression, e.g. (2+3)/7-2^.5 .
  564   ANS( std_num_cmp( $answer) ) or ANS( std_num_cmp( $answer,.01 ))  --
  565     The student answer can contain elementary functions, e.g. sin(.3+pi/2)
  566 
  567 =cut
  568 
  569 sub std_num_cmp {           # compare numbers allowing use of elementary functions
  570     my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
  571 
  572     my %options = ( 'tolerance'   =>  $relPercentTol,
  573         'format'    =>  $format,
  574         'zeroLevel'   =>  $zeroLevel,
  575         'zeroLevelTol'  =>  $zeroLevelTol
  576     );
  577 
  578     set_default_options( \%options,
  579        'tolType'  =>      'relative',
  580        'tolerance'    =>      $numRelPercentTolDefault,
  581        'mode'   =>  'std',
  582        'format' =>  $numFormatDefault,
  583        'relTol' =>  $numRelPercentTolDefault,
  584        'zeroLevel'    =>      $numZeroLevelDefault,
  585        'zeroLevelTol' =>      $numZeroLevelTolDefault,
  586        'debug'        =>      0,
  587     );
  588 
  589     num_cmp([$correctAnswer], %options);
  590 }
  591 
  592 ##  Similar to std_num_cmp but accepts a list of numbers in the form
  593 ##  std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...)
  594 ##  format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default
  595 ##  You must enter a format and tolerance
  596 
  597 sub std_num_cmp_list {
  598   my ( $relPercentTol, $format, @answerList) = @_;
  599 
  600   my %options = ( 'tolerance' =>      $relPercentTol,
  601       'format'        =>      $format,
  602   );
  603 
  604   set_default_options( \%options,
  605            'tolType'      =>      'relative',
  606            'tolerance'    =>      $numRelPercentTolDefault,
  607            'mode'         =>      'std',
  608            'format'       =>      $numFormatDefault,
  609            'relTol'       =>      $numRelPercentTolDefault,
  610            'zeroLevel'    =>      $numZeroLevelDefault,
  611            'zeroLevelTol' =>      $numZeroLevelTolDefault,
  612            'debug'        =>      0,
  613   );
  614 
  615   num_cmp(\@answerList, %options);
  616 
  617 }
  618 
  619 sub std_num_cmp_abs {     # compare numbers allowing use of elementary functions with absolute tolerance
  620   my ( $correctAnswer, $absTol, $format) = @_;
  621   my %options = ( 'tolerance'  => $absTol,
  622             'format'     => $format
  623   );
  624 
  625   set_default_options (\%options,
  626            'tolType'      =>      'absolute',
  627            'tolerance'    =>      $absTol,
  628            'mode'         =>      'std',
  629            'format'       =>      $numFormatDefault,
  630            'zeroLevel'    =>      0,
  631            'zeroLevelTol' =>      0,
  632            'debug'        =>      0,
  633   );
  634 
  635   num_cmp([$correctAnswer], %options);
  636 }
  637 
  638 ##  See std_num_cmp_list for usage
  639 
  640 sub std_num_cmp_abs_list {
  641   my ( $absTol, $format, @answerList ) = @_;
  642 
  643         my %options = ( 'tolerance'         =>      $absTol,
  644                         'format'            =>      $format,
  645   );
  646 
  647         set_default_options( \%options,
  648                              'tolType'      =>      'absolute',
  649                              'tolerance'    =>      $absTol,
  650                              'mode'         =>      'std',
  651                              'format'       =>      $numFormatDefault,
  652                              'zeroLevel'    =>      0,
  653                              'zeroLevelTol' =>      0,
  654                              'debug'        =>      0,
  655         );
  656 
  657         num_cmp(\@answerList, %options);
  658 }
  659 
  660 sub frac_num_cmp {            # only allow fractions and numbers as submitted answer
  661 
  662   my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
  663 
  664   my %options = (      'tolerance'   =>   $relPercentTol,
  665          'format'    =>   $format,
  666            'zeroLevel'   =>   $zeroLevel,
  667            'zeroLevelTol'  =>   $zeroLevelTol
  668   );
  669 
  670   set_default_options( \%options,
  671          'tolType'     =>   'relative',
  672          'tolerance'   =>   $relPercentTol,
  673          'mode'      =>   'frac',
  674          'format'    =>   $numFormatDefault,
  675          'zeroLevel'   =>   $numZeroLevelDefault,
  676          'zeroLevelTol'  =>   $numZeroLevelTolDefault,
  677          'relTol'    =>   $numRelPercentTolDefault,
  678          'debug'     =>   0,
  679    );
  680 
  681   num_cmp([$correctAnswer], %options);
  682 }
  683 
  684 ##  See std_num_cmp_list for usage
  685 sub frac_num_cmp_list {
  686   my ( $relPercentTol, $format, @answerList ) = @_;
  687 
  688   my %options = (      'tolerance'   =>   $relPercentTol,
  689                'format'    =>   $format
  690   );
  691 
  692   set_default_options( \%options,
  693        'tolType'     =>   'relative',
  694        'tolerance'   =>   $relPercentTol,
  695        'mode'      =>   'frac',
  696        'format'    =>   $numFormatDefault,
  697        'zeroLevel'   =>   $numZeroLevelDefault,
  698        'zeroLevelTol'  =>   $numZeroLevelTolDefault,
  699        'relTol'    =>   $numRelPercentTolDefault,
  700        'debug'     =>   0,
  701   );
  702 
  703   num_cmp(\@answerList, %options);
  704 }
  705 
  706 sub frac_num_cmp_abs {      # only allow fraction expressions as submitted answer with absolute tolerance
  707     my ( $correctAnswer, $absTol, $format ) = @_;
  708 
  709     my %options = (             'tolerance'    =>     $absTol,
  710               'format'       =>     $format
  711     );
  712 
  713   set_default_options (\%options,
  714       'tolType'    =>   'absolute',
  715       'tolerance'    =>   $absTol,
  716       'mode'       =>   'frac',
  717       'format'     =>   $numFormatDefault,
  718       'zeroLevel'    =>   0,
  719       'zeroLevelTol' =>   0,
  720       'debug'      =>   0,
  721   );
  722 
  723     num_cmp([$correctAnswer], %options);
  724 }
  725 
  726 ##  See std_num_cmp_list for usage
  727 
  728 sub frac_num_cmp_abs_list {
  729     my ( $absTol, $format, @answerList ) = @_;
  730 
  731     my %options = (             'tolerance'    =>     $absTol,
  732               'format'       =>     $format
  733     );
  734 
  735     set_default_options (\%options,
  736        'tolType'      =>     'absolute',
  737        'tolerance'    =>     $absTol,
  738        'mode'         =>     'frac',
  739        'format'       =>     $numFormatDefault,
  740        'zeroLevel'    =>     0,
  741        'zeroLevelTol' =>     0,
  742        'debug'        =>     0,
  743     );
  744 
  745     num_cmp(\@answerList, %options);
  746 }
  747 
  748 
  749 sub arith_num_cmp {           # only allow arithmetic expressions as submitted answer
  750 
  751     my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
  752 
  753     my %options = (     'tolerance'      =>     $relPercentTol,
  754       'format'         =>     $format,
  755       'zeroLevel'      =>     $zeroLevel,
  756       'zeroLevelTol'   =>     $zeroLevelTol
  757     );
  758 
  759     set_default_options( \%options,
  760                         'tolType'       =>     'relative',
  761                         'tolerance'     =>     $relPercentTol,
  762                         'mode'          =>     'arith',
  763                         'format'        =>     $numFormatDefault,
  764                         'zeroLevel'     =>     $numZeroLevelDefault,
  765                         'zeroLevelTol'  =>     $numZeroLevelTolDefault,
  766                         'relTol'        =>     $numRelPercentTolDefault,
  767                         'debug'         =>     0,
  768     );
  769 
  770     num_cmp([$correctAnswer], %options);
  771 }
  772 
  773 ##  See std_num_cmp_list for usage
  774 sub arith_num_cmp_list {
  775     my ( $relPercentTol, $format, @answerList ) = @_;
  776 
  777     my %options = (     'tolerance'     =>     $relPercentTol,
  778                         'format'        =>     $format,
  779     );
  780 
  781     set_default_options( \%options,
  782                          'tolType'       =>     'relative',
  783                          'tolerance'     =>     $relPercentTol,
  784                          'mode'          =>     'arith',
  785                          'format'        =>     $numFormatDefault,
  786                          'zeroLevel'     =>     $numZeroLevelDefault,
  787                          'zeroLevelTol'  =>     $numZeroLevelTolDefault,
  788                          'relTol'        =>     $numRelPercentTolDefault,
  789                          'debug'         =>     0,
  790     );
  791 
  792     num_cmp(\@answerList, %options);
  793 }
  794 
  795 sub arith_num_cmp_abs {     # only allow arithmetic expressions as submitted answer with absolute tolerance
  796     my ( $correctAnswer, $absTol, $format ) = @_;
  797 
  798     my %options = (      'tolerance'    =>     $absTol,
  799                          'format'       =>     $format
  800     );
  801 
  802     set_default_options (\%options,
  803                          'tolType'      =>     'absolute',
  804                          'tolerance'    =>     $absTol,
  805                          'mode'         =>     'arith',
  806                          'format'       =>     $numFormatDefault,
  807                          'zeroLevel'    =>     0,
  808                          'zeroLevelTol' =>     0,
  809                          'debug'        =>     0,
  810     );
  811 
  812     num_cmp([$correctAnswer], %options);
  813 }
  814 
  815 ##  See std_num_cmp_list for usage
  816 sub arith_num_cmp_abs_list {
  817     my ( $absTol, $format, @answerList ) = @_;
  818 
  819     my %options = (      'tolerance'    =>     $absTol,
  820                          'format'       =>     $format
  821     );
  822 
  823     set_default_options (\%options,
  824                          'tolType'      =>     'absolute',
  825                          'tolerance'    =>     $absTol,
  826                          'mode'         =>     'arith',
  827                          'format'       =>     $numFormatDefault,
  828                          'zeroLevel'    =>     0,
  829                          'zeroLevelTol' =>     0,
  830                          'debug'        =>     0,
  831     );
  832 
  833     num_cmp(\@answerList, %options);
  834 }
  835 
  836 sub strict_num_cmp {          # only allow numbers as submitted answer
  837     my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
  838 
  839     my %options = (      'tolerance'     =>     $relPercentTol,
  840                          'format'        =>     $format,
  841                          'zeroLevel'     =>     $zeroLevel,
  842                          'zeroLevelTol'  =>     $zeroLevelTol
  843     );
  844 
  845     set_default_options( \%options,
  846                          'tolType'       =>     'relative',
  847                          'tolerance'     =>     $relPercentTol,
  848                          'mode'          =>     'strict',
  849                          'format'        =>     $numFormatDefault,
  850                          'zeroLevel'     =>     $numZeroLevelDefault,
  851                          'zeroLevelTol'  =>     $numZeroLevelTolDefault,
  852                          'relTol'        =>     $numRelPercentTolDefault,
  853                          'debug'         =>     0,
  854     );
  855     num_cmp([$correctAnswer], %options);
  856 
  857 }
  858 
  859 ##  See std_num_cmp_list for usage
  860 sub strict_num_cmp_list {       # compare numbers
  861     my ( $relPercentTol, $format, @answerList ) = @_;
  862 
  863     my %options = (    'tolerance'     =>     $relPercentTol,
  864        'format'        =>     $format,
  865     );
  866 
  867     set_default_options( \%options,
  868                          'tolType'       =>     'relative',
  869                          'tolerance'     =>     $relPercentTol,
  870                          'mode'          =>     'strict',
  871                          'format'        =>     $numFormatDefault,
  872                          'zeroLevel'     =>     $numZeroLevelDefault,
  873                          'zeroLevelTol'  =>     $numZeroLevelTolDefault,
  874                          'relTol'        =>     $numRelPercentTolDefault,
  875                          'debug'         =>     0,
  876     );
  877 
  878     num_cmp(\@answerList, %options);
  879 }
  880 
  881 
  882 sub strict_num_cmp_abs {        # only allow numbers as submitted answer with absolute tolerance
  883     my ( $correctAnswer, $absTol, $format ) = @_;
  884 
  885     my %options = (       'tolerance'    =>     $absTol,
  886                     'format'       =>     $format
  887     );
  888 
  889     set_default_options (\%options,
  890                          'tolType'      =>     'absolute',
  891                          'tolerance'    =>     $absTol,
  892                          'mode'         =>     'strict',
  893                          'format'       =>     $numFormatDefault,
  894                          'zeroLevel'    =>     0,
  895                          'zeroLevelTol' =>     0,
  896                          'debug'        =>     0,
  897     );
  898     num_cmp([$correctAnswer], %options);
  899 
  900 }
  901 
  902 ##  See std_num_cmp_list for usage
  903 sub strict_num_cmp_abs_list {     # compare numbers
  904     my ( $absTol, $format, @answerList ) = @_;
  905 
  906     my %options = (      'tolerance'    =>     $absTol,
  907                          'format'       =>     $format
  908     );
  909 
  910     set_default_options (\%options,
  911                          'tolType'      =>     'absolute',
  912                          'tolerance'    =>     $absTol,
  913                          'mode'         =>     'strict',
  914                          'format'       =>     $numFormatDefault,
  915                          'zeroLevel'    =>     0,
  916                          'zeroLevelTol' =>     0,
  917                          'debug'        =>     0,
  918     );
  919 
  920     num_cmp(\@answerList, %options);
  921 }
  922 
  923 ## sub numerical_compare_with_units
  924 ## Compares a number with units
  925 ## Deprecated; use num_cmp()
  926 ##
  927 ## IN:  a string which includes the numerical answer and the units
  928 ##    a hash with the following keys (all optional):
  929 ##      mode    --  'std', 'frac', 'arith', or 'strict'
  930 ##      format    --  the format to use when displaying the answer
  931 ##      tol   --  an absolute tolerance, or
  932 ##      relTol    --  a relative tolerance
  933 ##      zeroLevel --  if the correct answer is this close to zero, then zeroLevelTol applies
  934 ##      zeroLevelTol  --  absolute tolerance to allow when correct answer is close to zero
  935 
  936 # This mode is depricated.  send input through num_cmp -- it can handle units.
  937 
  938 sub numerical_compare_with_units {
  939   my $correct_answer = shift;  # the answer is a string which includes both the numerical answer and the units.
  940   my %options = @_;    # all of the other inputs are (key value) pairs
  941 
  942   # Prepare the correct answer
  943   $correct_answer = str_filters( $correct_answer, 'trim_whitespace' );
  944 
  945   # it surprises me that the match below works since the first .* is greedy.
  946   my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/;
  947   $options{units} = $correct_units;
  948 
  949   num_cmp($correct_num_answer, %options);
  950 }
  951 
  952 
  953 =head3 std_num_str_cmp()
  954 
  955 NOTE: This function is maintained for compatibility. num_cmp() with the
  956     'strings' parameter is slightly preferred.
  957 
  958 std_num_str_cmp() is used when the correct answer could be either a number or a
  959 string. For example, if you wanted the student to evaluate a function at number
  960 of points, but write "Inf" or "Minf" if the function is unbounded. This routine
  961 will provide error messages that do not give a hint as to whether the correct
  962 answer is a string or a number. For numerical comparisons, std_num_cmp() is
  963 used internally; for string comparisons, std_str_cmp() is used.
  964 
  965  std_num_str_cmp( $correctAnswer ) OR
  966  std_num_str_cmp( $correctAnswer, $ra_legalStrings ) OR
  967  std_num_str_cmp( $correctAnswer, $ra_legalStrings, $relPercentTol ) OR
  968  std_num_str_cmp( $correctAnswer, $ra_legalStrings, $relPercentTol, $format ) OR
  969  std_num_str_cmp( $correctAnswer, $ra_legalStrings, $relPercentTol, $format, $zeroLevel ) OR
  970  std_num_str_cmp( $correctAnswer, $ra_legalStrings, $relPercentTol, $format,
  971           $zeroLevel, $zeroLevelTol )
  972 
  973   $correctAnswer    --  the correct answer
  974   $ra_legalStrings  --  a reference to an array of legal strings, e.g. ["str1", "str2"]
  975   $relPercentTol    --  the error tolerance as a percentage
  976   $format     --  the display format
  977   $zeroLevel    --  if the correct answer is this close to zero, then zeroLevelTol applies
  978   $zeroLevelTol   --  absolute tolerance to allow when correct answer is close to zero
  979 
  980 Example:
  981   ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) );
  982 
  983 =cut
  984 
  985 sub std_num_str_cmp {
  986   my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_;
  987   # warn ('This method is depreciated.  Use num_cmp instead.');
  988   return num_cmp ($correctAnswer, strings=>$ra_legalStrings, relTol=>$relpercentTol, format=>$format,
  989     zeroLevel=>$zeroLevel, zeroLevelTol=>$zeroLevelTol);
  990 }
  991 
  992 sub NUM_CMP {   # low level numeric compare
  993   my %num_params = @_;
  994 
  995   my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug );
  996   foreach my $key (@keys) {
  997       warn "$key must be defined in options when calling NUM_CMP" unless defined ($num_params{$key});
  998   }
  999 
 1000   my $correctAnswer = $num_params{'correctAnswer'};
 1001   my $format    = $num_params{'format'};
 1002   my $mode    = $num_params{'mode'};
 1003 
 1004   if( $num_params{tolType} eq 'relative' ) {
 1005     $num_params{'tolerance'} = .01*$num_params{'tolerance'};
 1006   }
 1007 
 1008   my $formattedCorrectAnswer;
 1009   my $correct_units;
 1010   my $correct_num_answer;
 1011   my %correct_units;
 1012   my $corrAnswerIsString = 0;
 1013 
 1014 
 1015   if (defined($num_params{units}) && $num_params{units}) {
 1016     $correctAnswer  = str_filters( $correctAnswer, 'trim_whitespace' );
 1017             # units are in form stuff space units where units contains no spaces.
 1018 
 1019     ($correct_num_answer, $correct_units) = $correctAnswer =~ /^(.*)\s+([^\s]*)$/;
 1020     %correct_units = Units::evaluate_units($correct_units);
 1021     if ( defined( $correct_units{'ERROR'} ) ) {
 1022        warn ("ERROR: The answer \"$correctAnswer\" in the problem definition cannot be parsed:\n" .
 1023         "$correct_units{'ERROR'}\n");
 1024     }
 1025     # $formattedCorrectAnswer = spf($correct_num_answer,$num_params{'format'}) . " $correct_units";
 1026     $formattedCorrectAnswer = prfmt($correct_num_answer,$num_params{'format'}) . " $correct_units";
 1027 
 1028   } elsif (defined($num_params{strings}) && $num_params{strings}) {
 1029     my $legalString = '';
 1030     my @legalStrings = @{$num_params{strings}};
 1031     $correct_num_answer = $correctAnswer;
 1032     $formattedCorrectAnswer = $correctAnswer;
 1033     foreach $legalString (@legalStrings) {
 1034       if ( uc($correctAnswer) eq uc($legalString) ) {
 1035         $corrAnswerIsString = 1;
 1036 
 1037         last;
 1038       }
 1039     }     ## at this point $corrAnswerIsString = 0 iff correct answer is numeric
 1040   } else {
 1041     $correct_num_answer = $correctAnswer;
 1042     $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} );
 1043   }
 1044 
 1045   $correct_num_answer = math_constants($correct_num_answer);
 1046 
 1047   my $PGanswerMessage = '';
 1048 
 1049   my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report);
 1050 
 1051   if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) {
 1052       ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer);
 1053   } else { # case of a string answer
 1054     $PG_eval_errors = ' ';
 1055     $correctVal = $correctAnswer;
 1056   }
 1057 
 1058   if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) {
 1059         ##error message from eval or above
 1060     warn "Error in 'correct' answer: $PG_eval_errors<br>
 1061           The answer $correctAnswer evaluates to $correctVal,
 1062           which cannot be interpreted as a number.  ";
 1063 
 1064   }
 1065   #########################################################################
 1066 
 1067   #construct the answer evaluator
 1068       my $answer_evaluator = new AnswerEvaluator;
 1069       $answer_evaluator->{debug} = $num_params{debug};
 1070       $answer_evaluator->ans_hash(
 1071                 correct_ans       =>  $correctVal,
 1072                 type          =>  "${mode}_number",
 1073                 tolerance       =>  $num_params{tolerance},
 1074               tolType         =>  $num_params{tolType},
 1075               units         =>  $correct_units,
 1076                 original_correct_ans  =>  $formattedCorrectAnswer,
 1077                 rh_correct_units    =>      \%correct_units,
 1078                 answerIsString      =>  $corrAnswerIsString,
 1079       );
 1080       my ($in, $formattedSubmittedAnswer);
 1081   $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift;
 1082     $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;}
 1083   );
 1084   if (defined($num_params{units}) && $num_params{units}) {
 1085       $answer_evaluator->install_pre_filter(\&check_units);
 1086   }
 1087   if (defined($num_params{strings}) && $num_params{strings}) {
 1088       $answer_evaluator->install_pre_filter(\&check_strings, %num_params);
 1089   }
 1090 
 1091   $answer_evaluator->install_pre_filter(\&check_syntax);
 1092 
 1093   $answer_evaluator->install_pre_filter(\&math_constants);
 1094 
 1095   if ($mode eq 'std') {
 1096         # do nothing
 1097   } elsif ($mode eq 'strict') {
 1098     $answer_evaluator->install_pre_filter(\&is_a_number);
 1099   } elsif ($mode eq 'arith') {
 1100       $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression);
 1101     } elsif ($mode eq 'frac') {
 1102       $answer_evaluator->install_pre_filter(\&is_a_fraction);
 1103 
 1104     } else {
 1105       $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.';
 1106       $formattedSubmittedAnswer = $in;
 1107     }
 1108 
 1109   if ($corrAnswerIsString == 0 ){   # avoiding running compare_numbers when correct answer is a string.
 1110     $answer_evaluator->install_evaluator(\&compare_numbers, %num_params);
 1111    }
 1112 
 1113 
 1114 ###############################################################################
 1115 # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's
 1116 # can be displayed in the answer message.  This may still cause a few anomolies when strings are used
 1117 #
 1118 ###############################################################################
 1119 
 1120   $answer_evaluator->install_post_filter(\&fix_answers_for_display);
 1121 
 1122       $answer_evaluator->install_post_filter(sub {my $rh_ans = shift;
 1123           return $rh_ans unless $rh_ans->catch_error('EVAL');
 1124           $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message};
 1125           $rh_ans->clear_error('EVAL'); } );
 1126       $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } );
 1127       $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('UNITS'); } );
 1128       $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('NUMBER'); } );
 1129   $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } );
 1130       $answer_evaluator;
 1131 }
 1132 
 1133 
 1134 
 1135 ##########################################################################
 1136 ##########################################################################
 1137 ## Function answer evaluators
 1138 
 1139 =head2 Function Answer Evaluators
 1140 
 1141 Function answer evaluators take in a function, compare it numerically to a
 1142 correct function, and return a score. They can require an exactly equivalent
 1143 function, or one that is equal up to a constant. They can accept or reject an
 1144 answer based on specified tolerances for numerical deviation.
 1145 
 1146 Function Comparison Options
 1147 
 1148   correctEqn  --  The correct equation, specified as a string. It may include
 1149           all basic arithmetic operations, as well as elementary
 1150           functions. Variable usage is described below.
 1151 
 1152   Variables --  The independent variable(s). When comparing the correct
 1153           equation to the student equation, each variable will be
 1154           replaced by a certain number of numerical values. If
 1155           the student equation agrees numerically with the correct
 1156           equation, they are considered equal. Note that all
 1157           comparison is numeric; it is possible (although highly
 1158           unlikely and never a practical concern) for two unequal
 1159           functions to yield the same numerical results.
 1160 
 1161   Limits    --  The limits of evaluation for the independent variables.
 1162           Each variable is evaluated only in the half-open interval
 1163           [lower_limit, upper_limit). This is useful if the function
 1164           has a singularity or is not defined in a certain range.
 1165           For example, the function "sqrt(-1-x)" could be evaluated
 1166           in [-2,-1).
 1167 
 1168   Tolerance --  Tolerance in function comparisons works exactly as in
 1169           numerical comparisons; see the numerical comparison
 1170           documentation for a complete description. Note that the
 1171           tolerance does applies to the function as a whole, not
 1172           each point individually.
 1173 
 1174   Number of --  Specifies how many points to evaluate each variable at. This
 1175   Points      is typically 3, but can be set higher if it is felt that
 1176           there is a strong possibility of "false positives."
 1177 
 1178   Maximum   --  Sets the maximum size of the constant of integration. For
 1179   Constant of   technical reasons concerning floating point arithmetic, if
 1180   Integration   the additive constant, i.e., the constant of integration, is
 1181           greater (in absolute value) than maxConstantOfIntegration
 1182           AND is greater than maxConstantOfIntegration times the
 1183           correct value, WeBWorK will give an error message saying
 1184           that it can not handle such a large constant of integration.
 1185           This is to prevent e.g. cos(x) + 1E20 or even 1E20 as being
 1186           accepted as a correct antiderivatives of sin(x) since
 1187           floating point arithmetic cannot tell the difference
 1188           between cos(x) + 1E20, 1E20, and -cos(x) + 1E20.
 1189 
 1190 Technical note: if you examine the code for the function routines, you will see
 1191 that most subroutines are simply doing some basic error-checking and then
 1192 passing the parameters on to the low-level FUNCTION_CMP(). Because this routine
 1193 is set up to handle multivariable functions, with single-variable functions as
 1194 a special case, it is possible to pass multivariable parameters to single-
 1195 variable functions. This usage is strongly discouraged as unnecessarily
 1196 confusing. Avoid it.
 1197 
 1198 Default Values (As of 7/24/2000) (Option -- Variable Name -- Value)
 1199 
 1200   Variable      --  $functVarDefault      --  'x'
 1201   Relative Tolerance    --  $functRelPercentTolDefault    --  .1
 1202   Absolute Tolerance    --  $functAbsTolDefault     --  .001
 1203   Lower Limit     --  $functLLimitDefault     --  .0000001
 1204   Upper Limit     --  $functULimitDefault     --  1
 1205   Number of Points    --  $functNumOfPoints     --  3
 1206   Zero Level      --  $functZeroLevelDefault      --  1E-14
 1207   Zero Level Tolerance    --  $functZeroLevelTolDefault   --  1E-12
 1208   Maximum Constant    --  $functMaxConstantOfIntegration    --  1E8
 1209     of Integration
 1210 
 1211 =cut
 1212 
 1213 
 1214 
 1215 =head3 fun_cmp()
 1216 
 1217 Compares a function or a list of functions, using a named hash of options to set
 1218 parameters. This can make for more readable code than using the function_cmp()
 1219 style, but some people find one or the other easier to remember.
 1220 
 1221 ANS( fun_cmp( answer or answer_array_ref, options_hash ) );
 1222 
 1223   1. a string containing the correct function, or a reference to an
 1224     array of correct functions
 1225   2. a hash containing the following items (all optional):
 1226     var           --  either the number of variables or a reference to an
 1227                       array of variable names (see below)
 1228     limits            --  reference to an array of arrays of limits (see below), or:
 1229     mode            --  'std' (default) (function must match exactly), or:
 1230                     'antider' (function must match up to a constant)
 1231     relTol            --  (default) a relative tolerance (as a percentage), or:
 1232     tol           --  an absolute tolerance for error
 1233     numPoints         --  the number of points to evaluate the function at
 1234     maxConstantOfIntegration      --  maximum size of the constant of integration
 1235     zeroLevel         --  if the correct answer is this close to zero, then
 1236                       zeroLevelTol applies
 1237     zeroLevelTol          --  absolute tolerance to allow when answer is close to zero
 1238     params                an array of "free" parameters which can be used to adapt
 1239                     the correct answer to the submitted answer. (e.g. ['c'] for
 1240                     a constant of integration in the answer x^3/3 + c.
 1241     debug           --  when set to 1 this provides extra information while checking the
 1242                         the answer.
 1243 
 1244   Returns an answer evaluator, or (if given a reference to an array
 1245   of answers), a list of answer evaluators
 1246 
 1247 ANSWER:
 1248 
 1249   The answer must be in the form of a string. The answer can contain
 1250   functions, pi, e, and arithmetic operations. However, the correct answer
 1251   string follows a slightly stricter syntax than student answers; specifically,
 1252   there is no implicit multiplication. So the correct answer must be "3*x" rather
 1253   than "3 x". Students can still enter "3 x".
 1254 
 1255 VARIABLES:
 1256 
 1257   The var parameter can contain either a number or a reference to an array of
 1258   variable names. If it contains a number, the variables are named automatically
 1259   as follows: 1 variable  --  x
 1260       2 variables --  x, y
 1261       3 variables --  x, y, z
 1262       4 or more --  x_1, x_2, x_3, etc.
 1263   If the var parameter contains a reference to an array of variable names, then
 1264   the number of variables is determined by the number of items in the array. A
 1265   reference to an array is created with brackets, e.g. "var => ['r', 's', 't']".
 1266   If only one variable is being used, you can write either "var => ['t']" for
 1267   consistency or "var => 't'" as a shortcut. The default is one variable, x.
 1268 
 1269 LIMITS:
 1270 
 1271   Limits are specified with the limits parameter. You may NOT use llimit/ulimit.
 1272   If you specify limits for one variable, you must specify them for all variables.
 1273   The limit parameter must be a reference to an array of arrays of the form
 1274   [lower_limit. upper_limit], each array corresponding to the lower and upper
 1275   endpoints of the (half-open) domain of one variable. For example,
 1276   "vars => 2, limits => [[0,2], [-3,8]]" would cause x to be evaluated in [0,2) and
 1277   y to be evaluated in [-3,8). If only one variable is being used, you can write
 1278   either "limits => [[0,3]]" for consistency or "limits => [0,3]" as a shortcut.
 1279 
 1280 EXAMPLES:
 1281 
 1282   fun_cmp( "3*x" )  --  standard compare, variable is x
 1283   fun_cmp( ["3*x", "4*x+3", "3*x**2"] ) --  standard compare, defaults used for all three functions
 1284   fun_cmp( "3*t", var => 't' )  --  standard compare, variable is t
 1285   fun_cmp( "5*x*y*z", var => 3 )  --  x, y and z are the variables
 1286   fun_cmp( "5*x", mode => 'antider' ) --  student answer must match up to constant (i.e., 5x+C)
 1287   fun_cmp( ["3*x*y", "4*x*y"], limits => [[0,2], [5,7]] ) --  x evaluated in [0,2)
 1288                                 y evaluated in [5,7)
 1289 
 1290 =cut
 1291 
 1292 sub fun_cmp {
 1293   my $correctAnswer = shift @_;
 1294   my %opt = @_;
 1295 
 1296     assign_option_aliases( \%opt,
 1297         'vars'    =>  'var',    # set the standard option 'var' to the one specified as vars
 1298           'domain'  =>  'limits', # set the standard option 'limits' to the one specified as domain
 1299           'reltol'    =>  'relTol',
 1300           'param'   =>  'params',
 1301     );
 1302 
 1303     set_default_options(  \%opt,
 1304         'var'         =>  $functVarDefault,
 1305             'params'        =>  [],
 1306         'limits'        =>  [[$functLLimitDefault, $functULimitDefault]],
 1307         'mode'          =>  'std',
 1308         'tolType'       =>    (defined($opt{tol}) ) ? 'absolute' : 'relative',
 1309         'tol'         =>  .01, # default mode should be relative, to obtain this tol must not be defined
 1310             'relTol'        =>  $functRelPercentTolDefault,
 1311         'numPoints'       =>  $functNumOfPoints,
 1312         'maxConstantOfIntegration'  =>  $functMaxConstantOfIntegration,
 1313         'zeroLevel'       =>  $functZeroLevelDefault,
 1314         'zeroLevelTol'      =>  $functZeroLevelTolDefault,
 1315             'debug'         =>  0,
 1316      );
 1317 
 1318     # allow var => 'x' as an abbreviation for var => ['x']
 1319   my %out_options = %opt;
 1320   unless ( ref($out_options{var}) eq 'ARRAY' ) {
 1321     $out_options{var} = [$out_options{var}];
 1322   }
 1323   # allow params => 'c' as an abbreviation for params => ['c']
 1324   unless ( ref($out_options{params}) eq 'ARRAY' ) {
 1325     $out_options{params} = [$out_options{params}];
 1326   }
 1327   my ($tolType, $tol);
 1328     if ($out_options{tolType} eq 'absolute') {
 1329     $tolType = 'absolute';
 1330     $tol = $out_options{'tol'};
 1331     delete($out_options{'relTol'}) if exists( $out_options{'relTol'} );
 1332   } else {
 1333     $tolType = 'relative';
 1334     $tol = $out_options{'relTol'};
 1335     delete($out_options{'tol'}) if exists( $out_options{'tol'} );
 1336   }
 1337 
 1338   my @output_list = ();
 1339   # thread over lists
 1340   my @ans_list = ();
 1341 
 1342   if ( ref($correctAnswer) eq 'ARRAY' ) {
 1343     @ans_list = @{$correctAnswer};
 1344   }
 1345   else {
 1346     push( @ans_list, $correctAnswer );
 1347   }
 1348 
 1349   # produce answer evaluators
 1350   foreach my $ans (@ans_list) {
 1351     push(@output_list,
 1352       FUNCTION_CMP(
 1353           'correctEqn'    =>  $ans,
 1354           'var'       =>  $out_options{'var'},
 1355           'limits'      =>  $out_options{'limits'},
 1356           'tolerance'     =>  $tol,
 1357           'tolType'     =>  $tolType,
 1358           'numPoints'     =>  $out_options{'numPoints'},
 1359           'mode'        =>  $out_options{'mode'},
 1360           'maxConstantOfIntegration'  =>  $out_options{'maxConstantOfIntegration'},
 1361           'zeroLevel'     =>  $out_options{'zeroLevel'},
 1362           'zeroLevelTol'    =>  $out_options{'zeroLevelTol'},
 1363           'params'      =>  $out_options{'params'},
 1364           'debug'       =>  $out_options{'debug'},
 1365       ),
 1366     );
 1367   }
 1368 
 1369   return (wantarray) ? @output_list : $output_list[0];
 1370 }
 1371 
 1372 =head3 Single-variable Function Comparisons
 1373 
 1374 There are four single-variable function answer evaluators: "normal," absolute
 1375 tolerance, antiderivative, and antiderivative with absolute tolerance. All
 1376 parameters (other than the correct equation) are optional.
 1377 
 1378  function_cmp( $correctEqn ) OR
 1379  function_cmp( $correctEqn, $var ) OR
 1380  function_cmp( $correctEqn, $var, $llimit, $ulimit ) OR
 1381  function_cmp( $correctEqn, $var, $llimit, $ulimit, $relPercentTol ) OR
 1382  function_cmp( $correctEqn, $var, $llimit, $ulimit,
 1383         $relPercentTol, $numPoints ) OR
 1384  function_cmp( $correctEqn, $var, $llimit, $ulimit,
 1385         $relPercentTol, $numPoints, $zeroLevel ) OR
 1386  function_cmp( $correctEqn, $var, $llimit, $ulimit, $relPercentTol, $numPoints,
 1387         $zeroLevel,$zeroLevelTol )
 1388 
 1389   $correctEqn   --  the correct equation, as a string
 1390   $var      --  the string representing the variable (optional)
 1391   $llimit     --  the lower limit of the interval to evaluate the
 1392               variable in (optional)
 1393   $ulimit     --  the upper limit of the interval to evaluate the
 1394               variable in (optional)
 1395   $relPercentTol  --  the error tolerance as a percentage (optional)
 1396   $numPoints    --  the number of points at which to evaluate the
 1397               variable (optional)
 1398   $zeroLevel    --  if the correct answer is this close to zero, then
 1399               zeroLevelTol applies (optional)
 1400   $zeroLevelTol --  absolute tolerance to allow when answer is close to zero
 1401 
 1402   function_cmp() uses standard comparison and relative tolerance. It takes a
 1403   string representing a single-variable function and compares the student
 1404   answer to that function numerically.
 1405 
 1406  function_cmp_up_to_constant( $correctEqn ) OR
 1407  function_cmp_up_to_constant( $correctEqn, $var ) OR
 1408  function_cmp_up_to_constant( $correctEqn, $var, $llimit, $ulimit ) OR
 1409  function_cmp_up_to_constant( $correctEqn, $var, $llimit, $ulimit,
 1410                 $relpercentTol ) OR
 1411  function_cmp_up_to_constant( $correctEqn, $var, $llimit, $ulimit,
 1412                 $relpercentTol, $numOfPoints ) OR
 1413  function_cmp_up_to_constant( $correctEqn, $var, $llimit, $ulimit,
 1414                 $relpercentTol, $numOfPoints,
 1415                 $maxConstantOfIntegration ) OR
 1416  function_cmp_up_to_constant( $correctEqn, $var, $llimit, $ulimit,
 1417                 $relpercentTol, $numOfPoints,
 1418                 $maxConstantOfIntegration, $zeroLevel)  OR
 1419  function_cmp_up_to_constant( $correctEqn, $var, $llimit, $ulimit,
 1420                 $relpercentTol, $numOfPoints,
 1421                 $maxConstantOfIntegration,
 1422                 $zeroLevel, $zeroLevelTol )
 1423 
 1424   $maxConstantOfIntegration --  the maximum size of the constant of
 1425                   integration
 1426 
 1427   function_cmp_up_to_constant() uses antiderivative compare and relative
 1428   tolerance. All options work exactly like function_cmp(), except of course
 1429   $maxConstantOfIntegration. It will accept as correct any function which
 1430   differs from $correctEqn by at most a constant; that is, if
 1431     $studentEqn = $correctEqn + C
 1432   the answer is correct.
 1433 
 1434  function_cmp_abs( $correctFunction ) OR
 1435  function_cmp_abs( $correctFunction, $var ) OR
 1436  function_cmp_abs( $correctFunction, $var, $llimit, $ulimit ) OR
 1437  function_cmp_abs( $correctFunction, $var, $llimit, $ulimit, $absTol ) OR
 1438  function_cmp_abs( $correctFunction, $var, $llimit, $ulimit, $absTol,
 1439           $numOfPoints )
 1440 
 1441   $absTol --  the tolerance as an absolute value
 1442 
 1443   function_cmp_abs() uses standard compare and absolute tolerance. All
 1444   other options work exactly as for function_cmp().
 1445 
 1446  function_cmp_up_to_constant_abs( $correctFunction ) OR
 1447  function_cmp_up_to_constant_abs( $correctFunction, $var ) OR
 1448  function_cmp_up_to_constant_abs( $correctFunction, $var, $llimit, $ulimit ) OR
 1449  function_cmp_up_to_constant_abs( $correctFunction, $var, $llimit, $ulimit,
 1450                   $absTol ) OR
 1451  function_cmp_up_to_constant_abs( $correctFunction, $var, $llimit, $ulimit,
 1452                   $absTol, $numOfPoints ) OR
 1453  function_cmp_up_to_constant_abs( $correctFunction, $var, $llimit, $ulimit,
 1454                   $absTol, $numOfPoints,
 1455                   $maxConstantOfIntegration )
 1456 
 1457   function_cmp_up_to_constant_abs() uses antiderivative compare
 1458   and absolute tolerance. All other options work exactly as with
 1459   function_cmp_up_to_constant().
 1460 
 1461 Examples:
 1462 
 1463   ANS( function_cmp( "cos(x)" ) ) --  Accepts cos(x), sin(x+pi/2),
 1464     sin(x)^2 + cos(x) + cos(x)^2 -1, etc. This assumes
 1465     $functVarDefault has been set to "x".
 1466   ANS( function_cmp( $answer, "t" ) ) --  Assuming $answer is "cos(t)",
 1467     accepts cos(t), etc.
 1468   ANS( function_cmp_up_to_constant( "cos(x)" ) )  --  Accepts any
 1469     antiderivative of sin(x), e.g. cos(x) + 5.
 1470   ANS( function_cmp_up_to_constant( "cos(z)", "z" ) ) --  Accepts any
 1471     antiderivative of sin(z), e.g. sin(z+pi/2) + 5.
 1472 
 1473 =cut
 1474 
 1475 sub adaptive_function_cmp {
 1476   my $correctEqn = shift;
 1477   my %options = @_;
 1478   set_default_options(  \%options,
 1479       'vars'      =>  [qw( x y )],
 1480                   'params'    =>  [],
 1481                   'limits'    =>  [ [0,1], [0,1]],
 1482                   'reltol'    =>  $main::functRelPercentTolDefault,
 1483                   'numPoints'   =>  $main::functNumOfPoints,
 1484                   'zeroLevel'   =>  $main::functZeroLevelDefault,
 1485                   'zeroLevelTol'    =>  $main::functZeroLevelTolDefault,
 1486                   'debug'     =>  0,
 1487   );
 1488 
 1489     my $var_ref = $options{'vars'};
 1490     my $ra_params = $options{ 'params'};
 1491     my $limit_ref = $options{'limits'};
 1492     my $relPercentTol= $options{'reltol'};
 1493     my $numPoints = $options{'numPoints'};
 1494     my $zeroLevel = $options{'zeroLevel'};
 1495     my $zeroLevelTol = $options{'zeroLevelTol'};
 1496 
 1497   FUNCTION_CMP( 'correctEqn'          =>  $correctEqn,
 1498       'var'           =>  $var_ref,
 1499       'limits'          =>  $limit_ref,
 1500       'tolerance'         =>  $relPercentTol,
 1501       'tolType'         =>  'relative',
 1502       'numPoints'         =>  $numPoints,
 1503       'mode'            =>  'std',
 1504       'maxConstantOfIntegration'      =>  10**100,
 1505       'zeroLevel'         =>  $zeroLevel,
 1506       'zeroLevelTol'          =>  $zeroLevelTol,
 1507       'scale_norm'                      =>    1,
 1508       'params'                          =>    $ra_params,
 1509       'debug'               =>  $options{debug} ,
 1510   );
 1511 }
 1512 
 1513 sub function_cmp {
 1514   my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$zeroLevel,$zeroLevelTol) = @_;
 1515 
 1516   if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) {
 1517     function_invalid_params( $correctEqn );
 1518   }
 1519   else {
 1520     FUNCTION_CMP( 'correctEqn'          =>  $correctEqn,
 1521         'var'           =>  $var,
 1522         'limits'          =>  [$llimit, $ulimit],
 1523         'tolerance'         =>  $relPercentTol,
 1524         'tolType'         =>  'relative',
 1525         'numPoints'         =>  $numPoints,
 1526         'mode'            =>  'std',
 1527         'maxConstantOfIntegration'      =>  0,
 1528         'zeroLevel'         =>  $zeroLevel,
 1529         'zeroLevelTol'          =>  $zeroLevelTol
 1530           );
 1531   }
 1532 }
 1533 
 1534 sub function_cmp_up_to_constant { ## for antiderivative problems
 1535   my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$maxConstantOfIntegration,$zeroLevel,$zeroLevelTol) = @_;
 1536 
 1537   if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) {
 1538     function_invalid_params( $correctEqn );
 1539   }
 1540   else {
 1541     FUNCTION_CMP( 'correctEqn'          =>  $correctEqn,
 1542         'var'           =>  $var,
 1543         'limits'          =>  [$llimit, $ulimit],
 1544         'tolerance'         =>  $relPercentTol,
 1545         'tolType'         =>  'relative',
 1546         'numPoints'         =>  $numPoints,
 1547         'mode'            =>  'antider',
 1548         'maxConstantOfIntegration'      =>  $maxConstantOfIntegration,
 1549         'zeroLevel'         =>  $zeroLevel,
 1550         'zeroLevelTol'          =>  $zeroLevelTol
 1551           );
 1552   }
 1553 }
 1554 
 1555 sub function_cmp_abs {      ## similar to function_cmp but uses absolute tolerance
 1556   my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints) = @_;
 1557 
 1558   if ( (scalar(@_) == 3) or (scalar(@_) > 6) or (scalar(@_) == 0) ) {
 1559     function_invalid_params( $correctEqn );
 1560   }
 1561   else {
 1562     FUNCTION_CMP( 'correctEqn'      =>  $correctEqn,
 1563         'var'       =>  $var,
 1564         'limits'      =>  [$llimit, $ulimit],
 1565         'tolerance'     =>  $absTol,
 1566         'tolType'     =>  'absolute',
 1567         'numPoints'     =>  $numPoints,
 1568         'mode'        =>  'std',
 1569         'maxConstantOfIntegration'  =>  0,
 1570         'zeroLevel'     =>  0,
 1571         'zeroLevelTol'      =>  0
 1572     );
 1573   }
 1574 }
 1575 
 1576 
 1577 sub function_cmp_up_to_constant_abs  {  ## for antiderivative problems
 1578                     ## similar to function_cmp_up_to_constant
 1579                     ## but uses absolute tolerance
 1580   my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints,$maxConstantOfIntegration) = @_;
 1581 
 1582   if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) {
 1583     function_invalid_params( $correctEqn );
 1584   }
 1585 
 1586   else {
 1587     FUNCTION_CMP( 'correctEqn'          =>  $correctEqn,
 1588         'var'           =>  $var,
 1589         'limits'          =>  [$llimit, $ulimit],
 1590         'tolerance'         =>  $absTol,
 1591         'tolType'         =>  'absolute',
 1592         'numPoints'         =>  $numPoints,
 1593         'mode'            =>  'antider',
 1594         'maxConstantOfIntegration'      =>  $maxConstantOfIntegration,
 1595         'zeroLevel'         =>  0,
 1596         'zeroLevelTol'          =>  0
 1597     );
 1598   }
 1599 }
 1600 
 1601 ## The following answer evaluator for comparing multivarable functions was
 1602 ## contributed by Professor William K. Ziemer
 1603 ## (Note: most of the multivariable functionality provided by Professor Ziemer
 1604 ## has now been integrated into fun_cmp and FUNCTION_CMP)
 1605 ############################
 1606 # W.K. Ziemer, Sep. 1999
 1607 # Math Dept. CSULB
 1608 # email: wziemer@csulb.edu
 1609 ############################
 1610 
 1611 =head3 multivar_function_cmp
 1612 
 1613 NOTE: this function is maintained for compatibility. fun_cmp() is
 1614     slightly preferred.
 1615 
 1616 usage:
 1617 
 1618   multivar_function_cmp( $answer, $var_reference, options)
 1619     $answer       --  string, represents function of several variables
 1620     $var_reference    --  number (of variables), or list reference (e.g. ["var1","var2"] )
 1621   options:
 1622     $limit_reference  --  reference to list of lists (e.g. [[1,2],[3,4]])
 1623     $relPercentTol    --  relative percent tolerance in answer
 1624     $numPoints      --  number of points to sample in for each variable
 1625     $zeroLevel      --  if the correct answer is this close to zero, then zeroLevelTol applies
 1626     $zeroLevelTol   --  absolute tolerance to allow when answer is close to zero
 1627 
 1628 =cut
 1629 
 1630 sub multivar_function_cmp {
 1631   my ($correctEqn,$var_ref,$limit_ref,$relPercentTol,$numPoints,$zeroLevel,$zeroLevelTol) = @_;
 1632 
 1633   if ( (scalar(@_) > 7) or (scalar(@_) < 2) ) {
 1634     function_invalid_params( $correctEqn );
 1635   }
 1636 
 1637   FUNCTION_CMP( 'correctEqn'      =>  $correctEqn,
 1638       'var'       =>  $var_ref,
 1639       'limits'      =>  $limit_ref,
 1640       'tolerance'     =>  $relPercentTol,
 1641       'tolType'     =>  'relative',
 1642       'numPoints'     =>  $numPoints,
 1643       'mode'        =>  'std',
 1644       'maxConstantOfIntegration'  =>  0,
 1645       'zeroLevel'     =>  $zeroLevel,
 1646       'zeroLevelTol'      =>  $zeroLevelTol
 1647   );
 1648 }
 1649 
 1650 ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION
 1651 ## NOTE: PG_answer_eval is used instead of PG_restricted_eval in order to insure that the answer
 1652 ## evaluated within the context of the package the problem was originally defined in.
 1653 ## Includes multivariable modifications contributed by Professor William K. Ziemer
 1654 ##
 1655 ## IN:  a hash consisting of the following keys (error checking to be added later?)
 1656 ##      correctEqn      --  the correct equation as a string
 1657 ##      var       --  the variable name as a string,
 1658 ##                or a reference to an array of variables
 1659 ##      limits        --  reference to an array of arrays of type [lower,upper]
 1660 ##      tolerance     --  the allowable margin of error
 1661 ##      tolType       --  'relative' or 'absolute'
 1662 ##      numPoints     --  the number of points to evaluate the function at
 1663 ##      mode        --  'std' or 'antider'
 1664 ##      maxConstantOfIntegration  --  maximum size of the constant of integration
 1665 ##      zeroLevel     --  if the correct answer is this close to zero,
 1666 ##                        then zeroLevelTol applies
 1667 ##      zeroLevelTol      --  absolute tolerance to allow when answer is close to zero
 1668 
 1669 
 1670 sub FUNCTION_CMP {
 1671   my %func_params = @_;
 1672 
 1673   my $correctEqn          = $func_params{'correctEqn'};
 1674   my $var           = $func_params{'var'};
 1675   my $ra_limits         = $func_params{'limits'};
 1676   my $tol           = $func_params{'tolerance'};
 1677   my $tolType         = $func_params{'tolType'};
 1678   my $numPoints         = $func_params{'numPoints'};
 1679   my $mode          = $func_params{'mode'};
 1680   my $maxConstantOfIntegration      = $func_params{'maxConstantOfIntegration'};
 1681   my $zeroLevel         = $func_params{'zeroLevel'};
 1682   my $zeroLevelTol        = $func_params{'zeroLevelTol'};
 1683 
 1684 
 1685     # Check that everything is defined:
 1686     $func_params{debug} = 0 unless defined($func_params{debug});
 1687     $mode = 'std' unless defined($mode);
 1688     my @VARS = get_var_array( $var );
 1689   my @limits = get_limits_array( $ra_limits );
 1690   my @PARAMS = ();
 1691   @PARAMS = @{$func_params{'params'}} if defined($func_params{'params'});
 1692 
 1693   if ($mode eq 'antider' ) {
 1694     # doctor the equation to allow addition of a constant
 1695     my $CONSTANT_PARAM = 'Q';  # unfortunately parameters must be single letters.
 1696                    # There is the possibility of conflict here.
 1697                    #  'Q' seemed less dangerous than  'C'.
 1698     $correctEqn = "( $correctEqn ) + $CONSTANT_PARAM";
 1699     push(@PARAMS, $CONSTANT_PARAM);
 1700   }
 1701     my $dim_of_param_space = @PARAMS;      # dimension of equivalence space
 1702 
 1703   if( $tolType eq 'relative' ) {
 1704     $tol = $functRelPercentTolDefault     unless defined $tol;
 1705     $tol *= .01;
 1706   }
 1707   else {
 1708     $tol = $functAbsTolDefault        unless defined $tol;
 1709   }
 1710 
 1711   #loop ensures that number of limits matches number of variables
 1712   for( my $i = 0; $i < scalar(@VARS); $i++ ) {
 1713     $limits[$i][0] = $functLLimitDefault      unless defined $limits[$i][0];
 1714     $limits[$i][1] = $functULimitDefault      unless defined $limits[$i][1];
 1715   }
 1716   $numPoints = $functNumOfPoints          unless defined $numPoints;
 1717   $maxConstantOfIntegration = $functMaxConstantOfIntegration  unless defined $maxConstantOfIntegration;
 1718   $zeroLevel = $functZeroLevelDefault       unless defined $zeroLevel;
 1719   $zeroLevelTol = $functZeroLevelTolDefault     unless defined $zeroLevelTol;
 1720 
 1721   $func_params{'var'}       = $var;
 1722       $func_params{'limits'}        = \@limits;
 1723       $func_params{'tolerance'}     = $tol;
 1724       $func_params{'tolType'}       = $tolType;
 1725       $func_params{'numPoints'}     = $numPoints;
 1726       $func_params{'mode'}        = $mode;
 1727       $func_params{'maxConstantOfIntegration'}  = $maxConstantOfIntegration;
 1728       $func_params{'zeroLevel'}     = $zeroLevel;
 1729       $func_params{'zeroLevelTol'}        =   $zeroLevelTol;
 1730 
 1731 ########################################################
 1732 #   End of cleanup of calling parameters
 1733 ########################################################
 1734   my $i;            #for use with loops
 1735   my $PGanswerMessage = "";
 1736   my $originalCorrEqn = $correctEqn;
 1737 
 1738 #prepare the correct answer and check it's syntax
 1739       my $rh_correct_ans = new AnswerHash;
 1740   $rh_correct_ans->input($correctEqn);
 1741   $rh_correct_ans = check_syntax($rh_correct_ans);
 1742   warn  $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
 1743   $rh_correct_ans->clear_error();
 1744   $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => [ @VARS, @PARAMS ],
 1745                                                            store_in =>'rf_correct_ans',
 1746                                                            debug =>  $func_params{debug});
 1747   my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans};
 1748   warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
 1749 
 1750 #create the evaluation points
 1751   my $random_for_answers = new PGrandom($main::PG_original_problemSeed);
 1752       my $NUMBER_OF_STEPS_IN_RANDOM = 1000;    # determines the granularity of the random_for_answers number generator
 1753   my (@evaluation_points);
 1754   for( my $count = 0; $count < @PARAMS+1+$numPoints; $count++ ) {
 1755       my (@vars,$iteration_limit);
 1756     for( my $i = 0; $i < @VARS; $i++ ) {
 1757       my $iteration_limit = 10;
 1758       while (  0 < --$iteration_limit ) {  # make sure that the endpoints of the interval are not included
 1759           $vars[$i] = $random_for_answers->random($limits[$i][0], $limits[$i][1], abs($limits[$i][1] - $limits[$i][0])/$NUMBER_OF_STEPS_IN_RANDOM );
 1760           last if $vars[$i]!=$limits[$i][0] and $vars[$i]!=$limits[$i][1];
 1761         }
 1762         warn "Unable to properly choose  evaluation points for this function in the interval ( $limits[$i][0] , $limits[$i][1] )"
 1763           if $iteration_limit == 0;
 1764     };
 1765 
 1766     push(@evaluation_points,\@vars);
 1767   }
 1768   my $evaluation_points = Matrix->new_from_array_ref(\@evaluation_points);
 1769 
 1770   #my $COEFFS = determine_param_coeffs($correct_eqn_sub,$evaluation_points[0],$numOfParameters);
 1771       #warn "coeff", join(" | ", @{$COEFFS});
 1772 
 1773 #construct the answer evaluator
 1774     my $answer_evaluator = new AnswerEvaluator;
 1775     $answer_evaluator->{debug} = $func_params{debug};
 1776     $answer_evaluator->ans_hash(  correct_ans     =>  $originalCorrEqn,
 1777           rf_correct_ans    =>  $rh_correct_ans->{rf_correct_ans},
 1778           evaluation_points =>  \@evaluation_points,
 1779           ra_param_vars     =>  \@PARAMS,
 1780           ra_vars     =>  \@VARS,
 1781           type      =>  'function',
 1782     );
 1783 
 1784     $answer_evaluator->install_pre_filter(\&check_syntax);
 1785     $answer_evaluator->install_pre_filter(\&function_from_string2, ra_vars => \@VARS,debug=>$func_params{debug},); # @VARS has been guaranteed to be an array, $var might be a single string.
 1786     $answer_evaluator->install_pre_filter(\&best_approx_parameters, %func_params, param_vars => \@PARAMS);
 1787     $answer_evaluator->install_evaluator(\&calculate_difference_vector, %func_params);
 1788     $answer_evaluator->install_evaluator(\&is_zero_array, tolerance => $tol );
 1789     $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); $rh_ans;} );
 1790     $answer_evaluator->install_post_filter(
 1791             sub {my $rh_ans = shift;
 1792             if ($rh_ans->catch_error('EVAL') ) {
 1793               $rh_ans->{ans_message} = $rh_ans->{error_message};
 1794               $rh_ans->clear_error('EVAL');
 1795             }
 1796             $rh_ans;
 1797           }
 1798   );
 1799     $answer_evaluator;
 1800 }
 1801 
 1802 
 1803 ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION
 1804 ##
 1805 ## IN:  a hash containing the following items (error-checking to be added later?):
 1806 ##      correctAnswer --  the correct answer
 1807 ##      tolerance   --  the allowable margin of error
 1808 ##      tolType     --  'relative' or 'absolute'
 1809 ##      format      --  the display format of the answer
 1810 ##      mode      --  one of 'std', 'strict', 'arith', or 'frac';
 1811 ##                  determines allowable formats for the input
 1812 ##      zeroLevel   --  if the correct answer is this close to zero, then zeroLevelTol applies
 1813 ##      zeroLevelTol  --  absolute tolerance to allow when answer is close to zero
 1814 
 1815 
 1816 ##########################################################################
 1817 ##########################################################################
 1818 ## String answer evaluators
 1819 
 1820 =head2 String Answer Evaluators
 1821 
 1822 String answer evaluators compare a student string to the correct string.
 1823 Different filters can be applied to allow various degrees of variation.
 1824 Both the student and correct answers are subject to the same filters, to
 1825 ensure that there are no unexpected matches or rejections.
 1826 
 1827 String Filters
 1828 
 1829   remove_whitespace --  Removes all whitespace from the string.
 1830             It applies the following substitution
 1831             to the string:
 1832               $filteredAnswer =~ s/\s+//g;
 1833 
 1834   compress_whitespace --  Removes leading and trailing whitespace, and
 1835             replaces all other blocks of whitespace by a
 1836             single space. Applies the following substitutions:
 1837               $filteredAnswer =~ s/^\s*//;
 1838               $filteredAnswer =~ s/\s*$//;
 1839               $filteredAnswer =~ s/\s+/ /g;
 1840 
 1841   trim_whitespace   --  Removes leading and trailing whitespace.
 1842             Applies the following substitutions:
 1843               $filteredAnswer =~ s/^\s*//;
 1844               $filteredAnswer =~ s/\s*$//;
 1845 
 1846   ignore_case     --  Ignores the case of the string. More accurately,
 1847             it converts the string to uppercase (by convention).
 1848             Applies the following function:
 1849               $filteredAnswer = uc $filteredAnswer;
 1850 
 1851   ignore_order    --  Ignores the order of the letters in the string.
 1852             This is used for problems of the form "Choose all
 1853             that apply." Specifically, it removes all
 1854             whitespace and lexically sorts the letters in
 1855             ascending alphabetical order. Applies the following
 1856             functions:
 1857               $filteredAnswer = join( "", lex_sort(
 1858                 split( /\s*/, $filteredAnswer ) ) );
 1859 
 1860 =cut
 1861 
 1862 ################################
 1863 ## STRING ANSWER FILTERS
 1864 
 1865 ## IN:  --the string to be filtered
 1866 ##    --a list of the filters to use
 1867 ##
 1868 ## OUT: --the modified string
 1869 ##
 1870 ## Use this subroutine instead of the
 1871 ## individual filters below it
 1872 
 1873 sub str_filters {
 1874   my $stringToFilter = shift @_;
 1875   my @filters_to_use = @_;
 1876   my %known_filters = ( 'remove_whitespace'   =>  undef,
 1877         'compress_whitespace'   =>  undef,
 1878         'trim_whitespace'   =>  undef,
 1879         'ignore_case'     =>  undef,
 1880         'ignore_order'      =>  undef
 1881   );
 1882 
 1883   #test for unknown filters
 1884   my $filter;
 1885   foreach $filter (@filters_to_use) {
 1886     die "Unknown string filter $filter (try checking the parameters to str_cmp() )"
 1887                 unless exists $known_filters{$filter};
 1888   }
 1889 
 1890   if( grep( /remove_whitespace/i, @filters_to_use ) ) {
 1891     $stringToFilter = remove_whitespace( $stringToFilter );
 1892   }
 1893   if( grep( /compress_whitespace/i, @filters_to_use ) ) {
 1894     $stringToFilter = compress_whitespace( $stringToFilter );
 1895   }
 1896   if( grep( /trim_whitespace/i, @filters_to_use ) ) {
 1897     $stringToFilter = trim_whitespace( $stringToFilter );
 1898   }
 1899   if( grep( /ignore_case/i, @filters_to_use ) ) {
 1900     $stringToFilter = ignore_case( $stringToFilter );
 1901   }
 1902   if( grep( /ignore_order/i, @filters_to_use ) ) {
 1903     $stringToFilter = ignore_order( $stringToFilter );
 1904   }
 1905 
 1906   return $stringToFilter;
 1907 }
 1908 
 1909 sub remove_whitespace {
 1910   my $filteredAnswer = shift;
 1911 
 1912   $filteredAnswer =~ s/\s+//g;    # remove all whitespace
 1913 
 1914   return $filteredAnswer;
 1915 }
 1916 
 1917 sub compress_whitespace {
 1918   my $filteredAnswer = shift;
 1919 
 1920   $filteredAnswer =~ s/^\s*//;    # remove initial whitespace
 1921   $filteredAnswer =~ s/\s*$//;    # remove trailing whitespace
 1922   $filteredAnswer =~ s/\s+/ /g;   # replace spaces by single space
 1923 
 1924   return $filteredAnswer;
 1925 }
 1926 
 1927 sub trim_whitespace {
 1928   my $filteredAnswer = shift;
 1929 
 1930   $filteredAnswer =~ s/^\s*//;    # remove initial whitespace
 1931   $filteredAnswer =~ s/\s*$//;    # remove trailing whitespace
 1932 
 1933   return $filteredAnswer;
 1934 }
 1935 
 1936 sub ignore_case {
 1937   my $filteredAnswer = shift;
 1938 
 1939   $filteredAnswer = uc $filteredAnswer;
 1940 
 1941   return $filteredAnswer;
 1942 }
 1943 
 1944 sub ignore_order {
 1945   my $filteredAnswer = shift;
 1946 
 1947   $filteredAnswer = join( "", lex_sort( split( /\s*/, $filteredAnswer ) ) );
 1948 
 1949   return $filteredAnswer;
 1950 }
 1951 ################################
 1952 ## END STRING ANSWER FILTERS
 1953 
 1954 
 1955 =head3 str_cmp()
 1956 
 1957 Compares a string or a list of strings, using a named hash of options to set
 1958 parameters. This can make for more readable code than using the "mode"_str_cmp()
 1959 style, but some people find one or the other easier to remember.
 1960 
 1961 ANS( str_cmp( answer or answer_array_ref, options_hash ) );
 1962 
 1963   1. the correct answer or a reference to an array of answers
 1964   2. either a list of filters, or:
 1965      a hash consisting of
 1966     filters - a reference to an array of filters
 1967 
 1968   Returns an answer evaluator, or (if given a reference to an array of answers),
 1969   a list of answer evaluators
 1970 
 1971 FILTERS:
 1972 
 1973   remove_whitespace --  removes all whitespace
 1974   compress_whitespace --  removes whitespace from the beginning and end of the string,
 1975               and treats one or more whitespace characters in a row as a
 1976               single space (true by default)
 1977   trim_whitespace   --  removes whitespace from the beginning and end of the string
 1978   ignore_case   --  ignores the case of the letters (true by default)
 1979   ignore_order    --  ignores the order in which letters are entered
 1980 
 1981 EXAMPLES:
 1982 
 1983   str_cmp( "Hello" )  --  matches "Hello", "  hello" (same as std_str_cmp() )
 1984   str_cmp( ["Hello", "Goodbye"] ) --  same as std_str_cmp_list()
 1985   str_cmp( " hello ", trim_whitespace ) --  matches "hello", " hello  "
 1986   str_cmp( "ABC", filters => 'ignore_order' ) --  matches "ACB", "A B C", but not "abc"
 1987   str_cmp( "D E F", remove_whitespace, ignore_case )  --  matches "def" and "d e f" but not "fed"
 1988 
 1989 
 1990 =cut
 1991 
 1992 sub str_cmp {
 1993   my $correctAnswer = shift @_;
 1994   $correctAnswer = '' unless defined($correctAnswer);
 1995   my @options = @_;
 1996   my $ra_filters;
 1997 
 1998   # error-checking for filters occurs in the filters() subroutine
 1999   if( not defined( $options[0] ) ) {    # used with no filters as alias for std_str_cmp()
 2000     @options = ( 'compress_whitespace', 'ignore_case' );
 2001   }
 2002 
 2003   if( $options[0] eq 'filters' ) {    # using filters => [f1, f2, ...] notation
 2004     $ra_filters = $options[1];
 2005   }
 2006   else {            # using a list of filters
 2007     $ra_filters = \@options;
 2008   }
 2009 
 2010   # thread over lists
 2011   my @ans_list = ();
 2012 
 2013   if ( ref($correctAnswer) eq 'ARRAY' ) {
 2014     @ans_list = @{$correctAnswer};
 2015   }
 2016   else {
 2017     push( @ans_list, $correctAnswer );
 2018   }
 2019 
 2020   # final_answer;
 2021   my @output_list = ();
 2022 
 2023   foreach my $ans (@ans_list) {
 2024     push(@output_list, STR_CMP( 'correctAnswer' =>  $ans,
 2025             'filters' =>  $ra_filters,
 2026             'type'    =>  'str_cmp'
 2027          )
 2028     );
 2029   }
 2030 
 2031   return (wantarray) ? @output_list : $output_list[0] ;
 2032 }
 2033 
 2034 =head3 "mode"_str_cmp functions
 2035 
 2036 The functions of the the form "mode"_str_cmp() use different functions to
 2037 specify which filters to apply. They take no options except the correct
 2038 string. There are also versions which accept a list of strings.
 2039 
 2040  std_str_cmp( $correctString )
 2041  std_str_cmp_list( @correctStringList )
 2042   Filters: compress_whitespace, ignore_case
 2043 
 2044  std_cs_str_cmp( $correctString )
 2045  std_cs_str_cmp_list( @correctStringList )
 2046   Filters: compress_whitespace
 2047 
 2048  strict_str_cmp( $correctString )
 2049  strict_str_cmp_list( @correctStringList )
 2050   Filters: trim_whitespace
 2051 
 2052  unordered_str_cmp( $correctString )
 2053  unordered_str_cmp_list( @correctStringList )
 2054   Filters: ignore_order, ignore_case
 2055 
 2056  unordered_cs_str_cmp( $correctString )
 2057  unordered_cs_str_cmp_list( @correctStringList )
 2058   Filters: ignore_order
 2059 
 2060  ordered_str_cmp( $correctString )
 2061  ordered_str_cmp_list( @correctStringList )
 2062   Filters: remove_whitespace, ignore_case
 2063 
 2064  ordered_cs_str_cmp( $correctString )
 2065  ordered_cs_str_cmp_list( @correctStringList )
 2066   Filters: remove_whitespace
 2067 
 2068 Examples
 2069 
 2070   ANS( std_str_cmp( "W. Mozart" ) ) --  Accepts "W. Mozart", "W. MOZarT",
 2071     and so forth. Case insensitive. All internal spaces treated
 2072     as single spaces.
 2073   ANS( std_cs_str_cmp( "Mozart" ) ) --  Rejects "mozart". Same as
 2074     std_str_cmp() but case sensitive.
 2075   ANS( strict_str_cmp( "W. Mozart" ) )  --  Accepts only the exact string.
 2076   ANS( unordered_str_cmp( "ABC" ) ) --  Accepts "a c B", "CBA" and so forth.
 2077     Unordered, case insensitive, spaces ignored.
 2078   ANS( unordered_cs_str_cmp( "ABC" ) )  --  Rejects "abc". Same as
 2079     unordered_str_cmp() but case sensitive.
 2080   ANS( ordered_str_cmp( "ABC" ) ) --  Accepts "a b C", "A B C" and so forth.
 2081     Ordered, case insensitive, spaces ignored.
 2082   ANS( ordered_cs_str_cmp( "ABC" ) )  --  Rejects "abc", accepts "A BC" and
 2083     so forth. Same as ordered_str_cmp() but case sensitive.
 2084 
 2085 =cut
 2086 
 2087 sub std_str_cmp {         # compare strings
 2088   my $correctAnswer = shift @_;
 2089   my @filters = ( 'compress_whitespace', 'ignore_case' );
 2090   my $type = 'std_str_cmp';
 2091   STR_CMP(  'correctAnswer' =>  $correctAnswer,
 2092       'filters' =>  \@filters,
 2093       'type'    =>  $type
 2094   );
 2095 }
 2096 
 2097 sub std_str_cmp_list {        # alias for std_str_cmp
 2098   my @answerList = @_;
 2099   my @output;
 2100   while (@answerList) {
 2101     push( @output, std_str_cmp(shift @answerList) );
 2102   }
 2103   @output;
 2104 }
 2105 
 2106 sub std_cs_str_cmp {        # compare strings case sensitive
 2107   my $correctAnswer = shift @_;
 2108   my @filters = ( 'compress_whitespace' );
 2109   my $type = 'std_cs_str_cmp';
 2110   STR_CMP(  'correctAnswer' =>  $correctAnswer,
 2111       'filters' =>  \@filters,
 2112       'type'    =>  $type
 2113   );
 2114 }
 2115 
 2116 sub std_cs_str_cmp_list {     # alias for std_cs_str_cmp
 2117   my @answerList = @_;
 2118   my @output;
 2119   while (@answerList) {
 2120     push( @output, std_cs_str_cmp(shift @answerList) );
 2121   }
 2122   @output;
 2123 }
 2124 
 2125 sub strict_str_cmp {        # strict string compare
 2126   my $correctAnswer = shift @_;
 2127   my @filters = ( 'trim_whitespace' );
 2128   my $type = 'strict_str_cmp';
 2129   STR_CMP(  'correctAnswer' =>  $correctAnswer,
 2130       'filters' =>  \@filters,
 2131       'type'    =>  $type
 2132   );
 2133 }
 2134 
 2135 sub strict_str_cmp_list {     # alias for strict_str_cmp
 2136   my @answerList = @_;
 2137   my @output;
 2138   while (@answerList) {
 2139     push( @output, strict_str_cmp(shift @answerList) );
 2140   }
 2141   @output;
 2142 }
 2143 
 2144 sub unordered_str_cmp {       # unordered, case insensitive, spaces ignored
 2145   my $correctAnswer = shift @_;
 2146   my @filters = ( 'ignore_order', 'ignore_case' );
 2147   my $type = 'unordered_str_cmp';
 2148   STR_CMP(  'correctAnswer'   =>  $correctAnswer,
 2149       'filters'   =>  \@filters,
 2150       'type'      =>  $type
 2151   );
 2152 }
 2153 
 2154 sub unordered_str_cmp_list {    # alias for unordered_str_cmp
 2155   my @answerList = @_;
 2156   my @output;
 2157   while (@answerList) {
 2158     push( @output, unordered_str_cmp(shift @answerList) );
 2159   }
 2160   @output;
 2161 }
 2162 
 2163 sub unordered_cs_str_cmp {      # unordered, case sensitive, spaces ignored
 2164   my $correctAnswer = shift @_;
 2165   my @filters = ( 'ignore_order' );
 2166   my $type = 'unordered_cs_str_cmp';
 2167   STR_CMP(  'correctAnswer'   =>  $correctAnswer,
 2168       'filters'   =>  \@filters,
 2169       'type'      =>  $type
 2170   );
 2171 }
 2172 
 2173 sub unordered_cs_str_cmp_list {   # alias for unordered_cs_str_cmp
 2174   my @answerList = @_;
 2175   my @output;
 2176   while (@answerList) {
 2177     push( @output, unordered_cs_str_cmp(shift @answerList) );
 2178   }
 2179   @output;
 2180 }
 2181 
 2182 sub ordered_str_cmp {       # ordered, case insensitive, spaces ignored
 2183   my $correctAnswer = shift @_;
 2184   my @filters = ( 'remove_whitespace', 'ignore_case' );
 2185   my $type = 'ordered_str_cmp';
 2186   STR_CMP(  'correctAnswer' =>  $correctAnswer,
 2187       'filters' =>  \@filters,
 2188       'type'    =>  $type
 2189   );
 2190 }
 2191 
 2192 sub ordered_str_cmp_list {      # alias for ordered_str_cmp
 2193   my @answerList = @_;
 2194   my @output;
 2195   while (@answerList) {
 2196     push( @output, ordered_str_cmp(shift @answerList) );
 2197   }
 2198   @output;
 2199 }
 2200 
 2201 sub ordered_cs_str_cmp {      # ordered,  case sensitive, spaces ignored
 2202   my $correctAnswer = shift @_;
 2203   my @filters = ( 'remove_whitespace' );
 2204   my $type = 'ordered_cs_str_cmp';
 2205   STR_CMP(  'correctAnswer' =>  $correctAnswer,
 2206       'filters' =>  \@filters,
 2207       'type'    =>  $type
 2208   );
 2209 }
 2210 
 2211 sub ordered_cs_str_cmp_list {   # alias for ordered_cs_str_cmp
 2212   my @answerList = @_;
 2213   my @output;
 2214   while (@answerList) {
 2215     push( @output, ordered_cs_str_cmp(shift @answerList) );
 2216   }
 2217   @output;
 2218 }
 2219 
 2220 
 2221 ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION
 2222 ##
 2223 ## IN:  a hashtable with the following entries (error-checking to be added later?):
 2224 ##      correctAnswer --  the correct answer, before filtering
 2225 ##      filters     --  reference to an array containing the filters to be applied
 2226 ##      type      --  a string containing the type of answer evaluator in use
 2227 ## OUT: a reference to an answer evaluator subroutine
 2228 
 2229 sub STR_CMP {
 2230   my %str_params = @_;
 2231   $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} );
 2232   my $answer_evaluator = sub {
 2233     my $in = shift @_;
 2234     $in = '' unless defined $in;
 2235     my $original_student_ans = $in;
 2236     $in = str_filters( $in, @{$str_params{'filters'}} );
 2237     my $correctQ = ( $in eq $str_params{'correctAnswer'} ) ? 1: 0;
 2238     my $ans_hash = new AnswerHash(    'score'       =>  $correctQ,
 2239               'correct_ans'     =>  $str_params{'correctAnswer'},
 2240               'student_ans'     =>  $in,
 2241               'ans_message'     =>  '',
 2242               'type'        =>  $str_params{'type'},
 2243               'preview_text_string'   =>  $in,
 2244               'preview_latex_string'    =>  $in,
 2245               'original_student_ans'    =>  $original_student_ans
 2246     );
 2247     return $ans_hash;
 2248   };
 2249   return $answer_evaluator;
 2250 }
 2251 
 2252 ##########################################################################
 2253 ##########################################################################
 2254 ## Miscellaneous answer evaluators
 2255 
 2256 =head2 Miscellaneous Answer Evaluators (Checkboxes and Radio Buttons)
 2257 
 2258 These evaluators do not fit any of the other categories.
 2259 
 2260 checkbox_cmp( $correctAnswer )
 2261 
 2262   $correctAnswer  --  a string containing the names of the correct boxes,
 2263             e.g. "ACD". Note that this means that individual
 2264             checkbox names can only be one character. Internally,
 2265             this is largely the same as unordered_cs_str_cmp().
 2266 
 2267 radio_cmp( $correctAnswer )
 2268 
 2269   $correctAnswer  --  a string containing the name of the correct radio
 2270             button, e.g. "Choice1". This is case sensitive and
 2271             whitespace sensitive, so the correct answer must match
 2272             the name of the radio button exactly.
 2273 
 2274 =cut
 2275 
 2276 # added 6/14/2000 by David Etlinger
 2277 # because of the conversion of the answer
 2278 # string to an array, I thought it better not
 2279 # to force STR_CMP() to work with this
 2280 sub checkbox_cmp {
 2281   my  $correctAnswer = shift @_;
 2282   $correctAnswer = str_filters( $correctAnswer, 'ignore_order' );
 2283 
 2284   my  $answer_evaluator = sub {
 2285     my $in = shift @_;
 2286     $in = '' unless defined $in;      #in case no boxes checked
 2287 
 2288     my @temp = split( "\0", $in );      #convert "\0"-delimited string to array...
 2289     $in = join( "", @temp );        #and then to a single no-delimiter string
 2290 
 2291     my $original_student_ans = $in;     #well, almost original
 2292     $in = str_filters( $in, 'ignore_order' );
 2293 
 2294     my $correctQ = ($in eq $correctAnswer) ? 1: 0;
 2295 
 2296     my $ans_hash = new AnswerHash(
 2297               'score'     =>  $correctQ,
 2298               'correct_ans'   =>  $correctAnswer,
 2299               'student_ans'   =>  $in,
 2300               'ans_message'   =>  "",
 2301               'type'      =>  "checkbox_cmp",
 2302               'preview_text_string' =>  $in,
 2303               'original_student_ans'  =>  $original_student_ans
 2304     );
 2305     return $ans_hash;
 2306 
 2307   };
 2308   return $answer_evaluator;
 2309 }
 2310 
 2311 #added 6/28/2000 by David Etlinger
 2312 #exactly the same as strict_str_cmp,
 2313 #but more intuitive to the user
 2314 sub radio_cmp {
 2315   strict_str_cmp( @_ );
 2316 }
 2317 
 2318 ##########################################################################
 2319 ##########################################################################
 2320 ## Text and e-mail routines
 2321 
 2322 sub store_ans_at {
 2323   my $answerStringRef = shift;
 2324   my %options = @_;
 2325   my $ans_eval= '';
 2326   if ( ref($answerStringRef) eq 'SCALAR' ) {
 2327     $ans_eval= sub {
 2328       my $text = shift;
 2329       $text = '' unless defined($text);
 2330       $$answerStringRef = $$answerStringRef  . $text;
 2331       my $ans_hash = new AnswerHash(
 2332                'score'      =>  1,
 2333                'correct_ans'      =>  '',
 2334                'student_ans'      =>  $text,
 2335                'ans_message'      =>  '',
 2336                'type'       =>  'store_ans_at',
 2337                'original_student_ans'   =>  $text,
 2338                'preview_text_string'    =>  ''
 2339       );
 2340 
 2341     return $ans_hash;
 2342     };
 2343   }
 2344   else {
 2345     die "Syntax error: \n The argument to store_ans_at() must be a pointer to a scalar.\n(e.g.  store_ans_at(~~\$MSG) )\n\n";
 2346   }
 2347 
 2348   return $ans_eval;
 2349 }
 2350 
 2351 #### subroutines used in producing a questionnaire
 2352 #### these are at least good models for other answers of this type
 2353 
 2354 my $QUESTIONNAIRE_ANSWERS=''; #  stores the answers until it is time to send them
 2355        #  this must be initialized before the answer evaluators are run
 2356        #  but that happens long after all of the text in the problem is
 2357        #  evaluated.
 2358 # this is a utility script for cleaning up the answer output for display in
 2359 #the answers.
 2360 
 2361 sub DUMMY_ANSWER {
 2362   my $num = shift;
 2363   qq{<INPUT TYPE="HIDDEN" NAME="answer$num" VALUE="">}
 2364 }
 2365 
 2366 sub escapeHTML {
 2367   my $string = shift;
 2368   $string =~ s/\n/$BR/ge;
 2369   $string;
 2370 }
 2371 
 2372 # these next three subroutines show how to modify the "store_ans_at()" answer
 2373 # evaluator to add extra information before storing the info
 2374 # They provide a good model for how to tweak answer evaluators in special cases.
 2375 
 2376 sub anstext {
 2377   my $num = shift;
 2378   my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS);
 2379   my $ans_eval = sub {
 2380          my $text = shift;
 2381          $text = '' unless defined($text);
 2382          my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-Question-$num:\n $text "; # modify entered text
 2383          my $out = &$ans_eval_template($new_text);       # standard evaluator
 2384          #warn "$QUESTIONNAIRE_ANSWERS";
 2385          $out->{student_ans} = escapeHTML($text);  #  restore original entered text
 2386          $out->{correct_ans} = "Question  $num answered";
 2387          $out->{original_student_ans} = escapeHTML($text);
 2388          $out;
 2389     };
 2390    $ans_eval;
 2391 }
 2392 
 2393 sub ansradio {
 2394   my $num = shift;
 2395   my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS);
 2396   my $ans_eval = sub {
 2397          my $text = shift;
 2398          $text = '' unless defined($text);
 2399          my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-RADIO-$num:\n $text ";       # modify entered text
 2400          my $out = $ans_eval_template->($new_text);       # standard evaluator
 2401          $out->{student_ans} =escapeHTML($text);  # restore original entered text
 2402          $out->{original_student_ans} = escapeHTML($text);
 2403          $out;
 2404    };
 2405 
 2406    $ans_eval;
 2407 }
 2408 
 2409 sub anstext_non_anonymous {
 2410   ## this emails identifying information
 2411   my $num = shift;
 2412   my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS);
 2413   my $ans_eval = sub {
 2414          my $text = shift;
 2415          $text = '' unless defined($text);
 2416          my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-Question-$num:\n$main::studentLogin $main::studentID $main::studentName\n$text "; # modify entered text
 2417          my $out = &$ans_eval_template($new_text);       # standard evaluator
 2418          #warn "$QUESTIONNAIRE_ANSWERS";
 2419          $out->{student_ans} = escapeHTML($text);  #  restore original entered text
 2420          $out->{correct_ans} = "Question  $num answered";
 2421          $out->{original_student_ans} = escapeHTML($text);
 2422          $out;
 2423     };
 2424    $ans_eval;
 2425 }
 2426 
 2427 
 2428 #  This is another example of how to modify an  answer evaluator to obtain
 2429 #  the desired behavior in a special case.  Here the object is to have
 2430 #  have the last answer trigger the send_mail_to subroutine which mails
 2431 #  all of the answers to the designated address.
 2432 #  (This address must be listed in PG_environment{'ALLOW_MAIL_TO'} or an error occurs.)
 2433 
 2434 sub mail_answers_to {  #accepts the last answer and mails off the result
 2435   my $user_address = shift;
 2436   my $ans_eval = sub {
 2437 
 2438     # then mail out all of the answers, including this last one.
 2439 
 2440     send_mail_to( $user_address,
 2441           'subject'   =>  "$main::courseName WeBWorK questionnaire",
 2442           'body'      =>  $QUESTIONNAIRE_ANSWERS,
 2443           'ALLOW_MAIL_TO'   =>  $main::ALLOW_MAIL_TO
 2444     );
 2445 
 2446     my $ans_hash = new AnswerHash(  'score'   =>  1,
 2447             'correct_ans' =>  '',
 2448             'student_ans' =>  'Answer recorded',
 2449             'ans_message' =>  '',
 2450             'type'    =>  'send_mail_to',
 2451     );
 2452 
 2453     return $ans_hash;
 2454   };
 2455 
 2456   return $ans_eval;
 2457 }
 2458 sub mail_answers_to2 {  #accepts the last answer and mails off the result
 2459   my $user_address = shift;
 2460   my $subject = shift;
 2461   $subject = "$main::courseName WeBWorK questionnaire" unless defined $subject;
 2462 
 2463   send_mail_to($user_address,
 2464       'subject'     => $subject,
 2465       'body'        => $QUESTIONNAIRE_ANSWERS,
 2466       'ALLOW_MAIL_TO'   => $main::ALLOW_MAIL_TO
 2467   );
 2468 }
 2469 
 2470 ##########################################################################
 2471 ##########################################################################
 2472 
 2473 
 2474 ###########################################################################
 2475 ### THE FOLLOWING ARE LOCAL SUBROUTINES THAT ARE MEANT TO BE CALLED ONLY FROM THIS SCRIPT.
 2476 
 2477 ## Internal routine that converts variables into the standard array format
 2478 ##
 2479 ## IN:  one of the following:
 2480 ##      an undefined value (i.e., no variable was specified)
 2481 ##      a reference to an array of variable names -- [var1, var2]
 2482 ##      a number (the number of variables desired) -- 3
 2483 ##      one or more variable names -- (var1, var2)
 2484 ## OUT: an array of variable names
 2485 
 2486 sub get_var_array {
 2487   my $in = shift @_;
 2488   my @out;
 2489 
 2490   if( not defined($in) ) {      #if nothing defined, build default array and return
 2491     @out = ( $functVarDefault );
 2492     return @out;
 2493   }
 2494   elsif( ref( $in ) eq 'ARRAY' ) {  #if given an array ref, dereference and return
 2495     return @{$in};
 2496   }
 2497   elsif( $in =~ /^\d+/ ) {      #if given a number, set up the array and return
 2498     if( $in == 1 ) {
 2499       $out[0] = 'x';
 2500     }
 2501     elsif( $in == 2 ) {
 2502       $out[0] = 'x';
 2503       $out[1] = 'y';
 2504     }
 2505     elsif( $in == 3 ) {
 2506       $out[0] = 'x';
 2507       $out[1] = 'y';
 2508       $out[2] = 'z';
 2509     }
 2510     else {  #default to the x_1, x_2, ... convention
 2511       my ($i, $tag);
 2512       for( $i=0; $i < $in; $i++ ) {
 2513               ## akp the above seems to be off by one 1/4/00
 2514         $tag = $i + 1;                            ## akp 1/4/00
 2515         $out[$i] = "${functVarDefault}_" . $tag;              ## akp 1/4/00
 2516       }
 2517     }
 2518     return @out;
 2519   }
 2520   else {            #if given one or more names, return as an array
 2521     unshift( @_, $in );
 2522     return @_;
 2523   }
 2524 }
 2525 
 2526 ## Internal routine that converts limits into the standard array of arrays format
 2527 ##  Some of the cases are probably unneccessary, but better safe than sorry
 2528 ##
 2529 ## IN:  one of the following:
 2530 ##      an undefined value (i.e., no limits were specified)
 2531 ##      a reference to an array of arrays of limits -- [[llim,ulim], [llim,ulim]]
 2532 ##      a reference to an array of limits -- [llim, ulim]
 2533 ##      an array of array references -- ([llim,ulim], [llim,ulim])
 2534 ##      an array of limits -- (llim,ulim)
 2535 ## OUT: an array of array references -- ([llim,ulim], [llim,ulim]) or ([llim,ulim])
 2536 
 2537 sub get_limits_array {
 2538   my $in = shift @_;
 2539   my @out;
 2540 
 2541   if( not defined($in) ) {        #if nothing defined, build default array and return
 2542     @out = ( [$functLLimitDefault, $functULimitDefault] );
 2543     return @out;
 2544   }
 2545   elsif( ref($in) eq 'ARRAY' ) {        #$in is either ref to array, or ref to array of refs
 2546     my @deref = @{$in};
 2547 
 2548     if( ref( $in->[0] ) eq 'ARRAY' ) {    #$in is a ref to an array of array refs
 2549       return @deref;
 2550     }
 2551     else {            #$in was just a ref to an array of numbers
 2552       @out = ( $in );
 2553       return @out;
 2554     }
 2555   }
 2556   else {              #$in was an array of references or numbers
 2557     unshift( @_, $in );
 2558 
 2559     if( ref($_[0]) eq 'ARRAY' ) {     #$in was an array of references, so just return it
 2560       return @_;
 2561     }
 2562     else {            #$in was an array of numbers
 2563       @out = ( \@_ );
 2564       return @out;
 2565     }
 2566   }
 2567 }
 2568 
 2569 #sub check_option_list {
 2570 # my $size = scalar(@_);
 2571 # if( ( $size % 2 ) != 0 ) {
 2572 #   warn "ERROR in answer evaluator generator:\n" .
 2573 #     "Usage: <CODE>str_cmp([\$ans1,  \$ans2],%options)</CODE>
 2574 #     or <CODE> num_cmp([\$num1, \$num2], %options)</CODE><BR>
 2575 #     A list of inputs must be inclosed in square brackets <CODE>[\$ans1, \$ans2]</CODE>";
 2576 # }
 2577 #}
 2578 
 2579 # simple subroutine to display an error message when
 2580 # function compares are called with invalid parameters
 2581 sub function_invalid_params {
 2582   my $correctEqn = shift @_;
 2583   my $error_response = sub {
 2584     my $PGanswerMessage = "Tell your professor that there is an error with the parameters " .
 2585             "to the function answer evaluator";
 2586     return ( 0, $correctEqn, "", $PGanswerMessage );
 2587   };
 2588   return $error_response;
 2589 }
 2590 
 2591 sub clean_up_error_msg {
 2592   my $msg = $_[0];
 2593   $msg =~ s/^\[[^\]]*\][^:]*://;
 2594   $msg =~ s/Unquoted string//g;
 2595   $msg =~ s/may\s+clash.*/does not make sense here/;
 2596   $msg =~ s/\sat.*line [\d]*//g;
 2597   $msg = 'error: '. $msg;
 2598 
 2599   return $msg;
 2600 }
 2601 
 2602 #formats the student and correct answer as specified
 2603 #format must be of a form suitable for sprintf (e.g. '%0.5g'),
 2604 #with the exception that a '#' at the end of the string
 2605 #will cause trailing zeros in the decimal part to be removed
 2606 sub prfmt {
 2607   my($number,$format) = @_;  # attention, the order of format and number are reversed
 2608   my $out;
 2609   if ($format) {
 2610     warn "Incorrect format used: $format. <BR> Format should look something like %4.5g<BR>"
 2611                 unless $format =~ /^\s*%\d*\.?\d*\w#?\s*$/;
 2612 
 2613     if( $format =~ s/#\s*$// ) {  # remove trailing zeros in the decimal
 2614       $out = sprintf( $format, $number );
 2615       $out =~ s/(\.\d*?)0+$/$1/;
 2616       $out =~ s/\.$//;      # in case all decimal digits were zero, remove the decimal
 2617       $out =~ s/e/E/g;        # only use capital E's for exponents. Little e is for 2.71828...
 2618     } elsif (is_a_number($number) ){
 2619       $out = sprintf( $format, $number );
 2620       $out =~ s/e/E/g;        # only use capital E's for exponents. Little e is for 2.71828...
 2621     } else { # number is probably a string representing an arithmetic expression
 2622       $out = $number;
 2623     }
 2624 
 2625   } else {
 2626     $out = $number;
 2627     $out =~ s/e/E/g;        # only use capital E's for exponents. Little e is for 2.71828...
 2628 
 2629   }
 2630   return $out;
 2631 }
 2632 #########################################################################
 2633 # Filters for answer evaluators
 2634 #########################################################################
 2635 
 2636 =head2 Filters
 2637 
 2638 =pod
 2639 
 2640 A filter is a short subroutine with the following structure.  It accepts an
 2641 AnswerHash, followed by a hash of options.  It returns an AnswerHash
 2642 
 2643   $ans_hash = filter($ans_hash, %options);
 2644 
 2645 See the AnswerHash.pm file for a list of entries which can be expected to be found
 2646 in an AnswerHash, such as 'student_ans', 'score' and so forth.  Other entries
 2647 may be present for specialized answer evaluators.
 2648 
 2649 The hope is that a well designed set of filters can easily be combined to form
 2650 a new answer_evaluator and that this method will produce answer evaluators which are
 2651 are more robust than the method of copying existing answer evaluators and modifying them.
 2652 
 2653 Here is an outline of how a filter is constructed:
 2654 
 2655   sub filter{
 2656     my $rh_ans = shift;
 2657     my %options = @_;
 2658     assign_option_aliases(\%options,
 2659         'alias1'  => 'option5'
 2660         'alias2'  => 'option7'
 2661     );
 2662     set_default_options(\%options,
 2663         '_filter_name'  =>  'filter',
 2664         'option5'   =>  .0001,
 2665         'option7'   =>  'ascii',
 2666         'allow_unknown_options  =>  0,
 2667     }
 2668     .... body code of filter .......
 2669       if ($error) {
 2670         $rh_ans->throw_error("FILTER_ERROR", "Something went wrong");
 2671         # see AnswerHash.pm for details on using the throw_error method.
 2672 
 2673     $rh_ans;  #reference to an AnswerHash object is returned.
 2674   }
 2675 
 2676 =cut
 2677 
 2678 =head4 compare_numbers
 2679 
 2680 
 2681 =cut
 2682 
 2683 
 2684 sub compare_numbers {
 2685   my ($rh_ans, %options) = @_;
 2686   my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans});
 2687   if ($PG_eval_errors) {
 2688     $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
 2689     $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
 2690     # return $rh_ans;
 2691   } else {
 2692     $rh_ans->{student_ans} = prfmt($inVal,$options{format});
 2693   }
 2694 
 2695   my $permitted_error;
 2696 
 2697   if ($rh_ans->{tolType} eq 'absolute') {
 2698     $permitted_error = $rh_ans->{tolerance};
 2699   }
 2700   elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) {
 2701       $permitted_error = $options{zeroLevelTol};  ## want $tol to be non zero
 2702   }
 2703   else {
 2704     $permitted_error = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans});
 2705   }
 2706 
 2707   my $is_a_number = is_a_number($inVal);
 2708   $rh_ans->{score} = 1 if ( ($is_a_number) and
 2709       (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) );
 2710   if (not $is_a_number) {
 2711     $rh_ans->{error_message} = "$rh_ans->{error_message}". 'Your answer does not evaluate to a number ';
 2712   }
 2713 
 2714   $rh_ans;
 2715 }
 2716 
 2717 =head4 std_num_filter
 2718 
 2719   std_num_filter($rh_ans, %options)
 2720   returns $rh_ans
 2721 
 2722 Replaces some constants using math_constants, then evaluates a perl expression.
 2723 
 2724 
 2725 =cut
 2726 
 2727 sub std_num_filter {
 2728   my $rh_ans = shift;
 2729   my %options = @_;
 2730   my $in = $rh_ans->input();
 2731   $in = math_constants($in);
 2732   $rh_ans->{type} = 'std_number';
 2733   my ($inVal,$PG_eval_errors,$PG_full_error_report);
 2734   if ($in =~ /\S/) {
 2735     ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in);
 2736   } else {
 2737     $PG_eval_errors = '';
 2738   }
 2739 
 2740   if ($PG_eval_errors) {        ##error message from eval or above
 2741     $rh_ans->{ans_message} = 'There is a syntax error in your answer';
 2742     $rh_ans->{student_ans} = clean_up_error_msg($PG_eval_errors);
 2743   } else {
 2744     $rh_ans->{student_ans} = $inVal;
 2745   }
 2746   $rh_ans;
 2747 }
 2748 
 2749 =head std_num_array_filter
 2750 
 2751   std_num_array_filter($rh_ans, %options)
 2752   returns $rh_ans
 2753 
 2754 Assumes the {student_ans} field is a numerical  array, and applies BOTH check_syntax and std_num_filter
 2755 to each element of the array.  Does it's best to generate sensible error messages for syntax errors.
 2756 A typical error message displayed in {studnet_ans} might be ( 56, error message, -4).
 2757 
 2758 =cut
 2759 
 2760 sub std_num_array_filter {
 2761   my $rh_ans= shift;
 2762   my %options = @_;
 2763   set_default_options(  \%options,
 2764         '_filter_name'  =>  'std_num_array_filter',
 2765     );
 2766   my @in = @{$rh_ans->{student_ans}};
 2767   my $temp_hash = new AnswerHash;
 2768   my @out=();
 2769   my $PGanswerMessage = '';
 2770   foreach my $item (@in)   {  # evaluate each number in the vector
 2771     $temp_hash->input($item);
 2772     $temp_hash = check_syntax($temp_hash);
 2773     if (defined($temp_hash->{error_flag}) and $temp_hash->{error_flag} eq 'SYNTAX') {
 2774       $PGanswerMessage .= $temp_hash->{ans_message};
 2775       $temp_hash->{ans_message} = undef;
 2776     } else {
 2777       #continue processing
 2778       $temp_hash = std_num_filter($temp_hash);
 2779       if (defined($temp_hash->{ans_message}) and $temp_hash->{ans_message} ) {
 2780         $PGanswerMessage .= $temp_hash->{ans_message};
 2781         $temp_hash->{ans_message} = undef;
 2782       }
 2783     }
 2784     push(@out, $temp_hash->input());
 2785 
 2786   }
 2787   if ($PGanswerMessage) {
 2788     $rh_ans->input( "( " . join(", ", @out ) . " )" );
 2789         $rh_ans->throw_error('SYNTAX', 'There is a syntax error in your answer.');
 2790   } else {
 2791     $rh_ans->input( [@out] );
 2792   }
 2793   $rh_ans;
 2794 }
 2795 
 2796 =head4 function_from_string2
 2797 
 2798 
 2799 
 2800 =cut
 2801 
 2802 sub function_from_string2 {
 2803     my $rh_ans = shift;
 2804     my %options = @_;
 2805   my $eqn = $rh_ans->{student_ans};
 2806   assign_option_aliases(\%options,
 2807         'vars'      => 'ra_vars',
 2808         'var'           => 'ra_vars',
 2809   );
 2810   set_default_options(  \%options,
 2811               'store_in'    =>      'rf_student_ans',
 2812           'ra_vars'   =>  [qw( x y )],
 2813           'debug'     =>  0,
 2814           '_filter_name'  =>  'function_from_string2',
 2815     );
 2816     $rh_ans->{_filter_name} = $options{_filter_name};
 2817     my @VARS = @{ $options{ 'ra_vars'}};
 2818     #warn "VARS = ", join("<>", @VARS) if defined($options{debug}) and $options{debug} ==1;
 2819     my $originalEqn = $eqn;
 2820     $eqn = &math_constants($eqn);
 2821     for( my $i = 0; $i < @VARS; $i++ ) {
 2822         #  This next line is a hack required for 5.6.0 -- it doesn't appear to be needed in 5.6.1
 2823         my ($temp,$er1,$er2) = PG_restricted_eval('"'. $VARS[$i] . '"');
 2824   #$eqn =~ s/\b$VARS[$i]\b/\$VARS[$i]/g;
 2825         $eqn  =~ s/\b$temp\b/\$VARS[$i]/g;
 2826 
 2827   }
 2828   #warn "equation evaluated = $eqn",$rh_ans->pretty_print(), "<br>\noptions<br>\n",
 2829   #     pretty_print(\%options)
 2830   #     if defined($options{debug}) and $options{debug} ==1;
 2831     my ($function_sub,$PG_eval_errors, $PG_full_errors) = PG_answer_eval( q!
 2832       sub {
 2833         my @VARS = @_;
 2834         my $input_str = '';
 2835         for( my $i=0; $i<@VARS; $i++ ) {
 2836           $input_str .= "\$VARS[$i] = $VARS[$i]; ";
 2837         }
 2838         my $PGanswerMessage;
 2839         $input_str .= '! . $eqn . q!';  # need the single quotes to keep the contents of $eqn from being
 2840                                         # evaluated when it is assigned to $input_str;
 2841         my ($out, $PG_eval_errors, $PG_full_errors) = PG_answer_eval($input_str); #Finally evaluated
 2842 
 2843         if ( defined($PG_eval_errors) and $PG_eval_errors =~ /\S/ ) {
 2844             $PGanswerMessage  = clean_up_error_msg($PG_eval_errors);
 2845 # This message seemed too verbose, but it does give extra information, we'll see if it is needed.
 2846 #                    "<br> There was an error in evaluating your function <br>
 2847 #           !. $originalEqn . q! <br>
 2848 #           at ( " . join(', ', @VARS) . " ) <br>
 2849 #            $PG_eval_errors
 2850 #           ";   # this message appears in the answer section which is not process by Latex2HTML so it must
 2851 #                # be in HTML.  That is why $BR is NOT used.
 2852 
 2853       }
 2854       (wantarray) ? ($out, $PGanswerMessage): $out;   # PGanswerMessage may be undefined.
 2855       };
 2856   !);
 2857 
 2858   if (defined($PG_eval_errors) and $PG_eval_errors =~/\S/ ) {
 2859         $PG_eval_errors = clean_up_error_msg($PG_eval_errors);
 2860 
 2861     my $PGanswerMessage = "There was an error in converting the expression
 2862       $main::BR $originalEqn $main::BR into a function.
 2863       $main::BR $PG_eval_errors.";
 2864     $rh_ans->{rf_student_ans} = $function_sub;
 2865     $rh_ans->{ans_message} = $PGanswerMessage;
 2866     $rh_ans->{error_message} = $PGanswerMessage;
 2867     $rh_ans->{error_flag} = 1;
 2868      # we couldn't compile the equation, we'll return an error message.
 2869   } else {
 2870 #     if (defined($options{store_in} )) {
 2871 #       $rh_ans ->{$options{store_in}} = $function_sub;
 2872 #     } else {
 2873 #         $rh_ans->{rf_student_ans} = $function_sub;
 2874 #       }
 2875       $rh_ans ->{$options{store_in}} = $function_sub;
 2876   }
 2877 
 2878     $rh_ans;
 2879 }
 2880 
 2881 =head4 is_zero_array
 2882 
 2883 
 2884 =cut
 2885 
 2886 
 2887 sub is_zero_array {
 2888     my $rh_ans = shift;
 2889     my %options = @_;
 2890     set_default_options(  \%options,
 2891         '_filter_name'  =>  'is_zero_array',
 2892         'tolerance' =>  0.000001,
 2893     );
 2894     my $array = $rh_ans -> {ra_differences};
 2895   my $num = @$array;
 2896   my $i;
 2897   my $max = 0; my $mm;
 2898   for ($i=0; $i< $num; $i++) {
 2899     $mm = $array->[$i] ;
 2900     if  (not is_a_number($mm) ) {
 2901       $max = $mm;  # break out if one of the elements is not a number
 2902       last;
 2903     }
 2904     $max = abs($mm) if abs($mm) > $max;
 2905   }
 2906   if (not is_a_number($max)) {
 2907     $rh_ans->{score} = 0;
 2908       my $error = "WeBWorK was unable evaluate your function. Please check that your
 2909                 expression doesn't take roots of negative numbers, or divide by zero.";
 2910     $rh_ans->throw_error('EVAL',$error);
 2911   } else {
 2912       $rh_ans->{score} = ($max < $options{tolerance} ) ? 1: 0;       # 1 if the array is close to 0;
 2913   }
 2914   $rh_ans;
 2915 }
 2916 
 2917 =head4 best_approx_parameters
 2918 
 2919   best_approx_parameters($rh_ans,%options);   #requires the following fields in $rh_ans
 2920                         {rf_student_ans}      # reference to the test answer
 2921                         {rf_correct_ans}      # reference to the comparison answer
 2922                         {evaluation_points},  # an array of row vectors indicating the points
 2923                                       # to evaluate when comparing the functions
 2924 
 2925                          %options       # debug => 1   gives more error answers
 2926                                     # param_vars => ['']  additional parameters used to adapt to function
 2927                          )
 2928 
 2929 
 2930 The parameters for the comparison function which best approximates the test_function are stored
 2931 in the field {ra_parameters}.
 2932 
 2933 
 2934 The last $dim_of_parms_space variables are assumed to be parameters, and it is also
 2935 assumed that the function \&comparison_fun
 2936 depends linearly on these variables.  This function finds the  values for these parameters which minimizes the
 2937 Euclidean distance (L2 distance) between the test function and the comparison function and the test points specified
 2938 by the array reference  \@rows_of_test_points.  This is assumed to be an array of arrays, with the inner arrays
 2939 determining a test point.
 2940 
 2941 The comparison function should have $dim_of_params_space more input variables than the test function.
 2942 
 2943 
 2944 
 2945 
 2946 
 2947 =cut
 2948 
 2949 # Used internally:
 2950 #
 2951 #   &$determine_param_coeff( $rf_comparison_function # a reference to the correct answer function
 2952 #                    $ra_variables                   # an array of the active input variables to the functions
 2953 #                    $dim_of_params_space            # indicates the number of parameters upon which the
 2954 #                                                    # the comparison function depends linearly.  These are assumed to
 2955 #                                                    # be the last group of inputs to the comparison function.
 2956 #
 2957 #                    %options                        # $options{debug} gives more error messages
 2958 #
 2959 #                                                    # A typical function might look like
 2960 #                                                    # f(x,y,z,a,b) = x^2+a*cos(xz) + b*sin(x) with a parameter
 2961 #                                                    # space of dimension 2 and a variable space of dimension 3.
 2962 #                   )
 2963 #         # returns a list of coefficients
 2964 
 2965 sub best_approx_parameters {
 2966     my $rh_ans = shift;
 2967     my %options = @_;
 2968     set_default_options(\%options,
 2969         '_filter_name'      =>  'best_approx_paramters',
 2970         'allow_unknown_options' =>  1,
 2971     );
 2972     my $errors = undef;
 2973     # This subroutine for the determining the coefficents of the parameters at a given point
 2974     # is pretty specialized, so it is included here as a sub-subroutine.
 2975     my $determine_param_coeffs  = sub {
 2976     my ($rf_fun, $ra_variables, $dim_of_params_space, %options) =@_;
 2977     my @zero_params=();
 2978     for(my $i=1;$i<=$dim_of_params_space;$i++){push(@zero_params,0); }
 2979     my @vars = @$ra_variables;
 2980     my @coeff = ();
 2981     my @inputs = (@vars,@zero_params);
 2982     my ($f0, $f1, $err);
 2983     ($f0, $err) = &{$rf_fun}(@inputs);
 2984     if (defined($err) ) {
 2985       $errors .= "$err ";
 2986     } else {
 2987       for (my $i=@vars;$i<@inputs;$i++) {
 2988         $inputs[$i]=1;  # set one parameter to 1;
 2989         my($f1,$err) = &$rf_fun(@inputs);
 2990         if (defined($err) ) {
 2991           $errors .= " $err ";
 2992         } else {
 2993           push(@coeff, $f1-$f0);
 2994         }
 2995         $inputs[$i]=0;  # set it back
 2996       }
 2997     }
 2998     (\@coeff, $errors);
 2999   };
 3000     my $rf_fun = $rh_ans->{rf_student_ans};
 3001     my $rf_correct_fun = $rh_ans->{rf_correct_ans};
 3002     my $ra_vars_matrix = $rh_ans->{evaluation_points};
 3003     my $dim_of_param_space = @{$options{param_vars}};
 3004     # Short cut.  Bail if there are no param_vars
 3005     unless ($dim_of_param_space >0) {
 3006     $rh_ans ->{ra_parameters} = [];
 3007     return $rh_ans;
 3008     }
 3009     # inputs are row arrays in this case.
 3010     my @zero_params=();
 3011 
 3012     for(my $i=1;$i<=$dim_of_param_space;$i++){push(@zero_params,0); }
 3013     my @rows_of_vars = @$ra_vars_matrix;
 3014     warn "input rows ", pretty_print(\@rows_of_vars) if defined($options{debug}) and $options{debug};
 3015     my $rows = @rows_of_vars;
 3016     my $matrix =new Matrix($rows,$dim_of_param_space);
 3017     my $rhs_vec = new Matrix($rows, 1);
 3018     my $row_num = 1;
 3019     my ($ra_coeff,$val2, $val1, $err1,$err2,@inputs,@vars);
 3020     my $number_of_data_points = $dim_of_param_space +2;
 3021     while (@rows_of_vars and $row_num <= $number_of_data_points) {
 3022      # get one set of data points from the test function;
 3023       @vars = @{ shift(@rows_of_vars) };
 3024       ($val2, $err1) = &{$rf_fun}(@vars);
 3025       $errors .= " $err1 "  if defined($err1);
 3026       @inputs = (@vars,@zero_params);
 3027       ($val1, $err2) = &{$rf_correct_fun}(@inputs);
 3028       $errors .= " $err2 " if defined($err2);
 3029 
 3030       unless (defined($err1) or defined($err2) ) {
 3031           $rhs_vec->assign($row_num,1, $val2-$val1 );
 3032 
 3033     # warn "rhs data  val1=$val1, val2=$val2, val2 - val1 = ", $val2 - $val1 if $options{debug};
 3034     # warn "vars ", join(" | ", @vars) if $options{debug};
 3035 
 3036       ($ra_coeff, $err1) = &{$determine_param_coeffs}($rf_correct_fun,\@vars,$dim_of_param_space,%options);
 3037       if (defined($err1) ) {
 3038         $errors .= " $err1 ";
 3039       } else {
 3040         my @coeff = @$ra_coeff;
 3041         my $col_num=1;
 3042           while(@coeff) {
 3043             $matrix->assign($row_num,$col_num, shift(@coeff) );
 3044             $col_num++;
 3045           }
 3046         }
 3047       }
 3048       $row_num++;
 3049       last if $errors;  # break if there are any errors.
 3050                       # This cuts down on the size of error messages.
 3051                       # However it impossible to check for equivalence at 95% of points
 3052             # which might be useful for functions that are not defined at some points.
 3053   }
 3054     warn "<br> best_approx_parameters: matrix1 <br>  ", " $matrix " if $options{debug};
 3055     warn "<br> best_approx_parameters: vector <br>  ", " $rhs_vec " if $options{debug};
 3056 
 3057    # we have   Matrix * parameter = data_vec + perpendicular vector
 3058    # where the matrix has column vectors defining the span of the parameter space
 3059    # multiply both sides by Matrix_transpose and solve for the parameters
 3060    # This is exactly what the method proj_coeff method does.
 3061    my @array;
 3062    if (defined($errors) ) {
 3063     @array = ();   #     new Matrix($dim_of_param_space,1);
 3064    } else {
 3065     @array = $matrix->proj_coeff($rhs_vec)->list();
 3066    }
 3067   # check size (hack)
 3068   my $max = 0;
 3069   foreach my $val (@array ) {
 3070     $max = abs($val) if  $max < abs($val);
 3071     if (not is_a_number($val) ) {
 3072       $max = "NaN: $val";
 3073       last;
 3074     }
 3075   }
 3076   if ($max =~/NaN/) {
 3077     $errors .= "WeBWorK was unable evaluate your function. Please check that your
 3078                 expression doesn't take roots of negative numbers, or divide by zero.";
 3079   } elsif ($max > $options{maxConstantOfIntegration} ) {
 3080     $errors .= "At least one of the adapting parameters
 3081              (perhaps the constant of integration) is too large: $max,
 3082              ( the maximum allowed is $options{maxConstantOfIntegration} )";
 3083   }
 3084 
 3085     $rh_ans->{ra_parameters} = \@array;
 3086     $rh_ans->throw_error('EVAL', $errors) if defined($errors);
 3087     $rh_ans;
 3088 }
 3089 
 3090 =head4 calculate_difference_vector
 3091 
 3092   calculate_difference_vector( $ans_hash, %options);
 3093 
 3094                 {rf_student_ans},     # a reference to the test function
 3095                                {rf_correct_ans},      # a reference to the correct answer function
 3096                                {evaluation_points},   # an array of row vectors indicating the points
 3097                                           # to evaluate when comparing the functions
 3098                                {ra_parameters}        # these are the (optional) additional inputs to
 3099                                                       # the comparison function which adapt it properly
 3100                                                       # to the problem at hand.
 3101 
 3102                                %options               # mode => 'rel'  specifies that each element in the
 3103                                                       # difference matrix is divided by the correct answer.
 3104                                                       # unless the correct answer is nearly 0.
 3105                               )
 3106 
 3107 =cut
 3108 
 3109 sub calculate_difference_vector {
 3110   my $rh_ans = shift;
 3111   my %options = @_;
 3112   # initialize
 3113   my $rf_fun = $rh_ans -> {rf_student_ans};
 3114   my $rf_correct_fun = $rh_ans -> {rf_correct_ans};
 3115   my $ra_parameters = $rh_ans ->{ra_parameters};
 3116   my @evaluation_points = @{$rh_ans->{evaluation_points} };
 3117   my @parameters = ();
 3118   @parameters = @$ra_parameters if defined($ra_parameters) and ref($ra_parameters) eq 'ARRAY';
 3119   my $errors = undef;
 3120   my @zero_params=();
 3121   for(my $i=1;$i<=@{$ra_parameters};$i++){push(@zero_params,0); }
 3122   my @differences = ();
 3123   my @student_values;
 3124   my @adjusted_student_values;
 3125   my @instructorVals;
 3126   my ($diff,$instructorVal);
 3127   # calculate the vector of differences between the test function and the comparison function.
 3128   while (@evaluation_points) {
 3129     my ($err1, $err2,$err3);
 3130     my @vars = @{ shift(@evaluation_points) };
 3131     my @inputs = (@vars, @parameters);
 3132     my ($inVal,  $correctVal);
 3133     ($inVal, $err1) = &{$rf_fun}(@vars);
 3134     $errors .= " $err1 "  if defined($err1);
 3135     $errors .= " Error detected evaluating student input at (".join(' , ',@vars) ." ) " if  defined($options{debug}) and $options{debug}=1 and defined($err1);
 3136     ($correctVal, $err2) =&{$rf_correct_fun}(@inputs);
 3137     $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err2 " if defined($err2);
 3138     $errors .= " Error detected evaluating correct adapted answer  at (".join(' , ',@inputs) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err2);
 3139     ($instructorVal,$err3)= &$rf_correct_fun(@vars, @zero_params);
 3140     $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err3 " if defined($err3);
 3141     $errors .= " Error detected evaluating instructor answer  at (".join(' , ',@vars, @zero_params) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err3);
 3142     unless (defined($err1) or defined($err2) or defined($err3) ) {
 3143       $diff = ( $inVal - ($correctVal -$instructorVal ) ) - $instructorVal;  #prevents entering too high a number?
 3144       #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff;
 3145       if (defined($options{tolType}) and $options{tolType} eq 'relative' ) {  #relative tolerance
 3146         #warn "diff = $diff";
 3147         #$diff = ( $inVal - ($correctVal-$instructorVal ) )/abs($instructorVal) -1    if abs($instructorVal) > $options{zeroLevel};
 3148         $diff = ( $inVal - ($correctVal-$instructorVal ) )/$instructorVal -1    if abs($instructorVal) > $options{zeroLevel};
 3149         #$diff = ( $inVal - ($correctVal-$instructorVal- $instructorVal ) )/abs($instructorVal)    if abs($instructorVal) > $options{zeroLevel};
 3150         #warn "diff = $diff,   ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal";
 3151       }
 3152     }
 3153     last if $errors;  # break if there are any errors.
 3154                   # This cuts down on the size of error messages.
 3155                   # However it impossible to check for equivalence at 95% of points
 3156                   # which might be useful for functions that are not defined at some points.
 3157         push(@student_values,$inVal);
 3158         push(@adjusted_student_values,(  $inVal - ($correctVal -$instructorVal) ) );
 3159     push(@differences, $diff);
 3160     push(@instructorVals,$instructorVal);
 3161   }
 3162   $rh_ans ->{ra_differences} = \@differences;
 3163   $rh_ans ->{ra_student_values} = \@student_values;
 3164   $rh_ans ->{ra_adjusted_student_values} = \@adjusted_student_values;
 3165   $rh_ans->{ra_instructor_values}=\@instructorVals;
 3166   $rh_ans->throw_error('EVAL', $errors) if defined($errors);
 3167   $rh_ans;
 3168 }
 3169 
 3170 =head4 fix_answer_for_display
 3171 
 3172 =cut
 3173 
 3174 sub fix_answers_for_display {
 3175   my ($rh_ans, %options) = @_;
 3176   if ( $rh_ans->{answerIsString} ==1) {
 3177     $rh_ans = evaluatesToNumber ($rh_ans, %options);
 3178   }
 3179   if (defined ($rh_ans->{student_units})) {
 3180     $rh_ans->{student_ans} = $rh_ans->{student_ans}. ' '. $rh_ans->{student_units};
 3181   }
 3182   $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans};
 3183 
 3184   $rh_ans;
 3185 }
 3186 
 3187 =head4 evaluatesToNumber
 3188 
 3189 =cut
 3190 
 3191 sub evaluatesToNumber {
 3192   my ($rh_ans, %options) = @_;
 3193   if (is_a_numeric_expression($rh_ans->{student_ans})) {
 3194     my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans});
 3195     if ($PG_eval_errors) { # this if statement should never be run
 3196       # change nothing
 3197     } else {
 3198       # change this
 3199       $rh_ans->{student_ans} = prfmt($inVal,$options{format});
 3200     }
 3201   }
 3202   $rh_ans;
 3203 }
 3204 
 3205 =head4 is_numeric_expression
 3206 
 3207 =cut
 3208 
 3209 sub is_a_numeric_expression {
 3210   my $testString = shift;
 3211   my $is_a_numeric_expression = 0;
 3212   my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($testString);
 3213   if ($PG_eval_errors) {
 3214     $is_a_numeric_expression = 0;
 3215   } else {
 3216     $is_a_numeric_expression = 1;
 3217   }
 3218   $is_a_numeric_expression;
 3219 }
 3220 
 3221 =head4 is_a_number
 3222 
 3223 =cut
 3224 
 3225 sub is_a_number {
 3226   my ($num,%options) =  @_;
 3227   my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
 3228   my ($rh_ans);
 3229   if ($process_ans_hash) {
 3230     $rh_ans = $num;
 3231     $num = $rh_ans->{student_ans};
 3232   }
 3233 
 3234   my $is_a_number = 0;
 3235   return $is_a_number unless defined($num);
 3236   $num =~ s/^\s*//; ## remove initial spaces
 3237   $num =~ s/\s*$//; ## remove trailing spaces
 3238 
 3239   ## the following is copied from the online perl manual
 3240   if ($num =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){
 3241     $is_a_number = 1;
 3242   }
 3243 
 3244   if ($process_ans_hash)   {
 3245         if ($is_a_number == 1 ) {
 3246           $rh_ans->{student_ans}=$num;
 3247           return $rh_ans;
 3248         } else {
 3249           $rh_ans->{student_ans} = "Incorrect number format:  You must enter a number, e.g. -6, 5.3, or 6.12E-3";
 3250           $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
 3251           return $rh_ans;
 3252         }
 3253   } else {
 3254     return $is_a_number;
 3255   }
 3256 }
 3257 
 3258 =head4 is_a_fraction
 3259 
 3260 =cut
 3261 
 3262 sub is_a_fraction {
 3263   my ($num,%options) =  @_;
 3264   my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
 3265   my ($rh_ans);
 3266   if ($process_ans_hash) {
 3267     $rh_ans = $num;
 3268     $num = $rh_ans->{student_ans};
 3269   }
 3270 
 3271   my $is_a_fraction = 0;
 3272   return $is_a_fraction unless defined($num);
 3273   $num =~ s/^\s*//; ## remove initial spaces
 3274   $num =~ s/\s*$//; ## remove trailing spaces
 3275 
 3276   if ($num =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) {
 3277     $is_a_fraction = 1;
 3278   }
 3279 
 3280     if ($process_ans_hash)   {
 3281       if ($is_a_fraction == 1 ) {
 3282         $rh_ans->{student_ans}=$num;
 3283         return $rh_ans;
 3284       } else {
 3285         $rh_ans->{student_ans} = "Not a number of fraction: You must enter a number or fraction, e.g. -6 or 7/13";
 3286         $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3');
 3287         return $rh_ans;
 3288       }
 3289 
 3290       } else {
 3291     return $is_a_fraction;
 3292   }
 3293 }
 3294 
 3295 =head4 is_an_arithemetic_expression
 3296 
 3297 =cut
 3298 
 3299 sub is_an_arithmetic_expression {
 3300   my ($num,%options) =  @_;
 3301   my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ;
 3302   my ($rh_ans);
 3303   if ($process_ans_hash) {
 3304     $rh_ans = $num;
 3305     $num = $rh_ans->{student_ans};
 3306   }
 3307 
 3308   my $is_an_arithmetic_expression = 0;
 3309   return $is_an_arithmetic_expression unless defined($num);
 3310   $num =~ s/^\s*//; ## remove initial spaces
 3311   $num =~ s/\s*$//; ## remove trailing spaces
 3312 
 3313   if ($num =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) {
 3314     $is_an_arithmetic_expression =  1;
 3315   }
 3316 
 3317     if ($process_ans_hash)   {
 3318       if ($is_an_arithmetic_expression == 1 ) {
 3319         $rh_ans->{student_ans}=$num;
 3320         return $rh_ans;
 3321       } else {
 3322 
 3323     $rh_ans->{student_ans} = "Not an arithmetic expression: You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2";
 3324         $rh_ans->throw_error('NUMBER', 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2');
 3325         return $rh_ans;
 3326       }
 3327 
 3328       } else {
 3329     return $is_an_arithmetic_expression;
 3330   }
 3331 }
 3332 
 3333 #
 3334 
 3335 =head4 math_constants
 3336 
 3337 replaces pi, e, and ^ with their Perl equivalents
 3338 
 3339 =cut
 3340 
 3341 sub math_constants {
 3342   my($in,%options) = @_;
 3343   my $rh_ans;
 3344   my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ;
 3345   if ($process_ans_hash) {
 3346     $rh_ans = $in;
 3347     $in = $rh_ans->{student_ans};
 3348   }
 3349   # The code fragment above allows this filter to be used when the input is simply a string
 3350   # as well as when the input is an AnswerHash, and options.
 3351   $in =~s/\bpi\b/(4*atan2(1,1))/ge;
 3352   $in =~s/\be\b/(exp(1))/ge;
 3353   $in =~s/\^/**/g;
 3354 
 3355   if ($process_ans_hash)   {
 3356       $rh_ans->{student_ans}=$in;
 3357       return $rh_ans;
 3358     } else {
 3359     return $in;
 3360   }
 3361 }
 3362 
 3363 
 3364 
 3365 =head4 is_array
 3366 
 3367   is_array($rh_ans)
 3368     returns: $rh_ans.   Throws error "NOTARRAY" if this is not an array
 3369 
 3370 =cut
 3371 
 3372 sub is_array  {
 3373   my $rh_ans = shift;
 3374     # return if the result is an array
 3375   return($rh_ans) if  ref($rh_ans->{student_ans}) eq 'ARRAY' ;
 3376   $rh_ans->throw_error("NOTARRAY","The answer is not an array");
 3377   $rh_ans;
 3378 }
 3379 
 3380 =head4 check_syntax
 3381 
 3382   check_syntax( $rh_ans, %options)
 3383     returns an answer hash.
 3384 
 3385 latex2html preview code are installed in the answer hash.
 3386 The input has been transformed, changing 7pi to 7*pi  or 7x to 7*x.
 3387 Syntax error messages may be generated and stored in student_ans
 3388 Additional syntax error messages are stored in {ans_message} and duplicated in {error_message}
 3389 
 3390 
 3391 =cut
 3392 
 3393 sub check_syntax {
 3394         my $rh_ans = shift;
 3395         my %options = @_;
 3396         unless ( defined( $rh_ans->{student_ans} ) ) {
 3397           warn "Check_syntax requires an equation in the field {student_ans} or input";
 3398           $rh_ans->throw_error("1","{student_ans} field not defined");
 3399           return $rh_ans;
 3400         }
 3401         my $in = $rh_ans->{student_ans};
 3402     my $parser = new AlgParserWithImplicitExpand;
 3403     my $ret = $parser -> parse($in);      #for use with loops
 3404 
 3405     if ( ref($ret) )  {   ## parsed successfully
 3406       $parser -> tostring();
 3407       $parser -> normalize();
 3408       $rh_ans->input( $parser -> tostring() );
 3409       $rh_ans->{preview_text_string} = $in;
 3410       $rh_ans->{preview_latex_string} = $parser -> tolatex();
 3411 
 3412     } else {          ## error in parsing
 3413 
 3414       $rh_ans->{'student_ans'}      = 'syntax error:'. $parser->{htmlerror},
 3415       $rh_ans->{'ans_message'}      = $parser -> {error_msg},
 3416       $rh_ans->{'preview_text_string'}  = '',
 3417       $rh_ans->{'preview_latex_string'} = '',
 3418       $rh_ans->throw_error('SYNTAX',  'syntax error in answer:'. $parser->{htmlerror} . "$BR" .$parser -> {error_msg});
 3419     }
 3420 $rh_ans;
 3421 
 3422 }
 3423 
 3424 =head4 check_strings
 3425 
 3426   check_strings ($rh_ans, %options)
 3427     returns $rh_ans
 3428 
 3429 =cut
 3430 
 3431 sub check_strings {
 3432   my ($rh_ans, %options) = @_;
 3433 
 3434   # if the student's answer is a number, simply return the answer hash (unchanged).
 3435 
 3436   if  ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/)   {
 3437     if ( $rh_ans->{answerIsString} == 1) {
 3438         #$rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number
 3439     }
 3440     return $rh_ans;
 3441   }
 3442   # the student's answer is recognized as a string
 3443   my $ans = $rh_ans->{student_ans};
 3444 
 3445 # OVERVIEW of reminder of function:
 3446 # if answer is correct, return correct.  (adjust score to 1)
 3447 # if answer is incorect:
 3448 # 1) determine if the answer is sensible.  if it is, return incorrect.
 3449 # 2) if the answer is not sensible (and incorrect), then return an error message indicating so.
 3450 # no matter what:  throw a 'STRING' error to skip numerical evaluations.  (error flag skips remainder of pre_filters and evaluators)
 3451 # last: 'STRING' post_filter will clear the error (avoiding pink screen.)
 3452 
 3453   my $sensibleAnswer = 0;
 3454   $ans = str_filters( $ans, 'compress_whitespace' );  # remove trailing, leading, and double spaces.
 3455   my ($ans_eval) = str_cmp($rh_ans->{correct_ans});
 3456   my $temp_ans_hash = &$ans_eval($ans);
 3457   $rh_ans->{test} = $temp_ans_hash;
 3458   if ($temp_ans_hash->{score} ==1 ) {     # students answer matches the correct answer.
 3459     $rh_ans->{score} = 1;
 3460     $sensibleAnswer = 1;
 3461   } else {            # students answer does not match the correct answer.
 3462     my $legalString = '';       # find out if string makes sense
 3463     my @legalStrings = @{$options{strings}};
 3464     foreach $legalString (@legalStrings) {
 3465       if ( uc($ans) eq uc($legalString) ) {
 3466         $sensibleAnswer = 1;
 3467         last;
 3468         }
 3469       }
 3470     $sensibleAnswer = 1 unless $ans =~ /\S/;  ## empty answers are sensible
 3471     $rh_ans->throw_error('EVAL', "Your answer is not a recognized answer") unless ($sensibleAnswer);
 3472     # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer);
 3473     # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) );
 3474   }
 3475   $rh_ans->{student_ans} = $ans;
 3476   if ($sensibleAnswer) {
 3477     $rh_ans->throw_error('STRING', "The student's answer $rh_ans->{student_ans} is interpreted as a string.");
 3478   }
 3479   # warn ("\$rh_ans->{answerIsString} = $rh_ans->{answerIsString}");
 3480   $rh_ans;
 3481 }
 3482 
 3483 =head4 check_units
 3484 
 3485   check_strings ($rh_ans, %options)
 3486     returns $rh_ans
 3487 
 3488 
 3489 =cut
 3490 
 3491 sub check_units {
 3492   my ($rh_ans, %options) = @_;
 3493   my %correct_units = %{$rh_ans-> {rh_correct_units}};
 3494   my $ans = $rh_ans->{student_ans};
 3495   # $ans = '' unless defined ($ans);
 3496   $ans = str_filters ($ans, 'trim_whitespace');
 3497   my $original_student_ans = $ans;
 3498   $rh_ans->{original_student_ans} = $original_student_ans;
 3499 
 3500   # it surprises me that the match below works since the first .* is greedy.
 3501   my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/;
 3502 
 3503   unless ( defined($num_answer) && $units ) {
 3504     # there is an error reading the input
 3505     if ( $ans =~ /\S/ )  {  # the answer is not blank
 3506       $rh_ans -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " .
 3507         "as a number or an arithmetic expression followed by a unit specification. " .
 3508         "Your answer must contain units." );
 3509       $rh_ans->throw_error('UNITS', "The answer \"$ans\" could not be interpreted " .
 3510         "as a number or an arithmetic expression followed by a unit specification. " .
 3511         "Your answer must contain units." );
 3512     }
 3513     return $rh_ans;
 3514   }
 3515 
 3516   # we have been able to parse the answer into a numerical part and a unit part
 3517 
 3518   # $num_answer = $1;   #$1 and $2 from the regular expression above
 3519   # $units    = $2;
 3520 
 3521   my %units = Units::evaluate_units($units);
 3522   if ( defined( $units{'ERROR'} ) ) {
 3523      # handle error condition
 3524           $units{'ERROR'} = clean_up_error_msg($units{'ERROR'});
 3525     $rh_ans -> setKeys( 'ans_message' => "$units{'ERROR'}" );
 3526     $rh_ans -> throw_error('UNITS', "$units{'ERROR'}");
 3527     return $rh_ans;
 3528   }
 3529 
 3530   my $units_match = 1;
 3531   my $fund_unit;
 3532   foreach $fund_unit (keys %correct_units) {
 3533     next if $fund_unit eq 'factor';
 3534     $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit};
 3535   }
 3536 
 3537   if ( $units_match ) {
 3538         # units are ok.  Evaluate the numerical part of the answer
 3539     $rh_ans->{'tolerance'} = $rh_ans->{'tolerance'}* $correct_units{'factor'}/$units{'factor'}  if
 3540           $rh_ans->{'tolType'} eq 'absolute'; # the tolerance is in the units specified by the instructor.
 3541     $rh_ans->{correct_ans} =  prfmt($rh_ans->{correct_ans}*$correct_units{'factor'}/$units{'factor'});
 3542     $rh_ans->{student_units} = $units;
 3543     $rh_ans->{student_ans} = $num_answer;
 3544 
 3545   } else {
 3546         $rh_ans -> setKeys( ans_message => 'There is an error in the units for this answer.' );
 3547         $rh_ans -> throw_error ( 'UNITS', 'There is an error in the units for this answer.' );
 3548   }
 3549 
 3550   return $rh_ans;
 3551 }
 3552 
 3553 
 3554 
 3555 =head2 Filter utilities
 3556 
 3557 These two subroutines can be used in filters to set default options.  They
 3558 help make filters perform in uniform, predictable ways, and also make it
 3559 easy to recognize from the code which options a given filter expects.
 3560 
 3561 
 3562 =head4 assign_option_aliases
 3563 
 3564 Use this to assign aliases for the standard options.  It must come before set_default_options
 3565 within the subroutine.
 3566 
 3567     assign_option_aliases(\%options,
 3568         'alias1'  => 'option5'
 3569         'alias2'  => 'option7'
 3570     );
 3571 
 3572 
 3573 If the subroutine is called with an option  " alias1 => 23 " it will behave as if it had been
 3574 called with the option " option5 => 23 "
 3575 
 3576 =cut
 3577 
 3578 
 3579 
 3580 sub assign_option_aliases {
 3581   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';
 3583   my @option_aliases = @_;
 3584   while (@option_aliases) {
 3585     my $alias = shift @option_aliases;
 3586     my $option_key = shift @option_aliases;
 3587 
 3588     if (defined($rh_options->{$alias} )) {                       # if the alias appears in the option list
 3589       if (not defined($rh_options->{$option_key}) ) {          # and the option itself is not defined,
 3590         $rh_options->{$option_key} = $rh_options->{$alias};  # insert the value defined by the alias into the option value
 3591                                                              # the FIRST alias for a given option takes precedence
 3592                                                              # (after the option itself)
 3593       } else {
 3594         warn "option $option_key is already defined as", $rh_options->{$option_key}, "<br>\n",
 3595              "The attempt to override this option with the alias $alias with value ", $rh_options->{$alias},
 3596              " was ignored.";
 3597       }
 3598     }
 3599     delete($rh_options->{$alias});                               # remove the alias from the initial list
 3600   }
 3601 
 3602 }
 3603 
 3604 =head4 set_default_options
 3605 
 3606     set_default_options(\%options,
 3607         '_filter_name'  =>  'filter',
 3608         'option5'   =>  .0001,
 3609         'option7'   =>  'ascii',
 3610         'allow_unknown_options  =>  0,
 3611     }
 3612 
 3613 Note that the first entry is a reference to the options with which the filter was called.
 3614 
 3615 The option5 is set to .0001 unless the option is explicitly set when the subroutine is called.
 3616 
 3617 The B<'_filter_name'> option should always be set, although there is no error if it is missing.
 3618 It is used mainly for debugging answer evaluators and allows
 3619 you to keep track of which filter is currently processing the answer.
 3620 
 3621 If B<'allow_unknown_options'> is set to 0 then if the filter is called with options which do NOT appear in the
 3622 set_default_options list an error will be signaled and a warning message will be printed out.  This provides
 3623 error checking against misspelling an option and is generally what is desired for most filters.
 3624 
 3625 Occasionally one wants to write a filter which accepts a long list of options, not all of which are known in advance,
 3626 but only uses a subset of the options
 3627 provided.  In this case, setting 'allow_unkown_options' to 1 prevents the error from being signaled.
 3628 
 3629 =cut
 3630 
 3631 sub set_default_options {
 3632   my $rh_options = shift;
 3633   warn "The first entry to set_default_options must be a reference to the option hash" unless ref($rh_options) eq 'HASH';
 3634   my %default_options = @_;
 3635   unless ( defined($default_options{allow_unknown_options}) and $default_options{allow_unknown_options} == 1 ) {
 3636     foreach  my $key1 (keys %$rh_options) {
 3637       warn "This option |$key1| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key1});
 3638     }
 3639   }
 3640   foreach my $key (keys %default_options) {
 3641     if  ( not defined($rh_options->{$key} ) and defined( $default_options{$key} )  ) {
 3642       $rh_options->{$key} = $default_options{$key};  #this allows     tol   => undef to allow the tol option, but doesn't define
 3643                                                      # this key unless tol is explicitly defined.
 3644     }
 3645   }
 3646 }
 3647 
 3648 =head2 Problem Grader Subroutines
 3649 
 3650 =cut
 3651 
 3652 ## Problem Grader Subroutines
 3653 
 3654 #####################################
 3655 # This is a model for plug-in problem graders
 3656 #####################################
 3657 sub install_problem_grader {
 3658   my $rf_problem_grader = shift;
 3659   $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = $rf_problem_grader;
 3660 }
 3661 
 3662 =head4 std_problem_grader
 3663 
 3664 This is an all-or-nothing grader.  A student must get all parts of the problem write
 3665 before receiving credit.  You should make sure to use this grader on multiple choice
 3666 and true-false questions, otherwise students will be able to deduce how many
 3667 answers are correct by the grade reported by webwork.
 3668 
 3669 
 3670   install_problem_grader(~~&std_problem_grader);
 3671 
 3672 =cut
 3673 
 3674 sub std_problem_grader {
 3675   my $rh_evaluated_answers = shift;
 3676   my $rh_problem_state = shift;
 3677   my %form_options = @_;
 3678   my %evaluated_answers = %{$rh_evaluated_answers};
 3679   #  The hash $rh_evaluated_answers typically contains:
 3680   #    'answer1' => 34, 'answer2'=> 'Mozart', etc.
 3681 
 3682   # By default the  old problem state is simply passed back out again.
 3683   my %problem_state = %$rh_problem_state;
 3684 
 3685   # %form_options might include
 3686   # The user login name
 3687   # The permission level of the user
 3688   # The studentLogin name for this psvn.
 3689   # Whether the form is asking for a refresh or is submitting a new answer.
 3690 
 3691   # initial setup of the answer
 3692   my %problem_result = ( score    => 0,
 3693                errors   => '',
 3694              type   => 'std_problem_grader',
 3695              msg    => '',
 3696   );
 3697   # Checks
 3698 
 3699   my $ansCount = keys %evaluated_answers;  # get the number of answers
 3700 
 3701   unless ($ansCount > 0 ) {
 3702 
 3703     $problem_result{msg} = "This problem did not ask any questions.";
 3704     return(\%problem_result,\%problem_state);
 3705   }
 3706 
 3707   if ($ansCount > 1 ) {
 3708     $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ;
 3709   }
 3710 
 3711   unless ($form_options{answers_submitted} == 1) {
 3712     return(\%problem_result,\%problem_state);
 3713   }
 3714 
 3715   my $allAnswersCorrectQ=1;
 3716   foreach my $ans_name (keys %evaluated_answers) {
 3717   # I'm not sure if this check is really useful.
 3718     if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) )  {
 3719       $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
 3720     }
 3721     else {
 3722       die "Error at file ",__FILE__,"line ", __LINE__,":  Answer |$ans_name| is not a hash reference\n".
 3723          $evaluated_answers{$ans_name} .
 3724          "This probably means that the answer evaluator for this answer\n" .
 3725          "is not working correctly.";
 3726       $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}";
 3727     }
 3728   }
 3729   # report the results
 3730   $problem_result{score} = $allAnswersCorrectQ;
 3731 
 3732   # I don't like to put in this bit of code.
 3733   # It makes it hard to construct error free problem graders
 3734   # I would prefer to know that the problem score was numeric.
 3735   unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
 3736     $problem_state{recorded_score} = 0;  # This gets rid of non-numeric scores
 3737   }
 3738   #
 3739   if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) {
 3740     $problem_state{recorded_score} = 1;
 3741   }
 3742   else {
 3743     $problem_state{recorded_score} = 0;
 3744   }
 3745 
 3746   $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1;
 3747   $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0;
 3748   (\%problem_result, \%problem_state);
 3749 }
 3750 
 3751 =head4 std_problem_grader2
 3752 
 3753 This is an all-or-nothing grader.  A student must get all parts of the problem write
 3754 before receiving credit.  You should make sure to use this grader on multiple choice
 3755 and true-false questions, otherwise students will be able to deduce how many
 3756 answers are correct by the grade reported by webwork.
 3757 
 3758 
 3759   install_problem_grader(~~&std_problem_grader2);
 3760 
 3761 The only difference between the two versions
 3762 is at the end of the subroutine, where std_problem_grader2
 3763 records the attempt only if there have been no syntax errors,
 3764 whereas std_problem_grader records it regardless.
 3765 
 3766 =cut
 3767 
 3768 
 3769 
 3770 sub std_problem_grader2 {
 3771   my $rh_evaluated_answers = shift;
 3772   my $rh_problem_state = shift;
 3773   my %form_options = @_;
 3774   my %evaluated_answers = %{$rh_evaluated_answers};
 3775   #  The hash $rh_evaluated_answers typically contains:
 3776   #    'answer1' => 34, 'answer2'=> 'Mozart', etc.
 3777 
 3778   # By default the  old problem state is simply passed back out again.
 3779   my %problem_state = %$rh_problem_state;
 3780 
 3781   # %form_options might include
 3782   # The user login name
 3783   # The permission level of the user
 3784   # The studentLogin name for this psvn.
 3785   # Whether the form is asking for a refresh or is submitting a new answer.
 3786 
 3787   # initial setup of the answer
 3788   my %problem_result = ( score        => 0,
 3789              errors       => '',
 3790              type       => 'std_problem_grader',
 3791              msg        => '',
 3792   );
 3793 
 3794   # syntax errors are not counted.
 3795   my $record_problem_attempt = 1;
 3796   # Checks
 3797 
 3798   my $ansCount = keys %evaluated_answers;  # get the number of answers
 3799   unless ($ansCount > 0 ) {
 3800     $problem_result{msg} = "This problem did not ask any questions.";
 3801     return(\%problem_result,\%problem_state);
 3802   }
 3803 
 3804   if ($ansCount > 1 ) {
 3805     $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ;
 3806   }
 3807 
 3808   unless ($form_options{answers_submitted} == 1) {
 3809     return(\%problem_result,\%problem_state);
 3810   }
 3811 
 3812   my  $allAnswersCorrectQ=1;
 3813   foreach my $ans_name (keys %evaluated_answers) {
 3814   # I'm not sure if this check is really useful.
 3815     if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) )  {
 3816       $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
 3817     }
 3818     else {
 3819       die "Error at file ",__FILE__,"line ", __LINE__,":  Answer |$ans_name| is not a hash reference\n".
 3820          $evaluated_answers{$ans_name} .
 3821          "This probably means that the answer evaluator for this answer\n" .
 3822          "is not working correctly.";
 3823       $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}";
 3824     }
 3825   }
 3826   # report the results
 3827   $problem_result{score} = $allAnswersCorrectQ;
 3828 
 3829   # I don't like to put in this bit of code.
 3830   # It makes it hard to construct error free problem graders
 3831   # I would prefer to know that the problem score was numeric.
 3832   unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
 3833     $problem_state{recorded_score} = 0;  # This gets rid of non-numeric scores
 3834   }
 3835   #
 3836   if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) {
 3837     $problem_state{recorded_score} = 1;
 3838   }
 3839   else {
 3840     $problem_state{recorded_score} = 0;
 3841   }
 3842   # record attempt only if there have been no syntax errors.
 3843 
 3844   if ($record_problem_attempt == 1) {
 3845     $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1;
 3846     $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0;
 3847   }
 3848   else {
 3849     $problem_result{show_partial_correct_answers} = 0 ;  # prevent partial correct answers from being shown for syntax errors.
 3850   }
 3851   (\%problem_result, \%problem_state);
 3852 }
 3853 
 3854 =head4 avg_problem_grader
 3855 
 3856 This grader gives a grade depending on how many questions from the problem are correct.  (The highest
 3857 grade is the one that is kept.  One can never lower the recorded grade on a problem by repeating it.)
 3858 Many professors (and almost all students :-)  ) prefer this grader.
 3859 
 3860 
 3861   install_problem_grader(~~&avg_problem_grader);
 3862 
 3863 =cut
 3864 
 3865 
 3866 sub avg_problem_grader {
 3867     my $rh_evaluated_answers = shift;
 3868   my $rh_problem_state = shift;
 3869   my %form_options = @_;
 3870   my %evaluated_answers = %{$rh_evaluated_answers};
 3871   #  The hash $rh_evaluated_answers typically contains:
 3872   #    'answer1' => 34, 'answer2'=> 'Mozart', etc.
 3873 
 3874   # By default the  old problem state is simply passed back out again.
 3875   my %problem_state = %$rh_problem_state;
 3876 
 3877 
 3878   # %form_options might include
 3879   # The user login name
 3880   # The permission level of the user
 3881   # The studentLogin name for this psvn.
 3882   # Whether the form is asking for a refresh or is submitting a new answer.
 3883 
 3884   # initial setup of the answer
 3885   my  $total=0;
 3886   my %problem_result = ( score        => 0,
 3887              errors       => '',
 3888              type       => 'avg_problem_grader',
 3889              msg        => '',
 3890   );
 3891   my $count = keys %evaluated_answers;
 3892   $problem_result{msg} = 'You can earn partial credit on this problem.' if $count >1;
 3893   # Return unless answers have been submitted
 3894   unless ($form_options{answers_submitted} == 1) {
 3895     return(\%problem_result,\%problem_state);
 3896   }
 3897 
 3898   # Answers have been submitted -- process them.
 3899   foreach my $ans_name (keys %evaluated_answers) {
 3900     # I'm not sure if this check is really useful.
 3901     if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) )  {
 3902       $total += $evaluated_answers{$ans_name}->{score};
 3903     }
 3904     else {
 3905       die "Error: Answer |$ans_name| is not a hash reference\n".
 3906          $evaluated_answers{$ans_name} .
 3907          "This probably means that the answer evaluator for this answer\n" .
 3908          "is not working correctly.";
 3909       $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}";
 3910     }
 3911   }
 3912   # Calculate score rounded to three places to avoid roundoff problems
 3913   $problem_result{score} = $total/$count if $count;
 3914   # increase recorded score if the current score is greater.
 3915   $problem_state{recorded_score} = $problem_result{score} if $problem_result{score} > $problem_state{recorded_score};
 3916 
 3917 
 3918   $problem_state{num_of_correct_ans}++ if $total == $count;
 3919   $problem_state{num_of_incorrect_ans}++ if $total < $count ;
 3920   warn "Error in grading this problem the total $total is larger than $count" if $total > $count;
 3921   (\%problem_result, \%problem_state);
 3922 }
 3923 
 3924 =head2 Utility subroutines
 3925 
 3926 =head4
 3927 
 3928   warn pretty_print( $rh_hash_input)
 3929 
 3930 This can be very useful for printing out messages about objects while debugging
 3931 
 3932 =cut
 3933 
 3934 sub pretty_print {
 3935     my $r_input = shift;
 3936     my $out = '';
 3937     if ( not ref($r_input) ) {
 3938       $out = $r_input;    # not a reference
 3939     } elsif ("$r_input" =~/hash/i) {  # this will pick up objects whose '$self' is hash and so works better than ref($r_iput).
 3940       local($^W) = 0;
 3941     $out .= "$r_input " ."<TABLE border = \"2\" cellpadding = \"3\" BGCOLOR = \"#FFFFFF\">";
 3942     foreach my $key (lex_sort( keys %$r_input )) {
 3943       $out .= "<tr><TD> $key</TD><TD>=&gt;</td><td>&nbsp;".pretty_print($r_input->{$key}) . "</td></tr>";
 3944     }
 3945     $out .="</table>";
 3946   } elsif (ref($r_input) eq 'ARRAY' ) {
 3947     my @array = @$r_input;
 3948     $out .= "( " ;
 3949     while (@array) {
 3950       $out .= pretty_print(shift @array) . " , ";
 3951     }
 3952     $out .= " )";
 3953   } elsif (ref($r_input) eq 'CODE') {
 3954     $out = "$r_input";
 3955   } else {
 3956     $out = $r_input;
 3957   }
 3958     $out;
 3959 }
 3960 
 3961 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9