# Semi Open Book Exams

A few years ago, I switched one of my first year courses to use what I call a semi-open-book approach.

Open-book exams of course allow students to bring whatever materials they wish into them, but they have the disadvantage that students will often bring in materials that they have not studied in detail, or even at all. In such cases, sifting through materials to help them answer a question could be counter productive.

On the other hand, the real world is now an increasingly “open-book” environment, which huge amounts of information available to those in the workplace which is now almost always Internet connected.

So I decided to look at another approach. Students are allowed to bring in a single, personalised, A4 sheet, on which they can write whatever they wish on both sides. There are a few rules:

• the sheet must be written on “by hand”, that is to say, it cannot be printed to from a computer, or typed;
• the sheet must be “original”, that is to say, it cannot be a photocopy of another sheet (though students may of course copy their original for reference);
• the sheet must be the student’s own work, and they must formally declare as much (with a tick box);
• the sheet must be handed in with the exam paper, although it is not marked.

The purpose of these restrictions are to ensure that each student takes a lead in producing an individual sheet, and to inhibit cottage industries of copied sheets.

In terms of what can go on the sheet? Well anything really. It can be sections from notes, important formulae, sample questions or solutions. The main purpose here is to prompt students to work out what they would individually distill down to an A4 page. So they go through all the module notes, tutorial problems and more, and work out the most valuable material that deserves to go on one A4 page. I believe that this process itself is the greatest value of the sheet, its production rather than its existence in the exam. I’m working on some research to test this.

So I email them each an A4 PDF, which they can print out at home, and on whatever colour paper they may desire. The sheet is individual and has their student number on it with a barcode, for automated processing and analysis afterwards for a project I’m working on, but this is anonymised. The student’s name in particular does not appear, since in Ulster University, it does not appear on the exam booklet.

The top of my sheet looks like this:

So, if you would like to do the same, I am enclosing the Python script, and LaTeX that I use to achieve this. You could of course use any other technology, or not individualise the sheet at all.

For convenience the most recent code will also be placed on a GitHub repository here, feel free to clone away.

My script has just been rewritten for Python 3.x, and I’ve added a lot of command line parameters to decouple it from me and Ulster University only use. It opens a CSV file from my University which contains student id numbers, student names, and emails in specific columns. These are the default for the script but can be changed. For each student it uses LaTeX to generate the page. It actually creates inserts for each student of the name and student number, you can then edit open-book.tex to allow the page to be as you wish it. You don’t need to know much LaTeX to achieve this, but ping me if you need help. I am also using a LaTeX package to create the barcodes automatically.

I’ve spent a bit of time adding command line parameters to this script, but you can try using

python3 open-book.py --help

for information. The script has been rewritten for Python 3. If you run it without parameters it will enter interactive mode and prompt you.

I’d strongly recommend running with the –test-only option at first to make sure all looks good, and opening open-book.pdf will show you the last generated page so you can see it’s what you want.

Anyway, feel free to do your own thing, or mutilate the code. Enjoy!

#!/usr/bin/env python

#
#
# Free and Open Source Software under GPL v3
#
import argparse

import csv
import re
import subprocess
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def process_student(args, row):
"""Takes a line from the CSV file, you will likely need to edit aspects of this."""
'kicks of the processing of a single student'
student_number = row[args.student_id_column]
student_name = row[args.student_name_column]
student_email = row[args.student_email_column]
print('  Processing:', student_name , ':', student_email)
create_latex_inserts(student_number, student_name)
create_pdf()
send_email(args, student_name, student_email)

def create_latex_inserts(student_number, student_name):
"""Write LaTeX inserts for the barcode and student name

For each student this will create two tiny LaTeX files:

* open-book-insert-barcode.tex which contains the LaTeX code for a barcode representing the student number
* open-book-insert-name.tex which will contain simply the student's name

These files can be included/inputted from open-book.tex as desired to personalise that document

student_number is the ID in the students record system for the student
student_name is the name of the student"""

# Open a tiny LaTeX file to put this in
file = open('open-book-insert-barcode.tex', 'w')

# All the file contains is LaTeX to code to create the bar code
string = '\psbarcode{' + student_number + '}{includetext height=0.25}{code39}'
file.write(string)
file.close()

# The same exercise for the second file to contain the student name
file = open('open-book-insert-name.tex', 'w')
string = student_name
file.write(string)
file.close()

def create_pdf():
"""Calls LaTeX and dvipdf to create the personalised PDF with inserts from create_latex_inserts()"""

