| … | |
… | |
| 51 | (the preview_latex_string is optional). The output hash is now being created |
51 | (the preview_latex_string is optional). The output hash is now being created |
| 52 | with the AnswerHash package "class", which is located at the end of this file. |
52 | with the AnswerHash package "class", which is located at the end of this file. |
| 53 | This class is currently just a wrapper for the hash, but this might change in |
53 | This class is currently just a wrapper for the hash, but this might change in |
| 54 | the future as new capabilities are added. |
54 | the future as new capabilities are added. |
| 55 | |
55 | |
| 56 | score => $correctQ, |
56 | score => $correctQ, |
| 57 | correct_ans => $originalCorrEqn, |
57 | correct_ans => $originalCorrEqn, |
| 58 | student_ans => $modified_student_ans |
58 | student_ans => $modified_student_ans |
| 59 | original_student_ans => $original_student_answer, |
59 | original_student_ans => $original_student_answer, |
| 60 | ans_message => $PGanswerMessage, |
60 | ans_message => $PGanswerMessage, |
| 61 | type => 'typeString', |
61 | type => 'typeString', |
| 62 | preview_text_string => $preview_text_string, |
62 | preview_text_string => $preview_text_string, |
| 63 | preview_latex_string => $preview_latex_string |
63 | preview_latex_string => $preview_latex_string |
| 64 | |
64 | |
| 65 | |
65 | |
| 66 | $ans_hash{score} -- a number between 0 and 1 indicating |
66 | $ans_hash{score} -- a number between 0 and 1 indicating |
| 67 | whether the answer is correct. Fractions |
67 | whether the answer is correct. Fractions |
| … | |
… | |
| 137 | # import defaults |
137 | # import defaults |
| 138 | # these are now imported from the %envir variable |
138 | # these are now imported from the %envir variable |
| 139 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
139 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
| 140 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
140 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
| 141 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
141 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
| 142 | $numAbsTolDefault = $main::numAbsTolDefault; |
142 | $numAbsTolDefault = $main::numAbsTolDefault; |
| 143 | $numFormatDefault = $main::numFormatDefault; |
143 | $numFormatDefault = $main::numFormatDefault; |
| 144 | |
144 | |
| 145 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
145 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
| 146 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
146 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
| 147 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
147 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
| 148 | $functAbsTolDefault = $main::functAbsTolDefault; |
148 | $functAbsTolDefault = $main::functAbsTolDefault; |
| 149 | $functNumOfPoints = $main::functNumOfPoints; |
149 | $functNumOfPoints = $main::functNumOfPoints; |
| 150 | $functVarDefault = $main::functVarDefault; |
150 | $functVarDefault = $main::functVarDefault; |
| 151 | $functLLimitDefault = $main::functLLimitDefault; |
151 | $functLLimitDefault = $main::functLLimitDefault; |
| 152 | $functULimitDefault = $main::functULimitDefault; |
152 | $functULimitDefault = $main::functULimitDefault; |
| 153 | $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
153 | $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
| 154 | |
154 | |
| 155 | |
155 | |
| 156 | |
156 | |
| 157 | } |
157 | } |
| 158 | |
158 | |
| … | |
… | |
| 266 | formatting will be used; this will show 'arbitrary' precision |
266 | formatting will be used; this will show 'arbitrary' precision |
| 267 | floating points. |
267 | floating points. |
| 268 | |
268 | |
| 269 | Default Values (As of 7/24/2000) (Option -- Variable Name -- Value) |
269 | Default Values (As of 7/24/2000) (Option -- Variable Name -- Value) |
| 270 | |
270 | |
| 271 | Format -- $numFormatDefault -- "%0.5f#" |
271 | Format -- $numFormatDefault -- "%0.5f#" |
| 272 | Relative Tolerance -- $numRelPercentTolDefault -- .1 |
272 | Relative Tolerance -- $numRelPercentTolDefault -- .1 |
| 273 | Absolute Tolerance -- $numAbsTolDefault -- .001 |
273 | Absolute Tolerance -- $numAbsTolDefault -- .001 |
| 274 | Zero Level -- $numZeroLevelDefault -- 1E-14 |
274 | Zero Level -- $numZeroLevelDefault -- 1E-14 |
| 275 | Zero Level Tolerance -- $numZeroLevelTolDefault -- 1E-12 |
275 | Zero Level Tolerance -- $numZeroLevelTolDefault -- 1E-12 |
| 276 | |
276 | |
| 277 | =cut |
277 | =cut |
| 278 | |
278 | |
| 279 | =head3 "mode"_num_cmp() functions |
279 | =head3 "mode"_num_cmp() functions |
| … | |
… | |
| 289 | std_num_cmp($correctAnswer, $relPercentTol, $format, $zeroLevel) OR |
289 | std_num_cmp($correctAnswer, $relPercentTol, $format, $zeroLevel) OR |
| 290 | std_num_cmp($correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol) |
290 | std_num_cmp($correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol) |
| 291 | |
291 | |
| 292 | $correctAnswer -- the correct answer |
292 | $correctAnswer -- the correct answer |
| 293 | $relPercentTol -- the tolerance, as a percentage (optional) |
293 | $relPercentTol -- the tolerance, as a percentage (optional) |
| 294 | $format -- the format of the displayed answer (optional) |
294 | $format -- the format of the displayed answer (optional) |
| 295 | $zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies (optional) |
295 | $zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies (optional) |
| 296 | $zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero (optional) |
296 | $zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero (optional) |
| 297 | |
297 | |
| 298 | std_num_cmp() uses standard mode (arithmetic operations and elementary |
298 | std_num_cmp() uses standard mode (arithmetic operations and elementary |
| 299 | functions allowed) and relative tolerance. Options are specified by |
299 | functions allowed) and relative tolerance. Options are specified by |
| 300 | one or more parameters. Note that if you wish to set an option which |
300 | one or more parameters. Note that if you wish to set an option which |
| … | |
… | |
| 302 | |
302 | |
| 303 | std_num_cmp_abs($correctAnswer) OR |
303 | std_num_cmp_abs($correctAnswer) OR |
| 304 | std_num_cmp_abs($correctAnswer, $absTol) OR |
304 | std_num_cmp_abs($correctAnswer, $absTol) OR |
| 305 | std_num_cmp_abs($correctAnswer, $absTol, $format) |
305 | std_num_cmp_abs($correctAnswer, $absTol, $format) |
| 306 | |
306 | |
| 307 | $correctAnswer -- the correct answer |
307 | $correctAnswer -- the correct answer |
| 308 | $absTol -- an absolute tolerance (optional) |
308 | $absTol -- an absolute tolerance (optional) |
| 309 | $format -- the format of the displayed answer (optional) |
309 | $format -- the format of the displayed answer (optional) |
| 310 | |
310 | |
| 311 | std_num_cmp_abs() uses standard mode and absolute tolerance. Options |
311 | std_num_cmp_abs() uses standard mode and absolute tolerance. Options |
| 312 | are set as with std_num_cmp(). Note that $zeroLevel and $zeroLevelTol |
312 | are set as with std_num_cmp(). Note that $zeroLevel and $zeroLevelTol |
| 313 | do not apply with absolute tolerance. |
313 | do not apply with absolute tolerance. |
| 314 | |
314 | |
| 315 | std_num_cmp_list($relPercentTol, $format, @answerList) |
315 | std_num_cmp_list($relPercentTol, $format, @answerList) |
| 316 | |
316 | |
| 317 | $relPercentTol -- the tolerance, as a percentage |
317 | $relPercentTol -- the tolerance, as a percentage |
| 318 | $format -- the format of the displayed answer(s) |
318 | $format -- the format of the displayed answer(s) |
| 319 | @answerList -- a list of one or more correct answers |
319 | @answerList -- a list of one or more correct answers |
| 320 | |
320 | |
| 321 | std_num_cmp_list() uses standard mode and relative tolerance. There |
321 | std_num_cmp_list() uses standard mode and relative tolerance. There |
| 322 | is no way to set $zeroLevel or $zeroLevelTol. Note that no |
322 | is no way to set $zeroLevel or $zeroLevelTol. Note that no |
| … | |
… | |
| 352 | ANS( std_num_cmp( $answer) ) or ANS( std_num_cmp( $answer,.01 )) -- |
352 | ANS( std_num_cmp( $answer) ) or ANS( std_num_cmp( $answer,.01 )) -- |
| 353 | 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) |
| 354 | |
354 | |
| 355 | =cut |
355 | =cut |
| 356 | |
356 | |
| 357 | sub std_num_cmp { # compare numbers allowing use of elementary functions |
357 | sub std_num_cmp { # compare numbers allowing use of elementary functions |
| 358 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
358 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 359 | |
359 | |
| 360 | my %options = ( 'tolerance' => $relPercentTol, |
360 | my %options = ( 'tolerance' => $relPercentTol, |
| 361 | 'format' => $format, |
361 | 'format' => $format, |
| 362 | 'zeroLevel' => $zeroLevel, |
362 | 'zeroLevel' => $zeroLevel, |
| … | |
… | |
| 364 | ); |
364 | ); |
| 365 | |
365 | |
| 366 | set_default_options( \%options, |
366 | set_default_options( \%options, |
| 367 | 'tolType' => 'relative', |
367 | 'tolType' => 'relative', |
| 368 | 'tolerance' => $numRelPercentTolDefault, |
368 | 'tolerance' => $numRelPercentTolDefault, |
| 369 | 'mode' => 'std', |
369 | 'mode' => 'std', |
| 370 | 'format' => $numFormatDefault, |
370 | 'format' => $numFormatDefault, |
| 371 | 'relTol' => $numRelPercentTolDefault, |
371 | 'relTol' => $numRelPercentTolDefault, |
| 372 | 'zeroLevel' => $numZeroLevelDefault, |
372 | 'zeroLevel' => $numZeroLevelDefault, |
| 373 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
373 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 374 | 'debug' => 0, |
374 | 'debug' => 0, |
| 375 | ); |
375 | ); |
| 376 | |
376 | |
| … | |
… | |
| 379 | |
379 | |
| 380 | ## 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 |
| 381 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
381 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
| 382 | ## 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 |
| 383 | ## You must enter a format and tolerance |
383 | ## You must enter a format and tolerance |
| 384 | sub std_num_cmp_list { |
384 | sub std_num_cmp_list { |
| 385 | my ( $relPercentTol, $format, @answerList) = @_; |
385 | my ( $relPercentTol, $format, @answerList) = @_; |
| 386 | |
386 | |
| 387 | my %options = ( 'tolerance' => $relPercentTol, |
387 | my %options = ( 'tolerance' => $relPercentTol, |
| 388 | 'format' => $format, |
388 | 'format' => $format, |
| 389 | ); |
389 | ); |
| 390 | |
390 | |
| 391 | set_default_options( \%options, |
391 | set_default_options( \%options, |
| 392 | 'tolType' => 'relative', |
392 | 'tolType' => 'relative', |
| 393 | 'tolerance' => $numRelPercentTolDefault, |
393 | 'tolerance' => $numRelPercentTolDefault, |
| … | |
… | |
| 401 | |
401 | |
| 402 | num_cmp(\@answerList, %options); |
402 | num_cmp(\@answerList, %options); |
| 403 | |
403 | |
| 404 | } |
404 | } |
| 405 | |
405 | |
| 406 | 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 |
| 407 | my ( $correctAnswer, $absTol, $format) = @_; |
407 | my ( $correctAnswer, $absTol, $format) = @_; |
| 408 | my %options = ( 'tolerance' => $absTol, |
408 | my %options = ( 'tolerance' => $absTol, |
| 409 | 'format' => $format); |
409 | 'format' => $format |
|
|
410 | ); |
| 410 | |
411 | |
| 411 | set_default_options (\%options, |
412 | set_default_options (\%options, |
| 412 | 'tolType' => 'absolute', |
413 | 'tolType' => 'absolute', |
| 413 | 'tolerance' => $absTol, |
414 | 'tolerance' => $absTol, |
| 414 | 'mode' => 'std', |
415 | 'mode' => 'std', |
| … | |
… | |
| 421 | num_cmp([$correctAnswer], %options); |
422 | num_cmp([$correctAnswer], %options); |
| 422 | } |
423 | } |
| 423 | |
424 | |
| 424 | ## See std_num_cmp_list for usage |
425 | ## See std_num_cmp_list for usage |
| 425 | |
426 | |
| 426 | sub std_num_cmp_abs_list { |
427 | sub std_num_cmp_abs_list { |
| 427 | my ( $absTol, $format, @answerList ) = @_; |
428 | my ( $absTol, $format, @answerList ) = @_; |
| 428 | |
429 | |
| 429 | my %options = ( 'tolerance' => $absTol, |
430 | my %options = ( 'tolerance' => $absTol, |
| 430 | 'format' => $format, |
431 | 'format' => $format, |
| 431 | ); |
432 | ); |
| … | |
… | |
| 442 | |
443 | |
| 443 | num_cmp(\@answerList, %options); |
444 | num_cmp(\@answerList, %options); |
| 444 | |
445 | |
| 445 | } |
446 | } |
| 446 | |
447 | |
| 447 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
448 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
| 448 | |
449 | |
| 449 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
450 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 450 | |
451 | |
| 451 | my %options = ( 'tolerance' => $relPercentTol, |
452 | my %options = ( 'tolerance' => $relPercentTol, |
| 452 | 'format' => $format, |
453 | 'format' => $format, |
| … | |
… | |
| 461 | 'format' => $numFormatDefault, |
462 | 'format' => $numFormatDefault, |
| 462 | 'zeroLevel' => $numZeroLevelDefault, |
463 | 'zeroLevel' => $numZeroLevelDefault, |
| 463 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
464 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 464 | 'relTol' => $numRelPercentTolDefault, |
465 | 'relTol' => $numRelPercentTolDefault, |
| 465 | 'debug' => 0, |
466 | 'debug' => 0, |
| 466 | ); |
467 | ); |
| 467 | |
468 | |
| 468 | num_cmp([$correctAnswer], %options); |
469 | num_cmp([$correctAnswer], %options); |
| 469 | } |
470 | } |
| 470 | |
471 | |
| 471 | ## See std_num_cmp_list for usage |
472 | ## See std_num_cmp_list for usage |
| 472 | sub frac_num_cmp_list { |
473 | sub frac_num_cmp_list { |
| 473 | my ( $relPercentTol, $format, @answerList ) = @_; |
474 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 474 | |
475 | |
| 475 | my %options = ( 'tolerance' => $relPercentTol, |
476 | my %options = ( 'tolerance' => $relPercentTol, |
| 476 | 'format' => $format |
477 | 'format' => $format |
| 477 | ); |
478 | ); |
| … | |
… | |
| 486 | 'relTol' => $numRelPercentTolDefault, |
487 | 'relTol' => $numRelPercentTolDefault, |
| 487 | 'debug' => 0, |
488 | 'debug' => 0, |
| 488 | ); |
489 | ); |
| 489 | |
490 | |
| 490 | num_cmp(\@answerList, %options); |
491 | num_cmp(\@answerList, %options); |
| 491 | |
|
|
| 492 | } |
492 | } |
| 493 | |
493 | |
| 494 | |
|
|
| 495 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
494 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
| 496 | my ( $correctAnswer, $absTol, $format ) = @_; |
495 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 497 | |
496 | |
| 498 | my %options = ( 'tolerance' => $absTol, |
497 | my %options = ( 'tolerance' => $absTol, |
| 499 | 'format' => $format |
498 | 'format' => $format |
| 500 | ); |
499 | ); |
| 501 | |
500 | |
| 502 | set_default_options (\%options, |
501 | set_default_options (\%options, |
| 503 | 'tolType' => 'absolute', |
502 | 'tolType' => 'absolute', |
| 504 | 'tolerance' => $absTol, |
503 | 'tolerance' => $absTol, |
| 505 | 'mode' => 'frac', |
504 | 'mode' => 'frac', |
| 506 | 'format' => $numFormatDefault, |
505 | 'format' => $numFormatDefault, |
| 507 | 'zeroLevel' => 0, |
506 | 'zeroLevel' => 0, |
| 508 | 'zeroLevelTol' => 0, |
507 | 'zeroLevelTol' => 0, |
| 509 | 'debug' => 0, |
508 | 'debug' => 0, |
| 510 | ); |
509 | ); |
|
|
510 | |
| 511 | num_cmp([$correctAnswer], %options); |
511 | num_cmp([$correctAnswer], %options); |
| 512 | |
|
|
| 513 | |
|
|
| 514 | } |
512 | } |
| 515 | |
513 | |
| 516 | ## See std_num_cmp_list for usage |
514 | ## See std_num_cmp_list for usage |
| 517 | sub frac_num_cmp_abs_list { |
515 | sub frac_num_cmp_abs_list { |
| 518 | my ( $absTol, $format, @answerList ) = @_; |
516 | my ( $absTol, $format, @answerList ) = @_; |
| 519 | |
517 | |
| 520 | my %options = ( 'tolerance' => $absTol, |
518 | my %options = ( 'tolerance' => $absTol, |
| 521 | 'format' => $format |
519 | 'format' => $format |
| 522 | ); |
520 | ); |
| 523 | |
521 | |
| 524 | set_default_options (\%options, |
522 | set_default_options (\%options, |
| 525 | 'tolType' => 'absolute', |
523 | 'tolType' => 'absolute', |
| 526 | 'tolerance' => $absTol, |
524 | 'tolerance' => $absTol, |
| 527 | 'mode' => 'frac', |
525 | 'mode' => 'frac', |
| … | |
… | |
| 533 | |
531 | |
| 534 | num_cmp(\@answerList, %options); |
532 | num_cmp(\@answerList, %options); |
| 535 | } |
533 | } |
| 536 | |
534 | |
| 537 | |
535 | |
| 538 | sub arith_num_cmp { # only allow arithmetic expressions as submitted answer |
536 | sub arith_num_cmp { # only allow arithmetic expressions as submitted answer |
| 539 | |
537 | |
| 540 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
538 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 541 | |
539 | |
| 542 | my %options = ( 'tolerance' => $relPercentTol, |
540 | my %options = ( 'tolerance' => $relPercentTol, |
| 543 | 'format' => $format, |
541 | 'format' => $format, |
| 544 | 'zeroLevel' => $zeroLevel, |
542 | 'zeroLevel' => $zeroLevel, |
| 545 | 'zeroLevelTol' => $zeroLevelTol |
543 | 'zeroLevelTol' => $zeroLevelTol |
| 546 | ); |
544 | ); |
| 547 | |
545 | |
|
|
546 | set_default_options( \%options, |
|
|
547 | 'tolType' => 'relative', |
|
|
548 | 'tolerance' => $relPercentTol, |
|
|
549 | 'mode' => 'arith', |
|
|
550 | 'format' => $numFormatDefault, |
|
|
551 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
552 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
553 | 'relTol' => $numRelPercentTolDefault, |
|
|
554 | 'debug' => 0, |
|
|
555 | ); |
|
|
556 | |
|
|
557 | num_cmp([$correctAnswer], %options); |
|
|
558 | } |
|
|
559 | |
|
|
560 | ## See std_num_cmp_list for usage |
|
|
561 | sub arith_num_cmp_list { |
|
|
562 | my ( $relPercentTol, $format, @answerList ) = @_; |
|
|
563 | |
|
|
564 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
565 | 'format' => $format, |
|
|
566 | ); |
|
|
567 | |
| 548 | set_default_options( \%options, |
568 | set_default_options( \%options, |
| 549 | 'tolType' => 'relative', |
569 | 'tolType' => 'relative', |
| 550 | 'tolerance' => $relPercentTol, |
570 | 'tolerance' => $relPercentTol, |
| 551 | 'mode' => 'arith', |
571 | 'mode' => 'arith', |
| 552 | 'format' => $numFormatDefault, |
572 | 'format' => $numFormatDefault, |
| 553 | 'zeroLevel' => $numZeroLevelDefault, |
573 | 'zeroLevel' => $numZeroLevelDefault, |
| 554 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
574 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 555 | 'relTol' => $numRelPercentTolDefault, |
575 | 'relTol' => $numRelPercentTolDefault, |
| 556 | 'debug' => 0, |
576 | 'debug' => 0, |
| 557 | ); |
577 | ); |
| 558 | |
|
|
| 559 | num_cmp([$correctAnswer], %options); |
|
|
| 560 | } |
|
|
| 561 | |
|
|
| 562 | ## See std_num_cmp_list for usage |
|
|
| 563 | sub arith_num_cmp_list { |
|
|
| 564 | my ( $relPercentTol, $format, @answerList ) = @_; |
|
|
| 565 | |
|
|
| 566 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
| 567 | 'format' => $format, |
|
|
| 568 | ); |
|
|
| 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); |
578 | num_cmp(\@answerList, %options); |
| 581 | } |
579 | } |
| 582 | |
580 | |
| 583 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
581 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
| 584 | my ( $correctAnswer, $absTol, $format ) = @_; |
582 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 585 | |
583 | |
| 586 | my %options = ( 'tolerance' => $absTol, |
584 | my %options = ( 'tolerance' => $absTol, |
| 587 | 'format' => $format |
585 | 'format' => $format |
| 588 | ); |
586 | ); |
| 589 | |
587 | |
| 590 | set_default_options (\%options, |
588 | set_default_options (\%options, |
| 591 | 'tolType' => 'absolute', |
589 | 'tolType' => 'absolute', |
| 592 | 'tolerance' => $absTol, |
590 | 'tolerance' => $absTol, |
| 593 | 'mode' => 'arith', |
591 | 'mode' => 'arith', |
| 594 | 'format' => $numFormatDefault, |
592 | 'format' => $numFormatDefault, |
| 595 | 'zeroLevel' => 0, |
593 | 'zeroLevel' => 0, |
| 596 | 'zeroLevelTol' => 0, |
594 | 'zeroLevelTol' => 0, |
| 597 | 'debug' => 0, |
595 | 'debug' => 0, |
| 598 | ); |
596 | ); |
| 599 | num_cmp([$correctAnswer], %options); |
597 | num_cmp([$correctAnswer], %options); |
| 600 | |
|
|
| 601 | |
|
|
| 602 | } |
598 | } |
| 603 | |
599 | |
| 604 | ## See std_num_cmp_list for usage |
600 | ## See std_num_cmp_list for usage |
| 605 | sub arith_num_cmp_abs_list { |
601 | sub arith_num_cmp_abs_list { |
| 606 | my ( $absTol, $format, @answerList ) = @_; |
602 | my ( $absTol, $format, @answerList ) = @_; |
| 607 | |
603 | |
| 608 | my %options = ( 'tolerance' => $absTol, |
604 | my %options = ( 'tolerance' => $absTol, |
| 609 | 'format' => $format |
605 | 'format' => $format |
| 610 | ); |
606 | ); |
| 611 | |
607 | |
| 612 | set_default_options (\%options, |
608 | set_default_options (\%options, |
| 613 | 'tolType' => 'absolute', |
609 | 'tolType' => 'absolute', |
| 614 | 'tolerance' => $absTol, |
610 | 'tolerance' => $absTol, |
| 615 | 'mode' => 'arith', |
611 | 'mode' => 'arith', |
| … | |
… | |
| 620 | ); |
616 | ); |
| 621 | num_cmp(\@answerList, %options); |
617 | num_cmp(\@answerList, %options); |
| 622 | |
618 | |
| 623 | } |
619 | } |
| 624 | |
620 | |
| 625 | sub strict_num_cmp { # only allow numbers as submitted answer |
621 | sub strict_num_cmp { # only allow numbers as submitted answer |
| 626 | |
|
|
| 627 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
622 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 628 | |
623 | |
| 629 | my %options = ( 'tolerance' => $relPercentTol, |
624 | my %options = ( 'tolerance' => $relPercentTol, |
| 630 | 'format' => $format, |
625 | 'format' => $format, |
| 631 | 'zeroLevel' => $zeroLevel, |
626 | 'zeroLevel' => $zeroLevel, |
| 632 | 'zeroLevelTol' => $zeroLevelTol |
627 | 'zeroLevelTol' => $zeroLevelTol |
| 633 | ); |
628 | ); |
| 634 | |
629 | |
| 635 | set_default_options( \%options, |
630 | set_default_options( \%options, |
| 636 | 'tolType' => 'relative', |
631 | 'tolType' => 'relative', |
| 637 | 'tolerance' => $relPercentTol, |
632 | 'tolerance' => $relPercentTol, |
| … | |
… | |
| 640 | 'zeroLevel' => $numZeroLevelDefault, |
635 | 'zeroLevel' => $numZeroLevelDefault, |
| 641 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
636 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 642 | 'relTol' => $numRelPercentTolDefault, |
637 | 'relTol' => $numRelPercentTolDefault, |
| 643 | 'debug' => 0, |
638 | 'debug' => 0, |
| 644 | ); |
639 | ); |
| 645 | |
|
|
| 646 | num_cmp([$correctAnswer], %options); |
640 | num_cmp([$correctAnswer], %options); |
| 647 | |
|
|
| 648 | } |
641 | } |
| 649 | |
642 | |
| 650 | ## See std_num_cmp_list for usage |
643 | ## See std_num_cmp_list for usage |
| 651 | sub strict_num_cmp_list { # compare numbers |
644 | sub strict_num_cmp_list { # compare numbers |
| 652 | my ( $relPercentTol, $format, @answerList ) = @_; |
645 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 653 | |
646 | |
| 654 | my %options = ( 'tolerance' => $relPercentTol, |
647 | my %options = ( 'tolerance' => $relPercentTol, |
| 655 | 'format' => $format, |
648 | 'format' => $format, |
| 656 | ); |
649 | ); |
| 657 | |
650 | |
| 658 | set_default_options( \%options, |
651 | set_default_options( \%options, |
| 659 | 'tolType' => 'relative', |
652 | 'tolType' => 'relative', |
| 660 | 'tolerance' => $relPercentTol, |
653 | 'tolerance' => $relPercentTol, |
| … | |
… | |
| 663 | 'zeroLevel' => $numZeroLevelDefault, |
656 | 'zeroLevel' => $numZeroLevelDefault, |
| 664 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
657 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 665 | 'relTol' => $numRelPercentTolDefault, |
658 | 'relTol' => $numRelPercentTolDefault, |
| 666 | 'debug' => 0, |
659 | 'debug' => 0, |
| 667 | ); |
660 | ); |
| 668 | |
|
|
| 669 | num_cmp(\@answerList, %options); |
661 | num_cmp(\@answerList, %options); |
| 670 | } |
662 | } |
| 671 | |
663 | |
| 672 | |
664 | |
| 673 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
665 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
| 674 | |
666 | |
| 675 | my ( $correctAnswer, $absTol, $format ) = @_; |
667 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 676 | |
668 | |
| 677 | my %options = ( 'tolerance' => $absTol, |
669 | my %options = ( 'tolerance' => $absTol, |
| 678 | 'format' => $format |
670 | 'format' => $format |
| 679 | ); |
671 | ); |
| 680 | |
672 | |
| 681 | set_default_options (\%options, |
673 | set_default_options (\%options, |
| 682 | 'tolType' => 'absolute', |
674 | 'tolType' => 'absolute', |
| 683 | 'tolerance' => $absTol, |
675 | 'tolerance' => $absTol, |
| 684 | 'mode' => 'strict', |
676 | 'mode' => 'strict', |
| 685 | 'format' => $numFormatDefault, |
677 | 'format' => $numFormatDefault, |
| 686 | 'zeroLevel' => 0, |
678 | 'zeroLevel' => 0, |
| 687 | 'zeroLevelTol' => 0, |
679 | 'zeroLevelTol' => 0, |
| 688 | 'debug' => 0, |
680 | 'debug' => 0, |
| 689 | ); |
681 | ); |
| 690 | |
682 | |
| 691 | num_cmp([$correctAnswer], %options); |
683 | num_cmp([$correctAnswer], %options); |
| 692 | |
684 | |
| 693 | } |
685 | } |
| 694 | |
686 | |
| 695 | ## See std_num_cmp_list for usage |
687 | ## See std_num_cmp_list for usage |
| 696 | sub strict_num_cmp_abs_list { # compare numbers |
688 | sub strict_num_cmp_abs_list { # compare numbers |
| 697 | my ( $absTol, $format, @answerList ) = @_; |
689 | my ( $absTol, $format, @answerList ) = @_; |
| 698 | |
|
|
| 699 | |
|
|
| 700 | my %options = ( 'tolerance' => $absTol, |
690 | my %options = ( 'tolerance' => $absTol, |
| 701 | 'format' => $format |
691 | 'format' => $format |
| 702 | ); |
692 | ); |
| 703 | |
693 | |
| 704 | set_default_options (\%options, |
694 | set_default_options (\%options, |
| 705 | 'tolType' => 'absolute', |
695 | 'tolType' => 'absolute', |
| 706 | 'tolerance' => $absTol, |
696 | 'tolerance' => $absTol, |
| 707 | 'mode' => 'strict', |
697 | 'mode' => 'strict', |
| … | |
… | |
| 711 | 'debug' => 0, |
701 | 'debug' => 0, |
| 712 | ); |
702 | ); |
| 713 | |
703 | |
| 714 | num_cmp(\@answerList, %options); |
704 | num_cmp(\@answerList, %options); |
| 715 | |
705 | |
| 716 | |
|
|
| 717 | |
|
|
| 718 | } |
706 | } |
| 719 | |
|
|
| 720 | |
707 | |
| 721 | ## Compares a number with units |
708 | ## Compares a number with units |
| 722 | ## Deprecated; use num_cmp() |
709 | ## Deprecated; use num_cmp() |
| 723 | ## |
710 | ## |
| 724 | ## IN: a string which includes the numerical answer and the units |
711 | ## IN: a string which includes the numerical answer and the units |
| … | |
… | |
| 728 | ## tol -- an absolute tolerance, or |
715 | ## tol -- an absolute tolerance, or |
| 729 | ## relTol -- a relative tolerance |
716 | ## relTol -- a relative tolerance |
| 730 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
717 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 731 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
718 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
| 732 | |
719 | |
| 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. |
720 | # This mode is depricated. send input through num_cmp -- it can handle units. |
|
|
721 | |
| 861 | sub numerical_compare_with_units { |
722 | sub numerical_compare_with_units { |
| 862 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
723 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
| 863 | my %options = @_; # all of the other inputs are (key value) pairs |
724 | my %options = @_; # all of the other inputs are (key value) pairs |
| 864 | |
725 | |
| 865 | # Prepare the correct answer |
726 | # Prepare the correct answer |
| 866 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
727 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
| 867 | |
728 | |
| 868 | # it surprises me that the match below works since the first .* is greedy. |
729 | # it surprises me that the match below works since the first .* is greedy. |
| 869 | my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/; |
730 | my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/; |
| 870 | |
|
|
| 871 | $options{units} = $correct_units; |
731 | $options{units} = $correct_units; |
| 872 | |
732 | |
| 873 | num_cmp($correct_num_answer, %options); |
733 | num_cmp($correct_num_answer, %options); |
| 874 | } |
734 | } |
| 875 | |
735 | |
| … | |
… | |
| 895 | $zeroLevel, $zeroLevelTol ) |
755 | $zeroLevel, $zeroLevelTol ) |
| 896 | |
756 | |
| 897 | $correctAnswer -- the correct answer |
757 | $correctAnswer -- the correct answer |
| 898 | $ra_legalStrings -- a reference to an array of legal strings, e.g. ["str1", "str2"] |
758 | $ra_legalStrings -- a reference to an array of legal strings, e.g. ["str1", "str2"] |
| 899 | $relPercentTol -- the error tolerance as a percentage |
759 | $relPercentTol -- the error tolerance as a percentage |
| 900 | $format -- the display format |
760 | $format -- the display format |
| 901 | $zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
761 | $zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 902 | $zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
762 | $zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
| 903 | |
763 | |
| 904 | Example: |
764 | Example: |
| 905 | ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) ); |
765 | ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) ); |
| 906 | |
766 | |
| 907 | =cut |
767 | =cut |
| 908 | |
768 | |
| 909 | sub std_num_str_cmp { |
769 | sub std_num_str_cmp { |
| 910 | my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
770 | my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 911 | # warn ('This method is depreciated. Use num_cmp instead.'); |
771 | # warn ('This method is depreciated. Use num_cmp instead.'); |
| 912 | return num_cmp ($correctAnswer, strings=>$ra_legalStrings, relTol=>$relpercentTol, format=>$format, |
772 | return num_cmp ($correctAnswer, strings=>$ra_legalStrings, relTol=>$relpercentTol, format=>$format, |
| 913 | zeroLevel=>$zeroLevel, zeroLevelTol=>$zeroLevelTol); |
773 | zeroLevel=>$zeroLevel, zeroLevelTol=>$zeroLevelTol); |
| 914 | } |
774 | } |
| … | |
… | |
| 926 | mode -- 'std' (default) (allows any expression evaluating to a number) |
786 | mode -- 'std' (default) (allows any expression evaluating to a number) |
| 927 | 'strict' (only numbers are allowed) |
787 | 'strict' (only numbers are allowed) |
| 928 | 'frac' (fractions are allowed) |
788 | 'frac' (fractions are allowed) |
| 929 | 'arith' (arithmetic expressions allowed) |
789 | 'arith' (arithmetic expressions allowed) |
| 930 | format -- '%0.5f#' (default); defines formatting for the correct answer |
790 | format -- '%0.5f#' (default); defines formatting for the correct answer |
| 931 | tol -- an absolute tolerance, or |
791 | tol -- an absolute tolerance, or |
| 932 | relTol -- a relative tolerance |
792 | relTol -- a relative tolerance |
| 933 | units -- the units to use for the answer(s) |
793 | units -- the units to use for the answer(s) |
| 934 | strings -- a reference to an array of strings which are valid |
794 | strings -- a reference to an array of strings which are valid |
| 935 | answers (works like std_num_str_cmp() ) |
795 | answers (works like std_num_str_cmp() ) |
| 936 | zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
796 | zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 937 | zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
797 | zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
| 938 | |
798 | |
| 939 | Returns an answer evaluator, or (if given a reference to an array of |
799 | Returns an answer evaluator, or (if given a reference to an array of |
| 940 | answers), a list of answer evaluators. Note that a reference to an array of |
800 | answers), a list of answer evaluators. Note that a reference to an array of |
| 941 | answers results is just a shortcut to writing a separate cum_cmp() for each |
801 | answers results is just a shortcut to writing a separate cum_cmp() for each |
| 942 | answer. It does not mean that any of those answers are considered correct |
802 | answer. It does not mean that any of those answers are considered correct |
| 943 | for one question. |
803 | for one question. |
| 944 | |
804 | |
| 945 | EXAMPLES: |
805 | EXAMPLES: |
| 946 | |
806 | |
| 947 | num_cmp( 5 ) -- correct answer is 5, using defaults for all options |
807 | num_cmp( 5 ) -- correct answer is 5, using defaults for all options |
| 948 | num_cmp( [5,6,7] ) -- correct answers are 5, 6, and 7, using defaults for all options |
808 | num_cmp( [5,6,7] ) -- correct answers are 5, 6, and 7, using defaults for all options |
| 949 | num_cmp( 5, mode => 'strict' ) -- correct answer is 5, mode is strict |
809 | num_cmp( 5, mode => 'strict' ) -- correct answer is 5, mode is strict |
| 950 | num_cmp( [5,6], relTol => 5 ) -- correct answers are 5 and 6, both with 5% relative tolerance |
810 | num_cmp( [5,6], relTol => 5 ) -- correct answers are 5 and 6, both with 5% relative tolerance |
| 951 | num_cmp( 6, strings => ["Inf", "Minf", "NaN"] ) -- correct answer is 6, "Inf", "Minf", and "NaN" |
811 | num_cmp( 6, strings => ["Inf", "Minf", "NaN"] ) -- correct answer is 6, "Inf", "Minf", and "NaN" |
| 952 | recognized as valid answers |
812 | recognized as valid answers |
| 953 | |
813 | |
| 954 | =cut |
814 | =cut |
| 955 | |
815 | |
| 956 | sub num_cmp { |
816 | sub num_cmp { |
| 957 | my $correctAnswer = shift @_; |
817 | my $correctAnswer = shift @_; |
| 958 | my @opt = @_; |
818 | my @opt = @_; |
| 959 | my %out_options; |
819 | my %out_options; |
| 960 | |
820 | |
| 961 | ######################################################################### |
821 | ######################################################################### |
| 962 | # Retain this first check for backword compatibility. Allows input of the form |
822 | # Retain this first check for backword compatibility. Allows input of the form |
| 963 | # num_cmp($ans, 1, '%0.5f') but warns against it |
823 | # num_cmp($ans, 1, '%0.5f') but warns against it |
| 964 | ######################################################################### |
824 | ######################################################################### |
| 965 | |
|
|
| 966 | my %known_options = ( 'mode' => 'std', |
825 | my %known_options = ( 'mode' => 'std', |
| 967 | 'format' => $numFormatDefault, |
826 | 'format' => $numFormatDefault, |
| 968 | 'tol' => $numAbsTolDefault, |
827 | 'tol' => $numAbsTolDefault, |
| 969 | 'relTol' => $numRelPercentTolDefault, |
828 | 'relTol' => $numRelPercentTolDefault, |
| 970 | 'units' => undef, |
829 | 'units' => undef, |
| … | |
… | |
| 975 | 'tolerance' => 1, |
834 | 'tolerance' => 1, |
| 976 | 'reltol' => undef, #alternate spelling |
835 | 'reltol' => undef, #alternate spelling |
| 977 | 'unit' => undef, #alternate spelling |
836 | 'unit' => undef, #alternate spelling |
| 978 | 'debug' => 0 |
837 | 'debug' => 0 |
| 979 | |
838 | |
| 980 | ); |
839 | ); |
| 981 | |
840 | |
| 982 | my @output_list; |
841 | my @output_list; |
| 983 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
842 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
| 984 | |
843 | |
| 985 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
844 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
| … | |
… | |
| 993 | " problem using the options style of parameter passing (or" . |
852 | " problem using the options style of parameter passing (or" . |
| 994 | " check that your first option is spelled correctly)."; |
853 | " check that your first option is spelled correctly)."; |
| 995 | |
854 | |
| 996 | |
855 | |
| 997 | %out_options = ( 'relTol' => $relPercentTol, |
856 | %out_options = ( 'relTol' => $relPercentTol, |
| 998 | 'format' => $format, |
857 | 'format' => $format, |
| 999 | 'zeroLevel' => $zeroLevel, |
858 | 'zeroLevel' => $zeroLevel, |
| 1000 | 'zeroLevelTol' => $zeroLevelTol, |
859 | 'zeroLevelTol' => $zeroLevelTol, |
| 1001 | 'mode' => 'std' |
860 | 'mode' => 'std' |
| 1002 | ); |
861 | ); |
| 1003 | } |
862 | } |
| 1004 | # else { |
863 | |
| 1005 | # # handle options |
|
|
| 1006 | # |
|
|
| 1007 | # |
|
|
| 1008 | # @opt = ( 'relTol' => $relPercentTol, |
|
|
| 1009 | # 'format' => $format, |
|
|
| 1010 | # 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 1011 | # 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 1012 | # 'mode' => 'std' |
|
|
| 1013 | # ); |
|
|
| 1014 | # } |
|
|
| 1015 | ######################################################################### |
864 | ######################################################################### |
| 1016 | # Now handle the options assuming they are entered in the form |
865 | # Now handle the options assuming they are entered in the form |
| 1017 | # num_cmp($ans, relTol=>1, format=>'%0.5f') |
866 | # num_cmp($ans, relTol=>1, format=>'%0.5f') |
| 1018 | ######################################################################### |
867 | ######################################################################### |
| 1019 | %out_options = @opt; |
868 | %out_options = @opt; |
| 1020 | assign_option_aliases( \%out_options, |
869 | assign_option_aliases( \%out_options, |
| 1021 | 'reltol' => 'relTol', |
870 | 'reltol' => 'relTol', |
| 1022 | 'unit' => 'units', |
871 | 'unit' => 'units', |
| 1023 | ); |
872 | ); |
| 1024 | |
|
|
| 1025 | |
|
|
| 1026 | |
|
|
| 1027 | |
873 | |
| 1028 | set_default_options( \%out_options, |
874 | set_default_options( \%out_options, |
| 1029 | 'tolType' => (defined($out_options{tol}) ) ? 'absolute' : 'relative', |
875 | 'tolType' => (defined($out_options{tol}) ) ? 'absolute' : 'relative', |
| 1030 | 'tolerance' => (defined($out_options{tol}) ) ? $numAbsTolDefault : $numRelPercentTolDefault, |
876 | 'tolerance' => (defined($out_options{tol}) ) ? $numAbsTolDefault : $numRelPercentTolDefault, |
| 1031 | 'mode' => 'std', |
877 | 'mode' => 'std', |
| … | |
… | |
| 1035 | 'units' => undef, |
881 | 'units' => undef, |
| 1036 | 'strings' => undef, |
882 | 'strings' => undef, |
| 1037 | 'zeroLevel' => $numZeroLevelDefault, |
883 | 'zeroLevel' => $numZeroLevelDefault, |
| 1038 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
884 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 1039 | 'debug' => 0, |
885 | 'debug' => 0, |
| 1040 | |
886 | ); |
| 1041 | ); |
|
|
| 1042 | |
|
|
| 1043 | |
|
|
| 1044 | |
|
|
| 1045 | |
|
|
| 1046 | |
|
|
| 1047 | |
887 | |
| 1048 | # can't use both units and strings |
888 | # can't use both units and strings |
| 1049 | if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) { |
889 | if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) { |
| 1050 | warn "Can't use both 'units' and 'strings' in the same problem " . |
890 | warn "Can't use both 'units' and 'strings' in the same problem " . |
| 1051 | "(check your parameters to num_cmp() )"; |
891 | "(check your parameters to num_cmp() )"; |
| 1052 | |
|
|
| 1053 | } |
892 | } |
| 1054 | |
|
|
| 1055 | |
893 | |
| 1056 | # my ($tolType, $tol); |
894 | # my ($tolType, $tol); |
| 1057 | if ($out_options{tolType} eq 'absolute') { |
895 | if ($out_options{tolType} eq 'absolute') { |
| 1058 | $out_options{'tolerance'}=$out_options{'tol'}; |
896 | $out_options{'tolerance'}=$out_options{'tol'}; |
| 1059 | delete($out_options{'relTol'}) if exists( $out_options{'relTol'} ); |
897 | delete($out_options{'relTol'}) if exists( $out_options{'relTol'} ); |
| … | |
… | |
| 1066 | my @ans_list = (); |
904 | my @ans_list = (); |
| 1067 | |
905 | |
| 1068 | if ( ref($correctAnswer) eq 'ARRAY' ) { |
906 | if ( ref($correctAnswer) eq 'ARRAY' ) { |
| 1069 | @ans_list = @{$correctAnswer}; |
907 | @ans_list = @{$correctAnswer}; |
| 1070 | } |
908 | } |
| 1071 | else { |
|
|
| 1072 | push( @ans_list, $correctAnswer ); |
909 | else { push( @ans_list, $correctAnswer ); |
| 1073 | } |
910 | } |
| 1074 | |
911 | |
| 1075 | # produce answer evaluators |
912 | # produce answer evaluators |
| 1076 | foreach my $ans (@ans_list) { |
913 | foreach my $ans (@ans_list) { |
| 1077 | if( defined( $out_options{'units'} ) ) { |
914 | if( defined( $out_options{'units'} ) ) { |
| … | |
… | |
| 1084 | 'mode' => $out_options{'mode'}, |
921 | 'mode' => $out_options{'mode'}, |
| 1085 | 'zeroLevel' => $out_options{'zeroLevel'}, |
922 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1086 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
923 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 1087 | 'debug' => $out_options{'debug'}, |
924 | 'debug' => $out_options{'debug'}, |
| 1088 | 'units' => $out_options{'units'}, |
925 | 'units' => $out_options{'units'}, |
| 1089 | ) |
926 | ) |
| 1090 | ); |
927 | ); |
| 1091 | } |
928 | } |
| 1092 | elsif( defined( $out_options{'strings'} ) ) { |
929 | elsif( defined( $out_options{'strings'} ) ) { |
| 1093 | #if( defined $out_options{'tol'} ) { |
930 | #if( defined $out_options{'tol'} ) { |
| 1094 | # warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
931 | # warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
| … | |
… | |
| 1103 | 'mode' => $out_options{'mode'}, |
940 | 'mode' => $out_options{'mode'}, |
| 1104 | 'zeroLevel' => $out_options{'zeroLevel'}, |
941 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1105 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
942 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 1106 | 'debug' => $out_options{'debug'}, |
943 | 'debug' => $out_options{'debug'}, |
| 1107 | 'strings' => $out_options{'strings'}, |
944 | 'strings' => $out_options{'strings'}, |
| 1108 | |
945 | ) |
| 1109 | ) |
|
|
| 1110 | ); |
946 | ); |
| 1111 | } |
|
|
| 1112 | else { |
947 | } else { |
| 1113 | |
|
|
| 1114 | push(@output_list, |
948 | push(@output_list, |
| 1115 | NUM_CMP( 'correctAnswer' => $ans, |
949 | NUM_CMP( 'correctAnswer' => $ans, |
| 1116 | 'tolerance' => $out_options{tolerance}, |
950 | 'tolerance' => $out_options{tolerance}, |
| 1117 | 'tolType' => $out_options{tolType}, |
951 | 'tolType' => $out_options{tolType}, |
| 1118 | 'format' => $out_options{'format'}, |
952 | 'format' => $out_options{'format'}, |
| 1119 | 'mode' => $out_options{'mode'}, |
953 | 'mode' => $out_options{'mode'}, |
| 1120 | 'zeroLevel' => $out_options{'zeroLevel'}, |
954 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1121 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
955 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 1122 | 'debug' => $out_options{'debug'}, |
956 | 'debug' => $out_options{'debug'}, |
| 1123 | |
|
|
| 1124 | ), |
957 | ), |
| 1125 | ); |
958 | ); |
| 1126 | } |
959 | } |
| 1127 | } |
960 | } |
| 1128 | |
961 | |
| 1129 | return @output_list; |
962 | return @output_list; |
| 1130 | } |
963 | } |
| 1131 | |
964 | |
| 1132 | #legacy code for compatability purposes |
965 | #legacy code for compatability purposes |
| 1133 | sub num_rel_cmp { # compare numbers |
966 | sub num_rel_cmp { # compare numbers |
| 1134 | std_num_cmp( @_ ); |
967 | std_num_cmp( @_ ); |
| 1135 | } |
968 | } |
| 1136 | |
969 | |
| 1137 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
|
|
| 1138 | ## |
|
|
| 1139 | ## IN: a hash containing the following items (error-checking to be added later?): |
|
|
| 1140 | ## correctAnswer -- the correct answer |
|
|
| 1141 | ## tolerance -- the allowable margin of error |
|
|
| 1142 | ## tolType -- 'relative' or 'absolute' |
|
|
| 1143 | ## format -- the display format of the answer |
|
|
| 1144 | ## mode -- one of 'std', 'strict', 'arith', or 'frac'; |
|
|
| 1145 | ## determines allowable formats for the input |
|
|
| 1146 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
|
|
| 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 | |
|
|
| 1184 | sub NUM_CMP { # low level numeric compare |
970 | sub NUM_CMP { # low level numeric compare |
| 1185 | my %num_params = @_; |
971 | my %num_params = @_; |
| 1186 | |
972 | |
| 1187 | my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); |
973 | my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); |
| 1188 | foreach my $key (@keys) { |
974 | foreach my $key (@keys) { |
| 1189 | warn "$key must be defined in options when calling NUM_CMP" unless defined ($num_params{$key}); |
975 | warn "$key must be defined in options when calling NUM_CMP" unless defined ($num_params{$key}); |
| … | |
… | |
| 1237 | if ( uc($correctAnswer) eq uc($legalString) ) { |
1023 | if ( uc($correctAnswer) eq uc($legalString) ) { |
| 1238 | $corrAnswerIsString = 1; |
1024 | $corrAnswerIsString = 1; |
| 1239 | last; |
1025 | last; |
| 1240 | } |
1026 | } |
| 1241 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
1027 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
| 1242 | |
|
|
| 1243 | |
1028 | |
| 1244 | } else { |
1029 | } else { |
| 1245 | $correct_num_answer = $correctAnswer; |
1030 | $correct_num_answer = $correctAnswer; |
| 1246 | $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} ); |
1031 | $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} ); |
| 1247 | } |
1032 | } |
| … | |
… | |
| 1253 | my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); |
1038 | my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); |
| 1254 | |
1039 | |
| 1255 | if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) { |
1040 | 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); |
1041 | ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer); |
| 1257 | } |
1042 | } |
| 1258 | else { |
|
|
| 1259 | $PG_eval_errors = ' '; |
1043 | else { $PG_eval_errors = ' '; |
| 1260 | } |
1044 | } |
| 1261 | |
1045 | |
| 1262 | if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { |
1046 | if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { |
| 1263 | ##error message from eval or above |
1047 | ##error message from eval or above |
| 1264 | warn "Error in 'correct' answer: $PG_eval_errors<br> |
1048 | warn "Error in 'correct' answer: $PG_eval_errors<br> |
| … | |
… | |
| 1333 | |
1117 | |
| 1334 | |
1118 | |
| 1335 | $answer_evaluator; |
1119 | $answer_evaluator; |
| 1336 | } |
1120 | } |
| 1337 | |
1121 | |
| 1338 | sub fix_answers_for_display { |
1122 | sub fix_answers_for_display { |
| 1339 | my ($rh_ans, %options) = @_; |
1123 | my ($rh_ans, %options) = @_; |
| 1340 | if ( $rh_ans->{answerIsString} ==1) { |
1124 | if ( $rh_ans->{answerIsString} ==1) { |
| 1341 | $rh_ans = evaluatesToNumber ($rh_ans, %options); |
1125 | $rh_ans = evaluatesToNumber ($rh_ans, %options); |
| 1342 | } |
1126 | } |
| 1343 | if (defined ($rh_ans->{student_units})) { |
1127 | if (defined ($rh_ans->{student_units})) { |
| … | |
… | |
| 1345 | } |
1129 | } |
| 1346 | $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans}; |
1130 | $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans}; |
| 1347 | $rh_ans; |
1131 | $rh_ans; |
| 1348 | } |
1132 | } |
| 1349 | |
1133 | |
| 1350 | sub evaluatesToNumber { |
1134 | sub evaluatesToNumber { |
| 1351 | my ($rh_ans, %options) = @_; |
1135 | my ($rh_ans, %options) = @_; |
| 1352 | if (is_a_numeric_expression($rh_ans->{student_ans})) { |
1136 | 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}); |
1137 | 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 |
1138 | if ($PG_eval_errors) { # this if statement should never be run |
| 1355 | # change nothing |
1139 | # change nothing |
| … | |
… | |
| 1359 | } |
1143 | } |
| 1360 | } |
1144 | } |
| 1361 | $rh_ans; |
1145 | $rh_ans; |
| 1362 | } |
1146 | } |
| 1363 | |
1147 | |
| 1364 | sub is_a_numeric_expression { |
1148 | sub is_a_numeric_expression { |
| 1365 | my $testString = shift; |
1149 | my $testString = shift; |
| 1366 | my $is_a_numeric_expression = 0; |
1150 | my $is_a_numeric_expression = 0; |
| 1367 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($testString); |
1151 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($testString); |
| 1368 | if ($PG_eval_errors) { |
1152 | if ($PG_eval_errors) { |
| 1369 | $is_a_numeric_expression = 0; |
1153 | $is_a_numeric_expression = 0; |
| … | |
… | |
| 1593 | 'debug' => $options{debug} , |
1377 | 'debug' => $options{debug} , |
| 1594 | ); |
1378 | ); |
| 1595 | |
1379 | |
| 1596 | } |
1380 | } |
| 1597 | |
1381 | |
| 1598 | sub function_cmp { |
1382 | sub function_cmp { |
| 1599 | my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$zeroLevel,$zeroLevelTol) = @_; |
1383 | my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$zeroLevel,$zeroLevelTol) = @_; |
| 1600 | |
1384 | |
| 1601 | if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) { |
1385 | if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) { |
| 1602 | function_invalid_params( $correctEqn ); |
1386 | function_invalid_params( $correctEqn ); |
| 1603 | } |
1387 | } |
| … | |
… | |
| 1614 | 'zeroLevelTol' => $zeroLevelTol |
1398 | 'zeroLevelTol' => $zeroLevelTol |
| 1615 | ); |
1399 | ); |
| 1616 | } |
1400 | } |
| 1617 | } |
1401 | } |
| 1618 | |
1402 | |
| 1619 | sub function_cmp_up_to_constant { ## for antiderivative problems |
1403 | sub function_cmp_up_to_constant { ## for antiderivative problems |
| 1620 | my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$maxConstantOfIntegration,$zeroLevel,$zeroLevelTol) = @_; |
1404 | my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$maxConstantOfIntegration,$zeroLevel,$zeroLevelTol) = @_; |
| 1621 | |
1405 | |
| 1622 | if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) { |
1406 | if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) { |
| 1623 | function_invalid_params( $correctEqn ); |
1407 | function_invalid_params( $correctEqn ); |
| 1624 | } |
1408 | } |
| … | |
… | |
| 1635 | 'zeroLevelTol' => $zeroLevelTol |
1419 | 'zeroLevelTol' => $zeroLevelTol |
| 1636 | ); |
1420 | ); |
| 1637 | } |
1421 | } |
| 1638 | } |
1422 | } |
| 1639 | |
1423 | |
| 1640 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
1424 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
| 1641 | my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints) = @_; |
1425 | my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints) = @_; |
| 1642 | |
1426 | |
| 1643 | if ( (scalar(@_) == 3) or (scalar(@_) > 6) or (scalar(@_) == 0) ) { |
1427 | if ( (scalar(@_) == 3) or (scalar(@_) > 6) or (scalar(@_) == 0) ) { |
| 1644 | function_invalid_params( $correctEqn ); |
1428 | function_invalid_params( $correctEqn ); |
| 1645 | } |
1429 | } |
| 1646 | else { |
1430 | else { |
| 1647 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1431 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1648 | 'var' => $var, |
1432 | 'var' => $var, |
| 1649 | 'limits' => [$llimit, $ulimit], |
1433 | 'limits' => [$llimit, $ulimit], |
| 1650 | 'tolerance' => $absTol, |
1434 | 'tolerance' => $absTol, |
| 1651 | 'tolType' => 'absolute', |
1435 | 'tolType' => 'absolute', |
| 1652 | 'numPoints' => $numPoints, |
1436 | 'numPoints' => $numPoints, |
| 1653 | 'mode' => 'std', |
1437 | 'mode' => 'std', |
| 1654 | 'maxConstantOfIntegration' => 0, |
1438 | 'maxConstantOfIntegration' => 0, |
| 1655 | 'zeroLevel' => 0, |
1439 | 'zeroLevel' => 0, |
| 1656 | 'zeroLevelTol' => 0 |
1440 | 'zeroLevelTol' => 0 |
| 1657 | ); |
1441 | ); |
| 1658 | } |
1442 | } |
| 1659 | } |
1443 | } |
| 1660 | |
1444 | |
| 1661 | |
1445 | |
| 1662 | sub function_cmp_up_to_constant_abs { ## for antiderivative problems |
1446 | sub function_cmp_up_to_constant_abs { ## for antiderivative problems |
| 1663 | ## similar to function_cmp_up_to_constant |
1447 | ## similar to function_cmp_up_to_constant |
| 1664 | ## but uses absolute tolerance |
1448 | ## but uses absolute tolerance |
| 1665 | my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints,$maxConstantOfIntegration) = @_; |
1449 | my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints,$maxConstantOfIntegration) = @_; |
| 1666 | |
1450 | |
| 1667 | if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) { |
1451 | if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) { |
| … | |
… | |
| 1908 | ## zeroLevel -- if the correct answer is this close to zero, |
1692 | ## zeroLevel -- if the correct answer is this close to zero, |
| 1909 | ## then zeroLevelTol applies |
1693 | ## then zeroLevelTol applies |
| 1910 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
1694 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
| 1911 | |
1695 | |
| 1912 | |
1696 | |
| 1913 | sub FUNCTION_CMP { |
1697 | sub FUNCTION_CMP { |
| 1914 | my %func_params = @_; |
1698 | my %func_params = @_; |
| 1915 | |
1699 | |
| 1916 | my $correctEqn = $func_params{'correctEqn'}; |
1700 | my $correctEqn = $func_params{'correctEqn'}; |
| 1917 | my $var = $func_params{'var'}; |
1701 | my $var = $func_params{'var'}; |
| 1918 | my $ra_limits = $func_params{'limits'}; |
1702 | my $ra_limits = $func_params{'limits'}; |
| … | |
… | |
| 2015 | |
1799 | |
| 2016 | #construct the answer evaluator |
1800 | #construct the answer evaluator |
| 2017 | my $answer_evaluator = new AnswerEvaluator; |
1801 | my $answer_evaluator = new AnswerEvaluator; |
| 2018 | $answer_evaluator->{debug} = $func_params{debug}; |
1802 | $answer_evaluator->{debug} = $func_params{debug}; |
| 2019 | $answer_evaluator->ans_hash( correct_ans => $originalCorrEqn, |
1803 | $answer_evaluator->ans_hash( correct_ans => $originalCorrEqn, |
| 2020 | rf_correct_ans => $rh_correct_ans->{rf_correct_ans}, |
1804 | rf_correct_ans => $rh_correct_ans->{rf_correct_ans}, |
| 2021 | evaluation_points => \@evaluation_points, |
1805 | evaluation_points => \@evaluation_points, |
| 2022 | ra_param_vars => \@PARAMS, |
1806 | ra_param_vars => \@PARAMS, |
| 2023 | ra_vars => \@VARS, |
1807 | ra_vars => \@VARS, |
| 2024 | type => 'function', |
1808 | type => 'function', |
| 2025 | ); |
1809 | ); |
| 2026 | |
1810 | |
| 2027 | $answer_evaluator->install_pre_filter(\&check_syntax); |
1811 | $answer_evaluator->install_pre_filter(\&check_syntax); |
| 2028 | $answer_evaluator->install_pre_filter(\&function_from_string2, ra_vars => \@VARS,debug=>$func_params{debug},); # @VARS has been guaranteed to be an array, $var might be a single string. |
1812 | $answer_evaluator->install_pre_filter(\&function_from_string2, ra_vars => \@VARS,debug=>$func_params{debug},); # @VARS has been guaranteed to be an array, $var might be a single string. |
| 2029 | $answer_evaluator->install_pre_filter(\&best_approx_parameters, %func_params, param_vars => \@PARAMS); |
1813 | $answer_evaluator->install_pre_filter(\&best_approx_parameters, %func_params, param_vars => \@PARAMS); |
| 2030 | $answer_evaluator->install_evaluator(\&calculate_difference_vector, %func_params); |
1814 | $answer_evaluator->install_evaluator(\&calculate_difference_vector, %func_params); |
| 2031 | $answer_evaluator->install_evaluator(\&is_zero_array, tol => $tol ); |
1815 | $answer_evaluator->install_evaluator(\&is_zero_array, tol => $tol ); |
| 2032 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); $rh_ans;} ); |
1816 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); $rh_ans;} ); |
| 2033 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
1817 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
| 2034 | if ($rh_ans->catch_error('EVAL') ) { |
1818 | if ($rh_ans->catch_error('EVAL') ) { |
| 2035 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
1819 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
| 2036 | $rh_ans->clear_error('EVAL'); |
1820 | $rh_ans->clear_error('EVAL'); |
| 2037 | } |
1821 | } |
| 2038 | $rh_ans; |
1822 | $rh_ans; |
| 2039 | }); |
1823 | }); |
| 2040 | $answer_evaluator; |
1824 | $answer_evaluator; |
| 2041 | } |
1825 | } |
| 2042 | |
1826 | |
| 2043 | =head4 Filters |
1827 | =head4 Filters |
| … | |
… | |
| 2047 | is_array($rh_ans) |
1831 | is_array($rh_ans) |
| 2048 | returns: $rh_ans. Throws error "NOTARRAY" if this is not an array |
1832 | returns: $rh_ans. Throws error "NOTARRAY" if this is not an array |
| 2049 | |
1833 | |
| 2050 | =cut |
1834 | =cut |
| 2051 | |
1835 | |
| 2052 | sub is_array{ |
1836 | sub is_array { |
| 2053 | my $rh_ans = shift; |
1837 | my $rh_ans = shift; |
| 2054 | # return if the result is an array |
1838 | # return if the result is an array |
| 2055 | return($rh_ans) if ref($rh_ans->{student_ans}) eq 'ARRAY' ; |
1839 | return($rh_ans) if ref($rh_ans->{student_ans}) eq 'ARRAY' ; |
| 2056 | $rh_ans->throw_error("NOTARRAY","The answer is not an array"); |
1840 | $rh_ans->throw_error("NOTARRAY","The answer is not an array"); |
| 2057 | $rh_ans; |
1841 | $rh_ans; |
| … | |
… | |
| 2105 | |
1889 | |
| 2106 | } |
1890 | } |
| 2107 | |
1891 | |
| 2108 | =pod |
1892 | =pod |
| 2109 | |
1893 | |
|
|
1894 | check_strings ($rh_ans, %options) |
|
|
1895 | returns $rh_ans |
|
|
1896 | |
|
|
1897 | |
|
|
1898 | =cut |
|
|
1899 | |
|
|
1900 | sub check_strings { |
|
|
1901 | my ($rh_ans, %options) = @_; |
|
|
1902 | |
|
|
1903 | # if the student's answer is a number, simply return the answer hash (unchanged). |
|
|
1904 | |
|
|
1905 | |
|
|
1906 | if ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) { |
|
|
1907 | if ( $rh_ans->{answerIsString} == 1) { |
|
|
1908 | #$rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number |
|
|
1909 | } |
|
|
1910 | return $rh_ans; |
|
|
1911 | } |
|
|
1912 | # the student's answer is recognized as a string |
|
|
1913 | my $ans = $rh_ans->{student_ans}; |
|
|
1914 | # OVERVIEW of remindar of function: |
|
|
1915 | # if answer is correct, return correct. (adjust score to 1) |
|
|
1916 | # if answer is incorect: |
|
|
1917 | # 1) determine if the answer is sensible. if it is, return incorrect. |
|
|
1918 | # 2) if the answer is not sensible (and incorrect), then return an error message indicating so. |
|
|
1919 | # no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators) |
|
|
1920 | # last: 'STRING' post_filter will clear the error (avoiding pink screen.) |
|
|
1921 | my $sensibleAnswer = 0; |
|
|
1922 | $ans = str_filters( $ans, 'compress_whitespace' ); # remove trailing, leading, and double spaces. |
|
|
1923 | my ($ans_eval) = str_cmp($rh_ans->{correct_ans}); |
|
|
1924 | my $temp_ans_hash = &$ans_eval($ans); |
|
|
1925 | $rh_ans->{test} = $temp_ans_hash; |
|
|
1926 | if ($temp_ans_hash->{score} ==1 ) { # students answer matches the correct answer. |
|
|
1927 | $rh_ans->{score} = 1; |
|
|
1928 | $sensibleAnswer = 1; |
|
|
1929 | } else { # students answer does not match the correct answer. |
|
|
1930 | my $legalString = ''; ## find out if string makes sense |
|
|
1931 | my @legalStrings = @{$options{strings}}; |
|
|
1932 | foreach $legalString (@legalStrings) { |
|
|
1933 | if ( uc($ans) eq uc($legalString) ) { |
|
|
1934 | $sensibleAnswer = 1; |
|
|
1935 | last; |
|
|
1936 | } |
|
|
1937 | } |
|
|
1938 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
|
|
1939 | $rh_ans->throw_error('EVAL', "$BR Your answer is not a recognized answer") unless ($sensibleAnswer); |
|
|
1940 | # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer); |
|
|
1941 | # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
|
|
1942 | } |
|
|
1943 | $rh_ans->{student_ans} = $ans; |
|
|
1944 | if ($sensibleAnswer) { |
|
|
1945 | $rh_ans->throw_error('STRING', "The student's answer $rh_ans->{student_ans} is interpreted as a string."); |
|
|
1946 | } |
|
|
1947 | # warn ("\$rh_ans->{answerIsString} = $rh_ans->{answerIsString}"); |
|
|
1948 | $rh_ans; |
|
|
1949 | } |
|
|
1950 | |
|
|
1951 | =pod |
|
|
1952 | |
|
|
1953 | check_strings ($rh_ans, %options) |
|
|
1954 | returns $rh_ans |
|
|
1955 | |
|
|
1956 | |
|
|
1957 | =cut |
|
|
1958 | |
|
|
1959 | sub check_units { |
|
|
1960 | my ($rh_ans, %options) = @_; |
|
|
1961 | my %correct_units = %{$rh_ans-> {rh_correct_units}}; |
|
|
1962 | my $ans = $rh_ans->{student_ans}; |
|
|
1963 | # $ans = '' unless defined ($ans); |
|
|
1964 | $ans = str_filters ($ans, 'trim_whitespace'); |
|
|
1965 | my $original_student_ans = $ans; |
|
|
1966 | $rh_ans->{original_student_ans} = $original_student_ans; |
|
|
1967 | |
|
|
1968 | # it surprises me that the match below works since the first .* is greedy. |
|
|
1969 | my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/; |
|
|
1970 | |
|
|
1971 | unless ( defined($num_answer) && $units ) { |
|
|
1972 | # there is an error reading the input |
|
|
1973 | if ( $ans =~ /\S/ ) { # the answer is not blank |
|
|
1974 | $rh_ans -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " . |
|
|
1975 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
1976 | "Your answer must contain units." ); |
|
|
1977 | $rh_ans->throw_error('UNITS', "The answer \"$ans\" could not be interpreted " . |
|
|
1978 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
1979 | "Your answer must contain units." ); |
|
|
1980 | } |
|
|
1981 | return $rh_ans; |
|
|
1982 | } |
|
|
1983 | |
|
|
1984 | # we have been able to parse the answer into a numerical part and a unit part |
|
|
1985 | |
|
|
1986 | # $num_answer = $1; #$1 and $2 from the regular expression above |
|
|
1987 | # $units = $2; |
|
|
1988 | |
|
|
1989 | my %units = Units::evaluate_units($units); |
|
|
1990 | if ( defined( $units{'ERROR'} ) ) { |
|
|
1991 | # handle error condition |
|
|
1992 | $units{'ERROR'} = clean_up_error_msg($units{'ERROR'}); |
|
|
1993 | $rh_ans -> setKeys( 'ans_message' => "$units{'ERROR'}" ); |
|
|
1994 | $rh_ans -> throw_error('UNITS', "$units{'ERROR'}"); |
|
|
1995 | return $rh_ans; |
|
|
1996 | } |
|
|
1997 | |
|
|
1998 | my $units_match = 1; |
|
|
1999 | my $fund_unit; |
|
|
2000 | foreach $fund_unit (keys %correct_units) { |
|
|
2001 | next if $fund_unit eq 'factor'; |
|
|
2002 | $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit}; |
|
|
2003 | } |
|
|
2004 | |
|
|
2005 | if ( $units_match ) { |
|
|
2006 | # units are ok. Evaluate the numerical part of the answer |
|
|
2007 | $rh_ans->{'tolerance'} = $rh_ans->{'tolerance'}* $correct_units{'factor'}/$units{'factor'} if |
|
|
2008 | $rh_ans->{'tolType'} eq 'absolute'; # the tolerance is in the units specified by the instructor. |
|
|
2009 | $rh_ans->{correct_ans} = prfmt($rh_ans->{correct_ans}*$correct_units{'factor'}/$units{'factor'}); |
|
|
2010 | $rh_ans->{student_units} = $units; |
|
|
2011 | $rh_ans->{student_ans} = $num_answer; |
|
|
2012 | |
|
|
2013 | } else { |
|
|
2014 | $rh_ans -> setKeys( ans_message => 'There is an error in the units for this answer.' ); |
|
|
2015 | $rh_ans -> throw_error ( 'UNITS', 'There is an error in the units for this answer.' ); |
|
|
2016 | } |
|
|
2017 | |
|
|
2018 | return $rh_ans; |
|
|
2019 | } |
|
|
2020 | |
|
|
2021 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
|
|
2022 | ## |
|
|
2023 | ## IN: a hash containing the following items (error-checking to be added later?): |
|
|
2024 | ## correctAnswer -- the correct answer |
|
|
2025 | ## tolerance -- the allowable margin of error |
|
|
2026 | ## tolType -- 'relative' or 'absolute' |
|
|
2027 | ## format -- the display format of the answer |
|
|
2028 | ## mode -- one of 'std', 'strict', 'arith', or 'frac'; |
|
|
2029 | ## determines allowable formats for the input |
|
|
2030 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
|
|
2031 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
|
|
2032 | |
|
|
2033 | sub compare_numbers { |
|
|
2034 | my ($rh_ans, %options) = @_; |
|
|
2035 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
|
|
2036 | if ($PG_eval_errors) { |
|
|
2037 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
2038 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
2039 | |
|
|
2040 | } else { |
|
|
2041 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
|
|
2042 | } |
|
|
2043 | |
|
|
2044 | my $permitted_error; |
|
|
2045 | |
|
|
2046 | if ($rh_ans->{tolType} eq 'absolute') { |
|
|
2047 | $permitted_error = $rh_ans->{tolerance}; |
|
|
2048 | } |
|
|
2049 | elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) { |
|
|
2050 | $permitted_error = $options{zeroLevelTol}; ## want $tol to be non zero |
|
|
2051 | } |
|
|
2052 | else { |
|
|
2053 | $permitted_error = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}); |
|
|
2054 | } |
|
|
2055 | |
|
|
2056 | my $is_a_number = is_a_number($inVal); |
|
|
2057 | $rh_ans->{score} = 1 if ( ($is_a_number) and |
|
|
2058 | (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) ); |
|
|
2059 | if (not $is_a_number) { |
|
|
2060 | $rh_ans->throw_error('EVAL','Your answer does not evaluate to a number'); |
|
|
2061 | } |
|
|
2062 | |
|
|
2063 | $rh_ans; |
|
|
2064 | } |
|
|
2065 | |
|
|
2066 | |
|
|
2067 | |
|
|
2068 | =pod |
|
|
2069 | |
| 2110 | std_num_filter($rh_ans, %options) |
2070 | std_num_filter($rh_ans, %options) |
| 2111 | returns $rh_ans |
2071 | returns $rh_ans |
| 2112 | |
2072 | |
| 2113 | Replaces some constants using math_constants, then evaluates a perl expression. |
2073 | Replaces some constants using math_constants, then evaluates a perl expression. |
| 2114 | |
2074 | |
| … | |
… | |
| 2122 | $in = math_constants($in); |
2082 | $in = math_constants($in); |
| 2123 | $rh_ans->{type} = 'std_number'; |
2083 | $rh_ans->{type} = 'std_number'; |
| 2124 | my ($inVal,$PG_eval_errors,$PG_full_error_report); |
2084 | my ($inVal,$PG_eval_errors,$PG_full_error_report); |
| 2125 | if ($in =~ /\S/) { |
2085 | if ($in =~ /\S/) { |
| 2126 | ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in); |
2086 | ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in); |
| 2127 | } else { |
2087 | } else { |
| 2128 | $PG_eval_errors = ''; |
2088 | $PG_eval_errors = ''; |
| 2129 | } |
2089 | } |
| 2130 | |
2090 | |
| 2131 | if ($PG_eval_errors) { ##error message from eval or above |
2091 | if ($PG_eval_errors) { ##error message from eval or above |
| 2132 | $rh_ans->{ans_message} = 'There is a syntax error in your answer'; |
2092 | $rh_ans->{ans_message} = 'There is a syntax error in your answer'; |
| … | |
… | |
| 2146 | to each element of the array. Does it's best to generate sensible error messages for syntax errors. |
2106 | to each element of the array. Does it's best to generate sensible error messages for syntax errors. |
| 2147 | A typical error message displayed in {studnet_ans} might be ( 56, error message, -4). |
2107 | A typical error message displayed in {studnet_ans} might be ( 56, error message, -4). |
| 2148 | |
2108 | |
| 2149 | =cut |
2109 | =cut |
| 2150 | |
2110 | |
| 2151 | sub std_num_array_filter{ |
2111 | sub std_num_array_filter { |
| 2152 | my $rh_ans= shift; |
2112 | my $rh_ans= shift; |
| 2153 | my %options = @_; |
2113 | my %options = @_; |
| 2154 | my @in = @{$rh_ans->{student_ans}}; |
2114 | my @in = @{$rh_ans->{student_ans}}; |
| 2155 | my $temp_hash = new AnswerHash; |
2115 | my $temp_hash = new AnswerHash; |
| 2156 | my @out=(); |
2116 | my @out=(); |
| … | |
… | |
| 2179 | $rh_ans->input( [@out] ); |
2139 | $rh_ans->input( [@out] ); |
| 2180 | } |
2140 | } |
| 2181 | $rh_ans; |
2141 | $rh_ans; |
| 2182 | } |
2142 | } |
| 2183 | |
2143 | |
| 2184 | |
|
|
| 2185 | |
|
|
| 2186 | sub function_from_string2 { |
2144 | sub function_from_string2 { |
| 2187 | my $rh_ans = shift; |
2145 | my $rh_ans = shift; |
| 2188 | my %options = @_; |
2146 | my %options = @_; |
| 2189 | my $eqn = $rh_ans->{student_ans}; |
2147 | my $eqn = $rh_ans->{student_ans}; |
| 2190 | set_default_options( \%options, |
2148 | set_default_options( \%options, |
| 2191 | 'store_in' => 'rf_student_ans', |
2149 | 'store_in' => 'rf_student_ans', |
| 2192 | 'ra_vars' => [qw( x y )], |
2150 | 'ra_vars' => [qw( x y )], |
| 2193 | 'debug' => 0, |
2151 | 'debug' => 0, |
| 2194 | ); |
2152 | ); |
| 2195 | my @VARS = @{ $options{ 'ra_vars'}}; |
2153 | my @VARS = @{ $options{ 'ra_vars'}}; |
| 2196 | warn "VARS = ", join("<>", @VARS) if defined($options{debug}) and $options{debug} ==1; |
2154 | warn "VARS = ", join("<>", @VARS) if defined($options{debug}) and $options{debug} ==1; |
| 2197 | my $originalEqn = $eqn; |
2155 | my $originalEqn = $eqn; |
| 2198 | $eqn = &math_constants($eqn); |
2156 | $eqn = &math_constants($eqn); |
| 2199 | for( my $i = 0; $i < @VARS; $i++ ) { |
2157 | 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 |
2158 | # 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] . '"'); |
2159 | my ($temp,$er1,$er2) = PG_restricted_eval('"'. $VARS[$i] . '"'); |
| 2202 | # $eqn =~ s/\b$VARS[$i]\b/\$VARS[$i]/g; |
2160 | #$eqn =~ s/\b$VARS[$i]\b/\$VARS[$i]/g; |
| 2203 | $eqn =~ s/\b$temp\b/\$VARS[$i]/g; |
2161 | $eqn =~ s/\b$temp\b/\$VARS[$i]/g; |
| 2204 | |
2162 | |
| 2205 | } |
2163 | } |
| 2206 | warn "equation evaluated = $eqn",$rh_ans->pretty_print(), "<br>\noptions<br>\n", |
2164 | warn "equation evaluated = $eqn",$rh_ans->pretty_print(), "<br>\noptions<br>\n", |
| 2207 | pretty_print(\%options) |
2165 | pretty_print(\%options) |
| … | |
… | |
| 2249 | # $rh_ans ->{$options{store_in}} = $function_sub; |
2207 | # $rh_ans ->{$options{store_in}} = $function_sub; |
| 2250 | # } else { |
2208 | # } else { |
| 2251 | # $rh_ans->{rf_student_ans} = $function_sub; |
2209 | # $rh_ans->{rf_student_ans} = $function_sub; |
| 2252 | # } |
2210 | # } |
| 2253 | $rh_ans ->{$options{store_in}} = $function_sub; |
2211 | $rh_ans ->{$options{store_in}} = $function_sub; |
| 2254 | |
|
|
| 2255 | } |
2212 | } |
| 2256 | |
2213 | |
| 2257 | $rh_ans; |
2214 | $rh_ans; |
| 2258 | } |
2215 | } |
| 2259 | |
2216 | |
| 2260 | |
2217 | |
| 2261 | sub is_zero_array{ |
2218 | sub is_zero_array { |
| 2262 | my $rh_ans = shift; |
2219 | my $rh_ans = shift; |
| 2263 | my %options = @_; |
2220 | my %options = @_; |
| 2264 | my $array = $rh_ans -> {ra_differences}; |
2221 | my $array = $rh_ans -> {ra_differences}; |
| 2265 | my $num = @$array; |
2222 | my $num = @$array; |
| 2266 | my $i; |
2223 | my $i; |
| … | |
… | |
| 2285 | |
2242 | |
| 2286 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
2243 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
| 2287 | } |
2244 | } |
| 2288 | $rh_ans; |
2245 | $rh_ans; |
| 2289 | } |
2246 | } |
|
|
2247 | |
| 2290 | =pod |
2248 | =pod |
| 2291 | |
2249 | |
| 2292 | best_approx_parameters($rh_ans,%options); |
2250 | best_approx_parameters($rh_ans,%options); |
| 2293 | {rf_student_ans} # reference to the test answer |
2251 | {rf_student_ans} # reference to the test answer |
| 2294 | {rf_correct_ans} # reference to the comparison answer |
2252 | {rf_correct_ans} # reference to the comparison answer |
| 2295 | {evaluation_points}, # an array of row vectors indicating the points |
2253 | {evaluation_points}, # an array of row vectors indicating the points |
| 2296 | # to evaluate when comparing the functions |
2254 | # to evaluate when comparing the functions |
| 2297 | %options # debug => 1 gives more error answers |
2255 | %options # debug => 1 gives more error answers |
| 2298 | # param_vars => [''] additional parameters used to adapt to function |
2256 | # param_vars => [''] additional parameters used to adapt to function |
| 2299 | ) |
2257 | ) |
| 2300 | returns $rh_ans; |
2258 | returns $rh_ans; |
| 2301 | The parameters for the comparison function which best approximates the test_function are stored |
2259 | The parameters for the comparison function which best approximates the test_function are stored |
| 2302 | in the field {ra_parameters}. |
2260 | in the field {ra_parameters}. |
| 2303 | |
2261 | |
| … | |
… | |
| 2310 | |
2268 | |
| 2311 | The comparison function should have $dim_of_params_space more input variables than the test function. |
2269 | The comparison function should have $dim_of_params_space more input variables than the test function. |
| 2312 | |
2270 | |
| 2313 | =cut |
2271 | =cut |
| 2314 | |
2272 | |
| 2315 | |
|
|
| 2316 | |
|
|
| 2317 | |
|
|
| 2318 | |
|
|
| 2319 | # =pod |
2273 | =pod |
| 2320 | # |
2274 | |
| 2321 | # Used internally: |
2275 | Used internally: |
| 2322 | # |
2276 | |
| 2323 | # &$determine_param_coeff( $rf_comparison_function # a reference to the correct answer function |
2277 | &$determine_param_coeff( $rf_comparison_function # a reference to the correct answer function |
| 2324 | # $ra_variables # an array of the active input variables to the functions |
2278 | $ra_variables # an array of the active input variables to the functions |
| 2325 | # $dim_of_params_space # indicates the number of parameters upon which the |
2279 | $dim_of_params_space # indicates the number of parameters upon which the |
| 2326 | # # the comparison function depends linearly. These are assumed to |
2280 | # the comparison function depends linearly. These are assumed to |
| 2327 | # # be the last group of inputs to the comparison function. |
2281 | # be the last group of inputs to the comparison function. |
| 2328 | # |
2282 | |
| 2329 | # %options # $options{debug} gives more error messages |
2283 | %options # $options{debug} gives more error messages |
| 2330 | # |
2284 | |
| 2331 | # # A typical function might look like |
2285 | # A typical function might look like |
| 2332 | # # f(x,y,z,a,b) = x^2+a*cos(xz) + b*sin(x) with a parameter |
2286 | # f(x,y,z,a,b) = x^2+a*cos(xz) + b*sin(x) with a parameter |
| 2333 | # # space of dimension 2 and a variable space of dimension 3. |
2287 | # space of dimension 2 and a variable space of dimension 3. |
| 2334 | # ) |
2288 | ) |
| 2335 | # # returns a list of coefficients |
2289 | # returns a list of coefficients |
| 2336 | # |
2290 | |
| 2337 | # =cut |
2291 | =cut |
| 2338 | |
2292 | |
| 2339 | |
|
|
| 2340 | sub best_approx_parameters{ |
2293 | sub best_approx_parameters { |
| 2341 | my $rh_ans = shift; |
2294 | my $rh_ans = shift; |
| 2342 | my %options = @_; |
2295 | my %options = @_; |
| 2343 | my $errors = undef; |
2296 | my $errors = undef; |
| 2344 | # This subroutine for the determining the coefficents of the parameters at a given point |
2297 | # This subroutine for the determining the coefficents of the parameters at a given point |
| 2345 | # is pretty specialized, so it is included here as a sub-subroutine. |
2298 | # is pretty specialized, so it is included here as a sub-subroutine. |
| … | |
… | |
| 2414 | while(@coeff) { |
2367 | while(@coeff) { |
| 2415 | $matrix->assign($row_num,$col_num, shift(@coeff) ); |
2368 | $matrix->assign($row_num,$col_num, shift(@coeff) ); |
| 2416 | $col_num++; |
2369 | $col_num++; |
| 2417 | } |
2370 | } |
| 2418 | } |
2371 | } |
| 2419 | |
|
|
| 2420 | } |
2372 | } |
| 2421 | $row_num++; |
2373 | $row_num++; |
| 2422 | last if $errors; # break if there are any errors. |
2374 | last if $errors; # break if there are any errors. |
| 2423 | # This cuts down on the size of error messages. |
2375 | # This cuts down on the size of error messages. |
| 2424 | # However it impossible to check for equivalence at 95% of points |
2376 | # However it impossible to check for equivalence at 95% of points |
| … | |
… | |
| 2517 | $diff = ( $inVal - ($correctVal -$tol_val ) ) - $tol_val; #prevents entering too high a number? |
2469 | $diff = ( $inVal - ($correctVal -$tol_val ) ) - $tol_val; #prevents entering too high a number? |
| 2518 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
2470 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
| 2519 | |
2471 | |
| 2520 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
2472 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
| 2521 | #warn "diff = $diff"; |
2473 | #warn "diff = $diff"; |
| 2522 | |
|
|
| 2523 | $diff = ( $inVal - ($correctVal-$tol_val ) )/abs($tol_val) -1 if abs($tol_val) > $options{zeroLevel}; |
2474 | $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}; |
2475 | #$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"; |
2476 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
| 2526 | } |
2477 | } |
| 2527 | } |
2478 | } |
| … | |
… | |
| 2641 | $filteredAnswer =~ s/\s+//g; # remove all whitespace |
2592 | $filteredAnswer =~ s/\s+//g; # remove all whitespace |
| 2642 | |
2593 | |
| 2643 | return $filteredAnswer; |
2594 | return $filteredAnswer; |
| 2644 | } |
2595 | } |
| 2645 | |
2596 | |
| 2646 | sub compress_whitespace { |
2597 | sub compress_whitespace { |
| 2647 | my $filteredAnswer = shift; |
2598 | my $filteredAnswer = shift; |
| 2648 | |
2599 | |
| 2649 | $filteredAnswer =~ s/^\s*//; # remove initial whitespace |
2600 | $filteredAnswer =~ s/^\s*//; # remove initial whitespace |
| 2650 | $filteredAnswer =~ s/\s*$//; # remove trailing whitespace |
2601 | $filteredAnswer =~ s/\s*$//; # remove trailing whitespace |
| 2651 | $filteredAnswer =~ s/\s+/ /g; # replace spaces by single space |
2602 | $filteredAnswer =~ s/\s+/ /g; # replace spaces by single space |
| … | |
… | |
| 2731 | ANS( ordered_cs_str_cmp( "ABC" ) ) -- Rejects "abc", accepts "A BC" and |
2682 | ANS( ordered_cs_str_cmp( "ABC" ) ) -- Rejects "abc", accepts "A BC" and |
| 2732 | so forth. Same as ordered_str_cmp() but case sensitive. |
2683 | so forth. Same as ordered_str_cmp() but case sensitive. |
| 2733 | |
2684 | |
| 2734 | =cut |
2685 | =cut |
| 2735 | |
2686 | |
| 2736 | sub std_str_cmp { # compare strings |
2687 | sub std_str_cmp { # compare strings |
| 2737 | my $correctAnswer = shift @_; |
2688 | my $correctAnswer = shift @_; |
| 2738 | my @filters = ( 'compress_whitespace', 'ignore_case' ); |
2689 | my @filters = ( 'compress_whitespace', 'ignore_case' ); |
| 2739 | my $type = 'std_str_cmp'; |
2690 | my $type = 'std_str_cmp'; |
| 2740 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2691 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2741 | 'filters' => \@filters, |
2692 | 'filters' => \@filters, |
| 2742 | 'type' => $type |
2693 | 'type' => $type |
| 2743 | ); |
2694 | ); |
| 2744 | } |
2695 | } |
| 2745 | |
2696 | |
| 2746 | sub std_str_cmp_list { # alias for std_str_cmp |
2697 | sub std_str_cmp_list { # alias for std_str_cmp |
| 2747 | my @answerList = @_; |
2698 | my @answerList = @_; |
| 2748 | my @output; |
2699 | my @output; |
| 2749 | while (@answerList) { |
2700 | while (@answerList) { |
| 2750 | push( @output, std_str_cmp(shift @answerList) ); |
2701 | push( @output, std_str_cmp(shift @answerList) ); |
| 2751 | } |
2702 | } |
| 2752 | @output; |
2703 | @output; |
| 2753 | } |
2704 | } |
| 2754 | |
2705 | |
| 2755 | sub std_cs_str_cmp { # compare strings case sensitive |
2706 | sub std_cs_str_cmp { # compare strings case sensitive |
| 2756 | my $correctAnswer = shift @_; |
2707 | my $correctAnswer = shift @_; |
| 2757 | my @filters = ( 'compress_whitespace' ); |
2708 | my @filters = ( 'compress_whitespace' ); |
| 2758 | my $type = 'std_cs_str_cmp'; |
2709 | my $type = 'std_cs_str_cmp'; |
| 2759 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2710 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2760 | 'filters' => \@filters, |
2711 | 'filters' => \@filters, |
| 2761 | 'type' => $type |
2712 | 'type' => $type |
| 2762 | ); |
2713 | ); |
| 2763 | } |
2714 | } |
| 2764 | |
2715 | |
| 2765 | sub std_cs_str_cmp_list { # alias for std_cs_str_cmp |
2716 | sub std_cs_str_cmp_list { # alias for std_cs_str_cmp |
| 2766 | my @answerList = @_; |
2717 | my @answerList = @_; |
| 2767 | my @output; |
2718 | my @output; |
| 2768 | while (@answerList) { |
2719 | while (@answerList) { |
| 2769 | push( @output, std_cs_str_cmp(shift @answerList) ); |
2720 | push( @output, std_cs_str_cmp(shift @answerList) ); |
| 2770 | } |
2721 | } |
| 2771 | @output; |
2722 | @output; |
| 2772 | } |
2723 | } |
| 2773 | |
2724 | |
| 2774 | sub strict_str_cmp { # strict string compare |
2725 | sub strict_str_cmp { # strict string compare |
| 2775 | my $correctAnswer = shift @_; |
2726 | my $correctAnswer = shift @_; |
| 2776 | my @filters = ( 'trim_whitespace' ); |
2727 | my @filters = ( 'trim_whitespace' ); |
| 2777 | my $type = 'strict_str_cmp'; |
2728 | my $type = 'strict_str_cmp'; |
| 2778 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2729 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2779 | 'filters' => \@filters, |
2730 | 'filters' => \@filters, |
| 2780 | 'type' => $type |
2731 | 'type' => $type |
| 2781 | ); |
2732 | ); |
| 2782 | } |
2733 | } |
| 2783 | |
2734 | |
| 2784 | sub strict_str_cmp_list { # alias for strict_str_cmp |
2735 | sub strict_str_cmp_list { # alias for strict_str_cmp |
| 2785 | my @answerList = @_; |
2736 | my @answerList = @_; |
| 2786 | my @output; |
2737 | my @output; |
| 2787 | while (@answerList) { |
2738 | while (@answerList) { |
| 2788 | push( @output, strict_str_cmp(shift @answerList) ); |
2739 | push( @output, strict_str_cmp(shift @answerList) ); |
| 2789 | } |
2740 | } |
| 2790 | @output; |
2741 | @output; |
| 2791 | } |
2742 | } |
| 2792 | |
2743 | |
| 2793 | sub unordered_str_cmp { # unordered, case insensitive, spaces ignored |
2744 | sub unordered_str_cmp { # unordered, case insensitive, spaces ignored |
| 2794 | my $correctAnswer = shift @_; |
2745 | my $correctAnswer = shift @_; |
| 2795 | my @filters = ( 'ignore_order', 'ignore_case' ); |
2746 | my @filters = ( 'ignore_order', 'ignore_case' ); |
| 2796 | my $type = 'unordered_str_cmp'; |
2747 | my $type = 'unordered_str_cmp'; |
| 2797 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2748 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2798 | 'filters' => \@filters, |
2749 | 'filters' => \@filters, |
| 2799 | 'type' => $type |
2750 | 'type' => $type |
| 2800 | ); |
2751 | ); |
| 2801 | } |
2752 | } |
| 2802 | |
2753 | |
| 2803 | sub unordered_str_cmp_list { # alias for unordered_str_cmp |
2754 | sub unordered_str_cmp_list { # alias for unordered_str_cmp |
| 2804 | my @answerList = @_; |
2755 | my @answerList = @_; |
| 2805 | my @output; |
2756 | my @output; |
| 2806 | while (@answerList) { |
2757 | while (@answerList) { |
| 2807 | push( @output, unordered_str_cmp(shift @answerList) ); |
2758 | push( @output, unordered_str_cmp(shift @answerList) ); |
| 2808 | } |
2759 | } |
| 2809 | @output; |
2760 | @output; |
| 2810 | } |
2761 | } |
| 2811 | |
2762 | |
| 2812 | sub unordered_cs_str_cmp { # unordered, case sensitive, spaces ignored |
2763 | sub unordered_cs_str_cmp { # unordered, case sensitive, spaces ignored |
| 2813 | my $correctAnswer = shift @_; |
2764 | my $correctAnswer = shift @_; |
| 2814 | my @filters = ( 'ignore_order' ); |
2765 | my @filters = ( 'ignore_order' ); |
| 2815 | my $type = 'unordered_cs_str_cmp'; |
2766 | my $type = 'unordered_cs_str_cmp'; |
| 2816 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2767 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2817 | 'filters' => \@filters, |
2768 | 'filters' => \@filters, |
| 2818 | 'type' => $type |
2769 | 'type' => $type |
| 2819 | ); |
2770 | ); |
| 2820 | } |
2771 | } |
| 2821 | |
2772 | |
| 2822 | sub unordered_cs_str_cmp_list { # alias for unordered_cs_str_cmp |
2773 | sub unordered_cs_str_cmp_list { # alias for unordered_cs_str_cmp |
| 2823 | my @answerList = @_; |
2774 | my @answerList = @_; |
| 2824 | my @output; |
2775 | my @output; |
| 2825 | while (@answerList) { |
2776 | while (@answerList) { |
| 2826 | push( @output, unordered_cs_str_cmp(shift @answerList) ); |
2777 | push( @output, unordered_cs_str_cmp(shift @answerList) ); |
| 2827 | } |
2778 | } |
| 2828 | @output; |
2779 | @output; |
| 2829 | } |
2780 | } |
| 2830 | |
2781 | |
| 2831 | sub ordered_str_cmp { # ordered, case insensitive, spaces ignored |
2782 | sub ordered_str_cmp { # ordered, case insensitive, spaces ignored |
| 2832 | my $correctAnswer = shift @_; |
2783 | my $correctAnswer = shift @_; |
| 2833 | my @filters = ( 'remove_whitespace', 'ignore_case' ); |
2784 | my @filters = ( 'remove_whitespace', 'ignore_case' ); |
| 2834 | my $type = 'ordered_str_cmp'; |
2785 | my $type = 'ordered_str_cmp'; |
| 2835 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2786 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2836 | 'filters' => \@filters, |
2787 | 'filters' => \@filters, |
| 2837 | 'type' => $type |
2788 | 'type' => $type |
| 2838 | ); |
2789 | ); |
| 2839 | } |
2790 | } |
| 2840 | |
2791 | |
| 2841 | sub ordered_str_cmp_list { # alias for ordered_str_cmp |
2792 | sub ordered_str_cmp_list { # alias for ordered_str_cmp |
| 2842 | my @answerList = @_; |
2793 | my @answerList = @_; |
| 2843 | my @output; |
2794 | my @output; |
| 2844 | while (@answerList) { |
2795 | while (@answerList) { |
| 2845 | push( @output, ordered_str_cmp(shift @answerList) ); |
2796 | push( @output, ordered_str_cmp(shift @answerList) ); |
| 2846 | } |
2797 | } |
| 2847 | @output; |
2798 | @output; |
| 2848 | |
|
|
| 2849 | } |
2799 | } |
| 2850 | |
2800 | |
| 2851 | sub ordered_cs_str_cmp { # ordered, case sensitive, spaces ignored |
2801 | sub ordered_cs_str_cmp { # ordered, case sensitive, spaces ignored |
| 2852 | my $correctAnswer = shift @_; |
2802 | my $correctAnswer = shift @_; |
| 2853 | my @filters = ( 'remove_whitespace' ); |
2803 | my @filters = ( 'remove_whitespace' ); |
| 2854 | my $type = 'ordered_cs_str_cmp'; |
2804 | my $type = 'ordered_cs_str_cmp'; |
| 2855 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2805 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2856 | 'filters' => \@filters, |
2806 | 'filters' => \@filters, |
| 2857 | 'type' => $type |
2807 | 'type' => $type |
| 2858 | ); |
2808 | ); |
| 2859 | } |
2809 | } |
| 2860 | |
2810 | |
| 2861 | sub ordered_cs_str_cmp_list { # alias for ordered_cs_str_cmp |
2811 | sub ordered_cs_str_cmp_list { # alias for ordered_cs_str_cmp |
| 2862 | my @answerList = @_; |
2812 | my @answerList = @_; |
| 2863 | my @output; |
2813 | my @output; |
| 2864 | while (@answerList) { |
2814 | while (@answerList) { |
| 2865 | push( @output, ordered_cs_str_cmp(shift @answerList) ); |
2815 | push( @output, ordered_cs_str_cmp(shift @answerList) ); |
| 2866 | } |
2816 | } |
| … | |
… | |
| 2901 | str_cmp( "ABC", filters => 'ignore_order' ) -- matches "ACB", "A B C", but not "abc" |
2851 | str_cmp( "ABC", filters => 'ignore_order' ) -- matches "ACB", "A B C", but not "abc" |
| 2902 | str_cmp( "D E F", remove_whitespace, ignore_case ) -- matches "def" and "d e f" but not "fed" |
2852 | str_cmp( "D E F", remove_whitespace, ignore_case ) -- matches "def" and "d e f" but not "fed" |
| 2903 | |
2853 | |
| 2904 | =cut |
2854 | =cut |
| 2905 | |
2855 | |
| 2906 | sub str_cmp { |
2856 | sub str_cmp { |
| 2907 | my $correctAnswer = shift @_; |
2857 | my $correctAnswer = shift @_; |
| 2908 | $correctAnswer = '' unless defined($correctAnswer); |
2858 | $correctAnswer = '' unless defined($correctAnswer); |
| 2909 | my @options = @_; |
2859 | my @options = @_; |
| 2910 | my $ra_filters; |
2860 | my $ra_filters; |
| 2911 | |
2861 | |
| … | |
… | |
| 2915 | } |
2865 | } |
| 2916 | |
2866 | |
| 2917 | if( $options[0] eq 'filters' ) { # using filters => [f1, f2, ...] notation |
2867 | if( $options[0] eq 'filters' ) { # using filters => [f1, f2, ...] notation |
| 2918 | $ra_filters = $options[1]; |
2868 | $ra_filters = $options[1]; |
| 2919 | } |
2869 | } |
| 2920 | else { # using a list of filters |
2870 | else { # using a list of filters |
| 2921 | $ra_filters = \@options; |
2871 | $ra_filters = \@options; |
| 2922 | } |
2872 | } |
| 2923 | |
2873 | |
| 2924 | # thread over lists |
2874 | # thread over lists |
| 2925 | my @ans_list = (); |
2875 | my @ans_list = (); |
| … | |
… | |
| 2951 | ## correctAnswer -- the correct answer, before filtering |
2901 | ## correctAnswer -- the correct answer, before filtering |
| 2952 | ## filters -- reference to an array containing the filters to be applied |
2902 | ## filters -- reference to an array containing the filters to be applied |
| 2953 | ## type -- a string containing the type of answer evaluator in use |
2903 | ## type -- a string containing the type of answer evaluator in use |
| 2954 | ## OUT: a reference to an answer evaluator subroutine |
2904 | ## OUT: a reference to an answer evaluator subroutine |
| 2955 | |
2905 | |
| 2956 | sub STR_CMP { |
2906 | sub STR_CMP { |
| 2957 | my %str_params = @_; |
2907 | my %str_params = @_; |
| 2958 | $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} ); |
2908 | $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} ); |
| 2959 | my $answer_evaluator = sub { |
2909 | my $answer_evaluator = sub { |
| 2960 | my $in = shift @_; |
2910 | my $in = shift @_; |
| 2961 | $in = '' unless defined $in; |
2911 | $in = '' unless defined $in; |
| … | |
… | |
| 3002 | |
2952 | |
| 3003 | # added 6/14/2000 by David Etlinger |
2953 | # added 6/14/2000 by David Etlinger |
| 3004 | # because of the conversion of the answer |
2954 | # because of the conversion of the answer |
| 3005 | # string to an array, I thought it better not |
2955 | # string to an array, I thought it better not |
| 3006 | # to force STR_CMP() to work with this |
2956 | # to force STR_CMP() to work with this |
| 3007 | sub checkbox_cmp { |
2957 | sub checkbox_cmp { |
| 3008 | my $correctAnswer = shift @_; |
2958 | my $correctAnswer = shift @_; |
| 3009 | $correctAnswer = str_filters( $correctAnswer, 'ignore_order' ); |
2959 | $correctAnswer = str_filters( $correctAnswer, 'ignore_order' ); |
| 3010 | |
2960 | |
| 3011 | my $answer_evaluator = sub { |
2961 | my $answer_evaluator = sub { |
| 3012 | my $in = shift @_; |
2962 | my $in = shift @_; |
| … | |
… | |
| 3049 | ########################################################################## |
2999 | ########################################################################## |
| 3050 | ########################################################################## |
3000 | ########################################################################## |
| 3051 | ## Text and e-mail routines |
3001 | ## Text and e-mail routines |
| 3052 | |
3002 | |
| 3053 | |
3003 | |
| 3054 | sub store_ans_at { |
3004 | sub store_ans_at { |
| 3055 | my $answerStringRef = shift; |
3005 | my $answerStringRef = shift; |
| 3056 | my %options = @_; |
3006 | my %options = @_; |
| 3057 | my $ans_eval= ''; |
3007 | my $ans_eval= ''; |
| 3058 | if ( ref($answerStringRef) eq 'SCALAR' ) { |
3008 | if ( ref($answerStringRef) eq 'SCALAR' ) { |
| 3059 | $ans_eval= sub { |
3009 | $ans_eval= sub { |
| … | |
… | |
| 3091 | # evaluated. |
3041 | # evaluated. |
| 3092 | # this is a utility script for cleaning up the answer output for display in |
3042 | # this is a utility script for cleaning up the answer output for display in |
| 3093 | #the answers. |
3043 | #the answers. |
| 3094 | |
3044 | |
| 3095 | |
3045 | |
| 3096 | sub DUMMY_ANSWER { |
3046 | sub DUMMY_ANSWER { |
| 3097 | my $num = shift; |
3047 | my $num = shift; |
| 3098 | qq{<INPUT TYPE="HIDDEN" NAME="answer$num" VALUE="">} |
3048 | qq{<INPUT TYPE="HIDDEN" NAME="answer$num" VALUE="">} |
| 3099 | } |
3049 | } |
| 3100 | |
3050 | |
| 3101 | sub escapeHTML { |
3051 | sub escapeHTML { |
| 3102 | my $string = shift; |
3052 | my $string = shift; |
| 3103 | $string =~ s/\n/$BR/ge; |
3053 | $string =~ s/\n/$BR/ge; |
| 3104 | $string; |
3054 | $string; |
| 3105 | } |
3055 | } |
| 3106 | |
3056 | |
| 3107 | # these next two subroutines show how to modify the "store_and_at()" answer |
3057 | # these next two subroutines show how to modify the "store_and_at()" answer |
| 3108 | # evaluator to add extra information before storing the info |
3058 | # evaluator to add extra information before storing the info |
| 3109 | # They provide a good model for how to tweak answer evaluators in special cases. |
3059 | # They provide a good model for how to tweak answer evaluators in special cases. |
| 3110 | sub anstext { |
3060 | sub anstext { |
| 3111 | my $num = shift; |
3061 | my $num = shift; |
| 3112 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
3062 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
| 3113 | my $ans_eval = sub { |
3063 | my $ans_eval = sub { |
| 3114 | my $text = shift; |
3064 | my $text = shift; |
| 3115 | $text = '' unless defined($text); |
3065 | $text = '' unless defined($text); |
| … | |
… | |
| 3122 | $out; |
3072 | $out; |
| 3123 | }; |
3073 | }; |
| 3124 | $ans_eval; |
3074 | $ans_eval; |
| 3125 | } |
3075 | } |
| 3126 | |
3076 | |
| 3127 | sub ansradio { |
3077 | sub ansradio { |
| 3128 | my $num = shift; |
3078 | my $num = shift; |
| 3129 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
3079 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
| 3130 | my $ans_eval = sub { |
3080 | my $ans_eval = sub { |
| 3131 | my $text = shift; |
3081 | my $text = shift; |
| 3132 | $text = '' unless defined($text); |
3082 | $text = '' unless defined($text); |
| … | |
… | |
| 3145 | # the desired behavior in a special case. Here the object is to have |
3095 | # the desired behavior in a special case. Here the object is to have |
| 3146 | # have the last answer trigger the send_mail_to subroutine which mails |
3096 | # have the last answer trigger the send_mail_to subroutine which mails |
| 3147 | # all of the answers to the designated address. |
3097 | # all of the answers to the designated address. |
| 3148 | # (This address must be listed in PG_environment{'ALLOW_MAIL_TO'} or an error occurs.) |
3098 | # (This address must be listed in PG_environment{'ALLOW_MAIL_TO'} or an error occurs.) |
| 3149 | |
3099 | |
| 3150 | sub mail_answers_to { #accepts the last answer and mails off the result |
3100 | sub mail_answers_to { #accepts the last answer and mails off the result |
| 3151 | my $user_address = shift; |
3101 | my $user_address = shift; |
| 3152 | my $ans_eval = sub { |
3102 | my $ans_eval = sub { |
| 3153 | |
3103 | |
| 3154 | # then mail out all of the answers, including this last one. |
3104 | # then mail out all of the answers, including this last one. |
| 3155 | |
3105 | |
| … | |
… | |
| 3169 | return $ans_hash; |
3119 | return $ans_hash; |
| 3170 | }; |
3120 | }; |
| 3171 | |
3121 | |
| 3172 | return $ans_eval; |
3122 | return $ans_eval; |
| 3173 | } |
3123 | } |
| 3174 | sub mail_answers_to2 { #accepts the last answer and mails off the result |
3124 | sub mail_answers_to2 { #accepts the last answer and mails off the result |
| 3175 | my $user_address = shift; |
3125 | my $user_address = shift; |
| 3176 | my $subject = shift; |
3126 | my $subject = shift; |
| 3177 | $subject = "$main::courseName WeBWorK questionnaire" unless defined $subject; |
3127 | $subject = "$main::courseName WeBWorK questionnaire" unless defined $subject; |
| 3178 | |
3128 | |
| 3179 | |
3129 | |
| … | |
… | |
| 3190 | |
3140 | |
| 3191 | ########################################################################## |
3141 | ########################################################################## |
| 3192 | ########################################################################## |
3142 | ########################################################################## |
| 3193 | ## Problem Grader Subroutines |
3143 | ## Problem Grader Subroutines |
| 3194 | |
3144 | |
| 3195 | |
|
|
| 3196 | ##################################### |
3145 | ##################################### |
| 3197 | # This is a model for plug-in problem graders |
3146 | # This is a model for plug-in problem graders |
| 3198 | ##################################### |
3147 | ##################################### |
| 3199 | sub install_problem_grader { |
3148 | sub install_problem_grader { |
| 3200 | my $rf_problem_grader = shift; |
3149 | my $rf_problem_grader = shift; |
| 3201 | $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = $rf_problem_grader; |
3150 | $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = $rf_problem_grader; |
| 3202 | } |
3151 | } |
| 3203 | |
3152 | |
| 3204 | #this is called std only for compatability purposes; |
3153 | #this is called std only for compatability purposes; |
| 3205 | #almost everyone uses avg_problem_grader |
3154 | #almost everyone uses avg_problem_grader |
| 3206 | sub std_problem_grader{ |
3155 | sub std_problem_grader { |
| 3207 | my $rh_evaluated_answers = shift; |
3156 | my $rh_evaluated_answers = shift; |
| 3208 | my $rh_problem_state = shift; |
3157 | my $rh_problem_state = shift; |
| 3209 | my %form_options = @_; |
3158 | my %form_options = @_; |
| 3210 | my %evaluated_answers = %{$rh_evaluated_answers}; |
3159 | my %evaluated_answers = %{$rh_evaluated_answers}; |
| 3211 | # The hash $rh_evaluated_answers typically contains: |
3160 | # The hash $rh_evaluated_answers typically contains: |
| … | |
… | |
| 3281 | |
3230 | |
| 3282 | #the only difference between the two versions |
3231 | #the only difference between the two versions |
| 3283 | #is at the end of the subroutine, where std_problem_grader2 |
3232 | #is at the end of the subroutine, where std_problem_grader2 |
| 3284 | #records the attempt only if there have been no syntax errors, |
3233 | #records the attempt only if there have been no syntax errors, |
| 3285 | #whereas std_problem_grader records it regardless |
3234 | #whereas std_problem_grader records it regardless |
| 3286 | sub std_problem_grader2{ |
3235 | sub std_problem_grader2 { |
| 3287 | my $rh_evaluated_answers = shift; |
3236 | my $rh_evaluated_answers = shift; |
| 3288 | my $rh_problem_state = shift; |
3237 | my $rh_problem_state = shift; |
| 3289 | my %form_options = @_; |
3238 | my %form_options = @_; |
| 3290 | my %evaluated_answers = %{$rh_evaluated_answers}; |
3239 | my %evaluated_answers = %{$rh_evaluated_answers}; |
| 3291 | # The hash $rh_evaluated_answers typically contains: |
3240 | # The hash $rh_evaluated_answers typically contains: |
| … | |
… | |
| 3362 | $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1; |
3311 | $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1; |
| 3363 | $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0; |
3312 | $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0; |
| 3364 | } |
3313 | } |
| 3365 | else { |
3314 | else { |
| 3366 | $problem_result{show_partial_correct_answers} = 0 ; # prevent partial correct answers from being shown for syntax errors. |
3315 | $problem_result{show_partial_correct_answers} = 0 ; # prevent partial correct answers from being shown for syntax errors. |
| 3367 | |
|
|
| 3368 | } |
3316 | } |
| 3369 | |
|
|
| 3370 | (\%problem_result, \%problem_state); |
3317 | (\%problem_result, \%problem_state); |
| 3371 | } |
3318 | } |
| 3372 | |
3319 | |
| 3373 | |
|
|
| 3374 | sub avg_problem_grader{ |
3320 | sub avg_problem_grader { |
| 3375 | my $rh_evaluated_answers = shift; |
3321 | my $rh_evaluated_answers = shift; |
| 3376 | my $rh_problem_state = shift; |
3322 | my $rh_problem_state = shift; |
| 3377 | my %form_options = @_; |
3323 | my %form_options = @_; |
| 3378 | my %evaluated_answers = %{$rh_evaluated_answers}; |
3324 | my %evaluated_answers = %{$rh_evaluated_answers}; |
| 3379 | # The hash $rh_evaluated_answers typically contains: |
3325 | # The hash $rh_evaluated_answers typically contains: |
| … | |
… | |
| 3425 | |
3371 | |
| 3426 | $problem_state{num_of_correct_ans}++ if $total == $count; |
3372 | $problem_state{num_of_correct_ans}++ if $total == $count; |
| 3427 | $problem_state{num_of_incorrect_ans}++ if $total < $count ; |
3373 | $problem_state{num_of_incorrect_ans}++ if $total < $count ; |
| 3428 | warn "Error in grading this problem the total $total is larger than $count" if $total > $count; |
3374 | warn "Error in grading this problem the total $total is larger than $count" if $total > $count; |
| 3429 | (\%problem_result, \%problem_state); |
3375 | (\%problem_result, \%problem_state); |
| 3430 | |
|
|
| 3431 | } |
3376 | } |
| 3432 | |
|
|
| 3433 | |
|
|
| 3434 | |
3377 | |
| 3435 | ########################################################################### |
3378 | ########################################################################### |
| 3436 | ### THE FOLLOWING ARE LOCAL SUBROUTINES THAT ARE MEANT TO BE CALLED ONLY FROM THIS SCRIPT. |
3379 | ### THE FOLLOWING ARE LOCAL SUBROUTINES THAT ARE MEANT TO BE CALLED ONLY FROM THIS SCRIPT. |
| 3437 | |
|
|
| 3438 | |
3380 | |
| 3439 | ## Internal routine that converts variables into the standard array format |
3381 | ## Internal routine that converts variables into the standard array format |
| 3440 | ## |
3382 | ## |
| 3441 | ## IN: one of the following: |
3383 | ## IN: one of the following: |
| 3442 | ## an undefined value (i.e., no variable was specified) |
3384 | ## an undefined value (i.e., no variable was specified) |
| 3443 | ## a reference to an array of variable names -- [var1, var2] |
3385 | ## a reference to an array of variable names -- [var1, var2] |
| 3444 | ## a number (the number of variables desired) -- 3 |
3386 | ## a number (the number of variables desired) -- 3 |
| 3445 | ## one or more variable names -- (var1, var2) |
3387 | ## one or more variable names -- (var1, var2) |
| 3446 | ## OUT: an array of variable names |
3388 | ## OUT: an array of variable names |
|
|
3389 | |
| 3447 | sub get_var_array { |
3390 | sub get_var_array { |
| 3448 | my $in = shift @_; |
3391 | my $in = shift @_; |
| 3449 | my @out; |
3392 | my @out; |
| 3450 | |
3393 | |
| 3451 | if( not defined($in) ) { #if nothing defined, build default array and return |
3394 | if( not defined($in) ) { #if nothing defined, build default array and return |
| … | |
… | |
| 3469 | $out[2] = 'z'; |
3412 | $out[2] = 'z'; |
| 3470 | } |
3413 | } |
| 3471 | else { #default to the x_1, x_2, ... convention |
3414 | else { #default to the x_1, x_2, ... convention |
| 3472 | my ($i, $tag); |
3415 | my ($i, $tag); |
| 3473 | for( $i=0; $i < $in; $i++ ) { |
3416 | for( $i=0; $i < $in; $i++ ) { |
| 3474 | ## akp the above seems to be off by one 1/4/00 |
3417 | ## akp the above seems to be off by one 1/4/00 |
| 3475 | $tag = $i + 1; ## akp 1/4/00 |
3418 | $tag = $i + 1; ## akp 1/4/00 |
| 3476 | $out[$i] = "${functVarDefault}_" . $tag; ## akp 1/4/00 |
3419 | $out[$i] = "${functVarDefault}_" . $tag; ## akp 1/4/00 |
| 3477 | } |
3420 | } |
| 3478 | } |
3421 | } |
| 3479 | |
|
|
| 3480 | return @out; |
3422 | return @out; |
| 3481 | } |
3423 | } |
| 3482 | else { #if given one or more names, return as an array |
3424 | else { #if given one or more names, return as an array |
| 3483 | unshift( @_, $in ); |
3425 | unshift( @_, $in ); |
| 3484 | |
|
|
| 3485 | return @_; |
3426 | return @_; |
| 3486 | } |
3427 | } |
| 3487 | } |
3428 | } |
| 3488 | |
3429 | |
| 3489 | ## Internal routine that converts limits into the standard array of arrays format |
3430 | ## Internal routine that converts limits into the standard array of arrays format |
| … | |
… | |
| 3499 | |
3440 | |
| 3500 | sub get_limits_array { |
3441 | sub get_limits_array { |
| 3501 | my $in = shift @_; |
3442 | my $in = shift @_; |
| 3502 | my @out; |
3443 | my @out; |
| 3503 | |
3444 | |
| 3504 | if( not defined($in) ) { #if nothing defined, build default array and return |
3445 | if( not defined($in) ) { #if nothing defined, build default array and return |
| 3505 | @out = ( [$functLLimitDefault, $functULimitDefault] ); |
3446 | @out = ( [$functLLimitDefault, $functULimitDefault] ); |
| 3506 | return @out; |
3447 | return @out; |
| 3507 | } |
3448 | } |
| 3508 | elsif( ref($in) eq 'ARRAY' ) { #$in is either ref to array, or ref to array of refs |
3449 | elsif( ref($in) eq 'ARRAY' ) { #$in is either ref to array, or ref to array of refs |
| 3509 | my @deref = @{$in}; |
3450 | my @deref = @{$in}; |
| 3510 | |
3451 | |
| 3511 | if( ref( $in->[0] ) eq 'ARRAY' ) { #$in is a ref to an array of array refs |
3452 | if( ref( $in->[0] ) eq 'ARRAY' ) { #$in is a ref to an array of array refs |
| 3512 | return @deref; |
3453 | return @deref; |
| 3513 | } |
3454 | } |
| 3514 | else { #$in was just a ref to an array of numbers |
3455 | else { #$in was just a ref to an array of numbers |
| 3515 | @out = ( $in ); |
3456 | @out = ( $in ); |
| 3516 | return @out; |
3457 | return @out; |
| 3517 | } |
3458 | } |
| 3518 | } |
3459 | } |
| 3519 | else { #$in was an array of references or numbers |
3460 | else { #$in was an array of references or numbers |
| 3520 | unshift( @_, $in ); |
3461 | unshift( @_, $in ); |
| 3521 | |
3462 | |
| 3522 | if( ref($_[0]) eq 'ARRAY' ) { #$in was an array of references, so just return it |
3463 | if( ref($_[0]) eq 'ARRAY' ) { #$in was an array of references, so just return it |
| 3523 | return @_; |
3464 | return @_; |
| 3524 | } |
3465 | } |
| 3525 | else { #$in was an array of numbers |
3466 | else { #$in was an array of numbers |
| 3526 | @out = ( \@_ ); |
3467 | @out = ( \@_ ); |
| 3527 | return @out; |
3468 | return @out; |
| 3528 | } |
3469 | } |
| 3529 | } |
3470 | } |
| 3530 | } |
3471 | } |
| 3531 | |
3472 | |
| 3532 | sub check_option_list { |
3473 | sub check_option_list { |
| 3533 | my $size = scalar(@_); |
3474 | my $size = scalar(@_); |
| 3534 | if( ( $size % 2 ) != 0 ) { |
3475 | if( ( $size % 2 ) != 0 ) { |
| 3535 | warn "ERROR in answer evaluator generator:\n" . |
3476 | warn "ERROR in answer evaluator generator:\n" . |
| 3536 | "Usage: <CODE>str_cmp([\$ans1, \$ans2],%options)</CODE> |
3477 | "Usage: <CODE>str_cmp([\$ans1, \$ans2],%options)</CODE> |
| 3537 | or <CODE> num_cmp([\$num1, \$num2], %options)</CODE><BR> |
3478 | or <CODE> num_cmp([\$num1, \$num2], %options)</CODE><BR> |
| … | |
… | |
| 3542 | # simple subroutine to display an error message when |
3483 | # simple subroutine to display an error message when |
| 3543 | # function compares are called with invalid parameters |
3484 | # function compares are called with invalid parameters |
| 3544 | sub function_invalid_params { |
3485 | sub function_invalid_params { |
| 3545 | my $correctEqn = shift @_; |
3486 | my $correctEqn = shift @_; |
| 3546 | my $error_response = sub { |
3487 | my $error_response = sub { |
| 3547 | my $PGanswerMessage = "Tell your professor that there is an error with the parameters " . |
3488 | my $PGanswerMessage = "Tell your professor that there is an error with the parameters " . |
| 3548 | "to the function answer evaluator"; |
3489 | "to the function answer evaluator"; |
| 3549 | return ( 0, $correctEqn, "", $PGanswerMessage ); |
3490 | return ( 0, $correctEqn, "", $PGanswerMessage ); |
| 3550 | }; |
3491 | }; |
| 3551 | |
|
|
| 3552 | return $error_response; |
3492 | return $error_response; |
| 3553 | } |
3493 | } |
| 3554 | |
|
|
| 3555 | |
3494 | |
| 3556 | ######################################################################### |
3495 | ######################################################################### |
| 3557 | # Filters for answer evaluators |
3496 | # Filters for answer evaluators |
| 3558 | ######################################################################### |
3497 | ######################################################################### |
| 3559 | |
3498 | |
| 3560 | |
|
|
| 3561 | sub is_a_number { |
3499 | sub is_a_number { |
| 3562 | my ($num,%options) = @_; |
3500 | my ($num,%options) = @_; |
| 3563 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
3501 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
| 3564 | my ($rh_ans); |
3502 | my ($rh_ans); |
| 3565 | if ($process_ans_hash) { |
3503 | if ($process_ans_hash) { |
| 3566 | $rh_ans = $num; |
3504 | $rh_ans = $num; |
| … | |
… | |
| 3589 | } else { |
3527 | } else { |
| 3590 | return $is_a_number; |
3528 | return $is_a_number; |
| 3591 | } |
3529 | } |
| 3592 | } |
3530 | } |
| 3593 | |
3531 | |
| 3594 | sub is_a_fraction { |
3532 | sub is_a_fraction { |
| 3595 | my ($num,%options) = @_; |
3533 | my ($num,%options) = @_; |
| 3596 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
3534 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
| 3597 | my ($rh_ans); |
3535 | my ($rh_ans); |
| 3598 | if ($process_ans_hash) { |
3536 | if ($process_ans_hash) { |
| 3599 | $rh_ans = $num; |
3537 | $rh_ans = $num; |
| … | |
… | |
| 3623 | return $is_a_fraction; |
3561 | return $is_a_fraction; |
| 3624 | } |
3562 | } |
| 3625 | } |
3563 | } |
| 3626 | |
3564 | |
| 3627 | |
3565 | |
| 3628 | sub is_an_arithmetic_expression { |
3566 | sub is_an_arithmetic_expression { |
| 3629 | my ($num,%options) = @_; |
3567 | my ($num,%options) = @_; |
| 3630 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
3568 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
| 3631 | my ($rh_ans); |
3569 | my ($rh_ans); |
| 3632 | if ($process_ans_hash) { |
3570 | if ($process_ans_hash) { |
| 3633 | $rh_ans = $num; |
3571 | $rh_ans = $num; |
| … | |
… | |
| 3658 | return $is_an_arithmetic_expression; |
3596 | return $is_an_arithmetic_expression; |
| 3659 | } |
3597 | } |
| 3660 | } |
3598 | } |
| 3661 | |
3599 | |
| 3662 | #replaces pi, e, and ^ with their Perl equivalents |
3600 | #replaces pi, e, and ^ with their Perl equivalents |
| 3663 | sub math_constants { |
3601 | sub math_constants { |
| 3664 | my($in,%options) = @_; |
3602 | my($in,%options) = @_; |
| 3665 | my $rh_ans; |
3603 | my $rh_ans; |
| 3666 | my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ; |
3604 | my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ; |
| 3667 | if ($process_ans_hash) { |
3605 | if ($process_ans_hash) { |
| 3668 | $rh_ans = $in; |
3606 | $rh_ans = $in; |
| … | |
… | |
| 3679 | } else { |
3617 | } else { |
| 3680 | return $in; |
3618 | return $in; |
| 3681 | } |
3619 | } |
| 3682 | } |
3620 | } |
| 3683 | |
3621 | |
| 3684 | sub clean_up_error_msg { |
3622 | sub clean_up_error_msg { |
| 3685 | my $msg = $_[0]; |
3623 | my $msg = $_[0]; |
| 3686 | $msg =~ s/^\[[^\]]*\][^:]*://; |
3624 | $msg =~ s/^\[[^\]]*\][^:]*://; |
| 3687 | $msg =~ s/Unquoted string//g; |
3625 | $msg =~ s/Unquoted string//g; |
| 3688 | $msg =~ s/may\s+clash.*/does not make sense here/; |
3626 | $msg =~ s/may\s+clash.*/does not make sense here/; |
| 3689 | $msg =~ s/\sat.*line [\d]*//g; |
3627 | $msg =~ s/\sat.*line [\d]*//g; |
| … | |
… | |
| 3694 | |
3632 | |
| 3695 | #formats the student and correct answer as specified |
3633 | #formats the student and correct answer as specified |
| 3696 | #format must be of a form suitable for sprintf (e.g. '%0.5g'), |
3634 | #format must be of a form suitable for sprintf (e.g. '%0.5g'), |
| 3697 | #with the exception that a '#' at the end of the string |
3635 | #with the exception that a '#' at the end of the string |
| 3698 | #will cause trailing zeros in the decimal part to be removed |
3636 | #will cause trailing zeros in the decimal part to be removed |
| 3699 | sub prfmt { |
3637 | sub prfmt { |
| 3700 | my($number,$format) = @_; # attention, the order of format and number are reversed |
3638 | my($number,$format) = @_; # attention, the order of format and number are reversed |
| 3701 | my $out; |
3639 | my $out; |
| 3702 | if ($format) { |
3640 | if ($format) { |
| 3703 | warn "Incorrect format used: $format. <BR> Format should look something like %4.5g<BR>" |
3641 | warn "Incorrect format used: $format. <BR> Format should look something like %4.5g<BR>" |
| 3704 | unless $format =~ /^\s*%\d*\.?\d*\w#?\s*$/; |
3642 | unless $format =~ /^\s*%\d*\.?\d*\w#?\s*$/; |
| 3705 | |
3643 | |
| 3706 | if( $format =~ s/#\s*$// ) { # remove trailing zeros in the decimal |
3644 | if( $format =~ s/#\s*$// ) { # remove trailing zeros in the decimal |
| 3707 | $out = sprintf( $format, $number ); |
3645 | $out = sprintf( $format, $number ); |
| 3708 | $out =~ s/(\.\d*?)0+$/$1/; |
3646 | $out =~ s/(\.\d*?)0+$/$1/; |
| 3709 | $out =~ s/\.$//; # in case all decimal digits were zero, remove the decimal |
3647 | $out =~ s/\.$//; # in case all decimal digits were zero, remove the decimal |
| 3710 | } |
3648 | } |
| 3711 | else { |
3649 | else { |
| 3712 | $out = sprintf( $format, $number ); |
3650 | $out = sprintf( $format, $number ); |
| 3713 | } |
3651 | } |
| 3714 | |
|
|
| 3715 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
3652 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
| 3716 | } |
3653 | } |
| 3717 | else { |
3654 | else { |
| 3718 | $out = $number; |
3655 | $out = $number; |
| 3719 | } |
3656 | } |
| 3720 | |
|
|
| 3721 | return $out; |
3657 | return $out; |
| 3722 | } |
3658 | } |
| 3723 | |
3659 | |
| 3724 | =head4 |
3660 | =head4 |
| 3725 | |
3661 | |
| … | |
… | |
| 3789 | } else { |
3725 | } else { |
| 3790 | warn "option $option_key is already defined as", $rh_options->{$option_key}, "<br>\n", |
3726 | warn "option $option_key is already defined as", $rh_options->{$option_key}, "<br>\n", |
| 3791 | "The attempt to override this option with the alias $alias with value ", $rh_options->{$alias}, |
3727 | "The attempt to override this option with the alias $alias with value ", $rh_options->{$alias}, |
| 3792 | " was ignored."; |
3728 | " was ignored."; |
| 3793 | } |
3729 | } |
| 3794 | |
|
|
| 3795 | } |
3730 | } |
| 3796 | delete($rh_options->{$alias}); # remove the alias from the initial list |
3731 | delete($rh_options->{$alias}); # remove the alias from the initial list |
| 3797 | } |
3732 | } |
| 3798 | |
3733 | |
| 3799 | } |
3734 | } |