pl-symbolic-input element¶
Fill in the blank field that allows for mathematical symbol input.
Sample element¶

<pl-symbolic-input answers-name="symbolic_math" variables="x, y" label="$z =$"></pl-symbolic-input>
import prairielearn as pl
import sympy
def generate(data):
# Declare math symbols
x, y = sympy.symbols("x y")
# Describe the equation
z = x + y + 1
# Answer to fill in the blank input stored as JSON.
data["correct_answers"]["symbolic_math"] = pl.to_json(z)
Customizations¶
| Attribute | Type | Default | Description |
|---|---|---|---|
additional-simplifications |
string | - | Simplifications that should be applied during grading before using SymPy's built-in equality checker. Using this attribute can prevent rare cases of non-convergence during grading. See the non-convergence section for more details before using this attribute. |
allow-blank |
boolean | false | Whether an empty input box is allowed. By default, an empty input box will not be graded (invalid format). |
allow-complex |
boolean | false | Whether complex numbers (expressions with i or j as the imaginary unit) are allowed. |
allow-sets |
boolean | false | Whether set and interval notation are allowed. See below for more details. |
allow-trig-functions |
boolean | true | Whether trigonometric functions (cos, atanh, ...) are allowed. |
answers-name |
string | — | Variable name to store data in. Note that this attribute has to be unique within a question, i.e., no value for this attribute should be repeated within a question. If the correct answer is set in server.py as a complex object, you should use import prairielearn as pl and data["correct_answers"][answers-name] = pl.to_json(ans). |
aria-label |
string | — | An accessible label for the element. |
blank-value |
string | 0 (zero) | Expression to be used as an answer if the answer is left blank. Only applied if allow-blank is true. Must be "" (empty string) or follow the same format as an expected user input (e.g., same variables, etc.). |
correct-answer |
string | See description | Correct answer for grading. Defaults to data["correct_answers"][answers-name]. |
custom-functions |
string | — | A comma-delimited list of custom functions that can be used in the symbolic expression. |
display |
"block" or "inline" |
"inline" |
How to display the input field. |
display-log-as-ln |
boolean | false |
Whether ln rather than log should be used when displaying submissions and correct answers. Both are considered equivalent and can be used by the student in their submitted answer. |
display-simplified-expression |
boolean | true |
Whether expressions submitted by students should be displayed in their simplified form. Setting display-simplified-expression="false" can prevent unintended simplifications that might confuse students. Note that, regardless of this setting, answers are always simplified for grading purposes. |
formula-editor |
boolean | false | Whether the element should provide a visual formula editor for students (recommended when answers are long or contain nested mathematical expressions). |
imaginary-unit-for-display |
string | "i" |
The imaginary unit that is used for display. It must be either "i" or "j". Again, this is only for display. Both i and j can be used by the student in their submitted answer, when allow-complex="true". |
initial-value |
string | — | Initial value to prefill the input box the first time it is rendered. |
label |
string | — | A prefix to display before the input box (e.g., label="$F =$"). |
placeholder |
string | "symbolic expression" |
Hint displayed inside the input box describing the expected type of input. |
show-help-text |
boolean | true | Show the question mark at the end of the input displaying required input parameters. |
show-score |
boolean | true | Whether to show the score badge next to this element. |
size |
integer | 35 | Size of the input box. |
suffix |
string | — | A suffix to display after the input box (e.g., suffix="$\rm m/s^2$"). |
variables |
string | — | A comma-delimited list of symbols that can be used in the symbolic expression. |
weight |
integer | 1 | Weight to use when computing a weighted average score over elements. |
Details¶
Correct answers are best created as sympy expressions and converted to json using pl.to_json. It is also possible to specify the correct answer simply as a string, e.g., x + y + 1.
Variables with the same name as greek letters (e.g., alpha, beta, etc.) will be automatically converted to their LaTeX equivalents for display on the correct answer and submission panels.
Do not include i or j in the list of variables if allow-complex="true", and do not include any other reserved name in your list of variables (e, pi, cos, sin, etc.). The element code will check for (and disallow) conflicts between your list of variables, custom-functions, and reserved names.
Note that variables created with additional assumptions in a correct answer will have those assumptions respected when evaluating student answers. See example question for details.
Set Notation¶
If allow-sets="true", the following additional layer of syntax is enabled:
- Set literals with explicitly listed members (e.g.
{1, 2, 3},{0, 2pi/3, 4pi/3},{ {}, { {} } }) - Interval set notation, including $\infty$ (e.g
(-sin(x), +sin(x)),(-infty, 5],[2, oo]) - Common set operators: union (
U,cup,+, or|), intersection (capor&), and difference (-)
Example implementations¶
See also¶
pl-number-inputfor numeric inputpl-integer-inputfor integer inputpl-string-inputfor string input
Non-convergence in grading¶
In rare cases, the pl-symbolic-input question element can produce grading results that mark a student answer as "non-converging" during grading. In these cases, answers are marked as invalid, since it could not be determined if they are correct or incorrect.
Why grading might not converge¶
The pl-symbolic-input question element uses the Python library SymPy to represent and grade expressions. During grading, the correct answer and the student submission are parsed into SymPy expressions and then compared using the library's built-in equality checker.
Checking for equality is non-trivial as it accounts for a wide range of mathematical equivalences (e.g., log(10*x) is considered equal to log(10)+log(x), or (x+1)**2 is equal to x**2+2*x+1). To account for all possible combinations of equivalence rules, SymPy applies simplifications heuristically and potentially repeatedly. Unfortunately this means that there is no bound on how long the equality checker might take to finish, and in rare cases it might even get stuck in a non-terminating simplification cycle.
PrairieLearn automatically terminates SymPy's equality check after a few seconds have passed. This check is applied in two cases. The element then marks student submission as invalid during grading if their submission caused a timeout. Note that both correct and incorrect answers can potentially trigger timeouts. Students are presented with an error message that tells them Your answer did not converge, try a simpler expression.. If desired, this behavior can be replaced with custom grading code in the server.py file of the question.
Preventing non-convergence by adding additional-simplifications¶
Terminating SymPy's equality check is a last resort to ensure that the normal grading process (including custom question grading defined in server.py) can complete. When presented with a non-convergence error, students might misinterpret it as a sign that the question is faulty, although even small changes to their answer (e.g., manually expanding a term) might resolve the issue and it is very rare that correct answers trigger non-convergence errors.
SymPy is less likely to experience issues if both the correct and submitted answer share the same structure. As an instructor, it therefore might be possible to prevent timeouts by "guiding" SymPy's equality checking process. This can be achieved by providing SymPy with a list of simplification steps that it should execute before relying on its built-in heuristics. For example, the trigsimp simplification step can convert sin(x)/cos(x) into tan(x), and expand can convert expressions like (x+1)**2 into x**2+2*x+1.
Warning
Applying additional-simplifications is not guaranteed to work around all timeout issues. The optimal simplification order for a specific question depends on the complexity and domain of the question (e.g., whether it uses trigonometric functions). If a question experiences non-convergence issues, it might be easier to change the expected answer rather than experimenting with this parameter.
The table below lists the possible simplifications that can be provided to the attribute additional-simplifications, and are applied in the order they are listed:
| Simplification | Description | Example |
|---|---|---|
expand |
Expanding polynomials | (x + 1)**2 => x**2 + 2*x + 1 |
powsimp |
Power simplifications | x**a*x**b => x**(a + b) |
trigsimp |
Trigonometric simplifications | sin(x)/cos(x) => tan(x) |
expand_log |
Logarithmic simplifications | log(x*y) => log(x) + log(y) |
Some specific examples of successful uses of additional-simplifications are:
(-8*x*sin(8*x)/cos(8*x) + log(cos(8*x)))*cos(8*x)**xbenefitted fromadditional-simplifications="trigsimp".7**(x + 8)*log(7)benefitted fromadditional-simplifications="expand"
Note
All of the simplifications above are already considered in SymPy's built-in heuristics, so listing them in additional-simplifications does not affect the display of expressions or the grading results except in cases where a timeout occurs. Also note that simplifications are always applied to both the correct and the submitted answer, because the goal is to increase their similarity (e.g., both are fully expanded) rather than aiming for a specific form (e.g., expanding vs. factoring).