Skip to content

Commit f2b4ec8

Browse files
committed
apply changes to question part
1 parent acfefcc commit f2b4ec8

File tree

1 file changed

+43
-7
lines changed

1 file changed

+43
-7
lines changed

classes/local/formulas_part.php

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ class formulas_part {
8383
/** @var int whether there are multiple possible answers */
8484
public int $answernotunique;
8585

86+
/** @var int whether students can leave one or more fields empty */
87+
public int $emptyallowed;
88+
8689
/** @var string definition of the grading criterion */
8790
public string $correctness;
8891

@@ -418,6 +421,11 @@ public function normalize_response(array $response): array {
418421
* @return bool
419422
*/
420423
public function is_gradable_response(array $response): bool {
424+
// If the part allows empty fields, we do not have to check anything; the response would be
425+
// gradable even if all fields were empty.
426+
if ($this->emptyallowed) {
427+
return true;
428+
}
421429
return !$this->is_unanswered($response);
422430
}
423431

@@ -431,6 +439,11 @@ public function is_gradable_response(array $response): bool {
431439
* @return bool
432440
*/
433441
public function is_complete_response(array $response): bool {
442+
// If the part allows empty fields, we do not have to check anything; the response can be
443+
// considered complete even if all fields are empty.
444+
if ($this->emptyallowed) {
445+
return true;
446+
}
434447
// First, we check if there is a combined unit field. In that case, there will
435448
// be only one field to verify.
436449
if ($this->has_combined_unit_field()) {
@@ -463,6 +476,9 @@ public function is_complete_response(array $response): bool {
463476
* @return bool
464477
*/
465478
public function is_unanswered(array $response): bool {
479+
if (array_key_exists('_seed', $response)) {
480+
return true;
481+
}
466482
if (!array_key_exists('normalized', $response)) {
467483
$response = $this->normalize_response($response);
468484
}
@@ -528,6 +544,10 @@ public function get_evaluated_answers(): array {
528544
// their numerical value.
529545
if ($isalgebraic) {
530546
foreach ($this->evaluatedanswers as &$answer) {
547+
// If the answer is $EMPTY, there is nothing to do.
548+
if ($answer === '$EMPTY') {
549+
continue;
550+
}
531551
$answer = $this->evaluator->substitute_variables_in_algebraic_formula($answer);
532552
}
533553
// In case we later write to $answer, this would alter the last entry of the $modelanswers
@@ -547,6 +567,11 @@ public function get_evaluated_answers(): array {
547567
*/
548568
private static function wrap_algebraic_formulas_in_quotes(array $formulas): array {
549569
foreach ($formulas as &$formula) {
570+
// We do not have to wrap the $EMPTY token in quotes.
571+
if ($formula === '$EMPTY') {
572+
continue;
573+
}
574+
550575
// If the formula is aready wrapped in quotes, we throw an Exception, because that
551576
// should not happen. It will happen, if the student puts quotes around their response, but
552577
// we want that to be graded wrong. The exception will be caught and dealt with upstream,
@@ -631,7 +656,7 @@ public function add_special_variables(array $studentanswers, float $conversionfa
631656
foreach ($studentanswers as $i => &$studentanswer) {
632657
// We only do the calculation if the answer type is not algebraic. For algebraic
633658
// answers, we don't do anything, because quotes have already been added.
634-
if (!$isalgebraic) {
659+
if (!$isalgebraic && $studentanswer !== '$EMPTY') {
635660
$studentanswer = $conversionfactor * $studentanswer;
636661
$ssqstudentanswer += $studentanswer ** 2;
637662
}
@@ -643,16 +668,19 @@ public function add_special_variables(array $studentanswers, float $conversionfa
643668
// The variable _d will contain the absolute differences between the model answer
644669
// and the student's response. Using the parser's diff() function will make sure
645670
// that algebraic answers are correctly evaluated.
671+
// Note: We *must* send the model answer first, because the function has a special check for the
672+
// EMPTY token.
646673
$command .= '_d = diff(_a, _r);';
647-
648-
// Prepare the variable _err which is the root of the sum of squared differences.
649674
$command .= "_err = sqrt(sum(map('*', _d, _d)));";
650675

651676
// Finally, calculate the relative error, unless the question uses an algebraic answer.
652677
if (!$isalgebraic) {
653678
// We calculate the sum of squares of all model answers.
654679
$ssqmodelanswer = 0;
655680
foreach ($this->get_evaluated_answers() as $answer) {
681+
if ($answer === '$EMPTY') {
682+
continue;
683+
}
656684
$ssqmodelanswer += $answer ** 2;
657685
}
658686
// If the sum of squares is 0 (i.e. all answers are 0), then either the student
@@ -731,16 +759,20 @@ public function grade(array $response, bool $finalsubmit = false): array {
731759
// Check whether the answer is valid for the given answer type. If it is not,
732760
// we just throw an exception to make use of the catch block. Note that if the
733761
// student's answer was empty, it will fail in this check.
734-
if (!$parser->is_acceptable_for_answertype($this->answertype)) {
762+
if (!$parser->is_acceptable_for_answertype($this->answertype, $this->emptyallowed)) {
735763
throw new Exception();
736764
}
737765

738766
// Make sure the stack is empty, as there might be left-overs from a previous
739767
// failed evaluation, e.g. caused by an invalid answer.
740768
$this->evaluator->clear_stack();
741769

742-
$evaluated = $this->evaluator->evaluate($parser->get_statements())[0];
743-
$evaluatedresponse[] = token::unpack($evaluated);
770+
// Evaluate. If the answer was empty (an empty string or the '$EMPTY'), the parser
771+
// will create an appropriate evaluable statement or return an empty array. The evaluator,
772+
// on the other hand, will know how to deal with the "false" return value from reset()
773+
// and return the $EMPTY token.
774+
$statements = $parser->get_statements();
775+
$evaluatedresponse[] = token::unpack($this->evaluator->evaluate(reset($statements)));
744776
} catch (Throwable $t) {
745777
// TODO: convert to non-capturing catch
746778
// If parsing, validity check or evaluation fails, we consider the answer as wrong.
@@ -828,8 +860,12 @@ public function get_correct_response(bool $forfeedback = false): array {
828860
$answers = $this->get_evaluated_answers();
829861

830862
// Numeric answers should be localized, if that functionality is enabled.
863+
// Empty answers should be just the empty string; a more user-friendly
864+
// output will be created in the renderer.
831865
foreach ($answers as &$answer) {
832-
if (is_numeric($answer)) {
866+
if ($answer === '$EMPTY') {
867+
$answer = '';
868+
} else if (is_numeric($answer)) {
833869
$answer = qtype_formulas::format_float($answer);
834870
}
835871
}

0 commit comments

Comments
 (0)