Multiple choice

Allow students to select from multiple options.

Simple example

A simple implementation of this example only requires writing a question.html file. Note that the question parameters, such as the ball mass $m$, the angle $\theta$, the height $h$, the initial velocity $v_0$ and the distance $d$, have fixed values. Consequently, the correct answer is also fixed.

<pl-question-panel>
<p>
A cannon ball with mass $m = 1.8 \rm\ kg$ is fired downward from a cliff at a height
$h = 2.67 \rm\ m$, at an angle $\theta = 31^o$ with respect to the horizontal, and
an initial velocity $v_0 = 20 \rm\ m/s$, as illustrated in the figure below.
</p>
<p><pl-figure file-name="image.png" directory="clientFilesQuestion" width="300"></pl-figure></p>
<p>
Suppose the ball hits the ground a distance $d = 4 \rm\ m$ from the base of the cliff. How long is the ball in the air?
Assume the acceleration due to gravity is $g=9.8 \rm\ m/s^2$.
</p>
</pl-question-panel>
<pl-multiple-choice answers-name="t" none-of-the-above="true">
<pl-answer correct="false">$t = 0.2 \rm\ s$</pl-answer>
<pl-answer correct="false">$t = 0.388 \rm\ s$</pl-answer>
<pl-answer correct="true" >$t = 0.233 \rm\ s$</pl-answer>
<pl-answer correct="false">$t = 0.259 \rm\ s$</pl-answer>
<pl-answer correct="false">$t = 0.738 \rm\ s$</pl-answer>
</pl-multiple-choice>

This question uses the attribute none-of-the-above="true" in the pl-multiple-choice element. This attribute adds the option "None of the above" as an alternative among the other options defined by the pl-answer tags. The answer "None of the above" will be true (replacing the correct answer) with $50\%$ probability.

Unfortunately, this implementation only creates one unique version of the question, with the same set of parameters and answers. The only level of randomization comes from the order in which the answers are displayed and the choice of the "None of the above" option as correct answer.

Complex example

To add variability to the question, we can include dynamically-generated values in question.html using Mustache template syntax. In this example, we can randomly generate the parameters $m$, $h$, $\theta$, $v_0$, and $d$ and compute the corresponding correct answers and distractors.

The modified question.html file that supports the randomization is:

<pl-question-panel>
<p>
A cannon ball with mass $m ={{params.m}}\rm\ kg$ is fired downward from a cliff at a height
$h = {{params.h}}\rm\ m$, at an angle $\theta = {{params.theta}}^o$ with respect to the horizontal, and
an initial velocity $v_0 = {{params.v0}} \rm\ m/s$, as illustrated in the figure below.
</p>
<p><pl-figure file-name="image.png" directory="clientFilesQuestion" width="300"></pl-figure></p>
<p>
Suppose the ball hits the ground a distance $d = {{params.d}} \rm\ m$ from the base of the cliff.
How long is the ball in the air?
Assume the acceleration due to gravity is $g=9.8 \rm\ m/s^2$.
</p>
</pl-question-panel>
<pl-multiple-choice answers-name="t" none-of-the-above={{params.none}}>
<pl-answer correct="true" >$t = {{params.t_c}}\rm\ s$</pl-answer>
<pl-answer correct="false">$t = {{params.t_x1}}\rm\ s$</pl-answer>
<pl-answer correct="false">$t = {{params.t_x2}}\rm\ s$</pl-answer>
<pl-answer correct="false">$t = {{params.t_x3}}\rm\ s$</pl-answer>
<pl-answer correct="false">$t = {{params.t_x4}}\rm\ s$</pl-answer>
</pl-multiple-choice>

To generate the parameters, we can use the following Python code in server.py:

import random, math
def generate(data):
# gravity (m/s^2)
g = 9.8
# mass of the ball (kg)
m = random.choice([3, 1.4, 1.6, 1.8])
# horizontal distance (m)
d = random.randint(4,16)
# angle with horizontal (in degrees)
theta = random.randint(20,40)
# initial velocity (m/s)
v0 = random.randint(18,25)
# initial velocity components (m/s)
v0x = v0*math.cos(theta*math.pi/180)
v0y = v0*math.sin(theta*math.pi/180)
# time in the air (s)
t = round(d/v0x)
# height of the cliff (m)
h = round(v0y*t + 0.5*g*t**2,3)
# storing the parameters
data["params"]["m"] = m
data["params"]["h"] = h
data["params"]["d"] = d
data["params"]["v0"] = v0
data["params"]["theta"] = theta
# determines if the option "none of the above" will be used or not
data["params"]["none"] = random.choice(["false","true"])
# this is the correct answer
data["params"]["t_c"] = t
# these are the distractors
data["params"]["t_x1"] = round(math.sqrt(2*h/g), 3)
data["params"]["t_x2"] = round(d/v0, 3)
data["params"]["t_x3"] = round(d/v0y, 3)
data["params"]["t_x4"] = round(h/v0y, 3)

The above script randomizes and computes several aspects of the question:

These values are stored in the data["params"] dictionary, which are used in the question.html template. With the addition of server.py and templated values in question.html, we can now generate many unique variants of this question.

More randomization!

Feeling adventurous? We can add even more variation to this question:

The modified question.html file that supports this additional randomization is:

