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'))); }
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; }
/** * 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()); }