WELCOME! CHECK OUT THE DEMO SITE HERE.
pySELL
is a Python-based Simple E-Learning Language designed for the rapid creation of interactive STEM quizzes, with a focus on randomized math questions.
Quizzes created with pySELL
can be used on mobile devices.
Compared to other solutions (e.g., STACK
questions), pySELL
has NO technological runtime dependencies, except for katex
for math rendering. Each generated quiz consists of a single self-contained HTML file. These files can be hosted on a web server or imported into existing LMS courses (e.g., Moodle via "file upload" or Ilias via "HTML course").
Student answers are not stored on servers, ensuring that pySELL
quizzes provide 100% anonymous training. This anonymity is highly appreciated by students when first engaging with new topics.
Teachers benefit from a simple-to-learn syntax. With some practice, even sophisticated questions can be generated with minimal time investment.
If you are using pySELL
in one of your (university) classes, I would love to hear about it! Please send feedback, bug reports, or feature requests to contact@compiler-construction.com
.
As a member of the Free Software Foundation (FSF), I have decided to publish pySELL
as free and open-source software under the GPLv3
license.
To install the pySELL
package from https://pypi.org/project/pysell/, simply run the following command:
pip install pysell
If you've already installed pySELL
, you can update it to the latest version with the following command:
pip install --upgrade pysell
Run the following command to generate a self-contained quiz website FILENAME.html
from the sources in FILENAME.txt
. An example is provided below, with more examples available in the examples/
directory.
pysell FILENAME.txt
Additionally, a file FILENAME_debug.html
is created for debugging purposes. The debug output differs from the release files in the following aspects:
- The sample solution is rendered in the input fields
- All questions are evaluated directly for testing purposes
- Single and multiple-choice answers are displayed in a static order
- Python and text sources are displayed with syntax highlighting
- Line numbers from the source file are shown for each exercise
If you would like to use SageMath
in your code, run the following commands for installation and usage:
sage -pip install pysell
sage -python -m pysell FILENAME.txt
Alternatively, if you'd prefer not to use a package manager, you can directly download the stand-alone file sell.py
from the repository. This is the only file required; all other files are used for the development of pySELL
.
Usage example:
python3 sell.py FILENAME.txt
Users: Only vanilla Python 3 is required to create basic questions. If you want to use symbolic calculations in your questions, you should also install sympy
(pip install sympy
). For linear algebra, you can use numpy
(pip install numpy
). To enable plotting, matplotlib
is supported (pip install matplotlib
). You can also use SageMath
for advanced mathematical computations.
Developers: Node.js and a local web server are recommended for debugging the web code. Alternatively, you can install the recommended VS Code extension available in this repository.
The following example code generates some questions, as can be seen in the figure. You may run the examples here.
Command:
pysell examples/ex1.txt
Files ex1.html
and ex1_DEBUG.html
will be generated. The latter file shows the sample solution.
Some contents of the example file examples/ex1.txt
are shown below. Get the complete example file here:
LANG en
TITLE pySELL Demo
AUTHOR Andreas Schwenk
QUESTION Multiple-Choice
Mark the correct answer(s)
[x] This answer is correct
[x] This answer is correct
[ ] This answer is incorrect
QUESTION Addition
"""
import random
x = random.randint(10, 20)
y = random.randint(10, 20)
z = x + y
"""
Calculate $x + y =$ %z
QUESTION Gaps
- Write 3 as a word: %"three"
- Write 7 as a word: %"seven"
- Write the name of one of the first two letters in the Greek alphabet: %"alpha|beta"
QUESTION Lists/Vectors
"""
fib = [1] * 7
for i in range(2,len(fib)):
fib[i] = fib[i-2] + fib[i-1]
fib3 = fib[3:]
"""
Continue the Fibonacci sequence
- $ 1, 1, 2, $ %!fib3, ...
QUESTION Terms 2: Integration
"""
from sympy import *
x = symbols('x')
f = parse_expr("(x+1) / exp(x)", evaluate=False)
i = integrate(f,x)
"""
Determine by **partial integration:** \\
- $ \displaystyle \int f ~ dx =$ %i $+ C$ \\
with $C \in \RR$
QUESTION Matrices with Sympy
"""
from sympy import *
A = randMatrix(3,3, min=-1, max=1, symmetric=True)
B = randMatrix(2,3, min=-2, max=2, symmetric=False)
x,y = symbols('x,y')
B[0,0] = cos(x) + sin(y)
C = A * B.transpose()
"""
- $A \cdot B^T=$ %C
QUESTION Images
!../docs/logo.svg:25
What is shown in the image?
(x) the pySELL logo
( ) the PostScript logo
Create timed quiz pages easily by adding the TIMER
keyword to the preamble. Once the timer expires, all questions will be automatically evaluated at once.
LANG en
TITLE pySELL demo with time limit
AUTHOR Andreas Schwenk
TIMER 30 # all questions will be evaluated when the timer runs out.
QUESTION Addition # student earns 1 points per default
"""
import random
x = random.randint(-10, 10)
y = random.randint(1, 10)
z = x + y
"""
Calculate $x + y =$ %z
QUESTION Multiplication (2 pts) # student earns 2 points
"""
import random
x = random.randint(-10, 10)
y = random.randint(1, 10)
z = x * y
"""
Calculate $x \cdot y =$ %z
This section describes the syntax of pySELL
. Many aspects are self-explanatory and can be understood from the example file.
-
LANG
defines the natural language used in the few built-in output strings. Currently supported languages areen
,de
,es
,it
, andfr
. -
TITLE
defines the title of the page. You may include HTML code, but everything must be written on the same line where the title keyword starts. -
AUTHOR
defines the author or institution of the quizzes. You may include HTML code, but everything must be written on the same line where the author keyword starts. -
QUESTION
indicates the start of a new question, with its title specified on the same line. By default, each correctly answered question earns the student one point. To specify a different point value, include the desired points in parentheses, such as(X pts)
, whereX
is the number of points. For example:QUESTION Turing Machine (3 pts)
-
TIMER
restricts the time students have to complete the quiz page. The time, specified in seconds, is written after a space. -
#
introduces a comment, i.e., text that is not considered by the compiler.
A question consists of a textual part and optionally includes Python code that generates random variables and calculates the sample solution.
Question text
All text shown to the student is written as plain text. Formatting options are as follows:
-
Italic text is enclosed in single asterisks
*
(e.g.,math is *cool*
). -
Bold text is enclosed in double asterisks
**
(e.g.,math can be **challenging**
). -
Embedded code is enclosed in backticks
`
. -
Items in a list are preceded by
-
. -
TeX-based inline math is enclosed in dollar signs
$
(e.g.,$\sqrt{x^2+y^2}$
for$\sqrt{x^2+y^2}$ ). -
TeX-based display style math is enclosed in double dollar signs
$$
. Display mode in inline math can also be activated by writing, e.g.,$\displaystyle \sum_{i=1}^n i^2$
. -
Multiple-choice questions use
[x]
for correct answers and[ ]
for incorrect answers, with text separated by a space (e.g.,[x] This answer is correct
). -
Single-choice questions use
(x)
for the correct answer and( )
for incorrect answers. Only one answer can be true (e.g.,( ) This answer is incorrect
). -
A line break can be forced with
\\
at the end of a line (e.g.,A new paragraph will start after this line. \\
). -
Static images can be included with
!
, followed by the path and optionally the width in percentage (path and width are separated by:
). For example,!myImage.svg:25
shows the image located atmyImage.svg
with a width of25%
relative to the question box. If the width is omitted,100%
is assumed. Supported image formats aresvg
,png
, andjpg
. Note that image data is directly embedded into the output files, so you do not need to publish them separately. Be mindful of image file sizes. SVG files are usually very small for vector graphics (hint: use the toolpdf2svg
to generate SVG files from PDF files. The latter can be generated bytikZ
). For dynamic plots viamatplotlib
, refer to the next section.
Question code
To generate randomized variables, arbitrary Python code can be evaluated (this is secure, as the code is executed only locally on the teacher's computer).
For each question that includes randomization (the compiler checks if your Python code contains the string rand
), 5 distinct instances are drawn. If your randomization is poor, some instances may be identical. If no random numbers are used, only one instance will be created.
-
Python code is embedded within a pair of triple quotes
"""
. The triple quotes must be on separate lines without any other characters on these lines. Python code must be provided before its variables are accessed in the textual part. -
Variables denoted in math mode are replaced by their actual values (the execution environment randomly selects one of the 5 instances). This behavior can be suppressed by embedding the variable name in double quotes (e.g., write
"x"
instead of justx
).
Warning: Variable names with underscores (e.g., x_1
) are not allowed, as the underscore can cause ambiguity in TeX.
-
Input fields are generated using
%
, followed by the variable name. The structure of the input field depends on the type of the variable (int
,set
,numpy.array
, etc.). If the variable is non-scalar, parentheses (or brackets, or braces) will be rendered around vectors/sets/etc. To suppress these parentheses, write%!
followed by the variable name. This is used, for example, in the Fibonacci example in the example file. Example:The answer is %answer
. -
In general, variables can only be accessed within math mode (i.e., in
$...$
). To use Python-generated variables of typestr
(strings) seamlessly in the question text, use the ampersand operator&
, followed by the variable name in text mode. Example:Today I feel &mood
. -
To create gap questions, use
%
followed by the expected word(s), enclosed in double quotes (e.g.,%"three"
). To accept multiple answers, separate the words with the pipe operator|
. Example:%"three|tres|trois|tre"
. -
Dynamic gaps can be created with Python code. Generate a string variable (e.g.,
answer = "three|tres"
) and ask it exactly as for number inputs (e.g.,%answer
). -
For static or dynamic plots, refer to the
Plot
example in the examples.pySELL
supports usingmatplotlib
.
Hint: If a question has no input fields, the evaluation button is not shown.
Important notes
-
Consider excluding certain Python variables from the output. For example,
matplotlib
requires defining axes, and the$x$ -vectorx = np.linspace(-10,10,1000)
has a length of 1000. By default,pySELL
will include this in the question database. To prevent this, you should writedel x
at the end of your Python code to excludex
. -
In general, you may import arbitrary Python libraries.
pySELL
will attempt to map data types to its internal data types (e.g., some commonly usedsage
data types are mapped). For all unimplemented types, the variable is considered a term and the value is exported usingstr(my_var)
. This may work or may not. Feel free to ask the author ofpySELL
to extend support for missing or exotic data types.
Generating questions can be time-consuming, but Large Language Models (LLMs) like ChatGPT can assist.
Use the following prompt to generate questions:
Generate 10 questions for students in a math course on the topic of complex numbers using the pySELL formal language. The pySELL language is defined here: https://raw.githubusercontent.com/andreas-schwenk/pysell/main/llm.md. Ensure that each question is correctly formatted according to the pySELL specification and covers a range of topics related to complex numbers, including arithmetic operations, modulus, argument, conjugate, and forms of representation.
Note that the specification in the llm.md
file is not yet complete. Additionally, the quality of generated questions may not be perfect and may require human post-correction.
Note: also read about the custom function rangeZ
, to exclude the zero from a range, below.
Read the docs:
Examples:
import random
a = random.randint(-2,5)
# equivalent:
a = random.choice(range(-2,5+1))
The examples explicitly write +1
to clarify that the upper bound is not included.
import random
a = random.choice([2,3,5,7])
Note that the parameter is actually a list.
import random
# store in a, b, c
[a,b,c] = random.choices(range(-2,5+1),k=3)
# store as array x
x = random.choices(range(-2,5+1),k=3)
Note that the upper bound of range
is excluded.
import random
# store in scalar variables a, b, c
[a,b,c] = random.sample(range(-2,5+1),k=3)
# store as an array/list x
x = random.sample(range(-2,5+1),k=3)
# store as a set y
y = set(random.sample(range(-2,5+1),k=3))
Note that the upper bound of range
is excluded.
import random
# in place shuffling
x = [2,4,6,8]
random.shuffle(x)
# one liner with immutable input
x = random.sample([2,4,6,8],k=4)
import numpy
A = numpy.random.randint(-2, 5, size=(2,3))
# overwrite element A_{0,0}
A[0,0] = 1337
Elements are limited to numbers.
from sympy import *
A = randMatrix(2,3, min=-2, max=5, symmetric=False)
# overwrite element A_{0,0}
x, y = symbols('x,y')
A[0,0] = sin(x) * cos(y)
Elements can also be terms.
In some cases, it may be beneficial to exclude the zero from random number generation. For example, a numerical question would be too easy to solve, if zero is drawn for a variable.
pySELL
provides a function rangeZ
that behaves syntactically similar to range
, but excludes the zero.
Example to draw 3 random numbers a
, b
, c
from {-2,-1,1,2,3}
with replacement:
import random
# get a single random number
a = random.choice(rangeZ(-2,3+1))
# get 3 numbers with replacement (some of a,b,c may be equal)
[a,b,c] = random.choices(rangeZ(-2,3+1),k=3)
# get 3 numbers without replacement (a,b,c are distinct)
[a,b,c] = random.sample(rangeZ(-2,3+1),k=3)
Note that the result of rangeZ
is of type list
, while the built-in function range
returns type range
. This may be destructive in some cases!
To debug (or extend) the web code, first convert an input file into a json file with the -J
option enabled, e.g. python3 sell.py -J examples/ex1.txt
. Then examples/ex1.json
is generated.
Then start a local web server (e.g. using python3 -m http.server 8000
) and open web/index.html
(e.g. localhost:8000/web/
, if your port number is 8000). The uncompressed JavaScript code in directory web/src/
is interpreted as module.
To update sell.py
after any change in the JavaScript code, and run ./build.sh
in order to update variable html
in file sell.py
as well as to rebuild the Python package.
Structure of the repository:
sell.py
mainly compiles an input file to a JSON file. The generation of HTML output files can be found at the end. HTML/CSS/JavaScript template code is inserted bybuild.py
.build.py
builds and minifies the JavaScript code in pathweb/src/
, inserts it intoweb/index.html
and finally writes the self-contained HTML file intosell.py
.docs/
contains the logo, as well as the showcaseexamples/
contains example quizzes.web/
contains the front end, i.e. HTML/CSS/JavaScript code.web/index.html
is (a) used for testing; in this case, JavaScript code in pathweb/src/
is loaded as module (b) used as template code for the final HTML insertion intosell.py
web/build.js
is called bybuild.py
. It usesesbuild
to build and minify JavaScript code in pathweb/src/
. Alternative build tools should also work without issues.
Update as follows:
- change the version number in
pyproject.toml
- update
CHANGELOG
- run
./build.sh
- run
twine upload dist/*
- commit the code and create a new release version for
https://github.com/andreas-schwenk/pysell/releases