<pl-question-panel>
<p>
A cannon ball with mass $m ={{params.m}}\rm\ kg$ is fired downward from a cliff at a height
$h = {{params.h}}\rm\ m$, at an angle $\theta = {{params.theta}}^o$ with respect to the horizontal, and
an initial velocity $v_0 = {{params.v0}} \rm\ m/s$, as illustrated in the figure below.
</p>
<p>
<pl-drawing gradable="false" grid-size="0" width="300" height=300>
<pl-drawing-initial>
<pl-rectangle x1=20 y1=180 width="40" height="240" color="brown"></pl-rectangle>
<pl-circle x1=40 y1=50 radius="10" color="blue"></pl-circle>
<pl-vector x1=40 y1=50 angle={{params.theta}} width="80" label="v_0"></pl-vector>
<pl-arc-dimensions x1=40 y1=50 radius="40" start-angle="0" end-angle={{params.theta}} label="\\theta" offsetx="10" offsety="5" start-support-line="true"></pl-arc-dimensions>
<pl-dimensions x1="40" y1="300" x2="40" y2="50" dim-offset="200" end-support-line="true" label="h" ></pl-dimensions>
<pl-dimensions x1="40" y1="200" x2="100" y2="200" label="d" ></pl-dimensions>
</pl-drawing-initial>
</pl-drawing>
</p>
<p>
{{params.question_text}} Assume the acceleration due to gravity is $g=9.8 \rm\ m/s^2$.
</p>
</pl-question-panel>
<pl-multiple-choice answers-name="t" none-of-the-above={{params.none}}>
<pl-answer correct="true" >{{params.t_c}}</pl-answer>
<pl-answer correct="false">{{params.t_x1}}</pl-answer>
<pl-answer correct="false">{{params.t_x2}}</pl-answer>
<pl-answer correct="false">{{params.t_x3}}</pl-answer>
<pl-answer correct="false">{{params.t_x4}}</pl-answer>
</pl-multiple-choice>

By using the <pl-drawing> element instead of <pl-figure>, we can create a dynamic image where the orientation of the arrow is consistent with the provided angle $\theta$. The image could be easily adapted such that the height of the cliff would also vary with the value of the parameter $h$.

Also note the use of {{params.question_text}} to display the randomly-picked problem statement.

As before, we'll use server.py to generate parameters for the question:

import random, math
def generate(data):
# gravity (m/s^2)
g = 9.8
# mass of the ball (kg)
m = random.choice([3, 1.4, 1.6, 1.8])
# angle with horizontal (in degrees)
theta = random.randint(20,40)
# initial velocity (m/s)
v0 = random.randint(18,25)
# initial velocity components (m/s)
v0x = v0*math.cos(theta*math.pi/180)
v0y = v0*math.sin(theta*math.pi/180)
# storing the parameters
data["params"]["m"] = m
data["params"]["v0"] = v0
data["params"]["theta"] = theta
# determines if the option "none of the above" will be used or not
data["params"]["none"] = "false" #random.choice(["false","true"])
if random.choice([0,1]): # This variant provides the distance and asks for the time
# horizontal distance (m)
d = random.randint(4,16)
# time in the air (s)
t = d/v0x
# height of the cliff (m)
h = round(v0y*t + 0.5*g*t**2,3)
data["params"]["h"] = h
# question statement
data["params"]["question_text"] = 'Suppose the ball hits the ground a distance $d = ' + str(d) + '\\rm\\ m$ from the base of the cliff. How long is the ball in the air?'
# this is the correct answer
data["params"]["t_c"] = '$t = ' + str(round(t,3)) + '\\rm\\ s$'
# these are the distractors
data["params"]["t_x1"] = '$t = ' + str(round(math.sqrt(2*h/g), 3)) + '\\rm\\ s$'
data["params"]["t_x2"] = '$t = ' + str(round(d/v0, 3)) + '\\rm\\ s$'
data["params"]["t_x3"] = '$t = ' + str(round(d/v0y, 3)) + '\\rm\\ s$'
data["params"]["t_x4"] = '$t = ' + str(round(h/v0y, 3)) + '\\rm\\ s$'
else: # This variant provides the time and asks for the distance
# time in the air (s)
t = round(random.uniform(0.5,0.8),2)
# horizontal distance (m)
d = v0x*t
# height of the cliff (m)
h = round(v0y*t + 0.5*g*t**2,3)
data["params"]["h"] = h
# question statement
data["params"]["question_text"] = 'Suppose the ball hits the ground after $t = ' + str(t) + '\\rm\\ s$. What is the distance from the base of the cliff that the ball hits the ground?'
# this is the correct answer
data["params"]["t_c"] = '$d = ' + str(round(d,3)) + '\\rm\\ m$'
# these are the distractors
data["params"]["t_x1"] = '$d = ' + str(round(v0*t,3)) + '\\rm\\ m$'
data["params"]["t_x2"] = '$d = ' + str(round(v0y*t,3)) + '\\rm\\ m$'
data["params"]["t_x3"] = '$d = ' + str(round(0.5*g*t**2,3)) + '\\rm\\ m$'
data["params"]["t_x4"] = '$d = ' + str(round(d + 0.5*g*t**2,3)) + '\\rm\\ m$'

Note the addition of code to generate a question and answers for the secondary problem statement.

Here's one instance of this fully randomized question: