Python answer tests and custom checkers
Use a Python checker when the task accepts a mathematical property rather than one displayed answer. Install CTM Assessment and use filters: [ctm-assessment]; the shared checker library supplies Python and SymPy. Checker source is trusted author code and is public in the static page. Keep credentials and private data out of it.
Choose the mathematical test
Use the XML’s ordinary answer tests for equivalence, written form and numeric tolerance. For matrix properties, PythonChecker nodes can select these predicates from ctm_checkers.matrix:
| Name | Options | What the node checks |
|---|---|---|
basis |
space: nullspace, columnspace or rowspace |
A basis for the space of the matrix in <tans> |
matrix_property |
property: integer_entries, determinant or identity |
The indicated property equals <tans> |
is_hermitian |
None | Hermitian symmetry equals the boolean <tans> |
is_positive_definite |
None | Positivity of the Hermitian part equals <tans> |
is_hpd |
None | Hermitian symmetry and positive definiteness together |
For example, a basis node uses:
<answertest>PythonChecker</answertest>
<sans>ans</sans>
<tans>A</tans>
<testoptions>{"checker":"basis","options":{"space":"nullspace"}}</testoptions>A is the defining matrix, not a displayed basis. The basis predicate accepts any independent family that spans the required space. It reports green, red and dependent-vector assessments without preferring an ordering. Library predicates keep their exact rational-domain and matrix-size restrictions. A question using them needs no checker filename or block checker:.
Symmetric positive definite matrices
For a real matrix, is_hpd requires symmetry and positive definiteness. For a complex matrix, it requires Hermitian symmetry and positive definiteness. The library checks every leading principal minor; a positive determinant alone is insufficient. Use integers or exact fractions for real and imaginary parts and %i for the imaginary unit.
Give any real symmetric or complex Hermitian positive definite \(2\times2\) matrix. Use integers or exact fractions for real and imaginary parts; enter %i for \(i\).
Show source
```{.stack-question #docs-spd-matrix}
#| label: docs-spd-matrix
#| file: ../questions/symmetric-positive-definite.xml
#| question: A positive definite matrix
#| review: true
```Show STACK source
<?xml version="1.0" encoding="UTF-8"?>
<!-- Project example, CC BY-SA 4.0. Uses matrix-checkers.py selected in Quarto. -->
<quiz>
<question type="stack">
<name>
<text>A positive definite matrix</text>
</name>
<questionvariables>
<text />
</questionvariables>
<questionsimplify>1</questionsimplify>
<questiontext format="html">
<text><p>Give any real symmetric or complex Hermitian positive definite \(2\times2\) matrix. Use integers or exact fractions for real and imaginary parts; enter %i for \(i\).</p><p>[[input:ansm]] [[validation:ansm]] [[feedback:result]]</p></text>
</questiontext>
<generalfeedback format="html">
<text><p>The checker uses Sylvester’s criterion to test positivity with exact arithmetic. Full credit requires both Hermitian symmetry and positivity. For real entries, Hermitian symmetry means ordinary symmetry.</p></text>
</generalfeedback>
<defaultgrade>1</defaultgrade>
<penalty>0.1</penalty>
<input>
<name>ansm</name>
<type>matrix</type>
<tans>matrix([1,0],[0,1])</tans>
<strictsyntax>1</strictsyntax>
<insertstars>0</insertstars>
<forbidfloat>1</forbidfloat>
<checkanswertype>1</checkanswertype>
<mustverify>1</mustverify>
<showvalidation>1</showvalidation>
</input>
<prt>
<name>result</name>
<value>1</value>
<autosimplify>1</autosimplify>
<node>
<name>0</name>
<answertest>PythonChecker</answertest>
<sans>ansm</sans>
<tans>true</tans>
<testoptions>{"checker": "is_hpd"}</testoptions>
<truescoremode>=</truescoremode>
<truescore>1</truescore>
<truenextnode>-1</truenextnode>
<trueanswernote>spd-T</trueanswernote>
<falsescoremode>=</falsescoremode>
<falsescore>0</falsescore>
<falsenextnode>-1</falsenextnode>
<falseanswernote>spd-F</falseanswernote>
</node>
</prt>
<qtest>
<testcase>1</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,0],[0,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>spd-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>2</testcase>
<testinput>
<name>ansm</name>
<value>matrix([2,1],[1,2])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>spd-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>3</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1/2,1/4],[1/4,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>spd-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>4</testcase>
<testinput>
<name>ansm</name>
<value>matrix([-1,0],[0,-1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>5</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,1],[1,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>6</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,2],[0,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>7</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,2],[2,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>8</testcase>
<testinput>
<name>ansm</name>
<value>matrix([2,1],[0,2])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>9</testcase>
<testinput>
<name>ansm</name>
<value>matrix([0,1],[0,0])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>10</testcase>
<testinput>
<name>ansm</name>
<value>matrix([2,%i],[-%i,2])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>spd-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>11</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,%i],[%i,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>12</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,2*%i],[-2*%i,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>13</testcase>
<testinput>
<name>ansm</name>
<value>matrix([0,%i],[0,0])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>spd-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>14</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1/2,%i/4],[-%i/4,1])</value>
</testinput>
<expected>
<name>result</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>spd-T</expectedanswernote>
</expected>
</qtest>
</question>
</quiz>
Separate is_hermitian and is_positive_definite response trees can award separate marks. A nonsymmetric matrix can pass the latter because it checks the Hermitian part; is_hpd requires both conditions. See the separate-mark example.
Try the same matrix with separate marks for symmetry and positivity:
Give any real symmetric or complex Hermitian positive definite \(2\times2\) matrix. Use integers or exact fractions for real and imaginary parts; enter %i for \(i\).
Half the marks are for Hermitian symmetry and half for positivity of the quadratic form.
Hermitian symmetry:
Positivity:
Show source
```{.stack-question #docs-spd-separate}
#| label: docs-spd-separate
#| file: ../questions/symmetric-positive-definite-prt.xml
#| question: Hermitian symmetry and positive definiteness separately
#| review: true
```Show STACK source
<?xml version="1.0" encoding="UTF-8"?>
<!-- Project example, CC BY-SA 4.0. Uses matrix-checkers.py selected in Quarto. -->
<quiz>
<question type="stack">
<name>
<text>Hermitian symmetry and positive definiteness separately</text>
</name>
<questionvariables>
<text />
</questionvariables>
<questionsimplify>1</questionsimplify>
<questiontext format="html">
<text><p>Give any real symmetric or complex Hermitian positive definite \(2\times2\) matrix. Use integers or exact fractions for real and imaginary parts; enter %i for \(i\).</p><p>Half the marks are for Hermitian symmetry and half for positivity of the quadratic form.</p><p>[[input:ansm]] [[validation:ansm]]</p><p>Hermitian symmetry: [[feedback:symmetry]]</p><p>Positivity: [[feedback:positivity]]</p></text>
</questiontext>
<generalfeedback format="html">
<text><p>The checker uses Sylvester’s criterion to test positivity with exact arithmetic. Full credit requires both Hermitian symmetry and positivity. For real entries, Hermitian symmetry means ordinary symmetry.</p></text>
</generalfeedback>
<defaultgrade>1</defaultgrade>
<penalty>0.1</penalty>
<input>
<name>ansm</name>
<type>matrix</type>
<tans>matrix([1,0],[0,1])</tans>
<strictsyntax>1</strictsyntax>
<insertstars>0</insertstars>
<forbidfloat>1</forbidfloat>
<checkanswertype>1</checkanswertype>
<mustverify>1</mustverify>
<showvalidation>1</showvalidation>
</input>
<prt>
<name>symmetry</name>
<value>0.5</value>
<autosimplify>1</autosimplify>
<node>
<truefeedback format="html">
<text><p>The matrix is Hermitian.</p></text>
</truefeedback>
<falsefeedback format="html">
<text />
</falsefeedback>
<name>0</name>
<answertest>PythonChecker</answertest>
<sans>ansm</sans>
<tans>true</tans>
<testoptions>{"checker": "is_hermitian"}</testoptions>
<truescoremode>=</truescoremode>
<truescore>1</truescore>
<truenextnode>-1</truenextnode>
<trueanswernote>symmetry-T</trueanswernote>
<falsescoremode>=</falsescoremode>
<falsescore>0</falsescore>
<falsenextnode>-1</falsenextnode>
<falseanswernote>symmetry-F</falseanswernote>
</node>
</prt>
<prt>
<name>positivity</name>
<value>0.5</value>
<autosimplify>1</autosimplify>
<node>
<truefeedback format="html">
<text><p>The matrix is positive definite.</p></text>
</truefeedback>
<falsefeedback format="html">
<text />
</falsefeedback>
<name>0</name>
<answertest>PythonChecker</answertest>
<sans>ansm</sans>
<tans>true</tans>
<testoptions>{"checker": "is_positive_definite"}</testoptions>
<truescoremode>=</truescoremode>
<truescore>1</truescore>
<truenextnode>-1</truenextnode>
<trueanswernote>positivity-T</trueanswernote>
<falsescoremode>=</falsescoremode>
<falsescore>0</falsescore>
<falsenextnode>-1</falsenextnode>
<falseanswernote>positivity-F</falseanswernote>
</node>
</prt>
<qtest>
<testcase>1</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,0],[0,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>2</testcase>
<testinput>
<name>ansm</name>
<value>matrix([2,1],[1,2])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>3</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1/2,1/4],[1/4,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>4</testcase>
<testinput>
<name>ansm</name>
<value>matrix([-1,0],[0,-1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>5</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,1],[1,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>6</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,2],[0,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>symmetry-F</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>7</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,2],[2,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>8</testcase>
<testinput>
<name>ansm</name>
<value>matrix([2,1],[0,2])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>symmetry-F</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>9</testcase>
<testinput>
<name>ansm</name>
<value>matrix([0,1],[0,0])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>symmetry-F</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>10</testcase>
<testinput>
<name>ansm</name>
<value>matrix([2,%i],[-%i,2])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>11</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,%i],[%i,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>symmetry-F</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>12</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1,2*%i],[-2*%i,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>13</testcase>
<testinput>
<name>ansm</name>
<value>matrix([0,%i],[0,0])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>symmetry-F</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>0</expectedscore>
<expectedpenalty>0.1</expectedpenalty>
<expectedanswernote>positivity-F</expectedanswernote>
</expected>
</qtest>
<qtest>
<testcase>14</testcase>
<testinput>
<name>ansm</name>
<value>matrix([1/2,%i/4],[-%i/4,1])</value>
</testinput>
<expected>
<name>symmetry</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>symmetry-T</expectedanswernote>
</expected>
<expected>
<name>positivity</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>positivity-T</expectedanswernote>
</expected>
</qtest>
</question>
</quiz>
PythonChecker API and a scalar example
Write a block checker: with check(response, symbols). The same function contract works in math-exercise when its field labels equal the STACK input names. This example accepts any positive integer multiple of the displayed number:
Enter any positive integer multiple of \(2\).
Show source
```{.stack-question #docs-custom-multiple}
#| label: docs-custom-multiple
#| variants: 3
#| file: ../questions/python-checker.xml
#| question: A positive integer multiple (PythonChecker)
#| checker: |
#| def check(response, symbols):
#| answer = response["answers"]["ans"]
#| divisor = response["variables"]["d"]
#| if answer.is_Integer is not True:
#| return {"valid": False, "feedback": "Enter an integer value."}
#| correct = answer > 0 and answer % divisor == 0
#| return {"correct": bool(correct),
#| "feedback": "" if correct else "Use a positive integer multiple of the given number."}
#| tests:
#| - name: displayed multiple
#| answers: {ans: d}
#| expect: correct
#| - name: wrong offset
#| answers: {ans: d+1}
#| expect: wrong
#| - name: fractional input
#| answers: {ans: 'raw("1/2")'}
#| expect: invalid
#| review: true
```Show STACK source
<?xml version="1.0" encoding="UTF-8"?>
<!-- CTM team, CC BY-SA 4.0. PythonChecker requires this port and questions/checkers.py. -->
<quiz><question type="stack">
<name><text>A positive integer multiple (PythonChecker)</text></name>
<questionvariables><text>d:rand(3)+2;</text></questionvariables>
<questionsimplify>1</questionsimplify>
<questiontext format="html"><text><![CDATA[<p>Enter any positive integer multiple of {@d@}.</p><p>[[input:ans]] [[validation:ans]] [[feedback:prt1]]</p>]]></text></questiontext>
<generalfeedback format="html"><text><![CDATA[<p>Multiplying the given integer by any positive integer gives an answer. The Python predicate decides whether the condition holds; the response tree awards credit.</p>]]></text></generalfeedback>
<defaultgrade>1</defaultgrade><penalty>0.1</penalty>
<input><name>ans</name><type>algebraic</type><tans>d</tans><strictsyntax>1</strictsyntax><insertstars>0</insertstars><forbidfloat>0</forbidfloat><checkanswertype>1</checkanswertype><mustverify>1</mustverify><showvalidation>1</showvalidation></input>
<prt><name>prt1</name><value>1</value><autosimplify>1</autosimplify><node><name>0</name><answertest>PythonChecker</answertest><sans>ans</sans><tans>d</tans><testoptions>{"checker": "check"}</testoptions>
<truescoremode>=</truescoremode><truescore>1</truescore><truenextnode>-1</truenextnode><trueanswernote>multiple-T</trueanswernote>
<falsescoremode>=</falsescoremode><falsescore>0</falsescore><falsenextnode>-1</falsenextnode><falseanswernote>multiple-F</falseanswernote>
</node></prt>
<qtest><testcase>1</testcase><testinput><name>ans</name><value>d</value></testinput><expected><name>prt1</name><expectedscore>1</expectedscore><expectedpenalty>0</expectedpenalty><expectedanswernote>multiple-T</expectedanswernote></expected></qtest>
<qtest><testcase>2</testcase><testinput><name>ans</name><value>3*d</value></testinput><expected><name>prt1</name><expectedscore>1</expectedscore><expectedpenalty>0</expectedpenalty><expectedanswernote>multiple-T</expectedanswernote></expected></qtest>
<qtest><testcase>3</testcase><testinput><name>ans</name><value>-d</value></testinput><expected><name>prt1</name><expectedscore>0</expectedscore><expectedpenalty>0.1</expectedpenalty><expectedanswernote>multiple-F</expectedanswernote></expected></qtest>
<qtest><testcase>4</testcase><testinput><name>ans</name><value>d+1</value></testinput><expected><name>prt1</name><expectedscore>0</expectedscore><expectedpenalty>0.1</expectedpenalty><expectedanswernote>multiple-F</expectedanswernote></expected></qtest>
</question></quiz>
The XML node uses {"checker":"check"}. It adds its own evaluated values as response["node"] = {"sans": ..., "tans": ..., "options": ...}. options is the literal JSON object inside testoptions, never evaluated as CAS or Python. A partial checker result takes the node’s false branch; the node’s normal branch scores still determine that XML tree’s score.
Response and result
The checker receives these values after the selected backend has validated all inputs:
| Key | Value |
|---|---|
kind |
"expressions" |
raw |
Student strings in XML input order |
expressions |
Parsed values in the same order |
answers |
Parsed values keyed by XML input name |
teacher |
Teacher values keyed by XML input name |
variables |
Exact question variables |
inputs |
Named input descriptions, including raw and canonical |
variant |
{n, id, hash}; an unapproved candidate has no published number |
node |
Present only during a PythonChecker node call |
Matrix input descriptions also carry rows, cols, flattened expressions and a SymPy matrix. symbols maps free symbol names to their SymPy symbols, with the question’s assumptions. The checker namespace contains the shared math-exercise helpers, including SymPy names and assess_basis.
Return True or False, a number between 0 and 1, or a dictionary containing score or correct. Optional keys are feedback, show_score and assessment. Feedback is plain text. A matrix assessment names the input:
return {"score": 0.5, "feedback": "Review the dependent columns.",
"assessment": {"ans": {"columns": ["correct", "dependent"]}}}Each assessment list must match the submitted row or column count. A result such as {"valid": False, "feedback": "Enter an integer value."} rejects the attempt without a score. Invalid attempts clear vector assessments. A checker that raises, returns a malformed result or cannot decode a required author value is an author error; it does not count as a wrong learner answer. Each invocation gets a fresh namespace.
Score and placement
A block checker creates an implicit response tree named checker, with checker-value: 1 by default. Its normalized score counts alongside the XML trees:
\[ \text{score}=\frac{\sum_{\text{trees}}\text{value}\times\text{score}} {\sum_{\text{trees}}\text{value}}. \]
For XML tree scores 1 and 0, both of weight 1, and a checker score 0.5 with checker-value: 2, the question scores 0.5. With no XML trees, the checker supplies the whole grade. Use [[feedback:checker]] in CASText to place its feedback. Penalties and score rounding follow the same rules as XML trees. The name checker is reserved when the block defines a checker.
The XML node and the implicit checker are separate evaluations. A node calls the function as a predicate and supplies response["node"]; the implicit checker call has no node key and contributes its numeric score. Write the function to support both contexts when the XML calls it.
Test and publish
Run XML and declarative question tests for every variant, inspect the student previews, and approve the variants before publishing. Review mode permits proposals for teacher inspection; it does not approve them. Normal publishing requires current approved variants.
The old python-checkers: filename, CHECKERS registry and ctm_checkers.stack.checker_api module have been removed. Move custom logic into checker: and change its arguments to response, symbols. Supplied matrix predicates now come from the shared library without a filename. Both backends implement this contract. Maxima calls the same checker with validated written values, while native Maxima owns question preparation and compiled response-tree routing. See the native guide.