/** * Create the CAS context in which we will evaluate this PRT. This contains * all the question variables, student responses, feedback variables, and all * the sans, tans and atoptions expressions from all the nodes. * * @param stack_cas_session $questionvars the question varaibles. * @param stack_options $options * @param array $answers name => value the student response. * @param int $seed the random number seed. * @return stack_cas_session initialised with all the expressions this PRT will need. */ protected function create_cas_context_for_evaluation($questionvars, $options, $answers, $seed) { // Start with the question variables (note that order matters here). $cascontext = clone $questionvars; // Set the value of simp from this point onwards. // If the question has simp:true, but the prt simp:false, then this needs to be done here. if ($this->simplify) { $simp = 'true'; } else { $simp = 'false'; } $cs = new stack_cas_casstring($simp); $cs->set_key('simp'); $answervars = array($cs); // Add the student's responses, but only those needed by this prt. // Some irrelevant but invalid answers might break the CAS connection. foreach ($this->get_required_variables(array_keys($answers)) as $name) { if (array_key_exists($name . '_val', $answers)) { $cs = new stack_cas_casstring($answers[$name . '_val']); } else { $cs = new stack_cas_casstring($answers[$name]); } // Validating as teacher at this stage removes the problem of "allowWords" which // we don't have access to. This effectively allows any words here. But the // student's answer has already been through validation. $cs->get_valid('t'); // Setting the key must come after validation. $cs->set_key($name); $answervars[] = $cs; } $cascontext->add_vars($answervars); // Add the feedback variables. $cascontext->merge_session($this->feedbackvariables); // Add all the expressions from all the nodes. // Note this approach does not allow for effective guard clauses in the PRT. // All the inputs to answer tests are evaluated at the start. foreach ($this->nodes as $key => $node) { $cascontext->add_vars($node->get_context_variables($key)); } $cascontext->instantiate(); return $cascontext; }
/** * Once we know the random seed, we can initialise all the other parts of the question. */ public function initialise_question_from_seed() { // Build up the question session out of all the bits that need to go into it. // 1. question variables. $questionvars = new stack_cas_keyval($this->questionvariables, $this->options, $this->seed, 't'); $session = $questionvars->get_session(); // 2. correct answer for all inputs. $response = array(); foreach ($this->inputs as $name => $input) { $cs = new stack_cas_casstring($input->get_teacher_answer()); $cs->get_valid('t'); $cs->set_key($name); $response[$name] = $cs; } $session->add_vars($response); $sessionlength = count($session->get_session()); // 3. CAS bits inside the question text. $questiontext = $this->prepare_cas_text($this->questiontext, $session); // 4. CAS bits inside the specific feedback. $feedbacktext = $this->prepare_cas_text($this->specificfeedback, $session); // 5. CAS bits inside the question note. $notetext = $this->prepare_cas_text($this->questionnote, $session); // 6. The standard PRT feedback. $prtcorrect = $this->prepare_cas_text($this->prtcorrect, $session); $prtpartiallycorrect = $this->prepare_cas_text($this->prtpartiallycorrect, $session); $prtincorrect = $this->prepare_cas_text($this->prtincorrect, $session); // Now instantiate the session. $session->instantiate(); if ($session->get_errors()) { // We throw an exception here because any problems with the CAS code // up to this point should have been caught during validation when // the question was edited or deployed. throw new stack_exception('qtype_stack_question : CAS error when instantiating the session: ' . $session->get_errors($this->user_can_edit())); } // Finally, store only those values really needed for later. $this->questiontextinstantiated = $questiontext->get_display_castext(); $this->specificfeedbackinstantiated = $feedbacktext->get_display_castext(); $this->questionnoteinstantiated = $notetext->get_display_castext(); $this->prtcorrectinstantiated = $prtcorrect->get_display_castext(); $this->prtpartiallycorrectinstantiated = $prtpartiallycorrect->get_display_castext(); $this->prtincorrectinstantiated = $prtincorrect->get_display_castext(); $session->prune_session($sessionlength); $this->session = $session; // Allow inputs to update themselves based on the model answers. $this->adapt_inputs(); }
/** * Create the actual response data. The response data in the test case may * include expressions in terms of the question variables. * @param qtype_stack_question $question the question - with $question->session initialised. * @return array the respones to send to $quba->process_action. */ public static function compute_response(qtype_stack_question $question, $inputs) { // If the question has simp:false, then the local options should reflect this. // In this case, test constructors (question authors) will need to explicitly simplify their test case constructions. $localoptions = clone $question->options; // Start with the question variables (note that order matters here). $cascontext = new stack_cas_session(null, $localoptions, $question->seed); $question->add_question_vars_to_session($cascontext); // Turn off simplification - we *always* need test cases to be unsimplified, even if the question option is true. $vars = array(); $cs = new stack_cas_casstring('false'); $cs->set_key('simp'); $vars[] = $cs; // Now add the expressions we want evaluated. foreach ($inputs as $name => $value) { if ('' !== $value) { $cs = new stack_cas_casstring($value); if ($cs->get_valid('t')) { $cs->set_key('testresponse_' . $name); $vars[] = $cs; } } } $cascontext->add_vars($vars); $cascontext->instantiate(); $response = array(); foreach ($inputs as $name => $notused) { $computedinput = $cascontext->get_value_key('testresponse_' . $name); // In the case we start with an invalid input, and hence don't send it to the CAS // We want the response to constitute the raw invalid input. // This permits invalid expressions in the inputs, and to compute with valid expressions. if ('' == $computedinput) { $computedinput = $inputs[$name]; } if (array_key_exists($name, $question->inputs)) { $response = array_merge($response, $question->inputs[$name]->maxima_to_response_array($computedinput)); } } return $response; }
/** * Get the context variables that this node uses, so that they can be * pre-evaluated prior to transversing the tree. * @param string $key used to make the variable names unique to this node. * @return array of stack_cas_casstring */ public function get_context_variables($key) { $variables = array(); $this->sans->set_key('PRSANS' . $key); $variables[] = $this->sans; $this->tans->set_key('PRTANS' . $key); $variables[] = $this->tans; if ($this->process_atoptions()) { $atopts = new stack_cas_casstring($this->atoptions); $atopts->get_valid('t', false, 0); $atopts->set_key('PRATOPT' . $key); $variables[] = $atopts; } return $variables; }
/** * Extract the CAS commands from the string * * @access public * @return bool false if no commands to extract, true if succeeds. */ private function extract_cas_commands() { // First check contains @s. $count = preg_match_all('~(?<!@)@(?!@)~', $this->trimmedcastext, $notused); if ($count == 0) { // Nothing to do. return null; } else { // Extract the CAS commands. $temp = stack_utils::all_substring_between($this->trimmedcastext, '@', '@', true); // Create array of commands matching with their labels. $i = 0; $valid = true; $errors = ''; $cmdarray = array(); $labels = array(); $sessionkeys = array(); if (is_a($this->session, 'stack_cas_session')) { $sessionkeys = $this->session->get_all_keys(); } foreach ($temp as $cmd) { // Trim of surrounding white space and CAS commands. $cmd = stack_utils::trim_commands($cmd); $cs = new stack_cas_casstring($cmd); $cs->get_valid($this->security, $this->syntax, $this->insertstars); do { // ... make sure names are not already in use. $key = 'caschat' . $i; $i++; } while (in_array($key, $sessionkeys)); $sesionkeys[] = $key; $labels[] = $key; $cs->set_key($key, true); $cmdarray[] = $cs; $valid = $valid && $cs->get_valid(); $errors .= $cs->get_errors(); } if (!$valid) { $this->valid = false; $this->errors .= stack_string('stackCas_invalidCommand') . '</br>' . $errors; } if (!empty($cmdarray)) { $newsession = $this->session; if (null === $newsession) { $newsession = new stack_cas_session($cmdarray, null, $this->seed); } else { $newsession->add_vars($cmdarray); } $this->session = $newsession; // Now replace the commannds with their labels in the text. $this->trimmedcastext = stack_utils::replace_between($this->trimmedcastext, '@', '@', $labels, true); } } }