public function test_implied_complex_mult3()
 {
     // This function name ends in an "i", so we need to check *s are not being inserted too many times here.
     $s = 'sa:cdf_bernoulli(x,p)';
     $at1 = new stack_cas_casstring($s);
     $this->assertTrue($at1->get_valid('s', false, 1));
     $this->assertEquals('cdf_bernoulli(x,p)', $at1->get_casstring());
 }
 /**
  * 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;
 }
 public function test_disp_mult_cross()
 {
     $a2 = array('make_multsgn("cross")', 'b:x*y');
     $s2 = array();
     foreach ($a2 as $s) {
         $cs = new stack_cas_casstring($s);
         $cs->get_valid('t');
         $s2[] = $cs;
     }
     $cs2 = new stack_cas_session($s2, null, 0);
     $this->assertTrue($cs2->get_valid());
     $at1 = new stack_cas_text('@b@', $cs2, 0, 't');
     $this->assertTrue($at1->get_valid());
     $at1->get_display_castext();
     $this->assertEquals($at1->get_display_castext(), '\\(x\\times y\\)');
 }
 /**
  * This is the basic validation of the student's "answer".
  * This method is only called if the input is not blank.
  *
  * Only a few input methods need to modify this method.
  * For example, Matrix types have two dimensional contents arrays to loop over.
  *
  * @param array $contents the content array of the student's input.
  * @return array of the validity, errors strings and modified contents.
  */
 protected function validate_contents($contents, $forbiddenkeys)
 {
     $errors = $this->extra_validation($contents);
     $valid = !$errors;
     // Now validate the input as CAS code.
     $modifiedcontents = array();
     $allowwords = $this->get_parameter('allowWords', '');
     foreach ($contents as $val) {
         $answer = new stack_cas_casstring($val);
         $answer->get_valid('s', $this->get_parameter('strictSyntax', true), $this->get_parameter('insertStars', 0), $allowwords);
         // Ensure student hasn't used a variable name used by the teacher.
         if ($forbiddenkeys) {
             $answer->check_external_forbidden_words($forbiddenkeys);
         }
         $forbiddenwords = $this->get_parameter('forbidWords', '');
         if ($forbiddenwords) {
             $answer->check_external_forbidden_words_literal($forbiddenwords);
         }
         $modifiedcontents[] = $answer->get_casstring();
         $valid = $valid && $answer->get_valid();
         $errors .= $answer->get_errors();
     }
     return array($valid, $errors, $modifiedcontents);
 }
 /**
  * Validates the options, when needed.
  *
  * @return (bool, string)
  * @access public
  */
 public function validate_atoptions($opt)
 {
     if ($this->processcasoptions) {
         $cs = new stack_cas_casstring($opt);
         return array($cs->get_valid('t'), $cs->get_errors());
     }
     return array(true, '');
 }
 /**
  * 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);
         }
     }
 }
 /**
  * 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;
 }
 private function validate()
 {
     if (empty($this->raw) or '' == trim($this->raw)) {
         $this->valid = true;
         return true;
     }
     // CAS keyval may not contain @ or $.
     if (strpos($this->raw, '@') !== false || strpos($this->raw, '$') !== false) {
         $this->errors = stack_string('illegalcaschars');
         $this->valid = false;
         return false;
     }
     // Subtle one: must protect things inside strings before we explode.
     $str = $this->raw;
     $strings = stack_utils::all_substring_strings($str);
     foreach ($strings as $key => $string) {
         $str = str_replace('"' . $string . '"', '[STR:' . $key . ']', $str);
     }
     $str = str_replace("\n", ';', $str);
     $str = stack_utils::remove_comments($str);
     $str = str_replace(';', "\n", $str);
     $kvarray = explode("\n", $str);
     foreach ($strings as $key => $string) {
         foreach ($kvarray as $kkey => $kstr) {
             $kvarray[$kkey] = str_replace('[STR:' . $key . ']', '"' . $string . '"', $kstr);
         }
     }
     // 23/4/12 - significant changes to the way keyvals are interpreted.  Use Maxima assignmentsm i.e. x:2.
     $errors = '';
     $valid = true;
     $vars = array();
     foreach ($kvarray as $kvs) {
         $kvs = trim($kvs);
         if ('' != $kvs) {
             $cs = new stack_cas_casstring($kvs);
             $cs->get_valid($this->security, $this->syntax, $this->insertstars);
             $vars[] = $cs;
         }
     }
     $this->session->add_vars($vars);
     $this->valid = $this->session->get_valid();
     $this->errors = $this->session->get_errors();
 }
 /**
  * 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;
 }
 public function test_exception_5()
 {
     $at1 = new stack_cas_casstring("x=1");
     $this->setExpectedException('stack_exception');
     $at1->get_valid('t', true, 'a');
 }
 public function test_plot_fail()
 {
     $cs = array('a:0', 'p:plot(a*x/0,[x,-2,2],[y,-2,2])');
     foreach ($cs as $s) {
         $cs = new stack_cas_casstring($s);
         $cs->get_valid('t');
         $s1[] = $cs;
     }
     $at1 = new stack_cas_session($s1, null, 0);
     $at1->instantiate();
     $this->assertEquals('0', $at1->get_value_key('a'));
     $this->assertEquals('Division by zero.', trim($at1->get_errors_key('p')));
     $this->assertFalse(strpos($at1->get_value_key('p'), 'STACK auto-generated plot of 0 with parameters'));
 }
 public function test_do_test_3()
 {
     // Nontrivial use of the feeback variables.
     // Error in authoring ends up in loop.   STACK should bail.
     $options = new stack_options();
     $seed = 12345;
     $questionvars = new stack_cas_keyval('n:3; p:(x+1)^n; ta:p;', $options, $seed, 't');
     // Feeback variables.
     $cstrings = array('sa1:sans', 'sa2:expand(sans)');
     foreach ($cstrings as $s) {
         $cs = new stack_cas_casstring($s);
         $cs->get_valid('t');
         $s1[] = $cs;
     }
     $feedbackvars = new stack_cas_session($s1, $options, $seed);
     $feedbackvars->get_valid();
     // Define the tree itself.
     $sans = new stack_cas_casstring('sa1');
     $sans->get_valid('t');
     $tans = new stack_cas_casstring('ta');
     $tans->get_valid('t');
     $node = new stack_potentialresponse_node($sans, $tans, 'AlgEquiv', '', true);
     $node->add_branch(0, '=', 0, '', -1, 'Test 1 false. Look: \\[@(sa1)^2@ \\neq @(sa2)^2@\\]', FORMAT_HTML, '1-0-0');
     $node->add_branch(1, '=', 1, '', 1, 'Test 1 true. ', FORMAT_HTML, '1-0-1');
     $potentialresponses[] = $node;
     $sans = new stack_cas_casstring('sa2');
     $sans->get_valid('t');
     $tans = new stack_cas_casstring('ta');
     $tans->get_valid('t');
     $node = new stack_potentialresponse_node($sans, $tans, 'FacForm', 'x', true);
     $node->add_branch(0, '-', 0.7, '', 0, 'Test 2 false.', FORMAT_HTML, '1-1-0');
     $node->add_branch(1, '+', 1, '', 3, 'Test 2 true', FORMAT_HTML, '1-1-1');
     $potentialresponses[] = $node;
     $tree = new stack_potentialresponse_tree('', '', true, 5, $feedbackvars, $potentialresponses, 0);
     // Some data from students.
     $answers = array('sans' => '(x+1)^3');
     $result = $tree->evaluate_response($questionvars->get_session(), $options, $answers, $seed);
     $this->assertTrue($result->valid);
     $this->assertEquals('', $result->errors);
     $this->assertEquals(0.3, $result->score);
     $this->assertEquals(0, $result->penalty);
     $this->assertEquals(2, count($result->feedback));
     $this->assertEquals('Test 1 true.', $result->feedback[0]->feedback);
     $this->assertEquals('Test 2 false.', $result->feedback[1]->feedback);
     $this->assertEquals(array('1-0-1', 'ATFacForm_notfactored.', '1-1-0', '[PRT-CIRCULARITY]=0'), $result->answernotes);
     $this->assertEquals(array('sa1', 'ta'), $tree->get_required_variables(array('sa1', 'sa3', 'ta', 'ssa1', 'a1', 't')));
 }
Example #13
0
 public static function make_stack_question_runtime_prt_err()
 {
     $q = self::make_a_stack_question();
     $q->name = 'runtime_prt_err';
     $q->questionvariables = "";
     $q->questiontext = '<p>Give an example of a system of equations with a unique solution.</p>' . '<p>[[input:ans1]] [[validation:ans1]]</p>';
     $q->specificfeedback = '[[feedback:Result]]';
     $q->questionnote = '';
     $q->inputs['ans1'] = stack_input_factory::make('algebraic', 'ans1', '[x+y=1,x-y=1]', array('boxWidth' => 25));
     $feedbackvars = new stack_cas_keyval('', null, 0, 't');
     $sans = new stack_cas_casstring('all_listp(equationp,ans1)');
     $sans->get_valid('t');
     $tans = new stack_cas_casstring('true');
     $tans->get_valid('t');
     $node0 = new stack_potentialresponse_node($sans, $tans, 'AlgEquiv', '', true);
     $node0->add_branch(0, '=', 0, '', -1, 'Your answer should be a list of equations!', FORMAT_HTML, 'Result-0-F');
     $node0->add_branch(1, '=', 0, '', 1, 'Your answer is a list of equations.', FORMAT_HTML, 'Result-0-T');
     $sans = new stack_cas_casstring('solve(ans1,listofvars(ans1))');
     $sans->get_valid('t');
     $tans = new stack_cas_casstring('[]');
     $tans->get_valid('t');
     $node1 = new stack_potentialresponse_node($sans, $tans, 'AlgEquiv', '', true);
     $node1->add_branch(0, '=', 0, $q->penalty, -1, 'Your equations have no solution!', FORMAT_HTML, 'Result-1-F');
     $node1->add_branch(1, '=', 0, $q->penalty, 2, 'You have some solutions!', FORMAT_HTML, 'Result-1-T');
     $sans = new stack_cas_casstring('length(solve(ans1,listofvars(ans1)))');
     $sans->get_valid('t');
     $tans = new stack_cas_casstring('1');
     $tans->get_valid('t');
     $node2 = new stack_potentialresponse_node($sans, $tans, 'AlgEquiv', '', true);
     $node2->add_branch(0, '=', 0, $q->penalty, -1, 'You should have only one solution.', FORMAT_HTML, 'Result-2-F');
     $node2->add_branch(1, '=', 1, $q->penalty, -1, 'Good, you have one solution.', FORMAT_HTML, 'Result-2-T');
     $q->prts['Result'] = new stack_potentialresponse_tree('Result', '', true, 1, $feedbackvars->get_session(), array($node0, $node1, $node2), 0);
     return $q;
 }
 protected function initialise_question_instance(question_definition $question, $questiondata)
 {
     parent::initialise_question_instance($question, $questiondata);
     $question->questionvariables = $questiondata->options->questionvariables;
     $question->questionnote = $questiondata->options->questionnote;
     $question->specificfeedback = $questiondata->options->specificfeedback;
     $question->specificfeedbackformat = $questiondata->options->specificfeedbackformat;
     $question->prtcorrect = $questiondata->options->prtcorrect;
     $question->prtcorrectformat = $questiondata->options->prtcorrectformat;
     $question->prtpartiallycorrect = $questiondata->options->prtpartiallycorrect;
     $question->prtpartiallycorrectformat = $questiondata->options->prtpartiallycorrectformat;
     $question->prtincorrect = $questiondata->options->prtincorrect;
     $question->prtincorrectformat = $questiondata->options->prtincorrectformat;
     $question->variantsselectionseed = $questiondata->options->variantsselectionseed;
     $question->options = new stack_options();
     $question->options->set_option('multiplicationsign', $questiondata->options->multiplicationsign);
     $question->options->set_option('complexno', $questiondata->options->complexno);
     $question->options->set_option('inversetrig', $questiondata->options->inversetrig);
     $question->options->set_option('matrixparens', $questiondata->options->matrixparens);
     $question->options->set_option('sqrtsign', (bool) $questiondata->options->sqrtsign);
     $question->options->set_option('simplify', (bool) $questiondata->options->questionsimplify);
     $question->options->set_option('assumepos', (bool) $questiondata->options->assumepositive);
     $requiredparams = stack_input_factory::get_parameters_used();
     foreach ($questiondata->inputs as $name => $inputdata) {
         $allparameters = array('boxWidth' => $inputdata->boxsize, 'strictSyntax' => (bool) $inputdata->strictsyntax, 'insertStars' => (int) $inputdata->insertstars, 'syntaxHint' => $inputdata->syntaxhint, 'forbidWords' => $inputdata->forbidwords, 'allowWords' => $inputdata->allowwords, 'forbidFloats' => (bool) $inputdata->forbidfloat, 'lowestTerms' => (bool) $inputdata->requirelowestterms, 'sameType' => (bool) $inputdata->checkanswertype, 'mustVerify' => (bool) $inputdata->mustverify, 'showValidation' => $inputdata->showvalidation);
         $parameters = array();
         foreach ($requiredparams[$inputdata->type] as $paramname) {
             if ($paramname == 'inputType') {
                 continue;
             }
             $parameters[$paramname] = $allparameters[$paramname];
         }
         // TODO: Do something with $inputdata->options here.
         $question->inputs[$name] = stack_input_factory::make($inputdata->type, $inputdata->name, $inputdata->tans, $parameters);
     }
     $totalvalue = 0;
     foreach ($questiondata->prts as $name => $prtdata) {
         $totalvalue += $prtdata->value;
     }
     if ($questiondata->prts && $totalvalue < 1.0E-7) {
         throw new coding_exception('There is an error authoring your question. ' . 'The $totalvalue, the marks available for the question, must be positive in question ' . $question->name);
     }
     foreach ($questiondata->prts as $name => $prtdata) {
         $nodes = array();
         foreach ($prtdata->nodes as $nodedata) {
             $sans = new stack_cas_casstring($nodedata->sans);
             $sans->get_valid('t');
             $tans = new stack_cas_casstring($nodedata->tans);
             $tans->get_valid('t');
             if (is_null($nodedata->falsepenalty) || $nodedata->falsepenalty === '') {
                 $falsepenalty = $questiondata->penalty;
             } else {
                 $falsepenalty = $nodedata->falsepenalty;
             }
             if (is_null($nodedata->truepenalty) || $nodedata->truepenalty === '') {
                 $truepenalty = $questiondata->penalty;
             } else {
                 $truepenalty = $nodedata->truepenalty;
             }
             $node = new stack_potentialresponse_node($sans, $tans, $nodedata->answertest, $nodedata->testoptions, (bool) $nodedata->quiet, '', $nodedata->id);
             $node->add_branch(0, $nodedata->falsescoremode, $nodedata->falsescore, $falsepenalty, $nodedata->falsenextnode, $nodedata->falsefeedback, $nodedata->falsefeedbackformat, $nodedata->falseanswernote);
             $node->add_branch(1, $nodedata->truescoremode, $nodedata->truescore, $truepenalty, $nodedata->truenextnode, $nodedata->truefeedback, $nodedata->truefeedbackformat, $nodedata->trueanswernote);
             $nodes[$nodedata->nodename] = $node;
         }
         // TODO $feedbackvariables, and $sans, $tans, should probably still be strings
         // here, and should be converted to CAS stuff later, only if needed.
         if ($prtdata->feedbackvariables) {
             $feedbackvariables = new stack_cas_keyval($prtdata->feedbackvariables, null, null, 't');
             $feedbackvariables = $feedbackvariables->get_session();
         } else {
             $feedbackvariables = null;
         }
         $question->prts[$name] = new stack_potentialresponse_tree($name, '', (bool) $prtdata->autosimplify, $prtdata->value / $totalvalue, $feedbackvariables, $nodes, $prtdata->firstnodename);
     }
     $question->deployedseeds = array_values($questiondata->deployedseeds);
 }
 public static function run_test($test)
 {
     // Note: What we would really like to do is the following.
     // $el = stack_input_factory::make('algebraic', 'sans1', 'x');
     // $el->set_parameter('insertStars', 1);
     // $el->set_parameter('strictSyntax', false);
     // $el->set_parameter('sameType', false);
     // $cs = $el->validate_student_response($test->rawstring);
     // However, we want to pull apart the bits to expose where the various errors occur.
     $cs = new stack_cas_casstring($test->rawstring);
     $cs->get_valid('s', false, 1);
     $cs->set_cas_validation_casstring('sans1', true, true, false, null);
     $phpvalid = $cs->get_valid();
     if ($phpvalid) {
         // Trim off stack_validate_typeless([..], true, true).
         $phpcasstring = $cs->get_casstring();
         $phpcasstring = substr($phpcasstring, 25);
         $phpcasstring = substr($phpcasstring, 0, strlen($phpcasstring) - 12);
         $outputphpcasstring = $phpcasstring;
     } else {
         $phpcasstring = '';
         $outputphpcasstring = 'N/A...';
     }
     $errors = $cs->get_errors();
     $passed = true;
     if ('php_true' === $test->phpvalid) {
         $expected = true;
     } else {
         $expected = false;
     }
     if ($phpvalid != $expected) {
         $passed = false;
         $errors .= ' ' . stack_string('phpvalidatemismatch');
     }
     if ($phpvalid && $phpcasstring != $test->phpcasstring) {
         $passed = false;
         $errors .= ' ' . stack_maxima_format_casstring($phpcasstring) . ' \\(\\neq \\) ' . stack_maxima_format_casstring($test->phpcasstring);
     }
     $casvalid = '';
     $caserrors = '';
     $casvalue = '';
     $casdisplay = '';
     if ($cs->get_valid()) {
         $options = new stack_options();
         $options->set_option('simplify', false);
         $session = new stack_cas_session(array($cs), $options, 0);
         $session->instantiate();
         $session = $session->get_session();
         $cs = $session[0];
         $caserrors = stack_maxima_translate($cs->get_errors());
         $casvalue = stack_maxima_format_casstring($cs->get_value());
         if ('cas_true' == $test->casvalid) {
             $casexpected = true;
         } else {
             $casexpected = false;
         }
         if ('' == $cs->get_value()) {
             $casvalid = false;
         } else {
             $casvalid = true;
         }
         if ($casexpected != $casvalid) {
             $passed = false;
             $caserrors .= ' ' . stack_string('casvalidatemismatch');
         }
         $casdisplay = $cs->get_display();
     }
     $answernote = $cs->get_answernote();
     if ($answernote != $test->ansnotes) {
         $passed = false;
         $errors .= ' ' . stack_string('ansnotemismatch');
     }
     return array($passed, $phpvalid, $phpcasstring, $errors, $casvalid, $caserrors, $casdisplay, $casvalue, $answernote);
 }
 public function do_test()
 {
     $this->atmark = 1;
     $anotes = array();
     // Note that in casting to an integer we are lucky here.
     // Non-integer strings get cast to zero, which is invalid anyway....
     $atestops = (int) $this->atoption;
     if (!is_int($atestops) or $atestops <= 0) {
         $this->aterror = 'TEST_FAILED';
         $this->atfeedback = stack_string('TEST_FAILED', array('errors' => ''));
         $this->atfeedback .= stack_string('ATNumDecPlaces_OptNotInt', array('opt' => $this->atoption));
         $this->atansnote = 'ATNumDecPlaces_STACKERROR_Option.';
         $this->atmark = 0;
         $this->atvalid = false;
         return null;
     }
     $commands = array($this->sanskey, $this->tanskey, (string) $this->atoption);
     foreach ($commands as $com) {
         $cs = new stack_cas_casstring($com);
         if (!$cs->get_valid('t', true, 0)) {
             $this->aterror = 'TEST_FAILED';
             $this->atfeedback = stack_string('TEST_FAILED', array('errors' => ''));
             $this->atfeedback .= stack_string('AT_InvalidOptions', array('errors' => $cs->get_errors()));
             $this->atansnote = 'ATNumDecPlaces_STACKERROR_Option.';
             $this->atmark = 0;
             $this->atvalid = false;
             return null;
         }
     }
     // Check that the first expression is a floating point number,
     // with the right number of decimal places.
     $sans = explode('.', $this->sanskey);
     if (2 === count($sans)) {
         if ($atestops != strlen($sans[1])) {
             $this->atfeedback .= stack_string('ATNumDecPlaces_Wrong_DPs');
             $anotes[] = 'ATNumDecPlaces_Wrong_DPs (' . strlen($sans[1]) . ' <> ' . $atestops . ')';
             $this->atmark = 0;
         } else {
             $anotes[] = 'ATNumDecPlaces_Correct';
         }
     } else {
         // No '.' found.
         $this->atfeedback .= stack_string('ATNumDecPlaces_NoDP');
         $anotes[] = 'ATNumDecPlaces_NoDP';
         $this->atmark = 0;
     }
     // Check that the two numbers evaluate to the same value.
     $cascommands = array();
     $cascommands[] = "caschat2:ev({$this->atoption},simp)";
     $cascommands[] = "caschat0:ev(float(round(10^caschat2*{$this->sanskey})/10^caschat2),simp)";
     $cascommands[] = "caschat1:ev(float(round(10^caschat2*{$this->tanskey})/10^caschat2),simp)";
     $cascommands[] = "caschat3:ev(second(ATAlgEquiv(caschat0,caschat1)),simp)";
     $cts = array();
     foreach ($cascommands as $com) {
         $cs = new stack_cas_casstring($com);
         $cs->get_valid('t', true, 0);
         $cts[] = $cs;
     }
     $session = new stack_cas_session($cts, null, 0);
     $session->instantiate();
     if ('' != $session->get_errors_key('caschat0')) {
         $this->aterror = 'TEST_FAILED';
         $this->atfeedback = stack_string('TEST_FAILED', array('errors' => $session->get_errors_key('caschat0')));
         $anotes[] = 'ATNumDecPlaces_STACKERROR_SAns';
         $this->atansnote = implode('. ', $anotes) . '.';
         $this->atmark = 0;
         $this->atvalid = false;
         return null;
     }
     if ('' != $session->get_errors_key('caschat1')) {
         $this->aterror = 'TEST_FAILED';
         $this->atfeedback = stack_string('TEST_FAILED', array('errors' => $session->get_errors_key('caschat1')));
         $anotes[] = 'ATNumDecPlaces_STACKERROR_TAns';
         $this->atansnote = implode('. ', $anotes) . '.';
         $this->atmark = 0;
         $this->atvalid = false;
         return null;
     }
     if ('' != $session->get_errors_key('caschat2')) {
         $this->aterror = 'TEST_FAILED';
         $this->atfeedback = stack_string('TEST_FAILED', array('errors' => ''));
         $this->atfeedback .= stack_string('AT_InvalidOptions', array('errors' => $session->get_errors_key('caschat2')));
         $anotes[] = 'ATNumDecPlaces_STACKERROR_Options.';
         $this->atansnote = implode('. ', $anotes) . '.';
         $this->atmark = 0;
         $this->atvalid = false;
         return null;
     }
     if ($session->get_value_key('caschat3') == 'true') {
         // Note, we only want the mark to *stay* at 1.
         $this->atmark *= 1;
         $anotes[] = 'ATNumDecPlaces_Equiv';
     } else {
         $this->atmark = 0;
         $anotes[] = 'ATNumDecPlaces_Not_equiv';
     }
     $this->atansnote = implode('. ', $anotes) . '.';
     if ($this->atmark) {
         return true;
     }
     return false;
 }
 public function test_ordergreat()
 {
     $cs = array('ordergreat(i,j,k)', 'p:matrix([-7],[2],[-3])', 'q:matrix([i],[j],[k])', 'v:dotproduct(p,q)');
     foreach ($cs as $s) {
         $cs = new stack_cas_casstring($s);
         $cs->get_valid('t');
         $s1[] = $cs;
     }
     $at1 = new stack_cas_session($s1, null, 0);
     $at1->instantiate();
     // There has been a subtle change to associativity in Maxima 5.37.0.
     $this->assertEquals('-7\\cdot i+2\\cdot j-3\\cdot k', $at1->get_display_key('v'));
 }
 /**
  * Validate all the maxima code in the question.
  *
  * This is done last, and separate from the other validation for two reasons:
  * 1. The rest of the validation is organised to validate the form in order,
  *    to match the way the form is defined. Here we need to validate in the
  *    order that the CAS is evaluated at run-time.
  * 2. This is the slowest part of validation, so we only do it at the end if
  *    everything else is OK.
  *
  * @param array $errors the errors array that validation is assembling.
  * @param array $fromform the submitted data to validate.
  * @return array updated $errors array.
  */
 protected function validate_question_cas_code($errors, $fromform, $fixingdollars)
 {
     $keyval = new stack_cas_keyval($fromform['questionvariables'], $this->options, $this->seed, 't');
     $keyval->instantiate();
     $session = $keyval->get_session();
     if ($session->get_errors()) {
         $errors['questionvariables'][] = $session->get_errors();
         return $errors;
     }
     // Instantiate all text fields and look for errors.
     $castextfields = array('questiontext', 'specificfeedback', 'generalfeedback');
     foreach ($castextfields as $field) {
         $errors = $this->validate_cas_text($errors, $fromform[$field]['text'], $field, $fixingdollars, clone $session);
     }
     $errors = $this->validate_cas_text($errors, $fromform['questionnote'], 'questionnote', $fixingdollars, clone $session);
     // Make a list of all inputs, instantiate it and then look for errors.
     $inputs = $this->get_input_names_from_question_text();
     $inputvalues = array();
     foreach ($inputs as $inputname => $notused) {
         $cs = new stack_cas_casstring($inputname . ':' . $fromform[$inputname . 'modelans']);
         $cs->get_valid('t');
         $inputvalues[] = $cs;
         if ($fromform[$inputname . 'options']) {
             $cs = new stack_cas_casstring('optionsfor' . $inputname . ':' . $fromform[$inputname . 'options']);
             $cs->get_valid('t');
             $inputvalues[] = $cs;
         }
     }
     $inputsession = clone $session;
     $inputsession->add_vars($inputvalues);
     $inputsession->instantiate();
     foreach ($inputs as $inputname => $notused) {
         if ($inputsession->get_errors_key($inputname)) {
             $errors[$inputname . 'modelans'][] = $inputsession->get_errors_key($inputname);
             // TODO: Send the acutal value to to input, and ask it to validate it.
             // For example, the matrix input type could check that the model answer is a matrix.
         }
         if ($fromform[$inputname . 'options'] && $inputsession->get_errors_key('optionsfor' . $inputname)) {
             $errors[$inputname . 'options'][] = $inputsession->get_errors_key('optionsfor' . $inputname);
         }
         // else TODO: Send the acutal value to the input, and ask it to validate it.
     }
     // At this point if we have errors, especially with inputs, there is no point in executing any of the PRTs.
     if (!empty($errors)) {
         return $errors;
     }
     // TODO: loop over all the PRTs in a similar manner....
     // Remember, to use
     // clone $inputsession
     // as the base session to have all the teacher's answers instantiated.
     // Otherwise we are likley to do illigitimate things to the various inputs.
     return $errors;
 }
