Skip to content
Snippets Groups Projects
Commit b98e21de authored by smmercuri's avatar smmercuri
Browse files

Merge from dev

parents 20a06936 4acf3596
No related branches found
No related tags found
No related merge requests found
Showing
with 176 additions and 81 deletions
...@@ -151,7 +151,8 @@ class StackQuestionLoader { ...@@ -151,7 +151,8 @@ class StackQuestionLoader {
); );
$question->options->set_option( $question->options->set_option(
'scientificnotation', 'scientificnotation',
isset($xmldata->question->scientificnotation) ? (string) $xmldata->question->scientificnotation : get_config('qtype_stack', 'scientificnotation') isset($xmldata->question->scientificnotation) ?
(string) $xmldata->question->scientificnotation : get_config('qtype_stack', 'scientificnotation')
); );
$inputmap = []; $inputmap = [];
......
...@@ -61,6 +61,8 @@ Tests ...@@ -61,6 +61,8 @@ Tests
1. whether the student's answer contains `opt` significant figures, and 1. whether the student's answer contains `opt` significant figures, and
2. whether the answer is accurate to `opt` significant figures. 2. whether the answer is accurate to `opt` significant figures.
Numerical accuracy here ensures the student's answer is within \(\pm .5\) of the last significant place. So, for example, by design this test will consider \(9.8\) to be equivalent to \(10\) to 2 significant figures. The student's answer has been written to 2sf, and the answer is within \(\pm 0.5\) of \(10\). Indeed, by this test `9.6`, `9.7`, `9.8`, `9.9` and `10` will all pass this test. But `10.1` etc will fail (3 significant figures), rather than accuracy. (This may make this test, with default options, less useful but changing the behaviour now would be problematic as well!)
If the option is a list `[n,m]` then we check the answer has been written to `n` significant figures, with an accuracy of up to `m` places. If the answer is too far out then rounding feedback will not be given. A common test would be to ask for \([n,n-1]\) to permit the student to enter the last digit incorrectly. If the option is a list `[n,m]` then we check the answer has been written to `n` significant figures, with an accuracy of up to `m` places. If the answer is too far out then rounding feedback will not be given. A common test would be to ask for \([n,n-1]\) to permit the student to enter the last digit incorrectly.
If the options are of the form `[n,0]` then only the number of significant figures in `sa` will be checked. This ignores any numerical accuracy and completely ignores the second argument to the function. Note, that this test is liberal in establishing the number of significant figures. For strict enforcement of the rules, use `StrictSigFigs` instead. If the options are of the form `[n,0]` then only the number of significant figures in `sa` will be checked. This ignores any numerical accuracy and completely ignores the second argument to the function. Note, that this test is liberal in establishing the number of significant figures. For strict enforcement of the rules, use `StrictSigFigs` instead.
......
...@@ -32,6 +32,25 @@ An example `[[geogebra]]` [question block](Question_blocks/index.md) is shown be ...@@ -32,6 +32,25 @@ An example `[[geogebra]]` [question block](Question_blocks/index.md) is shown be
This illustrates how the material_id is used. This illustrates how the material_id is used.
## Control the size of the applet
There are two places where the size of the applet can be defined:
Within the block, adding values to the GeoGebra parameters width and height will define the section of the applet that is to be shown.
Within the block's header, adding values to the iframe parameters width and height will enlarge or reduce the size of the applet, or even distort it.
```
[[geogebra height="100px" width="175px"]]
params["material_id"]="seehz3km";
params["height"]=200;
params["width"]=350;
[[/geogebra]]
```
In the block's head, `width="80%" aspect-ratio="2/3"` could be used instead to define relative sizes and possible distortions if needed.
If no size is defined the default is to have `width="500px" height="400px"` and these are also the dimensions used if values are missing and no aspect-ratio has been defined.
## Using the sub-tags "set", "watch" and "remember" ## Using the sub-tags "set", "watch" and "remember"
The "set", "watch" and "remember" tags to the `[[geogebra]]` question block link Maxima values to GeoGebra objects in various ways. The "set", "watch" and "remember" tags to the `[[geogebra]]` question block link Maxima values to GeoGebra objects in various ways.
......
...@@ -351,6 +351,12 @@ Writing bespoke validators is an advanced feature, but offers two significant be ...@@ -351,6 +351,12 @@ Writing bespoke validators is an advanced feature, but offers two significant be
2. Potential response tree authoring becomes much easier and more reliable because the validation acts as a "guard clause" only allowing correctly structured information through to the PRT. This means type-checking need not be done in the PRT before assessment. 2. Potential response tree authoring becomes much easier and more reliable because the validation acts as a "guard clause" only allowing correctly structured information through to the PRT. This means type-checking need not be done in the PRT before assessment.
3. The extra option `validator` is designed to allow you to choose extra expressions to be invalid. The extra option `feedback` will simply print an additional message to students in the validation feedback. 3. The extra option `validator` is designed to allow you to choose extra expressions to be invalid. The extra option `feedback` will simply print an additional message to students in the validation feedback.
### Extra option: monospace ###
This option is available for algebraic, numerical, units and varmatrix inputs. It controls if the student's answer is displayed using monospace font. `monospace` and `monospace:true` will force the input to use monospace. `monospace:false` will force proportional font.
If `monospace` is not specified, then the CURRENT system default for the given input type will be used when the question is displayed.
## Extra options ## ## Extra options ##
In the future we are likely to add additional functionality via the _extra options_ fields. This is because the form-based support becomes ever more complex, intimidating and difficult to navigate. In the future we are likely to add additional functionality via the _extra options_ fields. This is because the form-based support becomes ever more complex, intimidating and difficult to navigate.
......
...@@ -14,14 +14,12 @@ This version will require moodle 4.0+. Moodle 3.x is no longer supported. ...@@ -14,14 +14,12 @@ This version will require moodle 4.0+. Moodle 3.x is no longer supported.
5. Add in the `CT:...` and `RAW:...` options for test case construction to enable tests of invalid input (e.g. missing stars). 5. Add in the `CT:...` and `RAW:...` options for test case construction to enable tests of invalid input (e.g. missing stars).
6. STACK now has an [API](../Installation/API.md) to provide STACK questions as a web service. 6. STACK now has an [API](../Installation/API.md) to provide STACK questions as a web service.
7. Improve the display of floats. Numbers of decimal places are now respected in all parts of expressions, and floats such as `1.7E-9` are displayed at \(1.7 \times 10^{-9}\). There is a new question option to choose between \(1.7 \times 10^{-9}\) and \(1.7E-9\). 7. Improve the display of floats. Numbers of decimal places are now respected in all parts of expressions, and floats such as `1.7E-9` are displayed at \(1.7 \times 10^{-9}\). There is a new question option to choose between \(1.7 \times 10^{-9}\) and \(1.7E-9\).
8. Release first version of the API for longer term support, and better support for ILIAS.
TODO: TODO:
1. Major code tidy: Moodle code style now requires (i) short forms of arrays, i.e. `[]` not `array()`, and (ii) commas at the end of all list items. 1. Fix markdown problems. See issue #420.
2. Fix markdown problems. See issue #420. 2. Fix [issue #879](https://github.com/maths/moodle-qtype_stack/issues/879)
3. Fix [issue #879](https://github.com/maths/moodle-qtype_stack/issues/879) 3. Fix [issue #406](https://github.com/maths/moodle-qtype_stack/issues/406) (possibly for 4.7.0).
## Version 4.7.0 ## Version 4.7.0
......
...@@ -151,6 +151,8 @@ $string['generalfeedback_help'] = 'General feedback is CASText. General feedback ...@@ -151,6 +151,8 @@ $string['generalfeedback_help'] = 'General feedback is CASText. General feedback
$string['generalfeedback_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php/Authoring/CASText.md#general_feedback'; $string['generalfeedback_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php/Authoring/CASText.md#general_feedback';
$string['showvalidation'] = 'Show the validation'; $string['showvalidation'] = 'Show the validation';
$string['showvalidation_help'] = 'Displays any validation feedback from this input, including echoing back their expression in traditional two dimensional notation. Syntax errors are always reported back.'; $string['showvalidation_help'] = 'Displays any validation feedback from this input, including echoing back their expression in traditional two dimensional notation. Syntax errors are always reported back.';
$string['inputmonospace'] = 'Monospace font';
$string['inputmonospace_help'] = 'Select the types of input to default to monospace font. This affects all questions, not just new ones. These defaults can be overridden for a particular input with extra option settings \'monospace\' and \'monospace:false\'.';
$string['showvalidation_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php/Authoring/Inputs.md#Show_validation'; $string['showvalidation_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php/Authoring/Inputs.md#Show_validation';
$string['showvalidationno'] = 'No'; $string['showvalidationno'] = 'No';
$string['showvalidationyes'] = 'Yes, with variable list'; $string['showvalidationyes'] = 'Yes, with variable list';
......
...@@ -436,7 +436,7 @@ foreach (array_keys($summary) as $variant) { ...@@ -436,7 +436,7 @@ foreach (array_keys($summary) as $variant) {
foreach ($prtreportinputs[$variant][$prt][$dat] as $inputsummary => $inum) { foreach ($prtreportinputs[$variant][$prt][$dat] as $inputsummary => $inum) {
$sumout .= str_pad($inum, strlen((string) $pad) + 1) . '(' . $sumout .= str_pad($inum, strlen((string) $pad) + 1) . '(' .
str_pad(number_format((float) 100 * $inum / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) . str_pad(number_format((float) 100 * $inum / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) .
'%); ' . $inputsummary . "\n"; '%); ' . htmlentities($inputsummary, ENT_COMPAT) . "\n";
} }
$sumout .= "\n"; $sumout .= "\n";
} }
...@@ -466,7 +466,7 @@ foreach (array_keys($summary) as $variant) { ...@@ -466,7 +466,7 @@ foreach (array_keys($summary) as $variant) {
foreach ($data as $dat => $num) { foreach ($data as $dat => $num) {
$sumouti .= str_pad($num, strlen((string) $pad) + 1) . '(' . $sumouti .= str_pad($num, strlen((string) $pad) + 1) . '(' .
str_pad(number_format((float) 100 * $num / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) . str_pad(number_format((float) 100 * $num / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) .
'%); ' . $dat . "\n"; '%); ' . htmlentities($dat, ENT_COMPAT) . "\n";
} }
$sumouti .= "\n"; $sumouti .= "\n";
} }
...@@ -498,7 +498,7 @@ foreach ($inputreportsummary as $input => $idata) { ...@@ -498,7 +498,7 @@ foreach ($inputreportsummary as $input => $idata) {
foreach ($data as $dat => $num) { foreach ($data as $dat => $num) {
$sumouti .= str_pad($num, strlen((string) $pad) + 1) . '(' . $sumouti .= str_pad($num, strlen((string) $pad) + 1) . '(' .
str_pad(number_format((float) 100 * $num / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) . str_pad(number_format((float) 100 * $num / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) .
'%); ' . $dat . "\n"; '%); ' . htmlentities($dat, ENT_COMPAT) . "\n";
} }
$sumouti .= "\n"; $sumouti .= "\n";
} }
...@@ -526,7 +526,7 @@ foreach ($summary as $variant => $vdata) { ...@@ -526,7 +526,7 @@ foreach ($summary as $variant => $vdata) {
foreach ($vdata as $dat => $num) { foreach ($vdata as $dat => $num) {
$sumout .= str_pad($num, strlen((string) $pad) + 1) . '(' . $sumout .= str_pad($num, strlen((string) $pad) + 1) . '(' .
str_pad(number_format((float) 100 * $num / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) . str_pad(number_format((float) 100 * $num / $tot, 2, '.', ''), 6, ' ', STR_PAD_LEFT) .
'%); ' . $dat . "\n"; '%); ' . htmlentities($dat, ENT_COMPAT) . "\n";
} }
} }
} }
......
...@@ -32,7 +32,10 @@ require_once(__DIR__ . '/vle_specific.php'); ...@@ -32,7 +32,10 @@ require_once(__DIR__ . '/vle_specific.php');
* @copyright 2012 The Open University * @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
// @codingStandardsIgnoreStart
// There's a matching class name in the API.
class qtype_stack_renderer extends qtype_renderer { class qtype_stack_renderer extends qtype_renderer {
// @codingStandardsIgnoreEnd
public function formulation_and_controls(question_attempt $qa, question_display_options $options) { public function formulation_and_controls(question_attempt $qa, question_display_options $options) {
/* Return type should be @var qtype_stack_question $question. */ /* Return type should be @var qtype_stack_question $question. */
......
...@@ -208,6 +208,11 @@ $settings->add(new admin_setting_configselect('qtype_stack/inputshowvalidation', ...@@ -208,6 +208,11 @@ $settings->add(new admin_setting_configselect('qtype_stack/inputshowvalidation',
get_string('showvalidation_help', 'qtype_stack'), '1', get_string('showvalidation_help', 'qtype_stack'), '1',
stack_options::get_showvalidation_options())); stack_options::get_showvalidation_options()));
$settings->add(new admin_setting_configmultiselect('qtype_stack/inputmonospace',
get_string('inputmonospace', 'qtype_stack'),
get_string('inputmonospace_help', 'qtype_stack'), [],
stack_options::get_monospace_options()));
// Options for new questions. // Options for new questions.
$settings->add(new admin_setting_heading('questionoptionsheading', $settings->add(new admin_setting_heading('questionoptionsheading',
get_string('settingdefaultquestionoptions', 'qtype_stack'), get_string('settingdefaultquestionoptions', 'qtype_stack'),
......
...@@ -345,6 +345,7 @@ class stack_cas_castext2_parsons extends stack_cas_castext2_block { ...@@ -345,6 +345,7 @@ class stack_cas_castext2_parsons extends stack_cas_castext2_block {
// NOTE! List ordered by length. For the trimming logic. // NOTE! List ordered by length. For the trimming logic.
$validunits = [ $validunits = [
'vmin', 'vmax', 'rem', 'em', 'ex', 'px', 'cm', 'mm', 'vmin', 'vmax', 'rem', 'em', 'ex', 'px', 'cm', 'mm',
'in', 'pt', 'pc', 'ch', 'vh', 'vw', '%', 'in', 'pt', 'pc', 'ch', 'vh', 'vw', '%',
]; ];
......
...@@ -33,6 +33,7 @@ class stack_algebraic_input extends stack_input { ...@@ -33,6 +33,7 @@ class stack_algebraic_input extends stack_input {
'checkvars' => 0, 'checkvars' => 0,
'validator' => false, 'validator' => false,
'feedback' => false, 'feedback' => false,
'monospace' => false,
]; ];
public function render(stack_input_state $state, $fieldname, $readonly, $tavalue) { public function render(stack_input_state $state, $fieldname, $readonly, $tavalue) {
...@@ -55,6 +56,9 @@ class stack_algebraic_input extends stack_input { ...@@ -55,6 +56,9 @@ class stack_algebraic_input extends stack_input {
if ($this->extraoptions['align'] === 'right') { if ($this->extraoptions['align'] === 'right') {
$attributes['class'] = 'algebraic-right'; $attributes['class'] = 'algebraic-right';
} }
if ($this->extraoptions['monospace']) {
$attributes['class'] .= ' input-monospace';
}
$value = $this->contents_to_maxima($state->contents); $value = $this->contents_to_maxima($state->contents);
if ($value == 'EMPTYANSWER') { if ($value == 'EMPTYANSWER') {
......
...@@ -178,6 +178,7 @@ abstract class stack_input { ...@@ -178,6 +178,7 @@ abstract class stack_input {
*/ */
protected function internal_construct() { protected function internal_construct() {
$options = $this->get_parameter('options'); $options = $this->get_parameter('options');
$setoptions = [];
if (trim($options ?? '') != '') { if (trim($options ?? '') != '') {
$options = explode(',', $options); $options = explode(',', $options);
foreach ($options as $option) { foreach ($options as $option) {
...@@ -188,17 +189,42 @@ abstract class stack_input { ...@@ -188,17 +189,42 @@ abstract class stack_input {
if ($arg === '') { if ($arg === '') {
// Extra options with no argument set a Boolean flag. // Extra options with no argument set a Boolean flag.
$this->extraoptions[$option] = true; $this->extraoptions[$option] = true;
} else if ($arg === 'false') {
$this->extraoptions[$option] = false;
} else if ($arg === 'true') {
$this->extraoptions[$option] = true;
} else { } else {
$this->extraoptions[$option] = $arg; $this->extraoptions[$option] = $arg;
} }
$setoptions[] = $option;
} else { } else {
$this->errors[] = stack_string('inputoptionunknown', $option); $this->errors[] = stack_string('inputoptionunknown', $option);
} }
} }
} }
$this->set_defaults($setoptions);
$this->validate_extra_options(); $this->validate_extra_options();
} }
/**
* Set extra options with defaults to the default if they have not been explicitly set.
*
* @param [] $setoptions - array of options that have been explicity set
* @return void
*/
protected function set_defaults($setoptions) {
$optionswithdefaults = ['monospace'];
foreach ($optionswithdefaults as $currentoption) {
if (!array_key_exists($currentoption, $this->extraoptions) || array_search($currentoption, $setoptions) !== false) {
// Option not available for this input type or has been explicitly set.
continue;
}
$functionname = "is_{$currentoption}";
$this->extraoptions[$currentoption] = stack_options::$functionname(get_class($this));
}
}
/** /**
* Validate the individual extra options. * Validate the individual extra options.
*/ */
...@@ -356,6 +382,12 @@ abstract class stack_input { ...@@ -356,6 +382,12 @@ abstract class stack_input {
} }
break; break;
case 'monospace':
if (!(is_bool($arg))) {
$this->errors[] = stack_string('numericalinputoptboolerr', ['opt' => $option, 'val' => $arg]);
}
break;
case 'nounits': case 'nounits':
if (!(is_bool($arg))) { if (!(is_bool($arg))) {
$this->errors[] = stack_string('numericalinputoptboolerr', ['opt' => $option, 'val' => $arg]); $this->errors[] = stack_string('numericalinputoptboolerr', ['opt' => $option, 'val' => $arg]);
...@@ -1140,7 +1172,7 @@ abstract class stack_input { ...@@ -1140,7 +1172,7 @@ abstract class stack_input {
protected function validation_display($answer, $lvars, $caslines, $additionalvars, $valid, $errors, protected function validation_display($answer, $lvars, $caslines, $additionalvars, $valid, $errors,
$castextprocessor, $inertdisplayform, $ilines) { $castextprocessor, $inertdisplayform, $ilines) {
$display = stack_maxima_format_casstring(htmlentities($this->contents_to_maxima($this->rawcontents))); $display = stack_maxima_format_casstring(htmlentities($this->contents_to_maxima($this->rawcontents), ENT_COMPAT));
if ($answer->is_correctly_evaluated()) { if ($answer->is_correctly_evaluated()) {
$display = '\[ ' . $inertdisplayform->get_display() . ' \]'; $display = '\[ ' . $inertdisplayform->get_display() . ' \]';
if ($this->get_parameter('showValidation', 1) == 3) { if ($this->get_parameter('showValidation', 1) == 3) {
...@@ -1599,7 +1631,7 @@ abstract class stack_input { ...@@ -1599,7 +1631,7 @@ abstract class stack_input {
* Returns the definition of this input as it should appear in an API response * Returns the definition of this input as it should appear in an API response
* @return array * @return array
*/ */
public abstract function render_api_data($tavalue); abstract public function render_api_data($tavalue);
/** /**
* Returns the solution in the format used by the api * Returns the solution in the format used by the api
......
...@@ -202,7 +202,7 @@ class stack_notes_input extends stack_input { ...@@ -202,7 +202,7 @@ class stack_notes_input extends stack_input {
$contents = $state->contents; $contents = $state->contents;
$render = ''; $render = '';
if (array_key_exists(0, $contents)) { if (array_key_exists(0, $contents)) {
$render .= html_writer::tag('p', htmlentities($contents[0])); $render .= html_writer::tag('p', htmlentities($contents[0], ENT_COMPAT));
} }
$render .= html_writer::tag('p', stack_string('studentValidation_notes'), ['class' => 'stackinputnotice']); $render .= html_writer::tag('p', stack_string('studentValidation_notes'), ['class' => 'stackinputnotice']);
if ($lang !== null && $lang !== '') { if ($lang !== null && $lang !== '') {
......
...@@ -49,6 +49,7 @@ class stack_numerical_input extends stack_input { ...@@ -49,6 +49,7 @@ class stack_numerical_input extends stack_input {
'maxsf' => false, 'maxsf' => false,
'align' => 'left', 'align' => 'left',
'validator' => false, 'validator' => false,
'monospace' => false,
]; ];
public function render(stack_input_state $state, $fieldname, $readonly, $tavalue) { public function render(stack_input_state $state, $fieldname, $readonly, $tavalue) {
...@@ -71,6 +72,9 @@ class stack_numerical_input extends stack_input { ...@@ -71,6 +72,9 @@ class stack_numerical_input extends stack_input {
if ($this->extraoptions['align'] === 'right') { if ($this->extraoptions['align'] === 'right') {
$attributes['class'] = 'numerical-right'; $attributes['class'] = 'numerical-right';
} }
if ($this->extraoptions['monospace']) {
$attributes['class'] .= ' input-monospace';
}
$value = $this->contents_to_maxima($state->contents); $value = $this->contents_to_maxima($state->contents);
if ($this->is_blank_response($state->contents)) { if ($this->is_blank_response($state->contents)) {
......
...@@ -42,6 +42,7 @@ class stack_units_input extends stack_input { ...@@ -42,6 +42,7 @@ class stack_units_input extends stack_input {
'consolidatesubscripts' => false, 'consolidatesubscripts' => false,
'validator' => false, 'validator' => false,
'feedback' => false, 'feedback' => false,
'monospace' => false,
]; ];
...@@ -65,6 +66,9 @@ class stack_units_input extends stack_input { ...@@ -65,6 +66,9 @@ class stack_units_input extends stack_input {
if ($this->extraoptions['align'] === 'right') { if ($this->extraoptions['align'] === 'right') {
$attributes['class'] = 'algebraic-units-right'; $attributes['class'] = 'algebraic-units-right';
} }
if ($this->extraoptions['monospace']) {
$attributes['class'] .= ' input-monospace';
}
if ($state->contents == 'EMPTYANSWER') { if ($state->contents == 'EMPTYANSWER') {
// Active empty choices don't result in a syntax hint again (with that option set). // Active empty choices don't result in a syntax hint again (with that option set).
......
...@@ -31,6 +31,7 @@ class stack_varmatrix_input extends stack_input { ...@@ -31,6 +31,7 @@ class stack_varmatrix_input extends stack_input {
'consolidatesubscripts' => false, 'consolidatesubscripts' => false,
'checkvars' => 0, 'checkvars' => 0,
'validator' => false, 'validator' => false,
'monospace' => false,
]; ];
protected function is_blank_response($contents) { protected function is_blank_response($contents) {
...@@ -67,6 +68,10 @@ class stack_varmatrix_input extends stack_input { ...@@ -67,6 +68,10 @@ class stack_varmatrix_input extends stack_input {
'style' => 'width: '.$size.'em', 'style' => 'width: '.$size.'em',
]; ];
if ($this->extraoptions['monospace']) {
$attributes['class'] .= ' input-monospace';
}
if ($this->is_blank_response($state->contents)) { if ($this->is_blank_response($state->contents)) {
$current = $this->maxima_to_raw_input($this->parameters['syntaxHint']); $current = $this->maxima_to_raw_input($this->parameters['syntaxHint']);
if ($this->parameters['syntaxAttribute'] == '1') { if ($this->parameters['syntaxAttribute'] == '1') {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>. // along with Stack. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/mathsoutputfilterbase.class.php'); require_once(__DIR__ . '/mathsoutputfilterbase.class.php');
......
...@@ -64,10 +64,6 @@ proofp(ex) := block( ...@@ -64,10 +64,6 @@ proofp(ex) := block(
return(false) return(false)
); );
s_test_case(proofp(proof(1,2,3)), true);
s_test_case(proofp(proof_iff(1,2)), true);
s_test_case(proofp(sin(x)), false);
proof_validatep(ex) := block( proof_validatep(ex) := block(
if atom(ex) then return(true), if atom(ex) then return(true),
if op(ex) = proof_opt then if op(ex) = proof_opt then
...@@ -84,19 +80,6 @@ proof_validatep(ex) := block( ...@@ -84,19 +80,6 @@ proof_validatep(ex) := block(
return(false) return(false)
); );
s_test_case(proof_validatep(proof(1,2,3)), true);
s_test_case(proof_validatep(proof(1,2,proof(4,5,6))), true);
s_test_case(proof_validatep(proof(1,2,proof_iff(4,5))), true);
/* proof_opt must have exactly one sub-proof. */
s_test_case(proof_validatep(proof(1,2,proof_opt(4,5))), false);
/* proof_iff must have exactly two sub-proofs. */
s_test_case(proof_validatep(proof(1,2,proof_iff(4))), false);
s_test_case(proof_validatep(proof(1,2,proof_iff(4,5,6))), false);
/* proof_ind must have exactly four sub-proofs. */
s_test_case(proof_validatep(proof_ind(1,proof(2,3),proof(4,5),6)), true);
s_test_case(proof_validatep(proof_ind(1,proof(2,3),proof(4,5))), false);
s_test_case(proof_validatep(proof(1,proof_opt(2),proof_iff(4,5))), true);
/* Is this a type of proof which can reorder its arguments? */ /* Is this a type of proof which can reorder its arguments? */
proof_commutep(ex):=block( proof_commutep(ex):=block(
if atom(ex) then false, if atom(ex) then false,
...@@ -108,9 +91,6 @@ proof_commutep(ex):=block( ...@@ -108,9 +91,6 @@ proof_commutep(ex):=block(
/* Takes a proof tree and flattens this to a list. */ /* Takes a proof tree and flattens this to a list. */
proof_flatten(ex) := apply(proof, flatten(ev(ex, map(lambda([ex2], ex2="["), proof_types)))); proof_flatten(ex) := apply(proof, flatten(ev(ex, map(lambda([ex2], ex2="["), proof_types))));
s_test_case(proof_flatten(proof_iff(proof(A,B),proof(C))), proof(A,B,C));
s_test_case(proof_flatten(proof_c(proof(A,proof(B,C)),proof(D))), proof(A,B,C,D));
/* /*
* Create a normalised proof tree. * Create a normalised proof tree.
* To establish equivalence of proof trees we compare the normalised form. * To establish equivalence of proof trees we compare the normalised form.
...@@ -129,13 +109,6 @@ proof_normal(ex) := block( ...@@ -129,13 +109,6 @@ proof_normal(ex) := block(
return(apply(op(ex), map(proof_normal, args(ex)))) return(apply(op(ex), map(proof_normal, args(ex))))
); );
s_test_case(proof_normal(proof_c(B,A,D,C)), proof_c(A,B,C,D));
s_test_case(proof_normal(proof_iff(B,A)), proof_iff(A,B));
s_test_case(proof_normal(proof_ind(D,C,B,A)), proof_ind(D,B,C,A));
s_test_case(proof_normal(proof_cases(D,C,B,A)), proof_cases(D,A,B,C));
s_test_case(proof_normal(proof_goal(D,C,B,A)), proof_goal(B,C,D,A));
s_test_case(proof_normal(proof_iff(proof_c(proof_opt(C),A), B)), proof_iff(proof_c(A,C),B));
/******************************************************************/ /******************************************************************/
/* */ /* */
/* Assessment functions */ /* Assessment functions */
...@@ -193,14 +166,6 @@ proof_remove_nullproof(ex):= block( ...@@ -193,14 +166,6 @@ proof_remove_nullproof(ex):= block(
apply(op(ex), map(proof_remove_nullproof, sublist(args(ex), lambda([ex2], not(is(ex2=nullproof)))))) apply(op(ex), map(proof_remove_nullproof, sublist(args(ex), lambda([ex2], not(is(ex2=nullproof))))))
); );
s_test_case(proof_alternatives(proof(A,B,C,D)), [proof(A,B,C,D)]);
s_test_case(proof_alternatives(proof_c(A,B)), [proof_c(A,B),proof_c(B,A)]);
s_test_case(proof_alternatives(proof_iff(A,B)), [proof_iff(A,B),proof_iff(B,A)]);
s_test_case(proof_alternatives(proof_ind(A,B,C,D)), [proof_ind(A,B,C,D),proof_ind(A,C,B,D)]);
s_test_case(proof_alternatives(proof_cases(A,B,C)), [proof_cases(A,B,C),proof_cases(A,C,B)]);
s_test_case(proof_alternatives(proof_goal(A,B,C)), [proof_goal(A,B,C),proof_goal(B,A,C)]);
s_test_case(proof_alternatives(proof_iff(proof(proof_opt(A), B),C)), [proof_iff(proof(A,B),C),proof_iff(proof(B),C),proof_iff(C,proof(A,B)),proof_iff(C,proof(B))]);
/******************************************************************/ /******************************************************************/
/* */ /* */
/* STACK Parson's block functions */ /* STACK Parson's block functions */
...@@ -290,7 +255,6 @@ proof_keys_int(ex, proof_steps):= block( ...@@ -290,7 +255,6 @@ proof_keys_int(ex, proof_steps):= block(
* Replace displayed LaTeX mathematics delimiters with inline. * Replace displayed LaTeX mathematics delimiters with inline.
*/ */
proof_inline_maths(st) := ssubst("\\)", "\\]", ssubst("\\(", "\\[", st)); proof_inline_maths(st) := ssubst("\\)", "\\]", ssubst("\\(", "\\[", st));
s_test_case(proof_inline_maths("\\[ 3 = 2^{\\frac{p}{q}}\\]"), "\\( 3 = 2^{\\frac{p}{q}}\\)");
/* /*
* Prune out any narrative from the proof steps: used to display a proof without narrative. * Prune out any narrative from the proof steps: used to display a proof without narrative.
...@@ -418,13 +382,6 @@ proof_damerau_levenstein_tidy(L) := block( ...@@ -418,13 +382,6 @@ proof_damerau_levenstein_tidy(L) := block(
return(append([first(L)], proof_damerau_levenstein_tidy(rest(L)))) return(append([first(L)], proof_damerau_levenstein_tidy(rest(L))))
); );
s_test_case(proof_damerau_levenstein([1,2,3],[1,2,3]), [0,[]]);
s_test_case(proof_damerau_levenstein([1,2,3],[1,2,3,4]), [1,[dl_ok(1),dl_ok(2),dl_ok(3),dl_add(4)]]);
s_test_case(proof_damerau_levenstein([1,3,4],[1,2,3,4]), [1,[dl_ok(1),dl_add(2),dl_ok(3),dl_ok(4)]]);
s_test_case(proof_damerau_levenstein([3,4],[1,2,3,4]), [2,[dl_add(1),dl_add(2),dl_ok(3),dl_ok(4)]]);
s_test_case(proof_damerau_levenstein([1,3,2,4],[1,2,3,4]), [1,[dl_ok(1),dl_swap(3,2),dl_swap_follow(2),dl_ok(4)]]);
/* /*
This function performs assessment of the student's proof. This function performs assessment of the student's proof.
sa is the student's proof sa is the student's proof
......
/* Author Chris Sangwin
University of Edinburgh
Copyright (C) 2023 Chris Sangwin
This program is free software: you can redistribute it or modify
it under the terms of the GNU General Public License version two.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/******************************************************************/
/* Functions for representing, typesetting and assessing proof. */
/* Mostly for use with Parsons problems. */
/* */
/* Test cases. */
/* */
/* Chris Sangwin, <C.J.Sangwin@ed.ac.uk> */
/* V1.0 May 2024 */
/* */
/******************************************************************/
s_test_case(proofp(proof(1,2,3)), true);
s_test_case(proofp(proof_iff(1,2)), true);
s_test_case(proofp(sin(x)), false);
s_test_case(proof_validatep(proof(1,2,3)), true);
s_test_case(proof_validatep(proof(1,2,proof(4,5,6))), true);
s_test_case(proof_validatep(proof(1,2,proof_iff(4,5))), true);
/* proof_opt must have exactly one sub-proof. */
s_test_case(proof_validatep(proof(1,2,proof_opt(4,5))), false);
/* proof_iff must have exactly two sub-proofs. */
s_test_case(proof_validatep(proof(1,2,proof_iff(4))), false);
s_test_case(proof_validatep(proof(1,2,proof_iff(4,5,6))), false);
/* proof_ind must have exactly four sub-proofs. */
s_test_case(proof_validatep(proof_ind(1,proof(2,3),proof(4,5),6)), true);
s_test_case(proof_validatep(proof_ind(1,proof(2,3),proof(4,5))), false);
s_test_case(proof_validatep(proof(1,proof_opt(2),proof_iff(4,5))), true);
s_test_case(proof_flatten(proof_iff(proof(A,B),proof(C))), proof(A,B,C));
s_test_case(proof_flatten(proof_c(proof(A,proof(B,C)),proof(D))), proof(A,B,C,D));
s_test_case(proof_normal(proof_c(B,A,D,C)), proof_c(A,B,C,D));
s_test_case(proof_normal(proof_iff(B,A)), proof_iff(A,B));
s_test_case(proof_normal(proof_ind(D,C,B,A)), proof_ind(D,B,C,A));
s_test_case(proof_normal(proof_cases(D,C,B,A)), proof_cases(D,A,B,C));
s_test_case(proof_normal(proof_goal(D,C,B,A)), proof_goal(B,C,D,A));
s_test_case(proof_normal(proof_iff(proof_c(proof_opt(C),A), B)), proof_iff(proof_c(A,C),B));
s_test_case(proof_alternatives(proof(A,B,C,D)), [proof(A,B,C,D)]);
s_test_case(proof_alternatives(proof_c(A,B)), [proof_c(A,B),proof_c(B,A)]);
s_test_case(proof_alternatives(proof_iff(A,B)), [proof_iff(A,B),proof_iff(B,A)]);
s_test_case(proof_alternatives(proof_ind(A,B,C,D)), [proof_ind(A,B,C,D),proof_ind(A,C,B,D)]);
s_test_case(proof_alternatives(proof_cases(A,B,C)), [proof_cases(A,B,C),proof_cases(A,C,B)]);
s_test_case(proof_alternatives(proof_goal(A,B,C)), [proof_goal(A,B,C),proof_goal(B,A,C)]);
s_test_case(proof_alternatives(proof_iff(proof(proof_opt(A), B),C)), [proof_iff(proof(A,B),C),proof_iff(proof(B),C),proof_iff(C,proof(A,B)),proof_iff(C,proof(B))]);
s_test_case(proof_parsons_interpret("{\"used\":[\"0\",\"3\",\"5\"],\"available\":[\"1\",\"2\",\"4\",\"6\",\"7\"]}"), proof("0","3","5"));
s_test_case(proof_inline_maths("\\[ 3 = 2^{\\frac{p}{q}}\\]"), "\\( 3 = 2^{\\frac{p}{q}}\\)");
/******************************************************************/
s_test_case(proof_damerau_levenstein([1,2,3],[1,2,3]), [0,[]]);
s_test_case(proof_damerau_levenstein([1,2,3],[1,2,3,4]), [1,[dl_ok(1),dl_ok(2),dl_ok(3),dl_add(4)]]);
s_test_case(proof_damerau_levenstein([1,3,4],[1,2,3,4]), [1,[dl_ok(1),dl_add(2),dl_ok(3),dl_ok(4)]]);
s_test_case(proof_damerau_levenstein([3,4],[1,2,3,4]), [2,[dl_add(1),dl_add(2),dl_ok(3),dl_ok(4)]]);
s_test_case(proof_damerau_levenstein([1,3,2,4],[1,2,3,4]), [1,[dl_ok(1),dl_swap(3,2),dl_swap_follow(2),dl_ok(4)]]);
...@@ -29,8 +29,7 @@ validate_underscore(ex) := if is(sposition("_", string(ex)) = false) then "" ...@@ -29,8 +29,7 @@ validate_underscore(ex) := if is(sposition("_", string(ex)) = false) then ""
else "Underscore characters are not permitted in this input."; else "Underscore characters are not permitted in this input.";
/* Add in unit-test cases using STACK's s_test_case function. At least two please! */ /* Add in unit-test cases using STACK's s_test_case function. At least two please! */
s_test_case(validate_underscore(1+a1), ""); /* Place test cases in validators_test.mac */
s_test_case(validate_underscore(1+a_1), "Underscore characters are not permitted in this input.");
/* The student may not use a user-defined function, or arrays, anywhere in their input. */ /* The student may not use a user-defined function, or arrays, anywhere in their input. */
validate_nofunctions(ex):= block([op1,opp], validate_nofunctions(ex):= block([op1,opp],
...@@ -41,24 +40,11 @@ validate_nofunctions(ex):= block([op1,opp], ...@@ -41,24 +40,11 @@ validate_nofunctions(ex):= block([op1,opp],
apply(sconcat, map(validate_nofunctions, args(ex))) apply(sconcat, map(validate_nofunctions, args(ex)))
); );
s_test_case(validate_nofunctions(1+a1), "");
s_test_case(validate_nofunctions(sin(n*x)), "");
s_test_case(validate_nofunctions(-b#pm#sqrt(b^2-4*a*c)), "");
s_test_case(validate_nofunctions(x(2)), "User-defined functions are not permitted in this input. In your answer \\(x\\) appears to be used as a function. ");
s_test_case(validate_nofunctions(3*x(t)^2), "User-defined functions are not permitted in this input. In your answer \\(x\\) appears to be used as a function. ");
s_test_case(validate_nofunctions(1+f(x+1)), "User-defined functions are not permitted in this input. In your answer \\(f\\) appears to be used as a function. ");
s_test_case(validate_nofunctions(x(2)*y(3)), "User-defined functions are not permitted in this input. In your answer \\(x\\) appears to be used as a function. User-defined functions are not permitted in this input. In your answer \\(y\\) appears to be used as a function. ");
/* The student may only use single-character variable names in their answer. */ /* The student may only use single-character variable names in their answer. */
/* This is intended for use when Insert Stars is turned off, but we still want to indicate to students that they may have forgotten a star */ /* This is intended for use when Insert Stars is turned off, but we still want to indicate to students that they may have forgotten a star */
validate_all_one_letter_variables(ex) := if not(is(ev(lmax(map(lambda([ex2],slength(string(ex2))),listofvars(ex))),simp)>1)) then "" validate_all_one_letter_variables(ex) := if not(is(ev(lmax(map(lambda([ex2],slength(string(ex2))),listofvars(ex))),simp)>1)) then ""
else "Only single-character variable names are permitted in this input. Perhaps you forgot to use an asterisk (*) somewhere, or perhaps you used a Greek letter."; else "Only single-character variable names are permitted in this input. Perhaps you forgot to use an asterisk (*) somewhere, or perhaps you used a Greek letter.";
s_test_case(validate_all_one_letter_variables(1), "");
s_test_case(validate_all_one_letter_variables((A*x+B)/(x^2+1) + C/x), "");
s_test_case(validate_all_one_letter_variables((Ax+B)/(x^2+1) + C/x), "Only single-character variable names are permitted in this input. Perhaps you forgot to use an asterisk (*) somewhere, or perhaps you used a Greek letter.");
s_test_case(validate_all_one_letter_variables((theta*x+B)/(x^2+1) + C/x), "Only single-character variable names are permitted in this input. Perhaps you forgot to use an asterisk (*) somewhere, or perhaps you used a Greek letter.");
/* This provides more detailed feedback for students who try to enter fully closed or open intervals using [] or () instead of cc(a,b) or oo(a,b). */ /* This provides more detailed feedback for students who try to enter fully closed or open intervals using [] or () instead of cc(a,b) or oo(a,b). */
/* It is intended for early courses where students might be new to using this written notation and STACK. */ /* It is intended for early courses where students might be new to using this written notation and STACK. */
/* This does not work well with "Check type of response" turned on, and provides slightly awkward feedback when students take a union of multiple intervals with incorrect syntax. */ /* This does not work well with "Check type of response" turned on, and provides slightly awkward feedback when students take a union of multiple intervals with incorrect syntax. */
...@@ -68,11 +54,3 @@ validate_interval_syntax(ex):= block( ...@@ -68,11 +54,3 @@ validate_interval_syntax(ex):= block(
else if is(safe_op(ex)="%union") then apply(sconcat, map(validate_interval_syntax, args(ex))) else if is(safe_op(ex)="%union") then apply(sconcat, map(validate_interval_syntax, args(ex)))
else return("") else return("")
); );
s_test_case(validate_interval_syntax(cc(1,2)), "");
s_test_case(validate_interval_syntax(oo(1,2)), "");
s_test_case(validate_interval_syntax(%union(cc(1,2),oo(2,3))), "");
s_test_case(validate_interval_syntax([1,2]), "To give a closed interval, use <code>cc(1,2)</code>, not <code>[1,2]</code>. ");
s_test_case(validate_interval_syntax(ntuple(1,2)), "To give an open interval, use <code>oo(1,2)</code>, not <code>(1,2)</code>. ");
s_test_case(validate_interval_syntax(%union([1,2],ntuple(2,3))), "To give a closed interval, use <code>cc(1,2)</code>, not <code>[1,2]</code>. To give an open interval, use <code>oo(2,3)</code>, not <code>(2,3)</code>. ");
s_test_case(validate_interval_syntax(%union([1,2],%union(oo(1,2),[2,3]))), "To give a closed interval, use <code>cc(1,2)</code>, not <code>[1,2]</code>. To give a closed interval, use <code>cc(2,3)</code>, not <code>[2,3]</code>. ");
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment