- EDIT
- 6. Programming exercises
- 6.4 Personalized exercises
Personalized exercises¶
General description¶
Personalized exercises contain some parts that are not the same for every student. This is useful for preventing plagiarism: the students cannot simply copy their answers or solutions from their peers.
Usually, the structure of the exercise is the same for everyone, but, for example, some input data is different in each instance of the exercise. The instances are usually randomly generated. Each student is assigned one instance and multiple students may be assigned the same instance. The instance assigned to the student remains the same even after refreshing the exercise page.
A personalized exercise defines a generator program that generates N instances
to the exercise. The instances should be generated before the start of the course.
The shell script pregenerate-exercises.sh
can be used by teachers and assistants to
run the generator programs and to save the instance files in the directory
personalized_exercises
under the course Git repository root. In theory,
the instance files could also be created by some other way in the correct location.
The CLI command has some arguments to modify its behaviour: they are printed
with the --help
argument.
Example usage:
./pregenerate-exercises.sh # Generate 10 personalized instances for all personalized exercises
./pregenerate-exercises.sh --instances 5 # Generate 5 personalized instances for all personalized exercises
./pregenerate-exercises.sh --instances 5 --keep-old # Generate 5 more instances for all personalized exercises, while keeping the previously generated instances too
./pregenerate-exercises.sh <exercise-key> # Generate 10 instances for only one personalized exercise dictated by <exercise-key>
./pregenerate-exercises.sh --help # Show all command line arguments and their explanations
# Push newly generated exercise instances to Git
git add personalized_exercises/
git commit -m "Generate personalized exercises"
git push
An instance contains some files that the grader program can use while grading (the implementation of the exercise may decide freely how those instance files are used). The instance files may also be used to add content to the exercise description shown to the student. A student is connected to one instance (the choice feels random but is based on their user IDs). It is also possible to force the instance to change for the student, e.g., after every five submissions ("you get five attempts before you must start over"). That kind of regeneration of the instance is disabled by default.
Example exercises¶
Here we have two very simple example personalized exercises. Their graders work in containers and the exercises define generator programs that create random instances.
A+ presents the exercise submission form here.
A+ presents the exercise submission form here.
Detailed description of the "Personalized number" exercise¶
Configuration¶
The config.yaml
file in the personalized_number
directory contains the
configuration of the exercise for A+ and mooc-grader, as usual. In addition to
basic programming exercises, it has the following section, which defines the
personalization.
personalized: True
generated_files:
- file: number
key: number
content_in_template: True
url_in_template: True
allow_download: True
generator:
cmd: [ "python3", "generator.py" ]
cwd: exercises/personalized_number/
max_submissions_before_regeneration: 3
The obvious personalized
keyword tells A+ and mooc-grader that personalized
exercise instances should have been pregenerated by the course staff and each user is then assigned an
instance of the exercise.
The generated_files
section define a list of generated files for a
personalized exercise. Each list item defines the following settings:
file
: filename of the generated filekey
: key for accessing the file in HTML templatesurl_in_template
: if true, the exercise instructions shown to the student includes a HTML link to download the generated filecontent_in_template
: if true, template variable includes the content of the generated fileallow_download
: if true, the student can download the generated file
The generator
section has settings for the generator program that
creates one new instance of the exercise. At least cmd
must be set. The
generator command cmd
will be run from course_key dir (that is, course_key
is the cwd).
cmd
is the command that is used to run the generator in the shell. Note
that it is given as a Python list
where each word is its own item. Example: ["generator_script.sh"]
will run
generator_script.sh from course_key dir. Example:
["python3", "script_dir/generator.py"]
will run generator.py from
course_key/script_dir but keep course_key as cwd. Mooc-grader appends the
instance directory path to the argument list and the generator is expected to
write files into the directory. The file names should be listed under
generated_files
setting so that mooc-grader is aware of them.
For A+ administrators, the Django command used to pregenerate exercises is
python manage.py pregenerate_exercises course_key <exercise_key>
.
(The --help
option prints all possible arguments).
This command is not meant to be run manually anymore, since course staff
is able to generate the exercise instances by themselves.
cwd
: if set, this sets the current working directory for the generator
program. Since the default cwd is course_key, this applies to directories
in course_key. Example: cwd: "script_dir"
will change the cwd to
course_key/script_dir
and only after that run cmd.
max_submissions_before_regeneration
defines how many times the student may
submit before the personalized exercise is regenerated (the exercise instance is
changed to another one). If unset, the exercise is never regenerated.
Exercise instance generation¶
The exercise instances are generated by the course staff with a shell script
pregenerate-exercises.sh
in the course Git repository. The script runs a mooc-grader container and
calls the manage.py
command pregenerate_exercises
, which generates the exercise instances to the container
path /local/grader/ex-meta/<course_key>/pregenerated/<exercise_key>/
. (Reference: mooc-grader
source: access/management/commands/pregenerate_exercises.py
, util/personalized.py .)
The script then copies these exercise instances from the local mooc-grader container to the course Git repository,
so that in production the generated exercise instances are available at <course_root>/personalized_exercises/<exercise_key>
.
The directory for the personalized exercises in the course Git repository root is
personalized_exercises
. Each personalized exercise has its own subdirectory named by
module_page_key
, where module
is the subdirectory for the RST file
(here "programming_exercises"), page
is the RST file which refers to
the exercise (here "personalized_exercises"); and key
is the unique
identifier for the exercise (here "personalized_number" or
"personalized_python"). The pregenerated instances for
each exercise are inside these directories. For example, the directory
<course_root>/personalized_exercises/programming_exercises_personalized_exercises_personalized_number/
has subdirectories 0
, 1
, ..., 9
, one for each ten instances, and
each of those instance directories contains a text file named number
,
which has the personalized data for the instance.
When creating instances for the "Personalized number" exercise, mooc-grader will call the generator.py script of the exercise first with a command line argument which tells the directory to store the first instance:
python3 generator.py /local/grader/ex-meta/default/pregenerated/programming_exercises_personalized_exercises_personalized_number/0
The script generator.py starts and stores the path string /local/grader/ ... /0
to its variable instance_dir
. It creates a directory with that path, if it
does not exist. Then it generates a pseudorandom integer between 1 and 50,
writes it to a text file named number
inside the directory at instance_dir
and terminates.
Next, mooc-grader will call generator.py again, but this time with command line
argument /local/grader/ex-meta/default/pregenerated/programming_exercises_personalized_exercises_personalized_number/1
.
This procedure is repeated for all the ten exercise instances.
Finally, the directory structure for the "Personalized number" exercise inside the mooc-grader container looks like this:
/local/grader/ex-meta/default/pregenerated/
└── programming_exercises_personalized_exercises_personalized_number
├── 0
| └── number
├── 1
| └── number
├── 2
| └── number
├── 3
| └── number
├── 4
| └── number
├── 5
| └── number
├── 6
| └── number
├── 7
| └── number
├── 8
| └── number
└── 9
└── number
The role of the exercise generator and supported software
The generator program is meant for creating exercise instances from pseudorandom data or selecting subsets of some larger exercise dataset for each exercise instance. Because the generator is run inside the mooc-grader container, not a programming exercise grader container (such as apluslms/grade-python), there are limitations on what software can be used on the generator side.
The apluslms/run-mooc-grader container has the following software:
minimal Debian <https://www.debian.org>_ ("slim" version)
shells: bash, dash, sh
GCC, G++ (GNU C and C++ compilers)
libc6-dev (GNU C Library: Development Libraries and Header Files)
make (GNU utility for compilation)
Python 3 and its standard library
some Python tools as Debian packages
You will likely want to write your exercise generator in Python. Using a shell such as bash is also possible. In theory, writing a generator in C or C++ should also be possible, but the generator program should be either precompiled, or then a shell script should compile the generator just once.
For more information, see Dockerfiles of apluslms/run-mooc-grader and apluslms/service-base containers.
Grading the exercise¶
The directory structure inside the apluslms/grade-python container looks essentially like this in the beginning:
/
├── exercise
| ├── check_number.py
| ├── config.yaml
| ├── generator.py
| ├── run.sh
| └── template.html
├── submission
| └── user
| └── solution
└── personalized_exercise
└── number
As you can see, the directory structure is very similar to the
nonpersonalized Python programming exercise.
The student's answer is at /submission/user/solution
. The directory
/submission/user
is also the starting directory for the run.sh script
for the exercise. Also, inside that container, the personalized data for the
exercise instance assigned to the student is initially at
/personalized_exercise
. The contents of this directory is identical to the
directory
<course_root>/personalized_exercises/programming_exercises_personalized_exercises_personalized_number/X
in the mooc-grader container, where X
is the number of the instance.
Next, inside the grade-python container, the script run.sh copies the file
/personalized_exercise/number
to /submission/user
. Then run.sh starts
the actual Python-based grading script check_number.py inside commands
capture
and pre
. capture
will store the text output from check_number
and finally send it to mooc-grader and A+. pre
wraps the text output inside
HTML <pre>
tags.
The grading script check_number.py starts with current working directory as
/submission/user
. It reads both the files number
and solutions
, and
compares their contents. Next check_number prints feedback to the standard
output:
Original number was: X
Your solution was: Y
This is the feedback text that is shown to the student in A+ after the grading
is completed. Here X
and Y
are the actual contents of files number
and solution
parsed as integer values.
Finally, check_number prints two lines:
TotalPoints: A
MaxPoints: B
These lines are not shown as feedback for the student, but they are the
exercise score which is stored by A+ for this student and this submission.
A
is a nonnegative integer: the score that the grading script gave for
the solution. B
is a positive integer: the maximum score that the
grading script can give for the exercise. Note that B
can be different that
what is set in the max_points
part of the config.yaml file of the
exercise; A+ will rescale the points if necessary.
Detailed description of the "Personalized Python" exercise¶
This exercise is very similar to the "Personalized number" exercise. Instead of
randomly chosen integer, the file names
in the exercise directory has
list of names, and one of the names is chosen randomly for each exercise
instance. Similarly, each instance has a directory, numbered from 0
to
9
, and each of these directories has a text file named name
, which
contains a randomly chosen name. Therefore, the exercise instance directory
inside the mooc-grader container has the following structure:
<course_root>/personalized_exercises/
└── programming_exercises_personalized_exercises_personalized_python
├── 0
| └── name
├── 1
| └── name
├── 2
| └── name
├── 3
| └── name
├── 4
| └── name
├── 5
| └── name
├── 6
| └── name
├── 7
| └── name
├── 8
| └── name
└── 9
└── name
Note that config.yaml has very similar personalized
section to the
"Personalized number" exercise, but here the student's input is a file, not a
text field, and therefore there is a files
section instead of a fields
section.
The grading script check.py imports the solution.py submitted by the
student. run.sh modifies the PYTHONPATH
environment variable for easy
import. The output from the grading script is very similar to the one in the
"Personalized number" exercise.