Skip to content

Commit 39cbb80

Browse files
committed
Merge branch 'dev' of https://github.com/maths/moodle-qtype_stack into catform-namespace
2 parents 0a43c36 + 80f2324 commit 39cbb80

57 files changed

Lines changed: 1226 additions & 501 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/moodle-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ jobs:
3636
moodle-branch: 'main'
3737
database: 'pgsql'
3838
maxima: 'GCL'
39-
moodle-app: true
39+
moodle-app: false
4040
- php: '8.4'
4141
moodle-branch: 'MOODLE_502_STABLE'
4242
database: 'pgsql'
4343
maxima: 'SBCL'
44-
moodle-app: true
44+
moodle-app: false
4545
- php: '8.2'
4646
moodle-branch: 'MOODLE_500_STABLE'
4747
database: 'pgsql'

api/controller/TestController.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,15 +188,13 @@ public function qtype_stack_test_question($question, $testcases, $seed = null) {
188188
// We could just check if score === 1 at this point but by creating
189189
// a test and running it we get the full outcomes in the same
190190
// format as above.
191-
$answernotes = $result->get_answernotes();
192-
$answernote = [end($answernotes)];
193191
$qtest->add_expected_result($prtname, new \stack_potentialresponse_tree_state(
194192
1,
195193
true,
196194
1,
197195
0,
198196
'',
199-
$answernote
197+
$result->get_answernotes_testcase()
200198
));
201199
}
202200
$results = $qtest->process_results($question, $response);

db/install.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
<FIELD NAME="prtname" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false" COMMENT="The name of the PRT for which these are the expected outcomes."/>
176176
<FIELD NAME="expectedscore" TYPE="number" LENGTH="12" NOTNULL="false" SEQUENCE="false" DECIMALS="7" COMMENT="The expected score."/>
177177
<FIELD NAME="expectedpenalty" TYPE="number" LENGTH="12" NOTNULL="false" SEQUENCE="false" DECIMALS="7" COMMENT="The expected penalty."/>
178-
<FIELD NAME="expectedanswernote" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The expected answer note."/>
178+
<FIELD NAME="expectedanswernote" TYPE="char" LENGTH="1023" NOTNULL="true" SEQUENCE="false" COMMENT="The expected answer note."/>
179179
</FIELDS>
180180
<KEYS>
181181
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>

db/upgrade.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,16 @@ function xmldb_qtype_stack_upgrade($oldversion) {
10351035
// STACK savepoint reached.
10361036
upgrade_plugin_savepoint(true, 2025042500, 'qtype', 'stack');
10371037
}
1038+
1039+
if ($oldversion < 2026042402) {
1040+
$table = new xmldb_table('qtype_stack_qtest_expected');
1041+
$field = new xmldb_field('expectedanswernote', XMLDB_TYPE_CHAR, '1023', null, XMLDB_NOTNULL, null, null, 'expectedpenalty');
1042+
1043+
$dbman->change_field_type($table, $field);
1044+
1045+
// STACK savepoint reached.
1046+
upgrade_plugin_savepoint(true, 2026042402, 'qtype', 'stack');
1047+
}
10381048
// Add new upgrade blocks just above here.
10391049

10401050
// Check the version of the Maxima library code that comes with this version

doc/en/AbInitio/Authoring_quick_start_1.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ There are lots of fields, but only a few are compulsory:
2222

2323
1. The "question name",
2424
2. The "question text", which is shown to the student,
25-
3. The teacher's "model answer" `ta` using a variable in the "question variables",
25+
3. The teacher's "model answer" `ta1` using a variable in the "question variables",
2626
4. A test of "correctness" (defaults to Algebraic equivalence).
2727

2828
By default a new question automatically has one [input](../Authoring/Inputs/index.md), and one algorithm to test correctness of the answer.
@@ -39,10 +39,10 @@ You must give the question a name, for example `question1`.
3939

4040
There should be text in question variables by default.
4141

42-
ta:?;
42+
ta1:?;
4343
We should replace `?` with the model answer to the question. In this case, this wil be the derivative of \((x-1)^3\), which is \(3(x-1)^2\). So we should replace `?` with `3*(x-1)^2`.
4444

45-
ta:3*(x-1)^2;
45+
ta1:3*(x-1)^2;
4646

4747

4848
### Question text ###
@@ -110,7 +110,7 @@ Each branch can then
110110
We can leave the default settings for a minimal question, which is the following:
111111

112112
1. Specify the variable `ans1` in the `SAns` setting.
113-
2. Specify the correct answer in the `TAns` setting: `ta`.
113+
2. Specify the correct answer in the `TAns` setting: `ta1`.
114114
3. `AlgEquiv` in the _Answer test_ drop-down menu.
115115