# Suppress stdout, but we leave stderr enabled.
subprocess.call("latex open-book", stdout=subprocess.DEVNULL, shell=True)
subprocess.call("dvipdf open-book", stdout=subprocess.DEVNULL, shell=True)

def send_email(args, student_name, student_email):
"""Emails a single student with the generated PDF."""
#TODO: Might be useful to improve the to address
#TODO: Allow subject to be tailored.

subject = args.email_subject
# to_address = student_name + ' <' + student_email + '>'

msg = MIMEMultipart()
msg['Subject'] = subject

text = 'Dear Student\nPlease find enclosed your guide sheet template for the exam. Read the following email carefully.\n'
part1 = MIMEText(text, 'plain')
msg.attach(part1)

# Open the files in binary mode.  Let the MIMEImage class automatically
# guess the specific image type.
fp = open('open-book.pdf', 'rb')
fp.close()

msg.attach(img)

# Send the email via our own SMTP server, if we are not testing.
if not args.test_only:
s = smtplib.SMTP(args.smtp_server)
s.quit()

def override_arguments(args):
"""If necessary, prompt for arguments and override them

Takes, as input, args from an ArgumentParser and returns the same after processing or overrides.
"""

# If the user enabled batch mode, we disable interactive mode
if args.batch_mode:
args.interactive_mode = False

if args.interactive_mode:
override = input("CSV filename? default=[{}] :".format(args.input_file))
if len(override):
args.input_file = override

override = input("Student ID Column? default=[{}] :".format(args.student_id_column))
if len(override):
args.student_id_column = int(override)

override = input("Student Name Column? default=[{}] :".format(args.student_name_column))
if len(override):
args.student_name_column = int(override)

override = input("Student Email Column? default=[{}] :".format(args.student_email_column))
if len(override):
args.student_email_column = int(override)

override = input("Student ID Regular Expression? default=[{}] :".format(args.student_id_regexp))
if len(override):
args.student_id_regexp = override

override = input("SMTP Server? default=[{}] :".format(args.smtp_server))
if len(override):
args.smtp_server = override

override = input("Email subject? default=[{}] :".format(args.email_subject))
if len(override):
args.email_subject = override

override = input("Email sender address? default=[{}] :".format(args.email_sender))
if len(override):
args.email_sender = override

return(args)

def parse_arguments():
"""Get all the command line arguments for the file and return the args from an ArgumentParser"""

parser = argparse.ArgumentParser(
description="A script to email students study pages for a semi-open book exam",
epilog="Note that column count arguments start from zero."

)

action='store_true',
dest='batch_mode',
default=False,
help='run automatically with values given')

action='store_true',
dest='interactive_mode',
default=True,
help='prompt the user for details (default)')

dest='input_file',
default='students.csv',
help='the name of the input CSV file with one row per student')

dest='student_id_column',
default=1,
help='the column containing the student id (default 1)')

dest='student_name_column',
default=2,
help='the column containing the student name (default 2)')

dest='student_email_column',
default=9,
help='the column containing the student email (default 9)')

dest='student_id_regexp',
default='B[0-9]+',
help='a regular expression for valid student IDs (default B[0-9]+)')

dest='smtp_server',
default='localhost',
help='the address of an smtp server')

dest='email_subject',
help='the subject of emails that are sent')

dest='email_sender',
help='the sender address from which to send emails')

action='store_true',
dest='test_only',
default=False,
help='do not send any emails')

args = parser.parse_args()

# Allow for any overrides from program logic or interaction with the user
args = override_arguments(args)
return(args)

def main():
"""the main function that kicks everything else off"""

print("Hello")
args = parse_arguments()

print("Starting open-book...")
print(args)

student_count = 0
# Go through each row
student_number = row[args.student_id_column]
# Check if the second cell looks like a student number
if re.match(args.student_id_regexp, row[args.student_id_column]):
student_count = student_count + 1
process_student(args, row)
else:
print('  Skipping: non matching row')

print('Stopping open-book...')

if __name__ == '__main__':
main()


I use a LaTeX template for the base information, this can be easily edited for taste.

\documentclass[12pt,a4paper]{minimal}
\usepackage[latin1]{inputenc}
\usepackage{pst-barcode}
\usepackage[margin=2cm]{geometry}

%
% Does it all have to be Arial now? <sigh>
%
\renewcommand{\familydefault}{\sfdefault}

\author{Professor Colin Turner}
\begin{document}
\begin{centering}
\textbf{EEE122 Examination Guide Sheet}

