| 1 | #!/usr/bin/perl |
1 | #!/usr/local/bin/webwork-perl |
| 2 | |
2 | |
| 3 | # This file is PGanswermacros.pl |
3 | # This file is PGanswermacros.pl |
| 4 | # This includes the subroutines for the ANS macros, that |
4 | # This includes the subroutines for the ANS macros, that |
| 5 | # is, macros allowing a more flexible answer checking |
5 | # is, macros allowing a more flexible answer checking |
| 6 | #################################################################### |
6 | #################################################################### |
| 7 | # Copyright @ 1995-2000 University of Rochester |
7 | # Copyright @ 1995-2000 University of Rochester |
| 8 | # All Rights Reserved |
8 | # All Rights Reserved |
| 9 | #################################################################### |
9 | #################################################################### |
|
|
10 | #$Id$ |
| 10 | |
11 | |
| 11 | =head1 NAME |
12 | =head1 NAME |
| 12 | |
13 | |
| 13 | PGanswermacros.pl -- located in the courseScripts directory |
14 | PGanswermacros.pl -- located in the courseScripts directory |
| 14 | |
15 | |
| … | |
… | |
| 107 | |
108 | |
| 108 | BEGIN { |
109 | BEGIN { |
| 109 | be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix. |
110 | be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix. |
| 110 | } |
111 | } |
| 111 | |
112 | |
|
|
113 | |
|
|
114 | my ($BR , # convenient localizations. |
|
|
115 | $PAR , |
|
|
116 | $numRelPercentTolDefault , |
|
|
117 | $numZeroLevelDefault , |
|
|
118 | $numZeroLevelTolDefault , |
|
|
119 | $numAbsTolDefault , |
|
|
120 | $numFormatDefault , |
|
|
121 | $functRelPercentTolDefault , |
|
|
122 | $functZeroLevelDefault , |
|
|
123 | $functZeroLevelTolDefault , |
|
|
124 | $functAbsTolDefault , |
|
|
125 | $functNumOfPoints , |
|
|
126 | $functVarDefault , |
|
|
127 | $functLLimitDefault , |
|
|
128 | $functULimitDefault , |
|
|
129 | $functMaxConstantOfIntegration |
|
|
130 | ); |
|
|
131 | |
|
|
132 | sub _PGanswermacros_init { |
|
|
133 | |
| 112 | my $BR = $main::BR; # convenient localizations. |
134 | $BR = $main::BR; # convenient localizations. |
| 113 | my $PAR = $main::PAR; |
135 | $PAR = $main::PAR; |
| 114 | |
136 | |
| 115 | # import defaults |
137 | # import defaults |
| 116 | # these are now imported from the %envir variable |
138 | # these are now imported from the %envir variable |
| 117 | my $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
139 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
| 118 | my $numZeroLevelDefault = $main::numZeroLevelDefault; |
140 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
| 119 | my $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
141 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
| 120 | my $numAbsTolDefault = $main::numAbsTolDefault; |
142 | $numAbsTolDefault = $main::numAbsTolDefault; |
| 121 | my $numFormatDefault = $main::numFormatDefault; |
143 | $numFormatDefault = $main::numFormatDefault; |
| 122 | |
144 | |
| 123 | my $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
145 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
| 124 | my $functZeroLevelDefault = $main::functZeroLevelDefault; |
146 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
| 125 | my $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
147 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
| 126 | my $functAbsTolDefault = $main::functAbsTolDefault; |
148 | $functAbsTolDefault = $main::functAbsTolDefault; |
| 127 | my $functNumOfPoints = $main::functNumOfPoints; |
149 | $functNumOfPoints = $main::functNumOfPoints; |
| 128 | my $functVarDefault = $main::functVarDefault; |
150 | $functVarDefault = $main::functVarDefault; |
| 129 | my $functLLimitDefault = $main::functLLimitDefault; |
151 | $functLLimitDefault = $main::functLLimitDefault; |
| 130 | my $functULimitDefault = $main::functULimitDefault; |
152 | $functULimitDefault = $main::functULimitDefault; |
| 131 | my $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
153 | $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
| 132 | |
154 | |
| 133 | |
155 | |
|
|
156 | |
|
|
157 | } |
| 134 | |
158 | |
| 135 | ########################################################################## |
159 | ########################################################################## |
| 136 | ########################################################################## |
160 | ########################################################################## |
| 137 | ## Number answer evaluators |
161 | ## Number answer evaluators |
| 138 | |
162 | |
| … | |
… | |
| 329 | The student answer can contain elementary functions, e.g. sin(.3+pi/2) |
353 | The student answer can contain elementary functions, e.g. sin(.3+pi/2) |
| 330 | |
354 | |
| 331 | =cut |
355 | =cut |
| 332 | |
356 | |
| 333 | sub std_num_cmp { # compare numbers allowing use of elementary functions |
357 | sub std_num_cmp { # compare numbers allowing use of elementary functions |
| 334 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
358 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 335 | |
359 | |
| 336 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
360 | my %options = ( 'tolerance' => $relPercentTol, |
| 337 | 'tolerance' => $relPercentTol, |
|
|
| 338 | 'tolType' => 'relative', |
|
|
| 339 | 'format' => $format, |
361 | 'format' => $format, |
|
|
362 | 'zeroLevel' => $zeroLevel, |
|
|
363 | 'zeroLevelTol' => $zeroLevelTol |
|
|
364 | ); |
|
|
365 | |
|
|
366 | set_default_options( \%options, |
|
|
367 | 'tolType' => 'relative', |
|
|
368 | 'tolerance' => $numRelPercentTolDefault, |
| 340 | 'mode' => 'std', |
369 | 'mode' => 'std', |
| 341 | 'zeroLevel' => $zeroLevel, |
370 | 'format' => $numFormatDefault, |
| 342 | 'zeroLevelTol' => $zeroLevelTol |
371 | 'relTol' => $numRelPercentTolDefault, |
|
|
372 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
373 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
374 | 'debug' => 0, |
| 343 | ); |
375 | ); |
|
|
376 | |
|
|
377 | num_cmp([$correctAnswer], %options); |
| 344 | } |
378 | } |
| 345 | |
379 | |
| 346 | ## Similar to std_num_cmp but accepts a list of numbers in the form |
380 | ## Similar to std_num_cmp but accepts a list of numbers in the form |
| 347 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
381 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
| 348 | ## format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default |
382 | ## format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default |
| 349 | ## You must enter a format and tolerance |
383 | ## You must enter a format and tolerance |
| 350 | sub std_num_cmp_list { |
384 | sub std_num_cmp_list { |
| 351 | my ( $relPercentTol, $format, @answerList) = @_; |
385 | my ( $relPercentTol, $format, @answerList) = @_; |
| 352 | |
386 | |
| 353 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
387 | my %options = ( 'tolerance' => $relPercentTol, |
| 354 | 'tolType' => 'relative', |
388 | 'format' => $format, |
| 355 | 'format' => $format, |
|
|
| 356 | 'mode' => 'std', |
|
|
| 357 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 358 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 359 | 'answerList' => \@answerList |
|
|
| 360 | ); |
389 | ); |
| 361 | } |
|
|
| 362 | |
390 | |
|
|
391 | set_default_options( \%options, |
|
|
392 | 'tolType' => 'relative', |
|
|
393 | 'tolerance' => $numRelPercentTolDefault, |
|
|
394 | 'mode' => 'std', |
|
|
395 | 'format' => $numFormatDefault, |
|
|
396 | 'relTol' => $numRelPercentTolDefault, |
|
|
397 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
398 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
399 | 'debug' => 0, |
|
|
400 | ); |
|
|
401 | |
|
|
402 | num_cmp(\@answerList, %options); |
|
|
403 | |
|
|
404 | } |
|
|
405 | |
| 363 | sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance |
406 | sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance |
| 364 | my ( $correctAnswer, $absTol, $format) = @_; |
407 | my ( $correctAnswer, $absTol, $format) = @_; |
|
|
408 | my %options = ( 'tolerance' => $absTol, |
|
|
409 | 'format' => $format); |
|
|
410 | |
|
|
411 | set_default_options (\%options, |
|
|
412 | 'tolType' => 'absolute', |
|
|
413 | 'tolerance' => $absTol, |
|
|
414 | 'mode' => 'std', |
|
|
415 | 'format' => $numFormatDefault, |
|
|
416 | 'zeroLevel' => 0, |
|
|
417 | 'zeroLevelTol' => 0, |
|
|
418 | 'debug' => 0, |
|
|
419 | ); |
| 365 | |
420 | |
| 366 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
421 | num_cmp([$correctAnswer], %options); |
| 367 | 'tolerance' => $absTol, |
|
|
| 368 | 'tolType' => 'absolute', |
|
|
| 369 | 'format' => $format, |
|
|
| 370 | 'mode' => 'std', |
|
|
| 371 | 'zeroLevel' => 0, |
|
|
| 372 | 'zeroLevelTol' => 0 |
|
|
| 373 | ); |
|
|
| 374 | } |
422 | } |
| 375 | |
423 | |
| 376 | ## See std_num_cmp_list for usage |
424 | ## See std_num_cmp_list for usage |
|
|
425 | |
| 377 | sub std_num_cmp_abs_list { |
426 | sub std_num_cmp_abs_list { |
| 378 | my ( $absTol, $format, @answerList ) = @_; |
427 | my ( $absTol, $format, @answerList ) = @_; |
| 379 | |
428 | |
| 380 | NUM_CMP_LIST( 'tolerance' => $absTol, |
429 | my %options = ( 'tolerance' => $absTol, |
| 381 | 'tolType' => 'absolute', |
430 | 'format' => $format, |
| 382 | 'format' => $format, |
|
|
| 383 | 'mode' => 'std', |
|
|
| 384 | 'zeroLevel' => 0, |
|
|
| 385 | 'zeroLevelTol' => 0, |
|
|
| 386 | 'answerList' => \@answerList |
|
|
| 387 | ); |
431 | ); |
| 388 | } |
|
|
| 389 | |
432 | |
|
|
433 | set_default_options( \%options, |
|
|
434 | 'tolType' => 'absolute', |
|
|
435 | 'tolerance' => $absTol, |
|
|
436 | 'mode' => 'std', |
|
|
437 | 'format' => $numFormatDefault, |
|
|
438 | 'zeroLevel' => 0, |
|
|
439 | 'zeroLevelTol' => 0, |
|
|
440 | 'debug' => 0, |
|
|
441 | ); |
|
|
442 | |
|
|
443 | num_cmp(\@answerList, %options); |
|
|
444 | |
|
|
445 | } |
| 390 | |
446 | |
| 391 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
447 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
|
|
448 | |
| 392 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
449 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
|
|
450 | |
|
|
451 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
452 | 'format' => $format, |
|
|
453 | 'zeroLevel' => $zeroLevel, |
|
|
454 | 'zeroLevelTol' => $zeroLevelTol |
|
|
455 | ); |
| 393 | |
456 | |
| 394 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
457 | set_default_options( \%options, |
|
|
458 | 'tolType' => 'relative', |
| 395 | 'tolerance' => $relPercentTol, |
459 | 'tolerance' => $relPercentTol, |
| 396 | 'tolType' => 'relative', |
460 | 'mode' => 'frac', |
| 397 | 'format' => $format, |
461 | 'format' => $numFormatDefault, |
| 398 | 'mode' => 'frac', |
462 | 'zeroLevel' => $numZeroLevelDefault, |
| 399 | 'zeroLevel' => $zeroLevel, |
463 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 400 | 'zeroLevelTol' => $zeroLevelTol |
464 | 'relTol' => $numRelPercentTolDefault, |
|
|
465 | 'debug' => 0, |
| 401 | ); |
466 | ); |
|
|
467 | |
|
|
468 | num_cmp([$correctAnswer], %options); |
| 402 | } |
469 | } |
| 403 | |
470 | |
| 404 | ## See std_num_cmp_list for usage |
471 | ## See std_num_cmp_list for usage |
| 405 | sub frac_num_cmp_list { |
472 | sub frac_num_cmp_list { |
| 406 | my ( $relPercentTol, $format, @answerList ) = @_; |
473 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 407 | |
474 | |
| 408 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
475 | my %options = ( 'tolerance' => $relPercentTol, |
| 409 | 'tolType' => 'relative', |
476 | 'format' => $format |
| 410 | 'format' => $format, |
477 | ); |
| 411 | 'mode' => 'frac', |
478 | |
|
|
479 | set_default_options( \%options, |
|
|
480 | 'tolType' => 'relative', |
|
|
481 | 'tolerance' => $relPercentTol, |
|
|
482 | 'mode' => 'frac', |
|
|
483 | 'format' => $numFormatDefault, |
| 412 | 'zeroLevel' => $numZeroLevelDefault, |
484 | 'zeroLevel' => $numZeroLevelDefault, |
| 413 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
485 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 414 | 'answerList' => \@answerList |
486 | 'relTol' => $numRelPercentTolDefault, |
| 415 | ); |
487 | 'debug' => 0, |
|
|
488 | ); |
|
|
489 | |
|
|
490 | num_cmp(\@answerList, %options); |
|
|
491 | |
| 416 | } |
492 | } |
| 417 | |
493 | |
|
|
494 | |
| 418 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
495 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
| 419 | my ( $correctAnswer, $absTol, $format ) = @_; |
496 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 420 | |
497 | |
| 421 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
498 | my %options = ( 'tolerance' => $absTol, |
|
|
499 | 'format' => $format |
|
|
500 | ); |
|
|
501 | |
|
|
502 | set_default_options (\%options, |
|
|
503 | 'tolType' => 'absolute', |
| 422 | 'tolerance' => $absTol, |
504 | 'tolerance' => $absTol, |
| 423 | 'tolType' => 'absolute', |
505 | 'mode' => 'frac', |
| 424 | 'format' => $format, |
506 | 'format' => $numFormatDefault, |
| 425 | 'mode' => 'frac', |
507 | 'zeroLevel' => 0, |
| 426 | 'zeroLevel' => 0, |
|
|
| 427 | 'zeroLevelTol' => 0 |
508 | 'zeroLevelTol' => 0, |
| 428 | ); |
509 | 'debug' => 0, |
| 429 | } |
510 | ); |
|
|
511 | num_cmp([$correctAnswer], %options); |
|
|
512 | |
| 430 | |
513 | |
|
|
514 | } |
|
|
515 | |
| 431 | ## See std_num_cmp_list for usage |
516 | ## See std_num_cmp_list for usage |
| 432 | sub frac_num_cmp_abs_list { |
517 | sub frac_num_cmp_abs_list { |
| 433 | my ( $absTol, $format, @answerList ) = @_; |
518 | my ( $absTol, $format, @answerList ) = @_; |
| 434 | |
519 | |
| 435 | NUM_CMP_LIST( 'tolerance' => $absTol, |
520 | my %options = ( 'tolerance' => $absTol, |
| 436 | 'tolType' => 'absolute', |
521 | 'format' => $format |
| 437 | 'format' => $format, |
522 | ); |
| 438 | 'mode' => 'frac', |
523 | |
| 439 | 'zeroLevel' => 0, |
524 | set_default_options (\%options, |
|
|
525 | 'tolType' => 'absolute', |
|
|
526 | 'tolerance' => $absTol, |
|
|
527 | 'mode' => 'frac', |
|
|
528 | 'format' => $numFormatDefault, |
|
|
529 | 'zeroLevel' => 0, |
| 440 | 'zeroLevelTol' => 0, |
530 | 'zeroLevelTol' => 0, |
| 441 | 'answerList' => \@answerList |
531 | 'debug' => 0, |
| 442 | ); |
532 | ); |
|
|
533 | |
|
|
534 | num_cmp(\@answerList, %options); |
| 443 | } |
535 | } |
| 444 | |
536 | |
| 445 | |
537 | |
| 446 | sub arith_num_cmp { # only allow arithmetic expressions as submitted answer |
538 | sub arith_num_cmp { # only allow arithmetic expressions as submitted answer |
|
|
539 | |
| 447 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
540 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 448 | |
541 | |
| 449 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
542 | my %options = ( 'tolerance' => $relPercentTol, |
| 450 | 'tolerance' => $relPercentTol, |
543 | 'format' => $format, |
| 451 | 'tolType' => 'relative', |
544 | 'zeroLevel' => $zeroLevel, |
| 452 | 'format' => $format, |
|
|
| 453 | 'mode' => 'arith', |
|
|
| 454 | 'zeroLevel' => $zeroLevel, |
|
|
| 455 | 'zeroLevelTol' => $zeroLevelTol |
545 | 'zeroLevelTol' => $zeroLevelTol |
| 456 | ); |
546 | ); |
|
|
547 | |
|
|
548 | set_default_options( \%options, |
|
|
549 | 'tolType' => 'relative', |
|
|
550 | 'tolerance' => $relPercentTol, |
|
|
551 | 'mode' => 'arith', |
|
|
552 | 'format' => $numFormatDefault, |
|
|
553 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
554 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
555 | 'relTol' => $numRelPercentTolDefault, |
|
|
556 | 'debug' => 0, |
|
|
557 | ); |
|
|
558 | |
|
|
559 | num_cmp([$correctAnswer], %options); |
| 457 | } |
560 | } |
| 458 | |
561 | |
| 459 | ## See std_num_cmp_list for usage |
562 | ## See std_num_cmp_list for usage |
| 460 | sub arith_num_cmp_list { |
563 | sub arith_num_cmp_list { |
| 461 | my ( $relPercentTol, $format, @answerList ) = @_; |
564 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 462 | |
565 | |
| 463 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
566 | my %options = ( 'tolerance' => $relPercentTol, |
| 464 | 'tolType' => 'relative', |
567 | 'format' => $format, |
| 465 | 'format' => $format, |
568 | ); |
| 466 | 'mode' => 'arith', |
|
|
| 467 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 468 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 469 | 'answerList' => \@answerList |
|
|
| 470 | ); |
|
|
| 471 | } |
|
|
| 472 | |
569 | |
|
|
570 | set_default_options( \%options, |
|
|
571 | 'tolType' => 'relative', |
|
|
572 | 'tolerance' => $relPercentTol, |
|
|
573 | 'mode' => 'arith', |
|
|
574 | 'format' => $numFormatDefault, |
|
|
575 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
576 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
577 | 'relTol' => $numRelPercentTolDefault, |
|
|
578 | 'debug' => 0, |
|
|
579 | ); |
|
|
580 | num_cmp(\@answerList, %options); |
|
|
581 | } |
|
|
582 | |
| 473 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
583 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
| 474 | my ( $correctAnswer, $absTol, $format ) = @_; |
584 | my ( $correctAnswer, $absTol, $format ) = @_; |
|
|
585 | |
|
|
586 | my %options = ( 'tolerance' => $absTol, |
|
|
587 | 'format' => $format |
|
|
588 | ); |
|
|
589 | |
|
|
590 | set_default_options (\%options, |
|
|
591 | 'tolType' => 'absolute', |
|
|
592 | 'tolerance' => $absTol, |
|
|
593 | 'mode' => 'arith', |
|
|
594 | 'format' => $numFormatDefault, |
|
|
595 | 'zeroLevel' => 0, |
|
|
596 | 'zeroLevelTol' => 0, |
|
|
597 | 'debug' => 0, |
|
|
598 | ); |
|
|
599 | num_cmp([$correctAnswer], %options); |
| 475 | |
600 | |
| 476 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
601 | |
| 477 | 'tolerance' => $absTol, |
|
|
| 478 | 'tolType' => 'absolute', |
|
|
| 479 | 'format' => $format, |
|
|
| 480 | 'mode' => 'arith', |
|
|
| 481 | 'zeroLevel' => 0, |
|
|
| 482 | 'zeroLevelTol' => 0 |
|
|
| 483 | ); |
|
|
| 484 | } |
602 | } |
| 485 | |
603 | |
| 486 | ## See std_num_cmp_list for usage |
604 | ## See std_num_cmp_list for usage |
| 487 | sub arith_num_cmp_abs_list { |
605 | sub arith_num_cmp_abs_list { |
| 488 | my ( $absTol, $format, @answerList ) = @_; |
606 | my ( $absTol, $format, @answerList ) = @_; |
| 489 | |
607 | |
| 490 | NUM_CMP_LIST( 'tolerance' => $absTol, |
608 | my %options = ( 'tolerance' => $absTol, |
| 491 | 'tolType' => 'absolute', |
609 | 'format' => $format |
| 492 | 'format' => $format, |
610 | ); |
| 493 | 'mode' => 'arith', |
611 | |
| 494 | 'zeroLevel' => 0, |
612 | set_default_options (\%options, |
| 495 | 'zeroLevelTol' => 0, |
613 | 'tolType' => 'absolute', |
| 496 | 'answerList' => \@answerList |
614 | 'tolerance' => $absTol, |
| 497 | ); |
615 | 'mode' => 'arith', |
|
|
616 | 'format' => $numFormatDefault, |
|
|
617 | 'zeroLevel' => 0, |
|
|
618 | 'zeroLevelTol' => 0, |
|
|
619 | 'debug' => 0, |
|
|
620 | ); |
|
|
621 | num_cmp(\@answerList, %options); |
|
|
622 | |
| 498 | } |
623 | } |
| 499 | |
624 | |
| 500 | sub strict_num_cmp { # only allow numbers as submitted answer |
625 | sub strict_num_cmp { # only allow numbers as submitted answer |
|
|
626 | |
| 501 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
627 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
|
|
628 | |
|
|
629 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
630 | 'format' => $format, |
|
|
631 | 'zeroLevel' => $zeroLevel, |
|
|
632 | 'zeroLevelTol' => $zeroLevelTol |
|
|
633 | ); |
|
|
634 | |
|
|
635 | set_default_options( \%options, |
|
|
636 | 'tolType' => 'relative', |
|
|
637 | 'tolerance' => $relPercentTol, |
|
|
638 | 'mode' => 'strict', |
|
|
639 | 'format' => $numFormatDefault, |
|
|
640 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
641 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
642 | 'relTol' => $numRelPercentTolDefault, |
|
|
643 | 'debug' => 0, |
|
|
644 | ); |
|
|
645 | |
|
|
646 | num_cmp([$correctAnswer], %options); |
| 502 | |
647 | |
| 503 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
|
|
| 504 | 'tolerance' => $relPercentTol, |
|
|
| 505 | 'tolType' => 'relative', |
|
|
| 506 | 'format' => $format, |
|
|
| 507 | 'mode' => 'strict', |
|
|
| 508 | 'zeroLevel' => $zeroLevel, |
|
|
| 509 | 'zeroLevelTol' => $zeroLevelTol |
|
|
| 510 | ); |
|
|
| 511 | } |
648 | } |
| 512 | |
649 | |
| 513 | ## See std_num_cmp_list for usage |
650 | ## See std_num_cmp_list for usage |
| 514 | sub strict_num_cmp_list { # compare numbers |
651 | sub strict_num_cmp_list { # compare numbers |
| 515 | my ( $relPercentTol, $format, @answerList ) = @_; |
652 | my ( $relPercentTol, $format, @answerList ) = @_; |
|
|
653 | |
|
|
654 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
655 | 'format' => $format, |
|
|
656 | ); |
|
|
657 | |
|
|
658 | set_default_options( \%options, |
|
|
659 | 'tolType' => 'relative', |
|
|
660 | 'tolerance' => $relPercentTol, |
|
|
661 | 'mode' => 'strict', |
|
|
662 | 'format' => $numFormatDefault, |
|
|
663 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
664 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
665 | 'relTol' => $numRelPercentTolDefault, |
|
|
666 | 'debug' => 0, |
|
|
667 | ); |
| 516 | |
668 | |
| 517 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
669 | num_cmp(\@answerList, %options); |
| 518 | 'tolType' => 'relative', |
670 | } |
| 519 | 'format' => $format, |
671 | |
| 520 | 'mode' => 'strict', |
|
|
| 521 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 522 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 523 | 'answerList' => \@answerList |
|
|
| 524 | ); |
|
|
| 525 | } |
|
|
| 526 | |
672 | |
| 527 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
673 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
|
|
674 | |
| 528 | my ( $correctAnswer, $absTol, $format ) = @_; |
675 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 529 | |
676 | |
| 530 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
677 | my %options = ( 'tolerance' => $absTol, |
| 531 | 'tolerance' => $absTol, |
678 | 'format' => $format |
| 532 | 'tolType' => 'absolute', |
679 | ); |
| 533 | 'format' => $format, |
680 | |
| 534 | 'mode' => 'strict', |
681 | set_default_options (\%options, |
| 535 | 'zeroLevel' => 0, |
682 | 'tolType' => 'absolute', |
| 536 | 'zeroLevelTol' => 0 |
683 | 'tolerance' => $absTol, |
| 537 | ); |
684 | 'mode' => 'strict', |
|
|
685 | 'format' => $numFormatDefault, |
|
|
686 | 'zeroLevel' => 0, |
|
|
687 | 'zeroLevelTol' => 0, |
|
|
688 | 'debug' => 0, |
|
|
689 | ); |
|
|
690 | |
|
|
691 | num_cmp([$correctAnswer], %options); |
|
|
692 | |
| 538 | } |
693 | } |
| 539 | |
694 | |
| 540 | ## See std_num_cmp_list for usage |
695 | ## See std_num_cmp_list for usage |
| 541 | sub strict_num_cmp_abs_list { # compare numbers |
696 | sub strict_num_cmp_abs_list { # compare numbers |
| 542 | my ( $absTol, $format, @answerList ) = @_; |
697 | my ( $absTol, $format, @answerList ) = @_; |
| 543 | |
698 | |
| 544 | NUM_CMP_LIST( 'tolerance' => $absTol, |
699 | |
| 545 | 'tolType' => 'absolute', |
700 | my %options = ( 'tolerance' => $absTol, |
| 546 | 'format' => $format, |
701 | 'format' => $format |
| 547 | 'mode' => 'strict', |
702 | ); |
| 548 | 'zeroLevel' => 0, |
703 | |
| 549 | 'zeroLevelTol' => 0, |
704 | set_default_options (\%options, |
| 550 | 'answerList' => \@answerList |
705 | 'tolType' => 'absolute', |
| 551 | ); |
706 | 'tolerance' => $absTol, |
|
|
707 | 'mode' => 'strict', |
|
|
708 | 'format' => $numFormatDefault, |
|
|
709 | 'zeroLevel' => 0, |
|
|
710 | 'zeroLevelTol' => 0, |
|
|
711 | 'debug' => 0, |
|
|
712 | ); |
|
|
713 | |
|
|
714 | num_cmp(\@answerList, %options); |
|
|
715 | |
|
|
716 | |
|
|
717 | |
| 552 | } |
718 | } |
| 553 | |
719 | |
| 554 | |
720 | |
| 555 | ## Compares a number with units |
721 | ## Compares a number with units |
| 556 | ## Deprecated; use num_cmp() |
722 | ## Deprecated; use num_cmp() |
| … | |
… | |
| 561 | ## format -- the format to use when displaying the answer |
727 | ## format -- the format to use when displaying the answer |
| 562 | ## tol -- an absolute tolerance, or |
728 | ## tol -- an absolute tolerance, or |
| 563 | ## relTol -- a relative tolerance |
729 | ## relTol -- a relative tolerance |
| 564 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
730 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 565 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
731 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
|
|
732 | |
|
|
733 | |
|
|
734 | sub check_strings { |
|
|
735 | my ($rh_ans, %options) = @_; |
|
|
736 | |
|
|
737 | # if the student's answer is a number, simply return the answer hash (unchanged). |
|
|
738 | |
|
|
739 | |
|
|
740 | if ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) { |
|
|
741 | if ( $rh_ans->{answerIsString} == 1) { |
|
|
742 | #$rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number |
|
|
743 | } |
|
|
744 | return $rh_ans; |
|
|
745 | } |
|
|
746 | # the student's answer is recognized as a string |
|
|
747 | my $ans = $rh_ans->{student_ans}; |
|
|
748 | |
|
|
749 | # OVERVIEW of remindar of function: |
|
|
750 | # if answer is correct, return correct. (adjust score to 1) |
|
|
751 | # if answer is incorect: |
|
|
752 | # 1) determine if the answer is sensible. if it is, return incorrect. |
|
|
753 | # 2) if the answer is not sensible (and incorrect), then return an error message indicating so. |
|
|
754 | # no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators) |
|
|
755 | # last: 'STRING' post_filter will clear the error (avoiding pink screen.) |
|
|
756 | |
|
|
757 | my $sensibleAnswer = 0; |
|
|
758 | $ans = str_filters( $ans, 'compress_whitespace' ); # remove trailing, leading, and double spaces. |
|
|
759 | my ($ans_eval) = str_cmp($rh_ans->{correct_ans}); |
|
|
760 | my $temp_ans_hash = &$ans_eval($ans); |
|
|
761 | $rh_ans->{test} = $temp_ans_hash; |
|
|
762 | if ($temp_ans_hash->{score} ==1 ) { # students answer matches the correct answer. |
|
|
763 | $rh_ans->{score} = 1; |
|
|
764 | $sensibleAnswer = 1; |
|
|
765 | } else { # students answer does not match the correct answer. |
|
|
766 | ## find out if string makes sense |
|
|
767 | my $legalString = ''; |
|
|
768 | my @legalStrings = @{$options{strings}}; |
|
|
769 | foreach $legalString (@legalStrings) { |
|
|
770 | if ( uc($ans) eq uc($legalString) ) { |
|
|
771 | $sensibleAnswer = 1; |
|
|
772 | last; |
|
|
773 | } |
|
|
774 | } |
|
|
775 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
|
|
776 | $rh_ans->throw_error('EVAL', "$BR Your answer is not a recognized answer") unless ($sensibleAnswer); |
|
|
777 | # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer); |
|
|
778 | # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
|
|
779 | } |
|
|
780 | $rh_ans->{student_ans} = $ans; |
|
|
781 | if ($sensibleAnswer) { |
|
|
782 | $rh_ans->throw_error('STRING', "The student's answer $rh_ans->{student_ans} is interpreted as a string."); |
|
|
783 | } |
|
|
784 | # warn ("\$rh_ans->{answerIsString} = $rh_ans->{answerIsString}"); |
|
|
785 | |
|
|
786 | $rh_ans; |
|
|
787 | |
|
|
788 | } |
|
|
789 | |
|
|
790 | |
|
|
791 | |
|
|
792 | sub check_units { |
|
|
793 | |
|
|
794 | my ($rh_ans, %options) = @_; |
|
|
795 | |
|
|
796 | my %correct_units = %{$rh_ans-> {rh_correct_units}}; |
|
|
797 | |
|
|
798 | my $ans = $rh_ans->{student_ans}; |
|
|
799 | # $ans = '' unless defined ($ans); |
|
|
800 | $ans = str_filters ($ans, 'trim_whitespace'); |
|
|
801 | my $original_student_ans = $ans; |
|
|
802 | |
|
|
803 | $rh_ans->{original_student_ans} = $original_student_ans; |
|
|
804 | |
|
|
805 | # it surprises me that the match below works since the first .* is greedy. |
|
|
806 | my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/; |
|
|
807 | |
|
|
808 | unless ( defined($num_answer) && $units ) { |
|
|
809 | # there is an error reading the input |
|
|
810 | if ( $ans =~ /\S/ ) { # the answer is not blank |
|
|
811 | $rh_ans -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " . |
|
|
812 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
813 | "Your answer must contain units." ); |
|
|
814 | $rh_ans->throw_error('UNITS', "The answer \"$ans\" could not be interpreted " . |
|
|
815 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
816 | "Your answer must contain units." ); |
|
|
817 | } |
|
|
818 | |
|
|
819 | return $rh_ans; |
|
|
820 | } |
|
|
821 | |
|
|
822 | # we have been able to parse the answer into a numerical part and a unit part |
|
|
823 | |
|
|
824 | # $num_answer = $1; #$1 and $2 from the regular expression above |
|
|
825 | # $units = $2; |
|
|
826 | |
|
|
827 | my %units = Units::evaluate_units($units); |
|
|
828 | if ( defined( $units{'ERROR'} ) ) { |
|
|
829 | # handle error condition |
|
|
830 | $units{'ERROR'} = clean_up_error_msg($units{'ERROR'}); |
|
|
831 | $rh_ans -> setKeys( 'ans_message' => "$units{'ERROR'}" ); |
|
|
832 | $rh_ans -> throw_error('UNITS', "$units{'ERROR'}"); |
|
|
833 | return $rh_ans; |
|
|
834 | } |
|
|
835 | |
|
|
836 | my $units_match = 1; |
|
|
837 | my $fund_unit; |
|
|
838 | foreach $fund_unit (keys %correct_units) { |
|
|
839 | next if $fund_unit eq 'factor'; |
|
|
840 | $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit}; |
|
|
841 | } |
|
|
842 | |
|
|
843 | if ( $units_match ) { |
|
|
844 | # units are ok. Evaluate the numerical part of the answer |
|
|
845 | $rh_ans->{'tolerance'} = $rh_ans->{'tolerance'}* $correct_units{'factor'}/$units{'factor'} if |
|
|
846 | $rh_ans->{'tolType'} eq 'absolute'; # the tolerance is in the units specified by the instructor. |
|
|
847 | $rh_ans->{correct_ans} = prfmt($rh_ans->{correct_ans}*$correct_units{'factor'}/$units{'factor'}); |
|
|
848 | $rh_ans->{student_units} = $units; |
|
|
849 | $rh_ans->{student_ans} = $num_answer; |
|
|
850 | |
|
|
851 | } else { |
|
|
852 | $rh_ans -> setKeys( ans_message => 'There is an error in the units for this answer.' ); |
|
|
853 | $rh_ans -> throw_error ( 'UNITS', 'There is an error in the units for this answer.' ); |
|
|
854 | } |
|
|
855 | |
|
|
856 | return $rh_ans; |
|
|
857 | } |
|
|
858 | |
|
|
859 | |
|
|
860 | # This mode is depricated. send input through num_cmp -- it can handle units. |
| 566 | sub numerical_compare_with_units { |
861 | sub numerical_compare_with_units { |
| 567 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
862 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
| 568 | my %options = @_; # all of the other inputs are (key value) pairs |
863 | my %options = @_; # all of the other inputs are (key value) pairs |
| 569 | |
|
|
| 570 | # handle the defaults |
|
|
| 571 | $options{'mode'} = 'std' unless defined( $options{'mode'} ); |
|
|
| 572 | $options{'format'} = $numFormatDefault unless defined( $options{'format'} ); |
|
|
| 573 | $options{'zeroLevel'} = $numZeroLevelDefault unless defined( $options{'zeroLevel'} ); |
|
|
| 574 | $options{'zeroLevelTol'} = $numZeroLevelTolDefault unless defined( $options{'zeroLevelTol'} ); |
|
|
| 575 | |
|
|
| 576 | # both spellings are maintained for backward compatibility |
|
|
| 577 | # relTol is preferred |
|
|
| 578 | if( defined $options{'reltol'} ) { |
|
|
| 579 | $options{'relTol'} = $options{'reltol'}; |
|
|
| 580 | delete $options{'reltol'}; |
|
|
| 581 | } |
|
|
| 582 | |
|
|
| 583 | my ($tol, $tolerance_mode); |
|
|
| 584 | if ( defined $options{'tol'} ) { |
|
|
| 585 | $tol = $options{'tol'}; |
|
|
| 586 | $tolerance_mode = 'absolute'; |
|
|
| 587 | } |
|
|
| 588 | elsif( defined $options{'relTol'} ) { |
|
|
| 589 | $tol = $options{'relTol'}; |
|
|
| 590 | $tolerance_mode = 'relative'; |
|
|
| 591 | } |
|
|
| 592 | else { #the default is a relative tolerance |
|
|
| 593 | $tol = $numRelPercentTolDefault; |
|
|
| 594 | $tolerance_mode = 'relative'; |
|
|
| 595 | } |
|
|
| 596 | |
864 | |
| 597 | # Prepare the correct answer |
865 | # Prepare the correct answer |
| 598 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
866 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
| 599 | |
867 | |
| 600 | # it surprises me that the match below works since the first .* is greedy. |
868 | # it surprises me that the match below works since the first .* is greedy. |
| 601 | my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/; |
869 | my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/; |
| 602 | |
870 | |
| 603 | my %correct_units = Units::evaluate_units($correct_units); |
871 | $options{units} = $correct_units; |
| 604 | if ( defined( $correct_units{'ERROR'} ) ) { |
872 | |
| 605 | die "ERROR: The answer \"$correct_answer\" in the problem definition cannot be parsed:\n" . |
873 | num_cmp($correct_num_answer, %options); |
| 606 | "$correct_units{'ERROR'}\n"; |
|
|
| 607 | } |
|
|
| 608 | |
|
|
| 609 | my $ans_evaluator = sub { |
|
|
| 610 | |
|
|
| 611 | my $ans = shift; |
|
|
| 612 | $ans = '' unless defined($ans); |
|
|
| 613 | my $original_student_ans = $ans; |
|
|
| 614 | |
|
|
| 615 | $ans = str_filters( $ans, 'trim_whitespace' ); |
|
|
| 616 | |
|
|
| 617 | my $ans_hash = new AnswerHash( |
|
|
| 618 | 'score' => 0, |
|
|
| 619 | 'correct_ans' => spf($correct_num_answer,$options{'format'}) . " $correct_units", |
|
|
| 620 | 'student_ans' => $ans, |
|
|
| 621 | 'ans_message' => '', |
|
|
| 622 | 'type' => 'num_cmp_with_units', |
|
|
| 623 | 'preview_text_string' => '', |
|
|
| 624 | 'original_student_ans' => $original_student_ans |
|
|
| 625 | ); |
|
|
| 626 | |
|
|
| 627 | # it surprises me that the match below works since the first .* is greedy. |
|
|
| 628 | my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/; |
|
|
| 629 | |
|
|
| 630 | unless ( defined($num_answer) && $units ) { |
|
|
| 631 | # there is an error reading the input |
|
|
| 632 | if ( $ans =~ /\S/ ) { # the answer is not blank |
|
|
| 633 | $ans_hash -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " . |
|
|
| 634 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
| 635 | "Your answer must contain units." ); |
|
|
| 636 | } |
|
|
| 637 | |
|
|
| 638 | return $ans_hash; |
|
|
| 639 | } |
|
|
| 640 | |
|
|
| 641 | # we have been able to parse the answer into a numerical part and a unit part |
|
|
| 642 | |
|
|
| 643 | $num_answer = $1; #$1 and $2 from the regular expression above |
|
|
| 644 | $units = $2; |
|
|
| 645 | |
|
|
| 646 | my %units = Units::evaluate_units($units); |
|
|
| 647 | if ( defined( $units{'ERROR'} ) ) { |
|
|
| 648 | # handle error condition |
|
|
| 649 | $units{'ERROR'} = clean_up_error_msg($units{'ERROR'}); |
|
|
| 650 | |
|
|
| 651 | $ans_hash -> setKeys( 'ans_message' => "$units{'ERROR'}" ); |
|
|
| 652 | |
|
|
| 653 | return $ans_hash; |
|
|
| 654 | } |
|
|
| 655 | |
|
|
| 656 | my $units_match = 1; |
|
|
| 657 | my $fund_unit; |
|
|
| 658 | foreach $fund_unit (keys %correct_units) { |
|
|
| 659 | next if $fund_unit eq 'factor'; |
|
|
| 660 | $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit}; |
|
|
| 661 | } |
|
|
| 662 | |
|
|
| 663 | if ( $units_match ) { |
|
|
| 664 | |
|
|
| 665 | # units are ok. Evaluate the numerical part of the answer |
|
|
| 666 | $tol = $tol * $correct_units{'factor'}/$units{'factor'} if |
|
|
| 667 | $tolerance_mode eq 'absolute'; # the tolerance is in the units specified by the instructor. |
|
|
| 668 | |
|
|
| 669 | my $numerical_answer_evaluator = NUM_CMP( 'correctAnswer' => $correct_num_answer*$correct_units{'factor'}/$units{'factor'}, |
|
|
| 670 | 'tolerance' => $tol, |
|
|
| 671 | 'tolType' => $tolerance_mode, |
|
|
| 672 | 'format' => $options{'format'}, |
|
|
| 673 | 'mode' => $options{'mode'}, |
|
|
| 674 | 'zeroLevel' => $options{'zeroLevel'}, |
|
|
| 675 | 'zeroLevelTol' => $options{'zeroLevelTol'} ); |
|
|
| 676 | |
|
|
| 677 | # because num_answer may contain an arithmetic expression rather than |
|
|
| 678 | # a number we can't multiply it by the $units{'factor'} |
|
|
| 679 | # instead we divide the correct answer by this amount; |
|
|
| 680 | # this is also why the numerical_answer_evaluator is not defined outside this subroutine. |
|
|
| 681 | |
|
|
| 682 | $ans_hash = &$numerical_answer_evaluator($num_answer); |
|
|
| 683 | |
|
|
| 684 | # now we need to doctor the correct answer in order to add units |
|
|
| 685 | # to it and correct for the division we did before |
|
|
| 686 | $ans_hash -> {correct_ans} = |
|
|
| 687 | prfmt( ( $ans_hash->{'correct_ans'} )*$units{'factor'}/$correct_units{'factor'}, |
|
|
| 688 | $options{'format'} ) . " $correct_units"; |
|
|
| 689 | # we also need to doctor the submitted answer to get it back in its original format. |
|
|
| 690 | |
|
|
| 691 | # we don't add the units on if there is an error message from numerical_answer_evaluator |
|
|
| 692 | if ( ( $ans_hash -> {ans_message} ) =~ /^\s*$/ ) { |
|
|
| 693 | $ans_hash -> {student_ans} = $ans_hash -> {student_ans} . " $units"; |
|
|
| 694 | $ans_hash -> setKeys( original_student_ans => $ans ); |
|
|
| 695 | } |
|
|
| 696 | else { |
|
|
| 697 | # error message from numerical_answer_evaluator doesn't have units tacked on |
|
|
| 698 | $ans_hash -> setKeys( original_student_ans => $ans ); |
|
|
| 699 | } |
|
|
| 700 | } |
|
|
| 701 | else { |
|
|
| 702 | $ans_hash -> setKeys( ans_message => 'There is an error in the units for this answer.' ); |
|
|
| 703 | } |
|
|
| 704 | |
|
|
| 705 | return $ans_hash; |
|
|
| 706 | }; |
|
|
| 707 | |
|
|
| 708 | $ans_evaluator; |
|
|
| 709 | } |
874 | } |
|
|
875 | |
| 710 | |
876 | |
| 711 | =head3 std_num_str_cmp() |
877 | =head3 std_num_str_cmp() |
| 712 | |
878 | |
| 713 | NOTE: This function is maintained for compatibility. num_cmp() with the |
879 | NOTE: This function is maintained for compatibility. num_cmp() with the |
| 714 | 'strings' parameter is slightly preferred. |
880 | 'strings' parameter is slightly preferred. |
| 715 | |
881 | |
| 716 | std_num_str_cmp() is used when the correct answer could be either a number or a |
882 | std_num_str_cmp() is used when the correct answer could be either a number or a |
| 717 | string. For example, if you wanted the student to evaluate a function at number |
883 | string. For example, if you wanted the student to evaluate a function at number |
| … | |
… | |
| 738 | Example: |
904 | Example: |
| 739 | ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) ); |
905 | ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) ); |
| 740 | |
906 | |
| 741 | =cut |
907 | =cut |
| 742 | |
908 | |
| 743 | sub std_num_str_cmp { |
909 | sub std_num_str_cmp { |
| 744 | my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
910 | my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 745 | |
911 | # warn ('This method is depreciated. Use num_cmp instead.'); |
| 746 | $ra_legalStrings = [''] unless defined $ra_legalStrings; |
912 | return num_cmp ($correctAnswer, strings=>$ra_legalStrings, relTol=>$relpercentTol, format=>$format, |
| 747 | my @legalStrings = @{$ra_legalStrings}; |
913 | zeroLevel=>$zeroLevel, zeroLevelTol=>$zeroLevelTol); |
| 748 | |
|
|
| 749 | my $ans_evaluator = sub { |
|
|
| 750 | |
|
|
| 751 | my $ans = shift; |
|
|
| 752 | my $ans_hash; |
|
|
| 753 | my $corrAnswerIsString = 0; |
|
|
| 754 | # my $studAnswerIsString = 0; ## uses new incorrect logic |
|
|
| 755 | my $studAnswerIsString = 1; |
|
|
| 756 | |
|
|
| 757 | my $legalString = ''; |
|
|
| 758 | foreach $legalString (@legalStrings) { |
|
|
| 759 | if ( uc($correctAnswer) eq uc($legalString) ) { |
|
|
| 760 | $corrAnswerIsString = 1; |
|
|
| 761 | last; |
|
|
| 762 | } |
|
|
| 763 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
|
|
| 764 | |
|
|
| 765 | # Neither of these is perfect; the first is more general, but |
|
|
| 766 | # has problems with certain special strings like "ee", while the |
|
|
| 767 | # second doesn't support arithmetic expressions. |
|
|
| 768 | # |
|
|
| 769 | # if( $ans !~ m/^\s*([\+\-\*\/\^\(\)\[\]\{\}\s\d\.Ee]*|e|pi)\s*$/ ) { |
|
|
| 770 | # $studAnswerIsString = 1; |
|
|
| 771 | # } |
|
|
| 772 | #if( $ans !~ m/^\s*([\d+\-*\/^()]|e|pi)\s*$/ ) { |
|
|
| 773 | # $studAnswerIsString = 1; |
|
|
| 774 | #} |
|
|
| 775 | |
|
|
| 776 | ## Both the above new versions are incorrect. We replace this by the original logic namely that |
|
|
| 777 | ## an answer that contain any of the symbols |
|
|
| 778 | ## a digit(0-9), +, -, *, /, ^, (, ), {, }, [, ] |
|
|
| 779 | ## or an answer that consists of "pi" or "e" alone |
|
|
| 780 | ## will be considered an arithmetic expression rather than a string answer. |
|
|
| 781 | |
|
|
| 782 | if ($ans =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) {$studAnswerIsString = 0;} |
|
|
| 783 | |
|
|
| 784 | |
|
|
| 785 | ## at this point $studAnswerIsString = 0 iff correct answer is numeric |
|
|
| 786 | |
|
|
| 787 | if( $studAnswerIsString ) { |
|
|
| 788 | $ans = str_filters( $ans, 'compress_whitespace' ) |
|
|
| 789 | } |
|
|
| 790 | |
|
|
| 791 | if ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 1) ) { |
|
|
| 792 | my $string_answer_evaluator = std_str_cmp( $correctAnswer ); |
|
|
| 793 | $ans_hash = &$string_answer_evaluator( $ans ); |
|
|
| 794 | |
|
|
| 795 | if( ($ans_hash -> {score}) != 1 ) { ## find out if string makes sense |
|
|
| 796 | my $sensibleAnswer = 0; |
|
|
| 797 | foreach $legalString (@legalStrings) { |
|
|
| 798 | if ( uc($ans) eq uc($legalString) ) { |
|
|
| 799 | $sensibleAnswer = 1; |
|
|
| 800 | last; |
|
|
| 801 | } |
|
|
| 802 | } |
|
|
| 803 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
|
|
| 804 | |
|
|
| 805 | $ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) |
|
|
| 806 | unless ($sensibleAnswer); |
|
|
| 807 | $ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
|
|
| 808 | } |
|
|
| 809 | } |
|
|
| 810 | elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 0) ) { |
|
|
| 811 | my $numeric_answer_evaluator = std_num_cmp($correctAnswer,$relpercentTol,$format,$zeroLevel,$zeroLevelTol); |
|
|
| 812 | $ans_hash = &$numeric_answer_evaluator($ans); |
|
|
| 813 | } |
|
|
| 814 | elsif ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 0) ) { |
|
|
| 815 | my $numeric_answer_evaluator = std_num_cmp(1); |
|
|
| 816 | $ans_hash = &$numeric_answer_evaluator($ans); |
|
|
| 817 | $ans_hash -> setKeys( 'score' => 0, |
|
|
| 818 | 'correct_ans' => $correctAnswer |
|
|
| 819 | ); |
|
|
| 820 | } |
|
|
| 821 | elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 1) ) { |
|
|
| 822 | my $string_answer_evaluator = std_str_cmp('bad'); |
|
|
| 823 | $ans_hash = &$string_answer_evaluator($ans); |
|
|
| 824 | |
|
|
| 825 | $ans_hash -> setKeys( 'score' => 0, |
|
|
| 826 | 'correct_ans' => $correctAnswer |
|
|
| 827 | ); |
|
|
| 828 | |
|
|
| 829 | ## find out if string makes sense |
|
|
| 830 | my $sensibleAnswer = 0; |
|
|
| 831 | foreach $legalString (@legalStrings) { |
|
|
| 832 | if ( uc($ans) eq uc($legalString) ) { |
|
|
| 833 | $sensibleAnswer = 1; |
|
|
| 834 | last; |
|
|
| 835 | } |
|
|
| 836 | } |
|
|
| 837 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
|
|
| 838 | |
|
|
| 839 | $ans_hash -> setKeys( 'ans_message' => "Your answer is not a recognized answer" ) |
|
|
| 840 | unless $sensibleAnswer; |
|
|
| 841 | } |
|
|
| 842 | |
|
|
| 843 | return $ans_hash; |
|
|
| 844 | }; |
|
|
| 845 | |
|
|
| 846 | return $ans_evaluator; |
|
|
| 847 | } |
914 | } |
| 848 | |
915 | |
| 849 | =head3 num_cmp() |
916 | =head3 num_cmp() |
| 850 | |
917 | |
| 851 | Compares a number or a list of numbers, using a named hash of options to set |
918 | Compares a number or a list of numbers, using a named hash of options to set |
| … | |
… | |
| 887 | =cut |
954 | =cut |
| 888 | |
955 | |
| 889 | sub num_cmp { |
956 | sub num_cmp { |
| 890 | my $correctAnswer = shift @_; |
957 | my $correctAnswer = shift @_; |
| 891 | my @opt = @_; |
958 | my @opt = @_; |
|
|
959 | my %out_options; |
| 892 | |
960 | |
|
|
961 | ######################################################################### |
|
|
962 | # Retain this first check for backword compatibility. Allows input of the form |
|
|
963 | # num_cmp($ans, 1, '%0.5f') but warns against it |
|
|
964 | ######################################################################### |
|
|
965 | |
| 893 | my %known_options = ( 'mode' => 'std', |
966 | my %known_options = ( 'mode' => 'std', |
| 894 | 'format' => $numFormatDefault, |
967 | 'format' => $numFormatDefault, |
| 895 | 'tol' => $numAbsTolDefault, |
968 | 'tol' => $numAbsTolDefault, |
| 896 | 'relTol' => $numRelPercentTolDefault, |
969 | 'relTol' => $numRelPercentTolDefault, |
| 897 | 'units' => undef, |
970 | 'units' => undef, |
| 898 | 'strings' => undef, |
971 | 'strings' => undef, |
| 899 | 'zeroLevel' => $numZeroLevelDefault, |
972 | 'zeroLevel' => $numZeroLevelDefault, |
| 900 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
973 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 901 | |
974 | 'tolType' => 'relative', |
|
|
975 | 'tolerance' => 1, |
| 902 | 'reltol' => undef, #alternate spelling |
976 | 'reltol' => undef, #alternate spelling |
| 903 | 'unit' => undef #alternate spelling |
977 | 'unit' => undef, #alternate spelling |
| 904 | ); |
978 | 'debug' => 0 |
| 905 | my %in_options; |
979 | |
|
|
980 | ); |
|
|
981 | |
| 906 | my @output_list; |
982 | my @output_list; |
| 907 | my %out_options; |
983 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
| 908 | |
984 | |
| 909 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
985 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
| 910 | ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) { |
986 | ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) { |
| 911 | # unless the first parameter is a list of arrays |
987 | # unless the first parameter is a list of arrays |
| 912 | # or the second parameter is a known option or |
988 | # or the second parameter is a known option or |
| 913 | # no options were used, |
989 | # no options were used, |
| 914 | # use the old num_cmp which does not use options, but has inputs |
990 | # use the old num_cmp which does not use options, but has inputs |
| 915 | # $relPercentTol,$format,$zeroLevel,$zeroLevelTol |
991 | # $relPercentTol,$format,$zeroLevel,$zeroLevelTol |
| 916 | warn "This method of using num_cmp() is deprecated. Please rewrite this" . |
992 | warn "This method of using num_cmp() is deprecated. Please rewrite this" . |
| 917 | " problem using the options style of parameter passing (or" . |
993 | " problem using the options style of parameter passing (or" . |
| 918 | " check that your first option is spelled correctly)."; |
994 | " check that your first option is spelled correctly)."; |
| 919 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
995 | |
| 920 | |
996 | |
| 921 | %out_options = ( 'relTol' => $relPercentTol, |
997 | %out_options = ( 'relTol' => $relPercentTol, |
| 922 | 'format' => $format, |
998 | 'format' => $format, |
| 923 | 'zeroLevel' => $zeroLevel, |
999 | 'zeroLevel' => $zeroLevel, |
| 924 | 'zeroLevelTol' => $zeroLevelTol, |
1000 | 'zeroLevelTol' => $zeroLevelTol, |
| 925 | 'mode' => 'std' |
1001 | 'mode' => 'std' |
| 926 | ); |
1002 | ); |
| 927 | } |
1003 | } |
| 928 | else { |
1004 | # else { |
| 929 | # handle options |
1005 | # # handle options |
| 930 | |
1006 | # |
| 931 | check_option_list( @opt ); |
1007 | # |
|
|
1008 | # @opt = ( 'relTol' => $relPercentTol, |
|
|
1009 | # 'format' => $format, |
|
|
1010 | # 'zeroLevel' => $numZeroLevelDefault, |
|
|
1011 | # 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
1012 | # 'mode' => 'std' |
|
|
1013 | # ); |
|
|
1014 | # } |
|
|
1015 | ######################################################################### |
|
|
1016 | # Now handle the options assuming they are entered in the form |
|
|
1017 | # num_cmp($ans, relTol=>1, format=>'%0.5f') |
|
|
1018 | ######################################################################### |
| 932 | %in_options = @opt; |
1019 | %out_options = @opt; |
|
|
1020 | assign_option_aliases( \%out_options, |
|
|
1021 | 'reltol' => 'relTol', |
|
|
1022 | 'unit' => 'units', |
|
|
1023 | ); |
| 933 | |
1024 | |
| 934 | # both spellings maintained for compatibility |
1025 | |
| 935 | # relTol is preferred |
1026 | |
| 936 | if( defined( $in_options{'reltol'} ) ) { |
1027 | |
| 937 | $in_options{'relTol'} = $in_options{'reltol'}; |
1028 | set_default_options( \%out_options, |
| 938 | delete $in_options{'reltol'}; |
1029 | 'tolType' => (defined($out_options{tol}) ) ? 'absolute' : 'relative', |
|
|
1030 | 'tolerance' => (defined($out_options{tol}) ) ? $numAbsTolDefault : $numRelPercentTolDefault, |
|
|
1031 | 'mode' => 'std', |
|
|
1032 | 'format' => $numFormatDefault, |
|
|
1033 | 'tol' => $numAbsTolDefault, |
|
|
1034 | 'relTol' => $numRelPercentTolDefault, |
|
|
1035 | 'units' => undef, |
|
|
1036 | 'strings' => undef, |
|
|
1037 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
1038 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
1039 | 'debug' => 0, |
| 939 | } |
1040 | |
|
|
1041 | ); |
| 940 | |
1042 | |
| 941 | # both spellings maintained for compatibility |
|
|
| 942 | # units is preferred |
|
|
| 943 | if( defined( $in_options{'unit'} ) ) { |
|
|
| 944 | $in_options{'units'} = $in_options{'unit'}; |
|
|
| 945 | delete $in_options{'unit'}; |
|
|
| 946 | } |
|
|
| 947 | |
1043 | |
|
|
1044 | |
|
|
1045 | |
|
|
1046 | |
|
|
1047 | |
| 948 | # can't use both units and strings |
1048 | # can't use both units and strings |
| 949 | if( defined( $in_options{'units'} ) && defined( $in_options{'strings'} ) ) { |
1049 | if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) { |
| 950 | warn "Can't use both 'units' and 'strings' in the same problem " . |
1050 | warn "Can't use both 'units' and 'strings' in the same problem " . |
| 951 | "(check your parameters to num_cmp() )"; |
1051 | "(check your parameters to num_cmp() )"; |
|
|
1052 | |
| 952 | } |
1053 | } |
| 953 | |
1054 | |
| 954 | #%out_options = %known_options; |
|
|
| 955 | foreach my $opt_name (keys %in_options) { |
|
|
| 956 | |
1055 | |
| 957 | if( exists( $known_options{$opt_name} ) ) { |
|
|
| 958 | $out_options{$opt_name} = $in_options{$opt_name}; |
|
|
| 959 | } |
|
|
| 960 | else { |
|
|
| 961 | die "Option $opt_name is not defined for num_cmp. Answer is $correctAnswer; " . |
|
|
| 962 | "Default options are:<BR> ", pretty_print(\%known_options); |
|
|
| 963 | } |
|
|
| 964 | } |
|
|
| 965 | } |
|
|
| 966 | |
|
|
| 967 | # set tolerance flags -- note that the order of testing means that |
|
|
| 968 | # relative tolerance is the default |
|
|
| 969 | my ($tolType, $tol); |
1056 | # my ($tolType, $tol); |
| 970 | |
1057 | if ($out_options{tolType} eq 'absolute') { |
| 971 | if ( defined( $out_options{'tol'} ) ) { |
1058 | $out_options{'tolerance'}=$out_options{'tol'}; |
| 972 | $tolType = 'absolute'; |
1059 | delete($out_options{'relTol'}) if exists( $out_options{'relTol'} ); |
| 973 | $tol = $out_options{'tol'}; |
|
|
| 974 | } |
|
|
| 975 | else { |
1060 | } else { |
| 976 | $tolType = 'relative'; |
1061 | $out_options{'tolerance'}=$out_options{'relTol'}; |
| 977 | $tol = $out_options{'relTol'}; |
1062 | # delete($out_options{'tol'}) if exists( $out_options{'tol'} ); |
| 978 | } |
1063 | } |
| 979 | |
1064 | |
| 980 | # thread over lists |
1065 | # thread over lists |
| 981 | my @ans_list = (); |
1066 | my @ans_list = (); |
| 982 | |
1067 | |
| … | |
… | |
| 984 | @ans_list = @{$correctAnswer}; |
1069 | @ans_list = @{$correctAnswer}; |
| 985 | } |
1070 | } |
| 986 | else { |
1071 | else { |
| 987 | push( @ans_list, $correctAnswer ); |
1072 | push( @ans_list, $correctAnswer ); |
| 988 | } |
1073 | } |
|
|
1074 | |
| 989 | # produce answer evaluators |
1075 | # produce answer evaluators |
| 990 | foreach my $ans (@ans_list) { |
1076 | foreach my $ans (@ans_list) { |
| 991 | if( defined( $out_options{'units'} ) ) { |
1077 | if( defined( $out_options{'units'} ) ) { |
| 992 | $ans = "$ans $out_options{'units'}"; |
1078 | $ans = "$ans $out_options{'units'}"; |
| 993 | push( @output_list, numerical_compare_with_units($ans, %out_options) ); |
|
|
| 994 | } |
1079 | |
|
|
1080 | push( @output_list, NUM_CMP( 'correctAnswer' => $ans, |
|
|
1081 | 'tolerance' => $out_options{tolerance}, |
|
|
1082 | 'tolType' => $out_options{tolType}, |
|
|
1083 | 'format' => $out_options{'format'}, |
|
|
1084 | 'mode' => $out_options{'mode'}, |
|
|
1085 | 'zeroLevel' => $out_options{'zeroLevel'}, |
|
|
1086 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
|
|
1087 | 'debug' => $out_options{'debug'}, |
|
|
1088 | 'units' => $out_options{'units'}, |
|
|
1089 | ) |
|
|
1090 | ); |
|
|
1091 | } |
| 995 | elsif( defined( $out_options{'strings'} ) ) { |
1092 | elsif( defined( $out_options{'strings'} ) ) { |
| 996 | if( defined $out_options{'tol'} ) { |
1093 | #if( defined $out_options{'tol'} ) { |
| 997 | warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
1094 | # warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
| 998 | "compare, which currently only uses relative tolerance. The default " . |
1095 | # "compare, which currently only uses relative tolerance. The default " . |
| 999 | "tolerance will be used."; |
1096 | # "tolerance will be used."; |
| 1000 | } |
|
|
| 1001 | |
|
|
| 1002 | push( @output_list, std_num_str_cmp( $ans, $out_options{'strings'}, |
|
|
| 1003 | $out_options{'relTol'}, |
|
|
| 1004 | $out_options{'format'}, |
|
|
| 1005 | $out_options{'zeroLevel'}, |
|
|
| 1006 | $out_options{'zeroLevelTol'} |
|
|
| 1007 | ) |
|
|
| 1008 | ); |
|
|
| 1009 | } |
1097 | #} |
| 1010 | else { |
1098 | |
|
|
1099 | push( @output_list, NUM_CMP( 'correctAnswer' => $ans, |
|
|
1100 | 'tolerance' => $out_options{tolerance}, |
|
|
1101 | 'tolType' => $out_options{tolType}, |
|
|
1102 | 'format' => $out_options{'format'}, |
|
|
1103 | 'mode' => $out_options{'mode'}, |
|
|
1104 | 'zeroLevel' => $out_options{'zeroLevel'}, |
|
|
1105 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
|
|
1106 | 'debug' => $out_options{'debug'}, |
|
|
1107 | 'strings' => $out_options{'strings'}, |
|
|
1108 | |
|
|
1109 | ) |
|
|
1110 | ); |
|
|
1111 | } |
|
|
1112 | else { |
|
|
1113 | |
| 1011 | push(@output_list, |
1114 | push(@output_list, |
| 1012 | NUM_CMP( 'correctAnswer' => $ans, |
1115 | NUM_CMP( 'correctAnswer' => $ans, |
| 1013 | 'tolerance' => $tol, |
1116 | 'tolerance' => $out_options{tolerance}, |
| 1014 | 'tolType' => $tolType, |
1117 | 'tolType' => $out_options{tolType}, |
| 1015 | 'format' => $out_options{'format'}, |
1118 | 'format' => $out_options{'format'}, |
| 1016 | 'mode' => $out_options{'mode'}, |
1119 | 'mode' => $out_options{'mode'}, |
| 1017 | 'zeroLevel' => $out_options{'zeroLevel'}, |
1120 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1018 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
1121 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
|
|
1122 | 'debug' => $out_options{'debug'}, |
|
|
1123 | |
| 1019 | ), |
1124 | ), |
| 1020 | ); |
1125 | ); |
|
|
1126 | } |
| 1021 | } |
1127 | } |
| 1022 | } |
1128 | |
| 1023 | |
|
|
| 1024 | return @output_list; |
1129 | return @output_list; |
| 1025 | } |
1130 | } |
| 1026 | |
1131 | |
| 1027 | #legacy code for compatability purposes |
1132 | #legacy code for compatability purposes |
| 1028 | sub num_rel_cmp { # compare numbers |
1133 | sub num_rel_cmp { # compare numbers |
| 1029 | std_num_cmp( @_ ); |
1134 | std_num_cmp( @_ ); |
| 1030 | } |
1135 | } |
| 1031 | |
1136 | |
| 1032 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
1137 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
| 1033 | ## |
1138 | ## |
| 1034 | ## IN: a hash containing the following items (error-checking to be added later?): |
1139 | ## IN: a hash containing the following items (error-checking to be added later?): |
| … | |
… | |
| 1038 | ## format -- the display format of the answer |
1143 | ## format -- the display format of the answer |
| 1039 | ## mode -- one of 'std', 'strict', 'arith', or 'frac'; |
1144 | ## mode -- one of 'std', 'strict', 'arith', or 'frac'; |
| 1040 | ## determines allowable formats for the input |
1145 | ## determines allowable formats for the input |
| 1041 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
1146 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 1042 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
1147 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
|
|
1148 | |
|
|
1149 | sub compare_numbers { |
|
|
1150 | my ($rh_ans, %options) = @_; |
|
|
1151 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
|
|
1152 | if ($PG_eval_errors) { |
|
|
1153 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
1154 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
1155 | |
|
|
1156 | |
|
|
1157 | } else { |
|
|
1158 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
|
|
1159 | } |
|
|
1160 | |
|
|
1161 | my $permitted_error; |
|
|
1162 | |
|
|
1163 | if ($rh_ans->{tolType} eq 'absolute') { |
|
|
1164 | $permitted_error = $rh_ans->{tolerance}; |
|
|
1165 | |
|
|
1166 | } |
|
|
1167 | elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) { |
|
|
1168 | $permitted_error = $options{zeroLevelTol}; ## want $tol to be non zero |
|
|
1169 | } |
|
|
1170 | else { |
|
|
1171 | $permitted_error = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}); |
|
|
1172 | } |
|
|
1173 | |
|
|
1174 | my $is_a_number = is_a_number($inVal); |
|
|
1175 | $rh_ans->{score} = 1 if ( ($is_a_number) and |
|
|
1176 | (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) ); |
|
|
1177 | if (not $is_a_number) { |
|
|
1178 | $rh_ans->throw_error('EVAL','Your answer does not evaluate to a number'); |
|
|
1179 | } |
|
|
1180 | |
|
|
1181 | $rh_ans; |
|
|
1182 | } |
|
|
1183 | |
| 1043 | sub NUM_CMP { # low level numeric compare |
1184 | sub NUM_CMP { # low level numeric compare |
| 1044 | my %num_params = @_; |
1185 | my %num_params = @_; |
|
|
1186 | |
|
|
1187 | my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); |
|
|
1188 | foreach my $key (@keys) { |
|
|
1189 | warn "$key must be defined in options when calling NUM_CMP" unless defined ($num_params{$key}); |
|
|
1190 | } |
| 1045 | |
1191 | |
| 1046 | my $correctAnswer = $num_params{'correctAnswer'}; |
1192 | my $correctAnswer = $num_params{'correctAnswer'}; |
| 1047 | my $tol = $num_params{'tolerance'}; |
|
|
| 1048 | my $tolType = $num_params{'tolType'}; |
|
|
| 1049 | my $format = $num_params{'format'}; |
1193 | my $format = $num_params{'format'}; |
| 1050 | my $mode = $num_params{'mode'}; |
1194 | my $mode = $num_params{'mode'}; |
|
|
1195 | |
|
|
1196 | # my $tol = $num_params{'tolerance'}; |
|
|
1197 | # my $tolType = $num_params{'tolType'}; |
| 1051 | my $zeroLevel = $num_params{'zeroLevel'}; |
1198 | # my $zeroLevel = $num_params{'zeroLevel'}; |
| 1052 | my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
1199 | # my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
| 1053 | |
1200 | |
| 1054 | if( $tolType eq 'relative' ) { |
1201 | if( $num_params{tolType} eq 'relative' ) { |
| 1055 | $tol = $numRelPercentTolDefault unless defined $tol; |
1202 | $num_params{'tolerance'} = .01*$num_params{'tolerance'}; |
| 1056 | $tol *= .01; |
1203 | } |
|
|
1204 | |
|
|
1205 | #$format = $numFormatDefault unless defined $format; |
|
|
1206 | #$mode = 'std' unless defined $mode; |
|
|
1207 | #$zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
|
|
1208 | #$zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol; |
|
|
1209 | |
|
|
1210 | my $formattedCorrectAnswer; |
|
|
1211 | my $correct_units; |
|
|
1212 | my $correct_num_answer; |
|
|
1213 | my %correct_units; |
|
|
1214 | my $corrAnswerIsString = 0; |
|
|
1215 | |
|
|
1216 | |
|
|
1217 | if (defined($num_params{units}) && $num_params{units}) { |
|
|
1218 | $correctAnswer = str_filters( $correctAnswer, 'trim_whitespace' ); |
|
|
1219 | # units are in form stuff space units where units contains no spaces. |
|
|
1220 | |
|
|
1221 | ($correct_num_answer, $correct_units) = $correctAnswer =~ /^(.*)\s+([^\s]*)$/; |
|
|
1222 | %correct_units = Units::evaluate_units($correct_units); |
|
|
1223 | if ( defined( $correct_units{'ERROR'} ) ) { |
|
|
1224 | warn ("ERROR: The answer \"$correctAnswer\" in the problem definition cannot be parsed:\n" . |
|
|
1225 | "$correct_units{'ERROR'}\n"); |
|
|
1226 | } |
|
|
1227 | # $formattedCorrectAnswer = spf($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
|
|
1228 | $formattedCorrectAnswer = prfmt($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
|
|
1229 | |
|
|
1230 | } elsif (defined($num_params{strings}) && $num_params{strings}) { |
|
|
1231 | |
|
|
1232 | my $legalString = ''; |
|
|
1233 | my @legalStrings = @{$num_params{strings}}; |
|
|
1234 | $correct_num_answer = $correctAnswer; |
|
|
1235 | $formattedCorrectAnswer = $correctAnswer; |
|
|
1236 | foreach $legalString (@legalStrings) { |
|
|
1237 | if ( uc($correctAnswer) eq uc($legalString) ) { |
|
|
1238 | $corrAnswerIsString = 1; |
|
|
1239 | last; |
|
|
1240 | } |
|
|
1241 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
|
|
1242 | |
|
|
1243 | |
|
|
1244 | } else { |
|
|
1245 | $correct_num_answer = $correctAnswer; |
|
|
1246 | $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} ); |
|
|
1247 | } |
|
|
1248 | |
|
|
1249 | $correct_num_answer = math_constants($correct_num_answer); |
|
|
1250 | |
|
|
1251 | my $PGanswerMessage = ''; |
|
|
1252 | |
|
|
1253 | my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); |
|
|
1254 | |
|
|
1255 | if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) { |
|
|
1256 | ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer); |
| 1057 | } |
1257 | } |
| 1058 | else { |
1258 | else { |
| 1059 | $tol = $numAbsTolDefault unless defined $tol; |
1259 | $PG_eval_errors = ' '; |
| 1060 | } |
1260 | } |
| 1061 | $format = $numFormatDefault unless defined $format; |
|
|
| 1062 | $mode = 'std' unless defined $mode; |
|
|
| 1063 | $zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
|
|
| 1064 | $zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol; |
|
|
| 1065 | |
1261 | |
| 1066 | my $formattedCorrectAnswer = prfmt( $correctAnswer, $format ); |
1262 | if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { |
|
|
1263 | ##error message from eval or above |
|
|
1264 | warn "Error in 'correct' answer: $PG_eval_errors<br> |
|
|
1265 | The answer $correctAnswer evaluates to $correctVal, |
|
|
1266 | which cannot be interpreted as a number. "; |
|
|
1267 | |
|
|
1268 | } |
|
|
1269 | ######################################################################### |
| 1067 | |
1270 | |
| 1068 | my $answer_evaluator = sub { |
1271 | #construct the answer evaluator |
| 1069 | my $in = shift @_; |
1272 | my $answer_evaluator = new AnswerEvaluator; |
| 1070 | $in = '' unless defined $in; |
1273 | $answer_evaluator->{debug} = $num_params{debug}; |
| 1071 | my $score = 0; |
1274 | $answer_evaluator->ans_hash( correct_ans => $correct_num_answer, |
| 1072 | my $original_student_answer = $in; |
1275 | type => "${mode}_number", |
| 1073 | my $parser = new AlgParserWithImplicitExpand; |
1276 | tolerance => $num_params{tolerance}, |
| 1074 | my $ret = $parser -> parse($in); |
1277 | tolType => $num_params{tolType}, |
| 1075 | my $preview_text_string = ''; |
1278 | units => $correct_units, |
| 1076 | my $preview_latex_string = ''; |
1279 | original_correct_ans => $formattedCorrectAnswer, |
|
|
1280 | rh_correct_units => \%correct_units, |
|
|
1281 | answerIsString => $corrAnswerIsString, |
|
|
1282 | ); |
|
|
1283 | my ($in, $formattedSubmittedAnswer); |
|
|
1284 | $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift; |
|
|
1285 | $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;} |
|
|
1286 | ); |
|
|
1287 | if (defined($num_params{units}) && $num_params{units}) { |
|
|
1288 | $answer_evaluator->install_pre_filter(\&check_units); |
|
|
1289 | } |
|
|
1290 | if (defined($num_params{strings}) && $num_params{strings}) { |
|
|
1291 | $answer_evaluator->install_pre_filter(\&check_strings, %num_params); |
|
|
1292 | } |
| 1077 | |
1293 | |
| 1078 | if ( ref($ret) ) { ## parsed successfully |
1294 | $answer_evaluator->install_pre_filter(\&check_syntax); |
| 1079 | $parser -> tostring(); |
1295 | |
| 1080 | $parser -> normalize(); |
1296 | $answer_evaluator->install_pre_filter(\&math_constants); |
| 1081 | $in = $parser -> tostring(); |
1297 | |
| 1082 | $preview_text_string = $in; |
1298 | if ($mode eq 'std') { |
| 1083 | $preview_latex_string = $parser -> tolatex(); |
1299 | # do nothing |
|
|
1300 | } elsif ($mode eq 'strict') { |
|
|
1301 | $answer_evaluator->install_pre_filter(\&is_a_number); |
|
|
1302 | } elsif ($mode eq 'arith') { |
|
|
1303 | $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression); |
|
|
1304 | } elsif ($mode eq 'frac') { |
|
|
1305 | $answer_evaluator->install_pre_filter(\&is_a_fraction); |
| 1084 | |
1306 | |
|
|
1307 | } else { |
|
|
1308 | $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; |
|
|
1309 | $formattedSubmittedAnswer = $in; |
| 1085 | } |
1310 | } |
| 1086 | else { ## error in parsing |
1311 | |
| 1087 | my $ans_hash = new AnswerHash( |
1312 | if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. |
| 1088 | 'score' => $score, |
1313 | $answer_evaluator->install_evaluator(\&compare_numbers, %num_params); |
| 1089 | 'correct_ans' => $formattedCorrectAnswer, |
1314 | } |
| 1090 | 'student_ans' => "error: $parser->{htmlerror}", |
1315 | |
| 1091 | 'ans_message' => $parser -> {error_msg}, |
1316 | |
| 1092 | 'type' => "${mode}_number", |
1317 | ############################################################################### |
| 1093 | 'preview_text_string' => $preview_text_string, |
1318 | # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's |
| 1094 | 'preview_latex_string' => $preview_latex_string, |
1319 | # can be displayed in the answer message. This may still cause a few anomolies when strings are used |
| 1095 | 'original_student_ans' => $original_student_answer |
1320 | # |
| 1096 | ); |
1321 | ############################################################################### |
| 1097 | |
1322 | |
| 1098 | return $ans_hash; |
1323 | $answer_evaluator->install_post_filter(\&fix_answers_for_display); |
|
|
1324 | |
|
|
1325 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
|
|
1326 | return $rh_ans unless $rh_ans->catch_error('EVAL'); |
|
|
1327 | $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; |
|
|
1328 | $rh_ans->clear_error('EVAL'); } ); |
|
|
1329 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); |
|
|
1330 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('UNITS'); } ); |
|
|
1331 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('NUMBER'); } ); |
|
|
1332 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); |
|
|
1333 | |
|
|
1334 | |
|
|
1335 | $answer_evaluator; |
|
|
1336 | } |
|
|
1337 | |
|
|
1338 | sub fix_answers_for_display { |
|
|
1339 | my ($rh_ans, %options) = @_; |
|
|
1340 | if ( $rh_ans->{answerIsString} ==1) { |
|
|
1341 | $rh_ans = evaluatesToNumber ($rh_ans, %options); |
|
|
1342 | } |
|
|
1343 | if (defined ($rh_ans->{student_units})) { |
|
|
1344 | $rh_ans->{student_ans} = $rh_ans->{student_ans}. ' '. $rh_ans->{student_units}; |
|
|
1345 | } |
|
|
1346 | $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans}; |
|
|
1347 | $rh_ans; |
|
|
1348 | } |
|
|
1349 | |
|
|
1350 | sub evaluatesToNumber { |
|
|
1351 | my ($rh_ans, %options) = @_; |
|
|
1352 | if (is_a_numeric_expression($rh_ans->{student_ans})) { |
|
|
1353 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
|
|
1354 | if ($PG_eval_errors) { # this if statement should never be run |
|
|
1355 | # change nothing |
|
|
1356 | } else { |
|
|
1357 | # change this |
|
|
1358 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
| 1099 | } |
1359 | } |
| 1100 | |
|
|
| 1101 | my $PGanswerMessage = ''; |
|
|
| 1102 | |
|
|
| 1103 | my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); |
|
|
| 1104 | |
|
|
| 1105 | $inVal = ''; |
|
|
| 1106 | $correctAnswer = math_constants($correctAnswer); |
|
|
| 1107 | my $formattedSubmittedAnswer = ''; |
|
|
| 1108 | |
|
|
| 1109 | #special variable $@ holds the last error from a Perl eval statement |
|
|
| 1110 | $@=''; |
|
|
| 1111 | |
|
|
| 1112 | if ($correctAnswer =~ /\S/) { |
|
|
| 1113 | ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correctAnswer); |
|
|
| 1114 | } |
1360 | } |
| 1115 | else { |
1361 | $rh_ans; |
| 1116 | $PG_eval_errors = ' '; |
1362 | } |
| 1117 | } |
|
|
| 1118 | |
1363 | |
| 1119 | if ( $PG_eval_errors or not is_a_number($correctVal) ) { ##error message from eval or above |
1364 | sub is_a_numeric_expression { |
| 1120 | $formattedSubmittedAnswer = $PG_eval_errors; |
1365 | my $testString = shift; |
| 1121 | $formattedSubmittedAnswer = clean_up_error_msg($formattedSubmittedAnswer); |
1366 | my $is_a_numeric_expression = 0; |
| 1122 | $PGanswerMessage = 'Tell your professor that there is an error in this problem'; |
|
|
| 1123 | my $ans_hash = new AnswerHash( |
|
|
| 1124 | 'score' => $score, |
|
|
| 1125 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1126 | 'student_ans' => $formattedSubmittedAnswer, |
|
|
| 1127 | 'ans_message' => $PGanswerMessage, |
|
|
| 1128 | 'type' => 'number', |
|
|
| 1129 | 'preview_text_string' => $preview_text_string, |
|
|
| 1130 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1131 | 'original_student_ans' => $original_student_answer |
|
|
| 1132 | ); |
|
|
| 1133 | |
|
|
| 1134 | return $ans_hash; |
|
|
| 1135 | } |
|
|
| 1136 | |
|
|
| 1137 | $in = &math_constants($in); |
|
|
| 1138 | |
|
|
| 1139 | MODE_CASE: { ## bare block for "case" statement |
|
|
| 1140 | if ($mode eq 'std') { |
|
|
| 1141 | last MODE_CASE; |
|
|
| 1142 | } |
|
|
| 1143 | elsif ($mode eq 'strict') { |
|
|
| 1144 | unless (is_a_number($in)) { |
|
|
| 1145 | $PGanswerMessage = 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'; |
|
|
| 1146 | $formattedSubmittedAnswer = 'Incorrect number format'; |
|
|
| 1147 | } |
|
|
| 1148 | else { |
|
|
| 1149 | last MODE_CASE; |
|
|
| 1150 | } |
|
|
| 1151 | } |
|
|
| 1152 | elsif ($mode eq 'arith') { |
|
|
| 1153 | unless (is_an_arithmetic_expression($in)) { |
|
|
| 1154 | $PGanswerMessage = 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2'; |
|
|
| 1155 | $formattedSubmittedAnswer = 'Not an arithmetic expression'; |
|
|
| 1156 | } |
|
|
| 1157 | else { |
|
|
| 1158 | last MODE_CASE; |
|
|
| 1159 | } |
|
|
| 1160 | } |
|
|
| 1161 | elsif ($mode eq 'frac') { |
|
|
| 1162 | unless (is_a_fraction($in)) { |
|
|
| 1163 | $PGanswerMessage = 'You must enter a number or fraction , e.g. -6 or 7/13'; |
|
|
| 1164 | $formattedSubmittedAnswer = 'Not a number or fraction'; |
|
|
| 1165 | } |
|
|
| 1166 | else { |
|
|
| 1167 | last MODE_CASE; |
|
|
| 1168 | } |
|
|
| 1169 | } |
|
|
| 1170 | else { |
|
|
| 1171 | $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; |
|
|
| 1172 | $formattedSubmittedAnswer = $in; |
|
|
| 1173 | } |
|
|
| 1174 | |
|
|
| 1175 | my $ans_hash = new AnswerHash( |
|
|
| 1176 | score => $score, |
|
|
| 1177 | correct_ans => $formattedCorrectAnswer, |
|
|
| 1178 | student_ans => $formattedSubmittedAnswer, |
|
|
| 1179 | ans_message => $PGanswerMessage, |
|
|
| 1180 | type => "${mode}_number", |
|
|
| 1181 | preview_text_string => $preview_text_string, |
|
|
| 1182 | preview_latex_string => $preview_latex_string, |
|
|
| 1183 | original_student_ans => $original_student_answer |
|
|
| 1184 | ); |
|
|
| 1185 | |
|
|
| 1186 | return $ans_hash; |
|
|
| 1187 | } # end of MODE_CASES bare block |
|
|
| 1188 | |
|
|
| 1189 | $@ = ''; |
|
|
| 1190 | if ($in =~ /\S/) { |
|
|
| 1191 | |
|
|
| 1192 | ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in); |
1367 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($testString); |
| 1193 | } |
|
|
| 1194 | else { |
|
|
| 1195 | $PG_eval_errors = ' '; |
|
|
| 1196 | } |
|
|
| 1197 | |
|
|
| 1198 | if ($PG_eval_errors) { ##error message from eval or above |
|
|
| 1199 | $formattedSubmittedAnswer = $PG_eval_errors; |
|
|
| 1200 | $formattedSubmittedAnswer =clean_up_error_msg($formattedSubmittedAnswer); |
|
|
| 1201 | $PGanswerMessage = 'There is a syntax error in your answer'; |
|
|
| 1202 | $PGanswerMessage = '' if $PG_eval_errors eq ' '; |
|
|
| 1203 | my $ans_hash = new AnswerHash( |
|
|
| 1204 | 'score' => $score, |
|
|
| 1205 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1206 | 'student_ans' => $formattedSubmittedAnswer, |
|
|
| 1207 | 'ans_message' => $PGanswerMessage, |
|
|
| 1208 | 'type' => "${mode}_number", |
|
|
| 1209 | 'preview_text_string' => $preview_text_string, |
|
|
| 1210 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1211 | 'original_student_ans' => $original_student_answer |
|
|
| 1212 | ); |
|
|
| 1213 | |
|
|
| 1214 | return $ans_hash; |
|
|
| 1215 | } |
|
|
| 1216 | else { |
|
|
| 1217 | $formattedSubmittedAnswer = prfmt($inVal,$format); |
|
|
| 1218 | } |
|
|
| 1219 | |
|
|
| 1220 | my $permitted_error; |
|
|
| 1221 | if (defined($tolType) && $tolType eq 'absolute') { |
|
|
| 1222 | $permitted_error = $tol; |
|
|
| 1223 | } |
|
|
| 1224 | elsif ( abs($correctVal) <= $zeroLevel) { |
|
|
| 1225 | $permitted_error = $zeroLevelTol; ## want $tol to be non zero |
|
|
| 1226 | } |
|
|
| 1227 | else { |
|
|
| 1228 | $permitted_error = abs($tol*$correctVal); |
|
|
| 1229 | } |
|
|
| 1230 | |
|
|
| 1231 | my $is_a_number = is_a_number($inVal); |
|
|
| 1232 | $score = 1 if ( ($is_a_number) and |
|
|
| 1233 | (abs( $inVal - $correctVal ) <= $permitted_error) ); |
|
|
| 1234 | if ($PG_eval_errors) { |
1368 | if ($PG_eval_errors) { |
| 1235 | $PGanswerMessage = 'There is a syntax error in your answer'; |
1369 | $is_a_numeric_expression = 0; |
|
|
1370 | } else { |
|
|
1371 | $is_a_numeric_expression = 1; |
| 1236 | } |
1372 | |
| 1237 | elsif (not $is_a_number) { |
|
|
| 1238 | $PGanswerMessage = 'Your answer does not evaluate to a number'; |
|
|
| 1239 | } |
1373 | } |
| 1240 | |
1374 | |
| 1241 | my $ans_hash = new AnswerHash( |
1375 | $is_a_numeric_expression; |
| 1242 | 'score' => $score, |
|
|
| 1243 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1244 | 'student_ans' => $formattedSubmittedAnswer, |
|
|
| 1245 | 'ans_message' => $PGanswerMessage, |
|
|
| 1246 | 'type' => "${mode}_number", |
|
|
| 1247 | 'preview_text_string' => $preview_text_string, |
|
|
| 1248 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1249 | 'original_student_ans' => $original_student_answer |
|
|
| 1250 | ); |
|
|
| 1251 | |
|
|
| 1252 | return $ans_hash; |
|
|
| 1253 | }; |
|
|
| 1254 | |
|
|
| 1255 | return $answer_evaluator; |
|
|
| 1256 | } |
1376 | } |
| 1257 | |
|
|
| 1258 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
|
|
| 1259 | sub NUM_CMP_LIST { # low level numeric list compare |
|
|
| 1260 | my %num_params = @_; |
|
|
| 1261 | |
|
|
| 1262 | my @outputList; |
|
|
| 1263 | my $ans; |
|
|
| 1264 | |
|
|
| 1265 | while ( @{$num_params{'answerList'}} ) { |
|
|
| 1266 | $ans = shift @{$num_params{'answerList'}}; |
|
|
| 1267 | push( @outputList, NUM_CMP( 'correctAnswer' => $ans, |
|
|
| 1268 | 'tolerance' => $num_params{'tolerance'}, |
|
|
| 1269 | 'tolType' => $num_params{'tolType'}, |
|
|
| 1270 | 'format' => $num_params{'format'}, |
|
|
| 1271 | 'mode' => $num_params{'mode'}, |
|
|
| 1272 | 'zeroLevel' => $num_params{'zeroLevel'}, |
|
|
| 1273 | 'zeroLevelTol' => $num_params{'zeroLevelTol'} |
|
|
| 1274 | ) |
|
|
| 1275 | ); |
|
|
| 1276 | } |
|
|
| 1277 | |
|
|
| 1278 | return @outputList; |
|
|
| 1279 | } |
|
|
| 1280 | |
|
|
| 1281 | |
1377 | |
| 1282 | |
1378 | |
| 1283 | ########################################################################## |
1379 | ########################################################################## |
| 1284 | ########################################################################## |
1380 | ########################################################################## |
| 1285 | ## Function answer evaluators |
1381 | ## Function answer evaluators |
| … | |
… | |
| 1468 | 'params' => [], |
1564 | 'params' => [], |
| 1469 | 'limits' => [ [0,1], [0,1]], |
1565 | 'limits' => [ [0,1], [0,1]], |
| 1470 | 'reltol' => $main::functRelPercentTolDefault, |
1566 | 'reltol' => $main::functRelPercentTolDefault, |
| 1471 | 'numPoints' => $main::functNumOfPoints, |
1567 | 'numPoints' => $main::functNumOfPoints, |
| 1472 | 'zeroLevel' => $main::functZeroLevelDefault, |
1568 | 'zeroLevel' => $main::functZeroLevelDefault, |
| 1473 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
1569 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
| 1474 | 'debug' => 0, |
1570 | 'debug' => 0, |
| 1475 | ); |
1571 | ); |
| 1476 | |
1572 | |
| 1477 | my $var_ref = $options{'vars'}; |
1573 | my $var_ref = $options{'vars'}; |
| 1478 | my $ra_params = $options{ 'params'}; |
1574 | my $ra_params = $options{ 'params'}; |
| … | |
… | |
| 1480 | my $relPercentTol= $options{'reltol'}; |
1576 | my $relPercentTol= $options{'reltol'}; |
| 1481 | my $numPoints = $options{'numPoints'}; |
1577 | my $numPoints = $options{'numPoints'}; |
| 1482 | my $zeroLevel = $options{'zeroLevel'}; |
1578 | my $zeroLevel = $options{'zeroLevel'}; |
| 1483 | my $zeroLevelTol = $options{'zeroLevelTol'}; |
1579 | my $zeroLevelTol = $options{'zeroLevelTol'}; |
| 1484 | |
1580 | |
| 1485 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1581 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1486 | 'var' => $var_ref, |
1582 | 'var' => $var_ref, |
| 1487 | 'limits' => $limit_ref, |
1583 | 'limits' => $limit_ref, |
| 1488 | 'tolerance' => $relPercentTol, |
1584 | 'tolerance' => $relPercentTol, |
| 1489 | 'tolType' => 'relative', |
1585 | 'tolType' => 'relative', |
| 1490 | 'numPoints' => $numPoints, |
1586 | 'numPoints' => $numPoints, |
| 1491 | 'mode' => 'std', |
1587 | 'mode' => 'std', |
| 1492 | 'maxConstantOfIntegration' => 10**100, |
1588 | 'maxConstantOfIntegration' => 10**100, |
| 1493 | 'zeroLevel' => $zeroLevel, |
1589 | 'zeroLevel' => $zeroLevel, |
| 1494 | 'zeroLevelTol' => $zeroLevelTol, |
1590 | 'zeroLevelTol' => $zeroLevelTol, |
| 1495 | 'scale_norm' => 1, |
1591 | 'scale_norm' => 1, |
| 1496 | 'params' => $ra_params, |
1592 | 'params' => $ra_params, |
| 1497 | 'debug' => $options{debug} , |
1593 | 'debug' => $options{debug} , |
| 1498 | ); |
1594 | ); |
| 1499 | |
1595 | |
| 1500 | } |
1596 | } |
| 1501 | |
1597 | |
| 1502 | sub function_cmp { |
1598 | sub function_cmp { |
| … | |
… | |
| 1504 | |
1600 | |
| 1505 | if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) { |
1601 | if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) { |
| 1506 | function_invalid_params( $correctEqn ); |
1602 | function_invalid_params( $correctEqn ); |
| 1507 | } |
1603 | } |
| 1508 | else { |
1604 | else { |
| 1509 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1605 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1510 | 'var' => $var, |
1606 | 'var' => $var, |
| 1511 | 'limits' => [$llimit, $ulimit], |
1607 | 'limits' => [$llimit, $ulimit], |
| 1512 | 'tolerance' => $relPercentTol, |
1608 | 'tolerance' => $relPercentTol, |
| 1513 | 'tolType' => 'relative', |
1609 | 'tolType' => 'relative', |
| 1514 | 'numPoints' => $numPoints, |
1610 | 'numPoints' => $numPoints, |
| 1515 | 'mode' => 'std', |
1611 | 'mode' => 'std', |
| 1516 | 'maxConstantOfIntegration' => 0, |
1612 | 'maxConstantOfIntegration' => 0, |
| 1517 | 'zeroLevel' => $zeroLevel, |
1613 | 'zeroLevel' => $zeroLevel, |
| 1518 | 'zeroLevelTol' => $zeroLevelTol |
1614 | 'zeroLevelTol' => $zeroLevelTol |
| 1519 | ); |
1615 | ); |
| 1520 | } |
1616 | } |
| 1521 | } |
1617 | } |
| 1522 | |
1618 | |
| 1523 | sub function_cmp_up_to_constant { ## for antiderivative problems |
1619 | sub function_cmp_up_to_constant { ## for antiderivative problems |
| … | |
… | |
| 1525 | |
1621 | |
| 1526 | if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) { |
1622 | if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) { |
| 1527 | function_invalid_params( $correctEqn ); |
1623 | function_invalid_params( $correctEqn ); |
| 1528 | } |
1624 | } |
| 1529 | else { |
1625 | else { |
| 1530 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1626 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1531 | 'var' => $var, |
1627 | 'var' => $var, |
| 1532 | 'limits' => [$llimit, $ulimit], |
1628 | 'limits' => [$llimit, $ulimit], |
| 1533 | 'tolerance' => $relPercentTol, |
1629 | 'tolerance' => $relPercentTol, |
| 1534 | 'tolType' => 'relative', |
1630 | 'tolType' => 'relative', |
| 1535 | 'numPoints' => $numPoints, |
1631 | 'numPoints' => $numPoints, |
| 1536 | 'mode' => 'antider', |
1632 | 'mode' => 'antider', |
| 1537 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
1633 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
| 1538 | 'zeroLevel' => $zeroLevel, |
1634 | 'zeroLevel' => $zeroLevel, |
| 1539 | 'zeroLevelTol' => $zeroLevelTol |
1635 | 'zeroLevelTol' => $zeroLevelTol |
| 1540 | ); |
1636 | ); |
| 1541 | } |
1637 | } |
| 1542 | } |
1638 | } |
| 1543 | |
1639 | |
| 1544 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
1640 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
| … | |
… | |
| 1571 | if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) { |
1667 | if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) { |
| 1572 | function_invalid_params( $correctEqn ); |
1668 | function_invalid_params( $correctEqn ); |
| 1573 | } |
1669 | } |
| 1574 | |
1670 | |
| 1575 | else { |
1671 | else { |
| 1576 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1672 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1577 | 'var' => $var, |
1673 | 'var' => $var, |
| 1578 | 'limits' => [$llimit, $ulimit], |
1674 | 'limits' => [$llimit, $ulimit], |
| 1579 | 'tolerance' => $absTol, |
1675 | 'tolerance' => $absTol, |
| 1580 | 'tolType' => 'absolute', |
1676 | 'tolType' => 'absolute', |
| 1581 | 'numPoints' => $numPoints, |
1677 | 'numPoints' => $numPoints, |
| 1582 | 'mode' => 'antider', |
1678 | 'mode' => 'antider', |
| 1583 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
1679 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
| 1584 | 'zeroLevel' => 0, |
1680 | 'zeroLevel' => 0, |
| 1585 | 'zeroLevelTol' => 0 |
1681 | 'zeroLevelTol' => 0 |
| 1586 | ); |
1682 | ); |
| 1587 | } |
1683 | } |
| 1588 | } |
1684 | } |
| 1589 | |
1685 | |
| 1590 | ## The following answer evaluator for comparing multivarable functions was |
1686 | ## The following answer evaluator for comparing multivarable functions was |
| … | |
… | |
| 1716 | sub fun_cmp { |
1812 | sub fun_cmp { |
| 1717 | my $correctAnswer = shift @_; |
1813 | my $correctAnswer = shift @_; |
| 1718 | my %opt = @_; |
1814 | my %opt = @_; |
| 1719 | |
1815 | |
| 1720 | assign_option_aliases( \%opt, |
1816 | assign_option_aliases( \%opt, |
| 1721 | 'vars' => 'var', # set the standard option 'var' to the one specified as vars |
1817 | 'vars' => 'var', # set the standard option 'var' to the one specified as vars |
| 1722 | 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain |
1818 | 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain |
| 1723 | 'reltol' => 'relTol', |
1819 | 'reltol' => 'relTol', |
| 1724 | 'param' => 'params', |
1820 | 'param' => 'params', |
| 1725 | ); |
1821 | ); |
| 1726 | |
1822 | |
| 1727 | set_default_options( \%opt, |
1823 | set_default_options( \%opt, |
| 1728 | 'var' => $functVarDefault, |
1824 | 'var' => $functVarDefault, |
| 1729 | 'params' => [], |
1825 | 'params' => [], |
| 1730 | 'limits' => [[$functLLimitDefault, $functULimitDefault]], |
1826 | 'limits' => [[$functLLimitDefault, $functULimitDefault]], |
| 1731 | 'mode' => 'std', |
1827 | 'mode' => 'std', |
| 1732 | 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative', |
1828 | 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative', |
| 1733 | 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined |
1829 | 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined |
| 1734 | 'relTol' => $functRelPercentTolDefault, |
1830 | 'relTol' => $functRelPercentTolDefault, |
| 1735 | 'numPoints' => $functNumOfPoints, |
1831 | 'numPoints' => $functNumOfPoints, |
| 1736 | 'maxConstantOfIntegration' => $functMaxConstantOfIntegration, |
1832 | 'maxConstantOfIntegration' => $functMaxConstantOfIntegration, |
| 1737 | 'zeroLevel' => $functZeroLevelDefault, |
1833 | 'zeroLevel' => $functZeroLevelDefault, |
| 1738 | 'zeroLevelTol' => $functZeroLevelTolDefault, |
1834 | 'zeroLevelTol' => $functZeroLevelTolDefault, |
| 1739 | 'debug' => 0, |
1835 | 'debug' => 0, |
| 1740 | ); |
1836 | ); |
| 1741 | |
1837 | |
| 1742 | |
1838 | |
| 1743 | |
1839 | |
| 1744 | # allow var => 'x' as an abbreviation for var => ['x'] |
1840 | # allow var => 'x' as an abbreviation for var => ['x'] |
| … | |
… | |
| 1759 | $tolType = 'relative'; |
1855 | $tolType = 'relative'; |
| 1760 | $tol = $out_options{'relTol'}; |
1856 | $tol = $out_options{'relTol'}; |
| 1761 | delete($out_options{'tol'}) if exists( $out_options{'tol'} ); |
1857 | delete($out_options{'tol'}) if exists( $out_options{'tol'} ); |
| 1762 | } |
1858 | } |
| 1763 | |
1859 | |
| 1764 | |
|
|
| 1765 | |
|
|
| 1766 | my @output_list = (); |
1860 | my @output_list = (); |
| 1767 | # thread over lists |
1861 | # thread over lists |
| 1768 | my @ans_list = (); |
1862 | my @ans_list = (); |
| 1769 | |
1863 | |
| 1770 | if ( ref($correctAnswer) eq 'ARRAY' ) { |
1864 | if ( ref($correctAnswer) eq 'ARRAY' ) { |
| … | |
… | |
| 1772 | } |
1866 | } |
| 1773 | else { |
1867 | else { |
| 1774 | push( @ans_list, $correctAnswer ); |
1868 | push( @ans_list, $correctAnswer ); |
| 1775 | } |
1869 | } |
| 1776 | |
1870 | |
| 1777 | |
|
|
| 1778 | |
|
|
| 1779 | # produce answer evaluators |
1871 | # produce answer evaluators |
| 1780 | foreach my $ans (@ans_list) { |
1872 | foreach my $ans (@ans_list) { |
| 1781 | push(@output_list, |
1873 | push(@output_list, |
| 1782 | FUNCTION_CMP( 'correctEqn' => $ans, |
1874 | FUNCTION_CMP( 'correctEqn' => $ans, |
| 1783 | 'var' => $out_options{'var'}, |
1875 | 'var' => $out_options{'var'}, |
| 1784 | 'limits' => $out_options{'limits'}, |
1876 | 'limits' => $out_options{'limits'}, |
| 1785 | 'tolerance' => $tol, |
1877 | 'tolerance' => $tol, |
| 1786 | 'tolType' => $tolType, |
1878 | 'tolType' => $tolType, |
| 1787 | 'numPoints' => $out_options{'numPoints'}, |
1879 | 'numPoints' => $out_options{'numPoints'}, |
| 1788 | 'mode' => $out_options{'mode'}, |
1880 | 'mode' => $out_options{'mode'}, |
| 1789 | 'maxConstantOfIntegration' => $out_options{'maxConstantOfIntegration'}, |
1881 | 'maxConstantOfIntegration' => $out_options{'maxConstantOfIntegration'}, |
| 1790 | 'zeroLevel' => $out_options{'zeroLevel'}, |
1882 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1791 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
1883 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 1792 | 'params' => $out_options{'params'}, |
1884 | 'params' => $out_options{'params'}, |
| 1793 | 'debug' => $out_options{'debug'}, |
1885 | 'debug' => $out_options{'debug'}, |
| 1794 | ), |
1886 | ), |
| 1795 | ); |
1887 | ); |
| 1796 | } |
1888 | } |
| 1797 | |
1889 | |
| 1798 | return @output_list; |
1890 | return @output_list; |
| … | |
… | |
| 1820 | |
1912 | |
| 1821 | sub FUNCTION_CMP { |
1913 | sub FUNCTION_CMP { |
| 1822 | my %func_params = @_; |
1914 | my %func_params = @_; |
| 1823 | |
1915 | |
| 1824 | my $correctEqn = $func_params{'correctEqn'}; |
1916 | my $correctEqn = $func_params{'correctEqn'}; |
| 1825 | my $var = $func_params{'var'}; |
1917 | my $var = $func_params{'var'}; |
| 1826 | my $ra_limits = $func_params{'limits'}; |
1918 | my $ra_limits = $func_params{'limits'}; |
| 1827 | my $tol = $func_params{'tolerance'}; |
1919 | my $tol = $func_params{'tolerance'}; |
| 1828 | my $tolType = $func_params{'tolType'}; |
1920 | my $tolType = $func_params{'tolType'}; |
| 1829 | my $numPoints = $func_params{'numPoints'}; |
1921 | my $numPoints = $func_params{'numPoints'}; |
| 1830 | my $mode = $func_params{'mode'}; |
1922 | my $mode = $func_params{'mode'}; |
| 1831 | my $maxConstantOfIntegration = $func_params{'maxConstantOfIntegration'}; |
1923 | my $maxConstantOfIntegration = $func_params{'maxConstantOfIntegration'}; |
| 1832 | my $zeroLevel = $func_params{'zeroLevel'}; |
1924 | my $zeroLevel = $func_params{'zeroLevel'}; |
| 1833 | my $zeroLevelTol = $func_params{'zeroLevelTol'}; |
1925 | my $zeroLevelTol = $func_params{'zeroLevelTol'}; |
| 1834 | |
1926 | |
| 1835 | |
1927 | |
| 1836 | # Check that everything is defined: |
1928 | # Check that everything is defined: |
| … | |
… | |
| 1867 | $numPoints = $functNumOfPoints unless defined $numPoints; |
1959 | $numPoints = $functNumOfPoints unless defined $numPoints; |
| 1868 | $maxConstantOfIntegration = $functMaxConstantOfIntegration unless defined $maxConstantOfIntegration; |
1960 | $maxConstantOfIntegration = $functMaxConstantOfIntegration unless defined $maxConstantOfIntegration; |
| 1869 | $zeroLevel = $functZeroLevelDefault unless defined $zeroLevel; |
1961 | $zeroLevel = $functZeroLevelDefault unless defined $zeroLevel; |
| 1870 | $zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol; |
1962 | $zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol; |
| 1871 | |
1963 | |
| 1872 | $func_params{'var'} = $var; |
1964 | $func_params{'var'} = $var; |
| 1873 | $func_params{'limits'} = \@limits; |
1965 | $func_params{'limits'} = \@limits; |
| 1874 | $func_params{'tolerance'}= $tol; |
1966 | $func_params{'tolerance'} = $tol; |
| 1875 | $func_params{'tolType'} = $tolType; |
1967 | $func_params{'tolType'} = $tolType; |
| 1876 | $func_params{'numPoints'}= $numPoints; |
1968 | $func_params{'numPoints'} = $numPoints; |
| 1877 | $func_params{'mode'} = $mode; |
1969 | $func_params{'mode'} = $mode; |
| 1878 | $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration; |
1970 | $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration; |
| 1879 | $func_params{'zeroLevel'} = $zeroLevel; |
1971 | $func_params{'zeroLevel'} = $zeroLevel; |
| 1880 | $func_params{'zeroLevelTol'} = $zeroLevelTol; |
1972 | $func_params{'zeroLevelTol'} = $zeroLevelTol; |
| 1881 | |
1973 | |
|
|
1974 | ######################################################## |
|
|
1975 | # End of cleanup of calling parameters |
|
|
1976 | ######################################################## |
| 1882 | my $i; #for use with loops |
1977 | my $i; #for use with loops |
| 1883 | my $PGanswerMessage = ""; |
1978 | my $PGanswerMessage = ""; |
| 1884 | my $originalCorrEqn = $correctEqn; |
1979 | my $originalCorrEqn = $correctEqn; |
| 1885 | |
1980 | |
| 1886 | #prepare the correct answer and check it's syntax |
1981 | #prepare the correct answer and check it's syntax |
| 1887 | my $rh_correct_ans = new AnswerHash; |
1982 | my $rh_correct_ans = new AnswerHash; |
| 1888 | $rh_correct_ans->input($correctEqn); |
1983 | $rh_correct_ans->input($correctEqn); |
| 1889 | $rh_correct_ans = check_syntax($rh_correct_ans); |
1984 | $rh_correct_ans = check_syntax($rh_correct_ans); |
| 1890 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
1985 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
| 1891 | $rh_correct_ans->clear_error(); |
1986 | $rh_correct_ans->clear_error(); |
| 1892 | $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => [ @VARS, @PARAMS ], |
1987 | $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => [ @VARS, @PARAMS ], |
| … | |
… | |
| 1895 | my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans}; |
1990 | my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans}; |
| 1896 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
1991 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
| 1897 | |
1992 | |
| 1898 | #create the evaluation points |
1993 | #create the evaluation points |
| 1899 | my $random_for_answers = new PGrandom($main::PG_original_problemSeed); |
1994 | my $random_for_answers = new PGrandom($main::PG_original_problemSeed); |
| 1900 | my $NUMBER_OF_STEPS_IN_RANDOM = 1000; # determines the granularity of the random_for_answers number generator |
1995 | my $NUMBER_OF_STEPS_IN_RANDOM = 1000; # determines the granularity of the random_for_answers number generator |
| 1901 | my (@evaluation_points); |
1996 | my (@evaluation_points); |
| 1902 | for( my $count = 0; $count < @PARAMS+1+$numPoints; $count++ ) { |
1997 | for( my $count = 0; $count < @PARAMS+1+$numPoints; $count++ ) { |
| 1903 | my (@vars,$iteration_limit); |
1998 | my (@vars,$iteration_limit); |
| 1904 | for( my $i = 0; $i < @VARS; $i++ ) { |
1999 | for( my $i = 0; $i < @VARS; $i++ ) { |
| 1905 | my $iteration_limit = 10; |
2000 | my $iteration_limit = 10; |
| … | |
… | |
| 1914 | push(@evaluation_points,\@vars); |
2009 | push(@evaluation_points,\@vars); |
| 1915 | } |
2010 | } |
| 1916 | my $evaluation_points = Matrix->new_from_array_ref(\@evaluation_points); |
2011 | my $evaluation_points = Matrix->new_from_array_ref(\@evaluation_points); |
| 1917 | |
2012 | |
| 1918 | #my $COEFFS = determine_param_coeffs($correct_eqn_sub,$evaluation_points[0],$numOfParameters); |
2013 | #my $COEFFS = determine_param_coeffs($correct_eqn_sub,$evaluation_points[0],$numOfParameters); |
| 1919 | #warn "coeff", join(" | ", @{$COEFFS}); |
2014 | #warn "coeff", join(" | ", @{$COEFFS}); |
| 1920 | |
2015 | |
| 1921 | #construct the answer evaluator |
2016 | #construct the answer evaluator |
| 1922 | my $answer_evaluator = new AnswerEvaluator; |
2017 | my $answer_evaluator = new AnswerEvaluator; |
| 1923 | $answer_evaluator->{debug} = $func_params{debug}; |
2018 | $answer_evaluator->{debug} = $func_params{debug}; |
| 1924 | $answer_evaluator->ans_hash( correct_ans => $originalCorrEqn, |
2019 | $answer_evaluator->ans_hash( correct_ans => $originalCorrEqn, |
| … | |
… | |
| 2077 | push(@out, $temp_hash->input()); |
2172 | push(@out, $temp_hash->input()); |
| 2078 | |
2173 | |
| 2079 | } |
2174 | } |
| 2080 | if ($PGanswerMessage) { |
2175 | if ($PGanswerMessage) { |
| 2081 | $rh_ans->input( "( " . join(", ", @out ) . " )" ); |
2176 | $rh_ans->input( "( " . join(", ", @out ) . " )" ); |
| 2082 | $rh_ans->throw_error('SYTNAX', 'There is a syntax error in your answer.'); |
2177 | $rh_ans->throw_error('SYNTAX', 'There is a syntax error in your answer.'); |
| 2083 | } else { |
2178 | } else { |
| 2084 | $rh_ans->input( [@out] ); |
2179 | $rh_ans->input( [@out] ); |
| 2085 | } |
2180 | } |
| 2086 | $rh_ans; |
2181 | $rh_ans; |
| 2087 | } |
2182 | } |
| … | |
… | |
| 2100 | my @VARS = @{ $options{ 'ra_vars'}}; |
2195 | my @VARS = @{ $options{ 'ra_vars'}}; |
| 2101 | warn "VARS = ", join("<>", @VARS) if defined($options{debug}) and $options{debug} ==1; |
2196 | warn "VARS = ", join("<>", @VARS) if defined($options{debug}) and $options{debug} ==1; |
| 2102 | my $originalEqn = $eqn; |
2197 | my $originalEqn = $eqn; |
| 2103 | $eqn = &math_constants($eqn); |
2198 | $eqn = &math_constants($eqn); |
| 2104 | for( my $i = 0; $i < @VARS; $i++ ) { |
2199 | for( my $i = 0; $i < @VARS; $i++ ) { |
|
|
2200 | # This next line is a hack required for 5.6.0 -- it doesn't appear to be needed in 5.6.1 |
|
|
2201 | my ($temp,$er1,$er2) = PG_restricted_eval('"'. $VARS[$i] . '"'); |
| 2105 | $eqn =~ s/\b$VARS[$i]\b/\$VARS[$i]/g; |
2202 | # $eqn =~ s/\b$VARS[$i]\b/\$VARS[$i]/g; |
|
|
2203 | $eqn =~ s/\b$temp\b/\$VARS[$i]/g; |
|
|
2204 | |
| 2106 | } |
2205 | } |
| 2107 | warn "equation evaluated = $eqn",$rh_ans->pretty_print(), "<br>\noptions<br>\n", |
2206 | warn "equation evaluated = $eqn",$rh_ans->pretty_print(), "<br>\noptions<br>\n", |
| 2108 | pretty_print(\%options) |
2207 | pretty_print(\%options) |
| 2109 | if defined($options{debug}) and $options{debug} ==1; |
2208 | if defined($options{debug}) and $options{debug} ==1; |
| 2110 | my ($function_sub,$PG_eval_errors, $PG_full_errors) = PG_answer_eval( q! |
2209 | my ($function_sub,$PG_eval_errors, $PG_full_errors) = PG_answer_eval( q! |
| … | |
… | |
| 2179 | my $error = "WeBWorK was unable evaluate your function. Please check that your |
2278 | my $error = "WeBWorK was unable evaluate your function. Please check that your |
| 2180 | expression doesn't take roots of negative numbers, or divide by zero."; |
2279 | expression doesn't take roots of negative numbers, or divide by zero."; |
| 2181 | $rh_ans->throw_error('EVAL',$error); |
2280 | $rh_ans->throw_error('EVAL',$error); |
| 2182 | } else { |
2281 | } else { |
| 2183 | my $tol = $options{tol} if defined($options{tol}); |
2282 | my $tol = $options{tol} if defined($options{tol}); |
| 2184 | $tol = 0.01*$options{reltol} if defined($options{reltol}); |
2283 | #$tol = 0.01*$options{reltol} if defined($options{reltol}); |
| 2185 | $tol = .000001 unless defined($tol); |
2284 | $tol = .000001 unless defined($tol); |
| 2186 | |
2285 | |
| 2187 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
2286 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
| 2188 | } |
2287 | } |
| 2189 | $rh_ans; |
2288 | $rh_ans; |
| … | |
… | |
| 2273 | my $rf_correct_fun = $rh_ans->{rf_correct_ans}; |
2372 | my $rf_correct_fun = $rh_ans->{rf_correct_ans}; |
| 2274 | my $ra_vars_matrix = $rh_ans->{evaluation_points}; |
2373 | my $ra_vars_matrix = $rh_ans->{evaluation_points}; |
| 2275 | my $dim_of_param_space = @{$options{param_vars}}; |
2374 | my $dim_of_param_space = @{$options{param_vars}}; |
| 2276 | # Short cut. Bail if there are no param_vars |
2375 | # Short cut. Bail if there are no param_vars |
| 2277 | unless ($dim_of_param_space >0) { |
2376 | unless ($dim_of_param_space >0) { |
| 2278 | $rh_ans ->{ra_paramters} = []; |
2377 | $rh_ans ->{ra_parameters} = []; |
| 2279 | return $rh_ans; |
2378 | return $rh_ans; |
| 2280 | } |
2379 | } |
| 2281 | # inputs are row arrays in this case. |
2380 | # inputs are row arrays in this case. |
| 2282 | my @zero_params=(); |
2381 | my @zero_params=(); |
| 2283 | |
2382 | |
| … | |
… | |
| 2315 | while(@coeff) { |
2414 | while(@coeff) { |
| 2316 | $matrix->assign($row_num,$col_num, shift(@coeff) ); |
2415 | $matrix->assign($row_num,$col_num, shift(@coeff) ); |
| 2317 | $col_num++; |
2416 | $col_num++; |
| 2318 | } |
2417 | } |
| 2319 | } |
2418 | } |
| 2320 | # which might be useful for functions that are not defined at some points. |
2419 | |
| 2321 | } |
2420 | } |
| 2322 | $row_num++; |
2421 | $row_num++; |
| 2323 | last if $errors; # break if there are any errors. |
2422 | last if $errors; # break if there are any errors. |
| 2324 | # This cuts down on the size of error messages. |
2423 | # This cuts down on the size of error messages. |
| 2325 | # However it impossible to check for equivalence at 95% of points |
2424 | # However it impossible to check for equivalence at 95% of points |
| 2326 | |
2425 | # which might be useful for functions that are not defined at some points. |
| 2327 | } |
2426 | } |
| 2328 | warn "<br> best_approx_parameters: matrix1 <br> ", " $matrix " if $options{debug}; |
2427 | warn "<br> best_approx_parameters: matrix1 <br> ", " $matrix " if $options{debug}; |
| 2329 | warn "<br> best_approx_parameters: vector <br> ", " $rhs_vec " if $options{debug}; |
2428 | warn "<br> best_approx_parameters: vector <br> ", " $rhs_vec " if $options{debug}; |
| 2330 | |
2429 | |
| 2331 | # we have Matrix * parameter = data_vec + perpendicular vector |
2430 | # we have Matrix * parameter = data_vec + perpendicular vector |
| … | |
… | |
| 2390 | my $ra_parameters = $rh_ans ->{ra_parameters}; |
2489 | my $ra_parameters = $rh_ans ->{ra_parameters}; |
| 2391 | my @evaluation_points = @{$rh_ans->{evaluation_points} }; |
2490 | my @evaluation_points = @{$rh_ans->{evaluation_points} }; |
| 2392 | my @parameters = (); |
2491 | my @parameters = (); |
| 2393 | @parameters = @$ra_parameters if defined($ra_parameters) and ref($ra_parameters) eq 'ARRAY'; |
2492 | @parameters = @$ra_parameters if defined($ra_parameters) and ref($ra_parameters) eq 'ARRAY'; |
| 2394 | my $errors = undef; |
2493 | my $errors = undef; |
|
|
2494 | my @zero_params=(); |
|
|
2495 | for(my $i=1;$i<=@{$ra_parameters};$i++){push(@zero_params,0); } |
| 2395 | my @differences = (); |
2496 | my @differences = (); |
| 2396 | my $diff; |
2497 | my @student_values; |
|
|
2498 | my @correct_values; |
|
|
2499 | my @tol_values; |
|
|
2500 | my ($diff,$tol_val); |
| 2397 | # calculate the vector of differences between the test function and the comparison function. |
2501 | # calculate the vector of differences between the test function and the comparison function. |
| 2398 | while (@evaluation_points) { |
2502 | while (@evaluation_points) { |
| 2399 | my ($err1, $err2); |
2503 | my ($err1, $err2,$err3); |
| 2400 | my @vars = @{ shift(@evaluation_points) }; |
2504 | my @vars = @{ shift(@evaluation_points) }; |
| 2401 | my @inputs = (@vars, @parameters); |
2505 | my @inputs = (@vars, @parameters); |
| 2402 | my ($inVal, $correctVal); |
2506 | my ($inVal, $correctVal); |
| 2403 | ($inVal, $err1) = &{$rf_fun}(@vars); |
2507 | ($inVal, $err1) = &{$rf_fun}(@vars); |
| 2404 | $errors .= " $err1 " if defined($err1); |
2508 | $errors .= " $err1 " if defined($err1); |
| 2405 | $errors .= " Error detected evaluating student input at @vars " if defined($options{debug}) and $options{debug}=1 and defined($err1); |
2509 | $errors .= " Error detected evaluating student input at (".join(' , ',@vars) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err1); |
| 2406 | ($correctVal, $err2) =&{$rf_correct_fun}(@inputs); |
2510 | ($correctVal, $err2) =&{$rf_correct_fun}(@inputs); |
| 2407 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err2 " if defined($err2); |
2511 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err2 " if defined($err2); |
| 2408 | $errors .= " Error detected evaluating correct answer at @inputs " if defined($options{debug}) and $options{debug}=1 and defined($err2); |
2512 | $errors .= " Error detected evaluating correct answer at (".join(' , ',@inputs) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err2); |
|
|
2513 | ($tol_val,$err3)= &$rf_correct_fun(@vars, @zero_params); |
|
|
2514 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err3 " if defined($err3); |
|
|
2515 | $errors .= " Error detected evaluating correct answer at (".join(' , ',@vars, @zero_params) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err3); |
| 2409 | unless (defined($err1) or defined($err2) ) { |
2516 | unless (defined($err1) or defined($err2) or defined($err3) ) { |
| 2410 | $diff = $inVal - $correctVal; |
2517 | $diff = ( $inVal - ($correctVal -$tol_val ) ) - $tol_val; #prevents entering too high a number? |
| 2411 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
2518 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
| 2412 | |
2519 | |
| 2413 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
2520 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
| 2414 | $diff = $diff/abs( &$rf_correct_fun(@inputs) ) if $correctVal > $options{zeroLevel}; |
2521 | #warn "diff = $diff"; |
|
|
2522 | |
|
|
2523 | $diff = ( $inVal - ($correctVal-$tol_val ) )/abs($tol_val) -1 if abs($tol_val) > $options{zeroLevel}; |
|
|
2524 | #$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel}; |
|
|
2525 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
| 2415 | } |
2526 | } |
| 2416 | } |
2527 | } |
| 2417 | last if $errors; # break if there are any errors. |
2528 | last if $errors; # break if there are any errors. |
| 2418 | # This cuts down on the size of error messages. |
2529 | # This cuts down on the size of error messages. |
| 2419 | # However it impossible to check for equivalence at 95% of points |
2530 | # However it impossible to check for equivalence at 95% of points |
| 2420 | # which might be useful for functions that are not defined at some points. |
2531 | # which might be useful for functions that are not defined at some points. |
|
|
2532 | push(@student_values,$inVal); |
|
|
2533 | push(@correct_values,( $inVal - ($correctVal-$tol_val ) )); |
| 2421 | push(@differences, $diff); |
2534 | push(@differences, $diff); |
|
|
2535 | push(@tol_values,$tol_val); |
| 2422 | } |
2536 | } |
| 2423 | $rh_ans ->{ra_differences} = \@differences; |
2537 | $rh_ans ->{ra_differences} = \@differences; |
|
|
2538 | $rh_ans ->{ra_student_values} = \@student_values; |
|
|
2539 | $rh_ans ->{ra_adjusted_student_values} = \@correct_values; |
|
|
2540 | $rh_ans->{ra_tol_values}=\@tol_values; |
| 2424 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
2541 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
| 2425 | $rh_ans; |
2542 | $rh_ans; |
| 2426 | } |
2543 | } |
| 2427 | |
2544 | |
| 2428 | |
2545 | |
| … | |
… | |
| 2484 | ## individual filters below it |
2601 | ## individual filters below it |
| 2485 | sub str_filters { |
2602 | sub str_filters { |
| 2486 | my $stringToFilter = shift @_; |
2603 | my $stringToFilter = shift @_; |
| 2487 | my @filters_to_use = @_; |
2604 | my @filters_to_use = @_; |
| 2488 | my %known_filters = ( 'remove_whitespace' => undef, |
2605 | my %known_filters = ( 'remove_whitespace' => undef, |
| 2489 | 'compress_whitespace' => undef, |
2606 | 'compress_whitespace' => undef, |
| 2490 | 'trim_whitespace' => undef, |
2607 | 'trim_whitespace' => undef, |
| 2491 | 'ignore_case' => undef, |
2608 | 'ignore_case' => undef, |
| 2492 | 'ignore_order' => undef |
2609 | 'ignore_order' => undef |
| 2493 | ); |
2610 | ); |
| 2494 | |
2611 | |
| 2495 | #test for unknown filters |
2612 | #test for unknown filters |
| 2496 | my $filter; |
2613 | my $filter; |
| 2497 | foreach $filter (@filters_to_use) { |
2614 | foreach $filter (@filters_to_use) { |
| … | |
… | |
| 2619 | sub std_str_cmp { # compare strings |
2736 | sub std_str_cmp { # compare strings |
| 2620 | my $correctAnswer = shift @_; |
2737 | my $correctAnswer = shift @_; |
| 2621 | my @filters = ( 'compress_whitespace', 'ignore_case' ); |
2738 | my @filters = ( 'compress_whitespace', 'ignore_case' ); |
| 2622 | my $type = 'std_str_cmp'; |
2739 | my $type = 'std_str_cmp'; |
| 2623 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2740 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2624 | 'filters' => \@filters, |
2741 | 'filters' => \@filters, |
| 2625 | 'type' => $type |
2742 | 'type' => $type |
| 2626 | ); |
2743 | ); |
| 2627 | } |
2744 | } |
| 2628 | |
2745 | |
| 2629 | sub std_str_cmp_list { # alias for std_str_cmp |
2746 | sub std_str_cmp_list { # alias for std_str_cmp |
| 2630 | my @answerList = @_; |
2747 | my @answerList = @_; |
| … | |
… | |
| 2833 | ## IN: a hashtable with the following entries (error-checking to be added later?): |
2950 | ## IN: a hashtable with the following entries (error-checking to be added later?): |
| 2834 | ## correctAnswer -- the correct answer, before filtering |
2951 | ## correctAnswer -- the correct answer, before filtering |
| 2835 | ## filters -- reference to an array containing the filters to be applied |
2952 | ## filters -- reference to an array containing the filters to be applied |
| 2836 | ## type -- a string containing the type of answer evaluator in use |
2953 | ## type -- a string containing the type of answer evaluator in use |
| 2837 | ## OUT: a reference to an answer evaluator subroutine |
2954 | ## OUT: a reference to an answer evaluator subroutine |
|
|
2955 | |
| 2838 | sub STR_CMP { |
2956 | sub STR_CMP { |
| 2839 | my %str_params = @_; |
2957 | my %str_params = @_; |
| 2840 | |
|
|
| 2841 | $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} ); |
2958 | $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} ); |
| 2842 | |
|
|
| 2843 | my $answer_evaluator = sub { |
2959 | my $answer_evaluator = sub { |
| 2844 | my $in = shift @_; |
2960 | my $in = shift @_; |
| 2845 | $in = '' unless defined $in; |
2961 | $in = '' unless defined $in; |
| 2846 | my $original_student_ans = $in; |
2962 | my $original_student_ans = $in; |
| 2847 | |
|
|
| 2848 | $in = str_filters( $in, @{$str_params{'filters'}} ); |
2963 | $in = str_filters( $in, @{$str_params{'filters'}} ); |
| 2849 | |
|
|
| 2850 | my $correctQ = ( $in eq $str_params{'correctAnswer'} ) ? 1: 0; |
2964 | my $correctQ = ( $in eq $str_params{'correctAnswer'} ) ? 1: 0; |
| 2851 | my $ans_hash = new AnswerHash( |
2965 | my $ans_hash = new AnswerHash( 'score' => $correctQ, |
| 2852 | 'score' => $correctQ, |
|
|
| 2853 | 'correct_ans' => $str_params{'correctAnswer'}, |
2966 | 'correct_ans' => $str_params{'correctAnswer'}, |
| 2854 | 'student_ans' => $in, |
2967 | 'student_ans' => $in, |
| 2855 | 'ans_message' => '', |
2968 | 'ans_message' => '', |
| 2856 | 'type' => $str_params{'type'}, |
2969 | 'type' => $str_params{'type'}, |
| 2857 | 'preview_text_string' => $in, |
2970 | 'preview_text_string' => $in, |
| 2858 | 'preview_latex_string' => $in, |
2971 | 'preview_latex_string' => $in, |
| 2859 | 'original_student_ans' => $original_student_ans |
2972 | 'original_student_ans' => $original_student_ans |
| 2860 | ); |
2973 | ); |
| 2861 | |
|
|
| 2862 | return $ans_hash; |
2974 | return $ans_hash; |
| 2863 | }; |
2975 | }; |
| 2864 | |
|
|
| 2865 | return $answer_evaluator; |
2976 | return $answer_evaluator; |
| 2866 | } |
2977 | } |
| 2867 | |
|
|
| 2868 | |
|
|
| 2869 | |
2978 | |
| 2870 | ########################################################################## |
2979 | ########################################################################## |
| 2871 | ########################################################################## |
2980 | ########################################################################## |
| 2872 | ## Miscellaneous answer evaluators |
2981 | ## Miscellaneous answer evaluators |
| 2873 | |
2982 | |
| … | |
… | |
| 3152 | $problem_result{score} = $allAnswersCorrectQ; |
3261 | $problem_result{score} = $allAnswersCorrectQ; |
| 3153 | |
3262 | |
| 3154 | # I don't like to put in this bit of code. |
3263 | # I don't like to put in this bit of code. |
| 3155 | # It makes it hard to construct error free problem graders |
3264 | # It makes it hard to construct error free problem graders |
| 3156 | # I would prefer to know that the problem score was numeric. |
3265 | # I would prefer to know that the problem score was numeric. |
| 3157 | unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
3266 | unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
| 3158 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
3267 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
| 3159 | } |
3268 | } |
| 3160 | # |
3269 | # |
| 3161 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
3270 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
| 3162 | $problem_state{recorded_score} = 1; |
3271 | $problem_state{recorded_score} = 1; |
| … | |
… | |
| 3385 | ## a reference to an array of arrays of limits -- [[llim,ulim], [llim,ulim]] |
3494 | ## a reference to an array of arrays of limits -- [[llim,ulim], [llim,ulim]] |
| 3386 | ## a reference to an array of limits -- [llim, ulim] |
3495 | ## a reference to an array of limits -- [llim, ulim] |
| 3387 | ## an array of array references -- ([llim,ulim], [llim,ulim]) |
3496 | ## an array of array references -- ([llim,ulim], [llim,ulim]) |
| 3388 | ## an array of limits -- (llim,ulim) |
3497 | ## an array of limits -- (llim,ulim) |
| 3389 | ## OUT: an array of array references -- ([llim,ulim], [llim,ulim]) or ([llim,ulim]) |
3498 | ## OUT: an array of array references -- ([llim,ulim], [llim,ulim]) or ([llim,ulim]) |
|
|
3499 | |
| 3390 | sub get_limits_array { |
3500 | sub get_limits_array { |
| 3391 | my $in = shift @_; |
3501 | my $in = shift @_; |
| 3392 | my @out; |
3502 | my @out; |
| 3393 | |
3503 | |
| 3394 | if( not defined($in) ) { #if nothing defined, build default array and return |
3504 | if( not defined($in) ) { #if nothing defined, build default array and return |
| … | |
… | |
| 3440 | }; |
3550 | }; |
| 3441 | |
3551 | |
| 3442 | return $error_response; |
3552 | return $error_response; |
| 3443 | } |
3553 | } |
| 3444 | |
3554 | |
| 3445 | # outputs a hash to the screen |
3555 | |
| 3446 | # sub display_options { |
3556 | ######################################################################### |
| 3447 | # my %options = @_; |
3557 | # Filters for answer evaluators |
| 3448 | # my $out_string = ""; |
3558 | ######################################################################### |
| 3449 | # foreach my $key (keys %options) { |
3559 | |
| 3450 | # $out_string .= " $key => $options{$key},<BR>"; |
|
|
| 3451 | # } |
|
|
| 3452 | # return $out_string; |
|
|
| 3453 | # } |
|
|
| 3454 | |
3560 | |
| 3455 | sub is_a_number { |
3561 | sub is_a_number { |
| 3456 | my ($num) = @_; |
3562 | my ($num,%options) = @_; |
|
|
3563 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
3564 | my ($rh_ans); |
|
|
3565 | if ($process_ans_hash) { |
|
|
3566 | $rh_ans = $num; |
|
|
3567 | $num = $rh_ans->{student_ans}; |
|
|
3568 | } |
|
|
3569 | |
| 3457 | my $is_a_number = 0; |
3570 | my $is_a_number = 0; |
| 3458 | return $is_a_number unless defined($num); |
3571 | return $is_a_number unless defined($num); |
| 3459 | $num =~ s/^\s*//; ## remove initial spaces |
3572 | $num =~ s/^\s*//; ## remove initial spaces |
| 3460 | $num =~ s/\s*$//; ## remove trailing spaces |
3573 | $num =~ s/\s*$//; ## remove trailing spaces |
| 3461 | |
3574 | |
| 3462 | ## the following is copied from the online perl manual |
3575 | ## the following is copied from the online perl manual |
| 3463 | if ($num =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){ |
3576 | if ($num =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){ |
| 3464 | $is_a_number = 1; |
3577 | $is_a_number = 1; |
| 3465 | } |
3578 | } |
| 3466 | |
3579 | |
|
|
3580 | if ($process_ans_hash) { |
|
|
3581 | if ($is_a_number == 1 ) { |
|
|
3582 | $rh_ans->{student_ans}=$num; |
|
|
3583 | return $rh_ans; |
|
|
3584 | } else { |
|
|
3585 | $rh_ans->{student_ans} = "Incorrect number format: You must enter a number, e.g. -6, 5.3, or 6.12E-3"; |
|
|
3586 | $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); |
|
|
3587 | return $rh_ans; |
|
|
3588 | } |
|
|
3589 | } else { |
| 3467 | return $is_a_number; |
3590 | return $is_a_number; |
|
|
3591 | } |
| 3468 | } |
3592 | } |
| 3469 | |
3593 | |
| 3470 | sub is_a_fraction { |
3594 | sub is_a_fraction { |
| 3471 | |
3595 | my ($num,%options) = @_; |
| 3472 | ## does not test for validity, just for allowed characters |
3596 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
| 3473 | ## note that an integer will qualify as a fraction |
3597 | my ($rh_ans); |
| 3474 | my ($exp) = @_; |
3598 | if ($process_ans_hash) { |
|
|
3599 | $rh_ans = $num; |
|
|
3600 | $num = $rh_ans->{student_ans}; |
|
|
3601 | } |
|
|
3602 | |
| 3475 | my $is_a_fraction = 0; |
3603 | my $is_a_fraction = 0; |
| 3476 | return $is_a_fraction unless defined($exp); |
3604 | return $is_a_fraction unless defined($num); |
|
|
3605 | $num =~ s/^\s*//; ## remove initial spaces |
|
|
3606 | $num =~ s/\s*$//; ## remove trailing spaces |
|
|
3607 | |
| 3477 | if ($exp =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) { |
3608 | if ($num =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) { |
| 3478 | $is_a_fraction = 1; |
3609 | $is_a_fraction = 1; |
| 3479 | } |
3610 | } |
| 3480 | |
3611 | |
|
|
3612 | if ($process_ans_hash) { |
|
|
3613 | if ($is_a_fraction == 1 ) { |
|
|
3614 | $rh_ans->{student_ans}=$num; |
|
|
3615 | return $rh_ans; |
|
|
3616 | } else { |
|
|
3617 | $rh_ans->{student_ans} = "Not a number of fraction: You must enter a number or fraction, e.g. -6 or 7/13"; |
|
|
3618 | $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); |
|
|
3619 | return $rh_ans; |
|
|
3620 | } |
|
|
3621 | |
|
|
3622 | } else { |
| 3481 | return $is_a_fraction; |
3623 | return $is_a_fraction; |
|
|
3624 | } |
| 3482 | } |
3625 | } |
| 3483 | |
3626 | |
|
|
3627 | |
| 3484 | sub is_an_arithmetic_expression { |
3628 | sub is_an_arithmetic_expression { |
| 3485 | ## does not test for validity, just for allowed characters |
3629 | my ($num,%options) = @_; |
| 3486 | my ($exp) = @_; |
3630 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
3631 | my ($rh_ans); |
|
|
3632 | if ($process_ans_hash) { |
|
|
3633 | $rh_ans = $num; |
|
|
3634 | $num = $rh_ans->{student_ans}; |
|
|
3635 | } |
|
|
3636 | |
| 3487 | my $is_an_arithmetic_expression = 0; |
3637 | my $is_an_arithmetic_expression = 0; |
|
|
3638 | return $is_an_arithmetic_expression unless defined($num); |
|
|
3639 | $num =~ s/^\s*//; ## remove initial spaces |
|
|
3640 | $num =~ s/\s*$//; ## remove trailing spaces |
|
|
3641 | |
| 3488 | if ($exp =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) { |
3642 | if ($num =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) { |
| 3489 | $is_an_arithmetic_expression = 1; |
3643 | $is_an_arithmetic_expression = 1; |
| 3490 | } |
3644 | } |
| 3491 | |
3645 | |
|
|
3646 | if ($process_ans_hash) { |
|
|
3647 | if ($is_an_arithmetic_expression == 1 ) { |
|
|
3648 | $rh_ans->{student_ans}=$num; |
|
|
3649 | return $rh_ans; |
|
|
3650 | } else { |
|
|
3651 | |
|
|
3652 | $rh_ans->{student_ans} = "Not an arithmetic expression: You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2"; |
|
|
3653 | $rh_ans->throw_error('NUMBER', 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2'); |
|
|
3654 | return $rh_ans; |
|
|
3655 | } |
|
|
3656 | |
|
|
3657 | } else { |
| 3492 | return $is_an_arithmetic_expression; |
3658 | return $is_an_arithmetic_expression; |
|
|
3659 | } |
| 3493 | } |
3660 | } |
| 3494 | |
3661 | |
| 3495 | #replaces pi, e, and ^ with their Perl equivalents |
3662 | #replaces pi, e, and ^ with their Perl equivalents |
| 3496 | sub math_constants { |
3663 | sub math_constants { |
| 3497 | my($in) = @_; |
3664 | my($in,%options) = @_; |
|
|
3665 | my $rh_ans; |
|
|
3666 | my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
3667 | if ($process_ans_hash) { |
|
|
3668 | $rh_ans = $in; |
|
|
3669 | $in = $rh_ans->{student_ans}; |
|
|
3670 | } |
|
|
3671 | |
| 3498 | $in =~s/\bpi\b/(4*atan2(1,1))/ge; |
3672 | $in =~s/\bpi\b/(4*atan2(1,1))/ge; |
| 3499 | $in =~s/\be\b/(exp(1))/ge; |
3673 | $in =~s/\be\b/(exp(1))/ge; |
| 3500 | $in =~s/\^/**/g; |
3674 | $in =~s/\^/**/g; |
| 3501 | |
3675 | |
|
|
3676 | if ($process_ans_hash) { |
|
|
3677 | $rh_ans->{student_ans}=$in; |
|
|
3678 | return $rh_ans; |
|
|
3679 | } else { |
| 3502 | return $in; |
3680 | return $in; |
|
|
3681 | } |
| 3503 | } |
3682 | } |
| 3504 | |
3683 | |
| 3505 | sub clean_up_error_msg { |
3684 | sub clean_up_error_msg { |
| 3506 | my $msg = $_[0]; |
3685 | my $msg = $_[0]; |
| 3507 | $msg =~ s/^\[[^\]]*\][^:]*://; |
3686 | $msg =~ s/^\[[^\]]*\][^:]*://; |
| … | |
… | |
| 3579 | # Use this to set default options |
3758 | # Use this to set default options |
| 3580 | sub set_default_options { |
3759 | sub set_default_options { |
| 3581 | my $rh_options = shift; |
3760 | 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'; |
3761 | warn "The first entry to set_default_options must be a reference to the option hash" unless ref($rh_options) eq 'HASH'; |
| 3583 | my %default_options = @_; |
3762 | my %default_options = @_; |
|
|
3763 | unless ( defined($default_options{allow_unknown_options}) and $default_options{allow_unknown_options} == 1 ) { |
| 3584 | foreach my $key (keys %$rh_options) { |
3764 | foreach my $key1 (keys %$rh_options) { |
| 3585 | warn "This option |$key| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key}); |
3765 | warn "This option |$key1| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key1}); |
|
|
3766 | } |
| 3586 | } |
3767 | } |
| 3587 | foreach my $key (keys %default_options) { |
3768 | foreach my $key (keys %default_options) { |
| 3588 | if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) { |
3769 | if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) { |
| 3589 | $rh_options->{$key} = $default_options{$key}; #this allows tol => undef to allow the tol option, but doesn't define |
3770 | $rh_options->{$key} = $default_options{$key}; #this allows tol => undef to allow the tol option, but doesn't define |
| 3590 | # this key unless tol is explicitly defined. |
3771 | # this key unless tol is explicitly defined. |