pySELL
is a Python-based Simple E-Learning Language for the rapid creation of interactive STEM quizzes, with a focus on randomized math questions.
Quizzes can be used on mobile devices.
Compared to other solutions (e.g. STACK
questions), pySELL
has NO technological runtime dependencies, except katex
for math rendering. Generated quizzes consist each of one self-contained HTML file. Theses files can be hosted on a web-server or imported to an existing LMS courses (e.g. Moodle via "file upload", or Ilias via "HTML course").
Student answers are not stored on servers, so pySELL
quizzes provide an 100 % anonymous training. This is very well received by students on their first contact with new topics.
Teachers benefit from a simple to learn syntax. After some practice, even sophisticated questions can be generated with very little time investment.
If you are using pySELL
in one of your (university) classes, I would love to hear about it! Write a mail with feedback / bug reports / feature requests to contact@compiler-construction.com
As a member of the Free Software Foundation (FSF), I decided to publish pySELL
as free and open-source software under the license of GPLv3
.
Download file sell.py
from this repository. Only this single file is required. All other files are used for the development of pySELL
.
Run python3 sell.py FILENAME.txt
to generate a self-contained quiz-website FILENAME.html
from sources in FILENAME.txt
. You will find an example below, and more examples in directory examples/
.
Also a file FILENAME_debug.html
is created, which can be used for debugging purposes. The debug output differs to the release files in the following aspects:
- The sample solution is rendered in the input fields
- All questions are evaluated directly, for testing the evaluation
- Single and multiple choice answers are displayed in static order
- Python and text sources are displayed with syntax highlighting
- The line numbers of the source file are shown for each exercise
If you like to use SageMath
in your code, then run sage -python sell.py FILENAME.txt
.
A short developer guide can be found at the end of this document.
Users: Only vanilla Python 3 is required to create basic questions. If you like to use symbolic calculations in your questions, then also sympy
should be installed (pip install sympy
). If you require linear algebra, for example numpy
can be used (pip install numpy
). For enabling plots, matplotlib
is supported (pip install matplotlib
). Also SageMath
can be used.
Developers: Node.js + a local web server for debugging the web code (or alternatively install the recommended VS-code extension in this repository).
The following example code generates some questions, as can be seen in the figure. You may run the examples here.
Command:
python3 sell.py 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
This section describes the syntax of pySELL
. Many aspects are self-explanatory and can be derived from the example file.
-
LANG
defines the natural language, used in the few built-in output strings. Currently,en
,de
,es
,it
,fr
are supported. -
TITLE
defines the title of the page. You may include HTML-code, but everything must be written in the same line, where the title-keyword starts. -
AUTHOR
defines the author/institution of the quizzes. You may include HTML-code, but everything must be written in the same line, where the author-keyword starts. -
QUESTION
marks the beginning of a new question. The title of the question is written into the same line. -
#
introduces a comment, i.e. text that is not considered by the compiler.
A question consists of a textual part, and optionally of 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 embedded into a pair of single asterisks
*
(e.g.math is *cool*
). -
Bold text is embedded into a pair of double asterisks
**
(e.g.math can be **challenging**
). -
Embedded code is written into a pair of accent graves ```.
-
Itemizations are preceded by
-
. -
TeX-based inline math is embedded into a pair of
$
(e.g.$\sqrt{x^2+y^2}$
for$\sqrt{x^2+y^2}$ ). -
TeX-based display style math is embedded into a pair of
$$
(display mode in inline math can also be activated in inline math mode by writing e.g.$\displaystyle \sum_{i=1}^n i^2$
). -
Multiple-Choice questions are preceded by
[x]
for correct answers, or[ ]
for incorrect answers, respectively. The text is separated by a space (e.g.[x] This answer is correct
). -
Single-Choice questions are preceded by
(x)
for the correct answer, and( )
for incorrect answers, respectively. Only one answer can be true. (e.g.( ) This answer is incorrect
). -
A line break can be forced by
\\
at the end of a line (e.g.A new paragraph will start after this line. \\
). -
Static images can be included with
!
, following the path and optionally following the width in percentage (path and width are separated by:
). For example,!myImage.svg:25
shows the image in pathmyImage.svg
with a width of25%
(related to the question box). File paths are relative to the currently translatedpySELL
input file. 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. You should care about the file size of images! Usually,svg
files are very small for vector graphics (hint: use the toolpdf2svg
to generate SVG-files from PDF-files. The latter can e.g. be generated bytikZ
). For dynamic plots viamatplotlib
, read the next section.
Question code
To generate randomized variables, arbitrary Python-Code can be evaluated (this is secure, since the code is executed only locally on the teachers computer).
For each question that includes randomization (the compiler checks, if your Python code contains the string rand
), 5 distinct instances are drawn. If you use a bad randomization, some instances may be equal. In case that you won't use random numbers, there will be only one instance.
-
Python code is embedded into a pair of
"""
. The triple-quotations must be written in distinct lines, without any other characters in these lines. Python code must be provided before its variables are accessed in the textual part. -
Variables denoted in math mode are replaced by its actual values (the execution environment randomly chooses one of the 5 instances). This behavior can be suppressed by embedding the variable name into double quotes (e.g. write
"x"
instead of justx
).Warning: Variable names with underscore (e.g.
x_1
) are not allowed, since the underscore would be ambiguous in TeX. -
Input fields are generated by
%
, followed by the referred variable name. The structure of the input field depends on the variable type of the variable (int
,set
,numpy.array
, ...). If the variable is non-scalar, parentheses (or brackets, or braces) will be rendered around vectors/sets/... To suppress theses parentheses, write%!
, followed by the variable name. This is e.g. used 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
$...$
). If you like 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
. -
For asking gap questions, use
%
, followed the expected word(s), embedded into double quotes (e.g.%"three"
). If you like to accept more than one answer, separate the words by the pipe operator|
. Example:%"three|tres|trois|tre"
. -
Dynamic gaps can be created with Python code. Just 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 as no input fields, the evaluation button is not shown.
Important notes
-
Consider to exclude Python variables from the output. For example,
matplotlib
requires to define axes. The$x$ -vectorx = np.linspace(-10,10,1000)
has length 1000.pySELL
will write that to the question database by default, so you should writedel x
at the end of your Python code, to excludex
. -
In general, you may import arbitrary Python libraries.
pySELL
will do its best to map data types to internal data types (e.g. some commonly usedsage
data types are mapped). For all unimplemented types, the variable is considered to be a term and the value is exported bystr(my_var)
. This may work, or may not. Feel free to ask the author ofpySELL
to extend support of missing/exotic data types.
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 python3 build.py
in order to update variable html
in file sell.py
.
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.