Skip to content

pySELL - A Python-based Simple E-Learning Language for the Rapid Creation of Interactive and Mobile-Friendly STEM Courses

License

Notifications You must be signed in to change notification settings

andreas-schwenk/pysell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pySELL

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.

User Guide

Download the file sell.py from this repository. This is the only file 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 the sources in FILENAME.txt. An example is provided below, with more examples available in the examples/ directory.

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 sage -python sell.py FILENAME.txt.

A short developer guide can be found at the end of this document.

Dependencies

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.

Example

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

Syntax

This section describes the syntax of pySELL. Many aspects are self-explanatory and can be understood from the example file.

Global

  • LANG defines the natural language used in the few built-in output strings. Currently supported languages are en, de, es, it, and fr.

  • 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 marks the beginning of a new question. The title of the question should be written on the same line.

  • # introduces a comment, i.e., text that is not considered by the compiler.

Questions

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 at myImage.svg with a width of 25% relative to the question box. If the width is omitted, 100% is assumed. Supported image formats are svg, png, and jpg. 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 tool pdf2svg to generate SVG files from PDF files. The latter can be generated by tikZ). For dynamic plots via matplotlib, 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 just x).

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 type str (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 using matplotlib.

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$-vector x = 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 write del x at the end of your Python code to exclude x.

  • In general, you may import arbitrary Python libraries. pySELL will attempt to map data types to its internal data types (e.g., some commonly used sage data types are mapped). For all unimplemented types, the variable is considered a term and the value is exported using str(my_var). This may work or may not. Feel free to ask the author of pySELL to extend support for missing or exotic data types.

LLM generated questions

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://github.com/raw/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.

Hints on generating random variables

Note: also read about the custom function rangeZ, to exclude the zero from a range, below.

Read the docs:

Examples:

Draw a random integer a from {-2,-1,...,5}:

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.

Choose a random number a from set {2,3,5,7}:

import random
a = random.choice([2,3,5,7])

Note that the parameter is actually a list.

Draw 3 random integers a, b, c from {-2,-1,...,5} with replacement:

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.

Draw 3 unique (i.e. without replacement) random integers a, b, c from {-2,-1,...,5}:

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.

Shuffle a list [2,4,6,8] (e.g. to get [6,8,4,2]):

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)

Generate a 2 x 3 matrix A with random integer elements from {-2,-1,...,5} using numpy:

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.

Generate a 2 x 3 matrix A with random integer elements from {-2,-1,...,5} using sympy:

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.

Excluding the zero

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!

Developer Guide

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 by build.py.
  • build.py builds and minifies the JavaScript code in path web/src/, inserts it into web/index.html and finally writes the self-contained HTML file into sell.py.
  • docs/ contains the logo, as well as the showcase
  • examples/ 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 path web/src/ is loaded as module (b) used as template code for the final HTML insertion into sell.py
  • web/build.js is called by build.py. It uses esbuild to build and minify JavaScript code in path web/src/. Alternative build tools should also work without issues.