116116
## Saving the question
@@ -123,7 +123,7 @@ To recap, we have
123123

124124
1. The "question name",
125125
2. The "question text",
126-
3. The teacher's "model answer", (`ta` in the question variables).
126+
3. The teacher's "model answer", (`ta1` in the question variables).
127127
4. A test of "correctness" (set to Algebraic equivalence).
128128

129129
Next we should try out our question by pressing the `Preview` link at the bottom of the page.

doc/en/AbInitio/Authoring_quick_start_2.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ We make more use of the question text field. For instance, we can start to use t
2525
Add the following to the question variables
2626

2727
```
28-
exp: 3*(x-1)^(-4);
29-
ta: int(exp,x)+c;
28+
ex1: 3*(x-1)^(-4);
29+
ta1: int(ex1,x)+c;
3030
```
3131

3232
The coding in question variables is based on Maxima, which has an unusual syntax for assignment. In particular, the colon `:` is used to assign a value to a variable. So to assign the value of `5` to `a1`, we use the syntax `a1:5`. In general, it is good practice to give variables multi-character names, as single-character names are meant for student input. This is because multi-character variables the author defined cannot by default be input by students. Additionally, adding semicolons to the end of each line is optional, but good practice.
@@ -35,21 +35,21 @@ Notice we are using the CAS to determine the model answer by calling the `int()`
3535

3636
Now it will be a lot faster to fill out the rest of the question. Add the following to the question text:
3737

38-
<p>Find \(\int{@exp@} \mathrm{d}x\)</p>
38+
<p>Find \(\int{@ex1@} \mathrm{d}x\)</p>
3939
<p>[[input:ans1]] [[validation:ans1]]</p>
4040

41-
Notice that we have defined a local variable `exp`, and used the value of this in the Question text. There is a difference between mathematics enclosed between `\(..\)` symbols and `{@..@}` symbols. All the text-based fields in the question, including feedback, are [CAS text](../Authoring/CASText.md). This is HTML into which mathematics can be inserted. LaTeX is placed between `\(..\)`s, and CAS expressions (including your variables) between matching `{@..@}` symbols. The CAS expressions are evaluated in the context of the question variables and displayed as LaTeX.
41+
Notice that we have defined a local variable `ex1`, and used the value of this in the Question text. There is a difference between mathematics enclosed between `\(..\)` symbols and `{@..@}` symbols. All the text-based fields in the question, including feedback, are [CAS text](../Authoring/CASText.md). This is HTML into which mathematics can be inserted. LaTeX is placed between `\(..\)`s, and CAS expressions (including your variables) between matching `{@..@}` symbols. The CAS expressions are evaluated in the context of the question variables and displayed as LaTeX.
4242

43-
Since we have used `{@exp@}` here, the user will not see a \(exp\) on the screen when the question is instantiated, but the _displayed value_ of `exp`: \(\frac{3}{(x-1)^{-4}}\)
43+
Since we have used `{@ex1@}` here, the user will not see a \(ex1\) on the screen when the question is instantiated, but the _displayed value_ of `ex1`: \(\frac{3}{(x-1)^{-4}}\)
4444

45-
In the input `ans1`, confirm the default `model answer` is the variable `ta`.
45+
In the input `ans1`, confirm the default `model answer` is the variable `ta1`.
4646

47-
In the potential response tree, confirm the default `Answer test` is `AlgEquiv`, `SAns` is `ans1` and `TAns` is `ta`.
47+
In the potential response tree, confirm the default `Answer test` is `AlgEquiv`, `SAns` is `ans1` and `TAns` is `ta1`.
4848

4949
It is good practice to use question variables often, as they save time and let you change properties of the question easily in the future.
5050

5151
# Next step #
5252

5353
You should now be able to use question variables in STACK.
5454

55-
##### The next part of the authoring quick start guide looks at [improving feedback](Authoring_quick_start_3.md).
55+
##### The next part of the authoring quick start guide looks at [improving feedback](Authoring_quick_start_3.md).

doc/en/AbInitio/Authoring_quick_start_3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This part of the Authoring Quick Start Guide deals with improving feedback. The
99
<iframe width="560" height="315" src="https://www.youtube.com/embed/l6QAMmUA5Pk" frameborder="0" allowfullscreen></iframe>
1010
## Introduction
1111

12-
In the last part we started working with question variables. Specifically, we defined the variable `exp` for the expression to be integrated, and `ta` for the teacher's answer. Then we asked the student to find \(\int 3(x-1)^{-4}dx\).
12+
In the last part we started working with question variables. Specifically, we defined the variable `ex1` for the expression to be integrated, and `ta1` for the teacher's answer. Then we asked the student to find \(\int 3(x-1)^{-4}dx\).
1313

