diff --git a/deploy.php b/deploy.php index fb43b60aceda289195e163dad4abd997bca8ae7b..5c17e1ae7807af969523abc093eaf424369dc17f 100644 --- a/deploy.php +++ b/deploy.php @@ -80,13 +80,18 @@ if (!is_null($undeploy) && $question->deployedseeds) { // Process undeployall if applicable. $deployfromlist = optional_param('deployfromlist', null, PARAM_INT); -if (!is_null($deployfromlist)) { +$deploysystematic = optional_param('deploysystematic', null, PARAM_INT); +if (!is_null($deployfromlist) || !is_null($deploysystematic)) { // Check data integrity. $dataproblem = false; - $deploytxt = optional_param('deployfromlist', null, PARAM_TEXT); - $baseseeds = explode("\n", trim($deploytxt)); + if (!is_null($deployfromlist)) { + $deploytxt = optional_param('deployfromlist', null, PARAM_TEXT); + $baseseeds = explode("\n", trim($deploytxt)); + } else { + $baseseeds = range(1, $deploysystematic); + } $newseeds = array(); foreach ($baseseeds as $newseed) { // Now also explode over commas. diff --git a/doc/en/Authoring/Deploying.md b/doc/en/Authoring/Deploying.md index c16356cef98341efa94cd00e91d1caad5b60e094..50dd47d8fcfe8da22bac9183f7f85032113f4a74 100644 --- a/doc/en/Authoring/Deploying.md +++ b/doc/en/Authoring/Deploying.md @@ -17,6 +17,7 @@ Notes: 3. Variants are different if and only if the evaluated [question note](Question_note.md) is different. Any number of instances can be requested and deployed but only one instance of each [question note](Question_note.md) can be deployed. It is possible to deploy \(n\) variants in one go, but the system will give up if too many duplicate question notes are generated. The teacher is responsible to ensure question variants are different if and only if the question notes are different. The deployment management also allows specific variants to be dropped. You can also return to the question preview window and try a specific deployed variant. 4. Deployment is not required for authors to test questions: an instance is generated on-the-fly. 5. Once a quiz is underway it is still possible in Moodle to edit a question, and to re-grade students' attempts. This is useful in rare cases where there is a mistake, you want to improve the worked solution, you would like to add better feedback/partial credit for a particular etc. However, do not change anything related to the random generation of questions! Results are unpredictable, and may well result in a situation when the modified question is different to that answered by students taking the test prior to modifications... +6. It is possible to [systematically deploy](../CAS/Systematic_deployment.md) all variants of a question in a simple manner. ## How to deploy question variants ## @@ -26,8 +27,3 @@ Notes: 1. You can click on the seed numbers to view a particular random variant. The testing page lists values of all the variables, displays the question and the worked solution. The testing page is a very efficient way to look at your random variants. 2. When you deploy new variants STACK will run all the question tests. If a test fails, the generation process will stop with an error message, showing the failing test. - - -## Limitations ## - -There is currently no way to loop systematically over all variants and deploy them all. diff --git a/doc/en/CAS/Random.md b/doc/en/CAS/Random.md index ee7ef0a2af57297e993667d525e213f6e578e3f8..e5c073076d9ff631e391983c01c19eff4ef611ed 100644 --- a/doc/en/CAS/Random.md +++ b/doc/en/CAS/Random.md @@ -10,6 +10,8 @@ For the purposes of learning and teaching, we do not need an algorithm which is It is very important to test each random version a student is likely to see and not to leave this to chance. To pre-generate and test random variants see the separate documentation on [deploying random variants](../Authoring/Deploying.md). +Users may also [systematically deploy](Systematic_deployment.md) all variants of a question in a simple manner. + ## rand() {#rand} STACK provides its own function `rand()`. diff --git a/doc/en/CAS/Systematic_deployment.md b/doc/en/CAS/Systematic_deployment.md new file mode 100644 index 0000000000000000000000000000000000000000..00f110a5d7742e33448e607c6f6c4d8657d149dd --- /dev/null +++ b/doc/en/CAS/Systematic_deployment.md @@ -0,0 +1,40 @@ +# Systematic deployment + +STACK has the option to create [random variants](Random.md) of questions. However, users also need to systematically deploy all variants of a question in a simple manner. + +Every CAS (Maxima) session contains a Maxima variable `stack_seed` which holds the integer value of the seed used by that variant of the question. + +Note, in Maxima the list index starts at 1. I.e the first element of a list is `l[1]` (not zero). + +## Deploying every variant + +The `stack_seed` variable can be used to deploy every variant of a question. As an example, consider the data below (from https://nssdc.gsfc.nasa.gov/planetary/factsheet/) + + planet:["Mercury", "Venus", "Earth", "Moon", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto"]; + /* mass 10^(24)kg. */ + mass:[0.330, 4.87, 5.97, 0.073, 0.642, 1898.0, 568.0, 86.8, 102.0, 0.0130]; + /* Orbital Period (days). */ + period[88.0, 224.7, 365.2, 27.3, 687.0, 4331, 10747, 30589, 59800, 90560]; + /* Distance from Sun (106 km) */ + dist:[57.9, 108.2, 149.6, 0.384, 228.0, 778.5, 1432.0, 2867.0, 4515.0, 5906.4]; + +If you want to use this data in a question, you can use a variable to index elements in these lists. In particular, you can deploy seeds 1,2,3,4,5,6,7,8,9,10. Then in castext you can use the variable `stack_seed` as an index to the data, e.g. + + The planet {@planet[stack_seed]@} has mass \({@mass[stack_seed]*10^(24)@} \mathrm{kg}\). + +If you want to exclude the moon, then you can omit seed 4, and deploy only seeds 1,2,3,5,6,7,8,9,10. + +It is your responsibility to make sure the index remains within range! You can ensure this by creating an index variable such as `n1:mod(stack_seed,11);` and then using this + + The planet {@planet[n1]@} has mass \({@mass[n1]*10^(24)@} \mathrm{kg}\). + +It is sensible to always ensure your `stack_seed` does not create run-time errors. + +Of course, there are many other ways to map deployed seeds onto systematic deployment of variants. Using consecutive integers from \(1, \ldots, n\) as the starting point is probably simplest and easiest to maintain. For this reason there is a special option to do this on the deploy variants page. + +Notes + +1. you can combine use of `stack_seed` with random functions. There is nothing wrong with seeding the random number generator from a small integer! +2. STACK auto-detects random functions. You must refer to `stack_seed`, or a random function, in the _question variables_ to trigger use of deployed variants. Otherwise STACK will think deployed variants are not needed. If necessary add in a variable `n1:stack_seed;` and then use `n1` as your index to make sure you have explicitly made use of `stack_seed`. +3. There is nothing special about the variable `stack_seed`. You are free to reassign values to this variable within the question. +4. Just as with randomisation, you must create a question note to distinguish between variants of a question. diff --git a/doc/en/Developer/Development_track.md b/doc/en/Developer/Development_track.md index 23849870fd56eb61d1b27ddd987b3b1ea5759b8a..743c2e02b042bcba79caa7bb413e2ef5213a5124 100644 --- a/doc/en/Developer/Development_track.md +++ b/doc/en/Developer/Development_track.md @@ -8,6 +8,7 @@ past development history is documented on [Development history](Development_hist ## Version 4.4.6 1. Refactor the healthcheck scripts, especially to make unicode requirements for maxima more prominent. +2. Allow users to [systematically deploy](../CAS/Systematic_deployment.md) all variants of a question in a simple manner. TODO: diff --git a/doc/en/Developer/Future_plans.md b/doc/en/Developer/Future_plans.md index 6da5c73dd752aa329963ed974db825156c87ae04..0f3d64552dbc17da033b709293a2eab2cba112d3 100644 --- a/doc/en/Developer/Future_plans.md +++ b/doc/en/Developer/Future_plans.md @@ -69,7 +69,6 @@ Note, where the feature is listed as "(done)" means we have prototype code in th * WIRIS * Possible Maxima packages: * Better support for rational expressions, in particular really firm up the PartFrac and SingleFrac functions with better support. -* Auto deploy. E.g. if the first variable in the question variables is a single a:rand(n), then loop a=0..(n-1). * When validating the editing form, also evaluate the Maxima code in the PRTs, using the teacher's model answers. diff --git a/doc/meta_en.json b/doc/meta_en.json index a0c5d56373cd425acd2bbe28e13fc0ca55cb9258..32ad204a5b29a26f247b595149bb0c29328a059a 100644 --- a/doc/meta_en.json +++ b/doc/meta_en.json @@ -518,6 +518,11 @@ "description":"Functions for displaying the tree representation of a mathematical expression." }, { + "file":"Workflow.md", + "title":"Workflow - STACK Documentation", + "description":"Suggestions for effective question authoring workflow, especially when working collaboratively." + }, + { "file":"Numerical_input.md", "title":"Numerical Input - STACK Documentation", "description":"Information on STACK's numerical input type." @@ -597,6 +602,11 @@ "description":"Information on using different functions in STACK to generate random values." }, { + "file":"Systematic_deployment.md", + "title":"Systematic deployment - STACK Documentation", + "description":"Information on how to systematically deploy all variants of a question." + }, + { "file":"Real_Intervals.md", "title":"Real intervals - STACK Documentation", "description":"Information on representing, displaying and manipulating intervals in the real line." diff --git a/lang/en/qtype_stack.php b/lang/en/qtype_stack.php index 9a2d4d8536dfa30de3ec10640f053cfc1991613a..10d0c68e334d8aede4ff7fed693a7b508266b0ac 100644 --- a/lang/en/qtype_stack.php +++ b/lang/en/qtype_stack.php @@ -515,6 +515,7 @@ $string['deployedvariants'] = 'Deployed variants'; $string['deployedvariantsn'] = 'Deployed variants ({$a})'; $string['deploymanybtn'] = 'Deploy # of variants:'; $string['deploymanyerror'] = 'Error in user input: cannot deploy "{$a->err}" variants.'; +$string['deploysystematicbtn'] = 'Deploy seeds from 1 to: '; $string['deployduplicateerror'] = 'Duplicate question notes detected in the deployed variants. We strongly recommend each question note is only deployed once, otherwise you will have difficulty collecting meaningful stats when grouping by variant. Please consider deleting some variants with duplicate notes.'; $string['deploytoomanyerror'] = 'STACK will try to deploy up to at most 100 new variants in any one request. No new variants deployed.'; $string['deploymanynonew'] = 'Too many repeated existing question notes were generated.'; diff --git a/question.php b/question.php index 0a2ff6ec5b4a09b234ac10838d45ffef82163106..5aa1b74342ef218f7402473659c1fd4b25b82ac4 100644 --- a/question.php +++ b/question.php @@ -1271,11 +1271,12 @@ class qtype_stack_question extends question_graded_automatically_with_countback } /** - * @param string Input text (raw keyvals) to check for random functions. + * @param string Input text (raw keyvals) to check for random functions, or use of stack_seed. * @return bool Actual test of whether text uses randomisation. */ public static function random_variants_check($text) { - return preg_match('~\brand~', $text) || preg_match('~\bmultiselqn~', $text); + return preg_match('~\brand~', $text) || preg_match('~\bmultiselqn~', $text) + || preg_match('~\bstack_seed~', $text); } public function get_num_variants() { diff --git a/questiontestrun.php b/questiontestrun.php index e78d4a3a35c0cd6879b5e2cf4208bf69a798176a..561bff39075822c411a8d00112dc123ec2bebb3c 100644 --- a/questiontestrun.php +++ b/questiontestrun.php @@ -431,6 +431,16 @@ if ($question->has_random_variants()) { echo ' ' . stack_string('deploymanynotes'); echo html_writer::end_tag('form'); + // Systematic deployment of variants. + echo html_writer::start_tag('form', array('method' => 'get', 'class' => 'deploysystematic', + 'action' => new moodle_url('/question/type/stack/deploy.php', $urlparams))); + echo html_writer::input_hidden_params(new moodle_url($PAGE->url, array('sesskey' => sesskey())), array('seed')); + echo ' ' . html_writer::empty_tag('input', array('type' => 'submit', 'class' => 'btn btn-secondary', + 'value' => stack_string('deploysystematicbtn'))); + echo ' ' . html_writer::empty_tag('input', array('type' => 'text', 'size' => 3, + 'id' => 'deploysystematicfield', 'name' => 'deploysystematic', 'value' => '')); + echo html_writer::end_tag('form'); + // Deploy many from a CS list of integer seeds. echo "\n" . html_writer::start_tag('form', array('method' => 'get', 'class' => 'deployfromlist', 'action' => new moodle_url('/question/type/stack/deploy.php', $urlparams))); diff --git a/stack/cas/cassession2.class.php b/stack/cas/cassession2.class.php index 55a6537b6f8ae28c7d12c0bf9a21e32e687d7443..c9723cc345367c1824555e7d47b09f57ef42a2be 100644 --- a/stack/cas/cassession2.class.php +++ b/stack/cas/cassession2.class.php @@ -315,8 +315,10 @@ class stack_cas_session2 { // We will build the whole command here. // No protection in the block. $preblock = ''; - $command = 'block([]' . + $command = 'block([stack_seed]' . self::SEP . 'stack_randseed(' . $this->seed . ')'; + // Make the value of the seed available in the session. + $command .= self::SEP . 'stack_seed:' . $this->seed; // The options. $command .= $this->options->get_cas_commands()['commands']; // Some parts of logic storage. diff --git a/stack/cas/security-map.json b/stack/cas/security-map.json index 89ca9a5feafe4a9d0c4cc9dc94893f14bede4865..e1335ea3b7ec3df20d903d4c3edcd475a1bc07db 100644 --- a/stack/cas/security-map.json +++ b/stack/cas/security-map.json @@ -7318,6 +7318,9 @@ "stackunits_make": { "function": "s" }, + "stack_seed": { + "variable": "t" + }, "stack_unit_si_declare": { "function": "s", "contextvariable": "true" diff --git a/tests/castext_test.php b/tests/castext_test.php index af548955db5e48068c5486eac7d480147afbd40b..a976e2ee12fa8938785af874dbd6bdd1cefede61 100644 --- a/tests/castext_test.php +++ b/tests/castext_test.php @@ -2159,4 +2159,29 @@ class castext_test extends qtype_stack_testcase { $cs2->instantiate(); $this->assertEquals("Hello world", $at2->get_rendered()); } + + /** + * @covers \qtype_stack\castext2_evaluatable::make_from_source + * @covers \qtype_stack\stack_cas_keyval + */ + public function test_stack_pick_seed() { + $a2 = array(); + $s2 = array(); + foreach ($a2 as $s) { + $cs = stack_ast_container::make_from_teacher_source($s, '', new stack_cas_security(), array()); + $this->assertTrue($cs->get_valid()); + $s2[] = $cs; + } + $options = new stack_options(); + $options->set_option('simplify', false); + $cs2 = new stack_cas_session2($s2, $options, 123456); + + $textinput = "{@stack_seed@}"; + $at1 = castext2_evaluatable::make_from_source($textinput, 'test-case'); + $this->assertTrue($at1->get_valid()); + $cs2->add_statement($at1); + $cs2->instantiate(); + + $this->assertEquals('\({123456}\)', $at1->get_rendered()); + } }