This sheet, and its contents that you have added, can be brought into
the examination for EEE122. The contents \textbf{must} be compiled
by yourself, be handwritten, and be original (i.e. \textbf{NOT}
photocopied or similar). You may use the
reverse side. You may retain a copy you have made before the examination
but the original must be handed in with your examination scripts at the

%\input{open-book-insert-name.tex}
% D'Oh! Not supposed to put a name on anything going in the exam.
\input{open-book-insert-barcode.tex}
\hfill
\begin{pspicture}(7,1in)
%\psbarcode{
%\input{./open_book_insert.tex}
%B00526636
%}{includetext height=0.25}{code39}
\input{open-book-insert-barcode.tex}
\end{pspicture}
\end{centering}

\vfill
\begin{centering}
Please read the following declaration and tick the box to indicate you agree:

I declare this sheet to have been compiled by myself and not by another, and that the student number above is mine.

%\rule[-1 cm]{10 cm}{1 pt}
\framebox[0.3 cm]{ }

\end{centering}

\end{document}


# Academic Family Tree, LaTeX and Tikz

A few years ago, I found out some information on my academic genealogy, going through my supervisor, Brian McMaster, back through others to G.H. Hardy, Newton and Galileo and a little further before the records run dry. Of course it is nice that mathematics is an old and well established discipline with great records. And also, the number of scholars back then was much lower, so it’s not surprising that famous names turn up quite quickly, if you just have the full records – just as on a less cerebral level – the clichÃ© goes that Charlemagne got about quite a bit. A bit of work with my mathematical colleagues all show such interesting family trees.

Anyway, I slowly built an SVG file in Inkscape to reflect this family tree, but it was slow and rather a pain to maintain. So I’ve long wondered if it can be done better in LaTeX, with the Tikz graphics packages. Tikz is awesome, and while it has quite a learning curve as for LaTeX itself, I used it extensively in producing the slides for my recent inaugural lecture. This meant mixing the formulae with the graphics was pretty effortless. This is a slightly different problem.

A quick scan, showed that Tikz can be used to build flowcharts rather easily. So I began in this way, building nodes of people and then positioning them relative to each other. A bit tedious but much more pleasing that doing it in a vector graphics program, for me anyway. I rapidly ran into problems in building very large page styles. In particular odd things happened when I tried to include graphics (the image became the whole page, regardless of scaling).

I experimented briefly with the tikzposter package, but couldn’t really get anywhere with it – perhaps because I hadn’t started from the ground up with it. I think I looked at beamerposter but again ran into problems, possibly because I didn’t start that way. In the end it was the simple a0poster package that did it for me, anyway – although I had to force the font throughout to be “\tiny”.

I also discovered the hard way that the wrapfig package doesn’t seem to work within a Tikz node, so I’ve messily improvised by adding another node for the images of people and relating its position to the relevant biographical section.

I harvested some basic biographical information from Wikipedia and one other website.

I managed to get something basic working. The image is big so the version below is blurry. A link to the smaller PDF and the LaTeX source on GitHub are shown below.

FamilyTree

# Upgrading from Serendipity to WordPress on Debian

As you may have noticed, I have upgraded from Serendipity, which was creaking a bit, and seems to no longer be supported by Debian to WordPress. It was a moderately complex task, as I wanted to preserve backwards compatibility and a lot of content with mathematics and code.

I installed the Debian package, and tried to follow the instructions on the Debian wiki but they are perhaps out of date. I got an error trying to setup the database, but found it was there and functional.

IÂ  then used this excellent script to help import the old serendipity data. It wasn’t without problems, the script needed to be placed (on the Debian installed package) under /var/lib/wordpress/wp-content/plugins/ within a directory to be registered by WordPress as a plugin, but I got that working in the end.

This was an attempt to preserve ID fields as well. It seems to have worked – which has simplified redirects (see below). Comments have been “flattened” as the script warned, and there’s clearly a character-set issue here and there, but these weren’t serious issues for me. Your mileage may vary.

I found a good plugin for GeHSI style code formatting which I was using in Serendipity, albeit the syntax is very slightly different so I have some work to do editing a few entries (I don’t want to attempt a global SQL regexp find and replace if I don’t have to). I found this excellent seeming plugin for Latex and switched it into site-wide mode. So far, checking a few old articles, it JustWorks (TM).

Some of my old posts have images in the serendipity media folders that will need moved, but I was keen to have links to the old blog redirect automatically. I was able to use

RedirectMatch permanent ^/serendipity/archives/([0-9]+).* http://www.piglets.org/blog/?p=\$1