1414
Try previewing this question and typing in `-1*(x-1)^(-3)+c`. The system should accept this as correct. Next type in `-1*(x-1)^(-3)+C`. This will be compared to the teacher's answer `-1*(x-1)^(-3)+c` by algebraic equivalence (recall we specified `AlgEquiv` in the potential response tree), and will not be accepted as equivalent. The reason is that `c` and `C` are different. A reasonable teacher will probably not care which letter is used for the constant of integration. Let us fix this problem.
1515

doc/en/AbInitio/Authoring_quick_start_4.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ In the last part, we worked with a problem about integrating \(3(x-1)^{-4}\) wit
1414
Let's take a look again at the question variables we declared:
1515

1616
```
17-
exp: 3*(x-1)^(-4);
18-
ta: int(exp,x)+c;
17+
ex1: 3*(x-1)^(-4);
18+
ta1: int(ex1,x)+c;
1919
```
2020

21-
We defined two local variables `exp` and `ta`, and used these values in other places such as the question text, input and potential response tree.
21+
We defined two local variables `ex1` and `ta1`, and used these values in other places such as the question text, input and potential response tree.
2222

2323
We are now in a position to generate a random question. To do this, modify the [question variables](../Authoring/Variables.md#Question_variables) to be
2424

2525
```
2626
a1 : 1+rand(6);
2727
a2 : 1+rand(6);
2828
nn : 1+rand(4);
29-
exp : a1*(x-a2)^(-nn);
30-
ta: int(exp, x)+c;
29+
ex1 : a1*(x-a2)^(-nn);
30+
ta1: int(ex1, x)+c;
3131
```
3232

33-
In this new question we are asking the student to find the anti-derivative of a question with a definite form \( a_1(x-a_2)^{-nn} \). `a1`, `a2` and `nn` are all variables which are assigned random positive integers. These are then used to define the variable `exp`, used in the question itself. We also have the CAS integrate the expression `exp` and store the result in the variable `ta`. It is good practice to use variables names with more than one character as single-character variables, like `x`, are meant for student input.
33+
In this new question we are asking the student to find the anti-derivative of a question with a definite form \( a_1(x-a_2)^{-nn} \). `a1`, `a2` and `nn` are all variables which are assigned random positive integers. These are then used to define the variable `ex1`, used in the question itself. We also have the CAS integrate the expression `ex1` and store the result in the variable `ta1`. It is good practice to use variables names with more than one character as single-character variables, like `x`, are meant for student input.
3434

3535
Remember that when generating random questions in STACK we talk about _random numbers_ when we really mean _pseudo-random numbers_. To keep track of which random numbers are generated for each user, there is a special `rand` command in STACK, which you should use instead of [Maxima](../CAS/Maxima_background.md)'s random command. The `rand` command is a general "random thing" generator, see the page on [random generation](../CAS/Random.md) for full details. `rand` can be used to generate random numbers and also to make selections from a list. `rand(n)` will select a random integer from 0 up to, **and not including**, `n`. So `rand(3)` will select a random number from the list `[0,1,2]` .
3636

@@ -40,7 +40,7 @@ Now that as our question contains random numbers, we need to record the actual q
4040
Fill the question note in as
4141

4242
```
43-
\[ \int {@exp@} \mathrm{d}x = {@ta@}.\]
43+
\[ \int {@ex1@} \mathrm{d}x = {@ta1@}.\]
4444
```
4545

4646
Two question variants are considered to be the same if and only if the question note is the same. It is the teacher's responsibility to create sensible notes.

doc/en/AbInitio/Authoring_quick_start_5.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ In the last couple of parts, we have been working with a simple integration ques
1313
a1 : 1+rand(6);
1414
a2 : 1+rand(6);
1515
nn : 2+rand(4);
16-
exp : a1*(x-a2)^(-nn);
17-
ta: int(exp, x)+c;
16+
ex1 : a1*(x-a2)^(-nn);
17+
ta1: int(ex1, x)+c;
1818
```
1919

2020
Testing questions is time consuming and tedious, but important to ensure questions work. To help with this process, STACK enables teachers to define "question tests". The principle is the same as "unit testing" in software engineering.
@@ -30,22 +30,22 @@ The penalty is a number deducted from the total mark for each incorrect attempt
3030
Fill in the following information for your first test case:
3131

3232
```
33-
ans1 = ta
33+
ans1 = ta1
3434
score = 1
3535
penalty = 0
3636
answernote = prt1-2-T
3737
```
3838

3939
I.e., if the student puts in the model answer they should pass the first node (checks if they have integrated correctly) and pass the second node (tests that their answer is factored) and end up with a score of 1 and no penalty.
4040

41-
Note that the input is evaluated before the test is conducted. Students are not allowed to enter the variable `ta` because it is a teacher-defined variable, however the evaluated form, fx. `-1*(x-1)^(-3)+c`, is an allowed input. For each test case, you can see the un-evaluated input under `Test input`, and the actual input tested under `Value entered`.
41+
Note that the input is evaluated before the test is conducted. Students are not allowed to enter the variable `ta1` because it is a teacher-defined variable, however the evaluated form, fx. `-1*(x-1)^(-3)+c`, is an allowed input. For each test case, you can see the un-evaluated input under `Test input`, and the actual input tested under `Value entered`.
4242

4343
You can run the test on all deployed versions by clicking on `Run all tests on all deployed variants` .
4444

4545
You can add as many tests as you think is needed, and it is usually a sensible idea to add one for each case you anticipate. Add in another test case for
4646

4747
```
48-
ans1 = int(exp,x)
48+
ans1 = int(ex1,x)
4949
score = 0
5050
penalty = 0.1
5151
answernote = prt1-1-F

doc/en/Authoring/Answer_Tests/Equivalence.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This list is in approximate order of the size of the equivalence classes from mo
2020

2121
### AlgEquiv {#AlgEquiv}
2222

23-
This is the most commonly used test. The pseudo code
23+
This is the most commonly used test. The pseudo code (for mathematical _expressions_ at least) is
2424

2525
If
2626
simplify(ex1-ex2) = 0
@@ -38,7 +38,24 @@ Note: exactly what this answer test does depends on what objects are given to it
3838

3939
For sets, the CAS tries to write the expression in a canonical form. It then compares the string representations these forms to remove duplicate elements and compare sets. This is subtly different from trying to simplify the difference of two expressions to zero. For example, imagine we have \(\{(x-a)^{6000}\}\) and \(\{(a-x)^{6000}\}\). One canonical form is to expand out both sides. While this work in principal, in practice this is much too slow for assessment.
4040

41-
Currently we do check multiplicity of roots, so that \( (x-2)^2=0\) and \( x=2\) are not considered to be equivalent. Similarly \(a^3b^3=0\) is not \(a=0 \text{ or } b=0\). This is a long-standing issue and we would need a separate test to ignore multiplicity of roots.
41+
Equations and expressions are very different. Two equations are equivalent if they have the same roots with the same multiplicity of roots. For example, \((x-2)^2=0\) and \(x=2\) are not considered to be equivalent. Similarly \(a^3b^3=0\) is not \(a=0 \text{ or } b=0\). To establish equivalence of equations we use the pseudo code
42+
43+
If
44+
numberp( (lhs(ex1)-rhs(ex1))/(lhs(ex2)-rhs(ex2)) )
45+
then
46+
true
47+
else
48+
false.
49+
50+
That is to say, we turn the equation `ex1` into an expression `x1:simplify(lhs(ex1)-rhs(ex1))` and the equation `ex2` into an expression `x2:simplify(lhs(ex2)-rhs(ex2))`, and then simplify the ratio of the two numerators of `x1` and `x2`:, i.e. `num(x1)/num(x2)` (with some edge case detection of course). If this is a number then we have cancelled algebraic factors precicely, and roots match (with multiplicity). Notice we do not actually "solve" the equations during this process, thereby side-stepping the decidability question. (We would need a separate test to ignore multiplicity of roots.)
51+
52+
In calculating the ratio `x1/x2` we use Maxima's simplifer, which may be modified by commands like `assume`. However, since we do not actually solve the equations we do not use `assume` to reject solutions themselves. For example, in the following situation
53+
54+
assume(x>0);
55+
eq1: a = (c*x^3)/x;
56+
eq2: a*x = c*x^3;
57+
58+
`eq1` does not have \(0\) as a solution, whereas `eq2` does have \(0\) as a solution, the ratio simplifes to `1/x` indicating that `eq2` has a zero solution which is not balanced by a corresponding solution in `eq`. Since the equations are never solved, the `assume(x>0)` never comes into play here. (We would need a separate test to use `assume` statements and reject roots.)
4259

4360
Inequalities are turned into sets of real numbers they represent. When this is done it is indicated by the answer note `ATInequality_solver.` If you want `a>1` to be _not_ the same as `x>1` then you need to test in a more syntactic way, not using algebraic equivalence.
4461

0 commit comments

Comments
 (0)