import json
import pathlib
import sys
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from IPython.display import FileLink, FileLinks
# we don't want out plots to show while building them
matplotlib.use('Agg')
So that the library is readily imported. This is unnecessary (and probably not recommended) if you have a local installation of py2gift
. It is meant for running online through a cloud service such as mybinder.
sys.path.append('..')
import py2gift.question
import py2gift.input_file
import py2gift.notebook
import py2gift.core
import py2gift.tex
import py2gift.file
A sample quiz.
A settings manager object (with default options)
settings = py2gift.input_file.Settings()
How many versions of a question are to be generated
n_instances = 2
We need to specify
Caveat: the variables below are used by %%statement
and %%feedback
magics to know what to modify (they determine the context). So, when moving back and forth between questions (up and down in the jupyter notebook), one should at least re-run the cell below before modifying anything in the corresponding question.
class_name = 'Question1'
category_name = 'Cat 1'
question_base_name='Toy question'
The category is registered in the settings object
category_name = settings.add_category(category_name=category_name)
The question is registered in the newly-created category
settings.add_or_update_class(
category_name=category_name, class_name=class_name, question_base_name=question_base_name,
n_instances=n_instances)
The statement of the question is entered through an ipython magic since it allows to capture freely-typed text. In principle, the text can be anything but if you want different versions of the same question, it should contain some variables that will be filled by Python code. These variables are prefixed by !
.
%%statement settings --cls {class_name} --category {json.dumps(category_name)}
What is the product of !factors?
'statement recorded'
%%feedback settings --cls {class_name} --category {json.dumps(category_name)}
Since blah blah
'feedback recorded'
The class implementing the question is defined. It should inherit from one of the classes in module py2gift.question
:
py2gift.question.MultipleChoiceQuestionGenerator
: for multiple-choice questionspy2gift.question.NumericalQuestionGenerator
: for numerical-answer questionsThe only mandatory method the new class must define is setup
. Its purpose is to fill in the blanks in both the statement
and feedback
of the question by calling, respectively, self.statement.fill
and self.feedback.fill
. Also, it should provide:
py2gift.question.NumericalQuestionGenerator
: one should set self.solution
to some number and self.error
to either a number or a string indicating a percentagepy2gift.question.MultipleChoiceQuestionGenerator
:In order to generate several instances (versions) of the same question, random numbers (or pictures!!) must be used somewhere (otherwise all the instances of the question will be identical). For that purpose, when one inherits from a class in py2gift.question
, a pseudo-random numbers generator, self.prng
, is provided. The method setup
, in the class below, will be called once for each new instance of the question.
class Question1(py2gift.question.NumericalQuestionGenerator):
def setup(self):
factors = self.prng.rand(4) * 10
# above `numpy` array needs to be turned into a `str` for the statement; one of the convenience functions
# in `py2gift.tex`can be used
str_factors = py2gift.tex.enumerate_math(factors)
# the statement is "filled" in
self.statement.fill(factors=str_factors)
self.solution = np.prod(factors)
self.error = '10%'
We can easily preview the first instance of the question
py2gift.util.render_latex(py2gift.core.generator_to_markdown(
settings.to_dict(), category_name, getattr(settings.fake_module, class_name)))
Statement
What is the product of $\Large 3.75$, $\Large 9.51$, $\Large 7.32$ and $\Large 5.99$?
Feedback
Since blah blah
Solution
1560.3966226689554 (error: 156.03966226689553)
$\LaTeX$ formulas are enlarged (\Large
is prepended) for better visualization inside the notebook, but they are kept as they were when written in the generated GIFT file. Also notice that n_instances
of this question will actually be generated, though only the first one was shown here.
class_name = 'Question2'
category_name = 'Cat 2'
question_base_name='Another question'
category_name = settings.add_category(category_name=category_name)
settings.add_or_update_class(
category_name=category_name, class_name=class_name, question_base_name=question_base_name,
n_instances=n_instances)
%%statement settings --cls {class_name} --category {json.dumps(category_name)}
Consider the heatmap
!heatmap
Now what?
'statement recorded'
%%feedback settings --cls {class_name} --category {json.dumps(category_name)}
Just ignore this stuff...
'feedback recorded'
If copy and pasting, you must remember to match the name of this class with whatever you specified above in class_name
. Since this is a multiple-choice question, we should set
self.right_answer
to a string with the right answerself.wrong_answers
to a list of strings with the wrong ones%mkdir -p 'images'
class Question2(py2gift.question.MultipleChoiceQuestionGenerator):
def setup(self):
# a random matrix...
matrix = self.prng.rand(2,2)
# ...is plotted as a heat map
fig, ax = plt.subplots()
im = ax.imshow(matrix)
# image must be saved as an svg...
# heatmap = pathlib.Path('images') / 'heatmap.svg'
heatmap = 'heatmap.svg'
# however, since different "versions" of this question (for different random matrices) are going to
# be created, we must make sure to choose a different name for each one; one way of achieving this is
# by using `py2gift.file.unique_name`
heatmap = py2gift.file.unique_name(heatmap)
fig.savefig(heatmap)
self.statement.fill(heatmap=heatmap)
# this must be a string...
self.right_answer = "This doesn't make any sense"
# ...and this a *list* of strings
self.wrong_answers = ['42', 'The information action ratio']
# for previewing the question
py2gift.util.render_latex(py2gift.core.generator_to_markdown(
settings.to_dict(), category_name, getattr(settings.fake_module, class_name)))
Statement
Consider the heatmap
Now what?
Feedback
Just ignore this stuff...
Choices
A minimal parameters file for gift-wrapper:
parameters = {
'latex': {'auxiliary file': '__latex_check.tex'}
}
# %%script false --no-raise-error
local_run = True
embed_images = True
py2gift.core.build(
settings.to_dict(), local_run=local_run, questions_module=settings.fake_module, parameters_file=parameters,
no_checks=True, embed_images=embed_images)
HBox(children=(FloatProgress(value=0.0, description='category', max=2.0, style=ProgressStyle(description_width…
HBox(children=(FloatProgress(value=0.0, description='question', max=2.0, style=ProgressStyle(description_width…
HBox(children=(FloatProgress(value=0.0, description='question', max=2.0, style=ProgressStyle(description_width…
file "quiz.gift.txt" created
Retrieve the created file from the link below (not present in the docs).
from IPython.display import FileLink, FileLinks
FileLink('quiz.gift.txt')