Example #19
0
 /**
  * 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();
 }
 public function test_ordergreat()
 {
     $cs = array('ordergreat(i,j,k)', 'p:matrix([-7],[2],[-3])', 'q:matrix([i],[j],[k])', 'v:dotproduct(p,q)');
     foreach ($cs as $s) {
         $cs = new stack_cas_casstring($s);
         $cs->get_valid('t');
         $s1[] = $cs;
     }
     $at1 = new stack_cas_session($s1, null, 0);
     $at1->instantiate();
     $this->assertEquals('-7*i+2*j-3*k', $at1->get_value_key('v'));
 }
 public function test_do_test_fail_quiet()
 {
     $sans = new stack_cas_casstring('ans1');
     $tans = new stack_cas_casstring('3*(x+2)');
     $tans->get_valid('t');
     $node = new stack_potentialresponse_node($sans, $tans, 'FacForm', 'x', true);
     $node->add_branch(0, '+', 0.5, '', -1, 'Boo! Your answer should be in factored form, i.e. @factor(ans1)@.', FORMAT_HTML, '1-0-0');
     $node->add_branch(1, '=', 2, '', 3, 'Yeah!', FORMAT_HTML, '1-0-1');
     $options = new stack_options();
     $result = new stack_potentialresponse_tree_state(1, true, 1);
     $nextnode = $node->do_test('3*x+6', '3*(x+2)', 'x', $options, $result);
     $this->assertEquals(1, count($result->feedback));
     $this->assertEquals('Boo! Your answer should be in factored form, i.e. @factor(ans1)@.', $result->feedback[0]->feedback);
     $this->assertEquals(1.5, $result->score);
     $data = array('factor(ans1)', 'ans1', '3*(x+2)', 'x');
     $this->assertEquals($data, $node->get_required_cas_strings());
 }