This post is about authoring questions in the Numbas e-assessment system.

I wanted to write a routine permutation drill question: express this permutation as a product of disjoint cycles, compute this composition, compute this inverse, etc. There’s a permutation extension to Numbas which will do the required operations on permutations, but I don’t see how to enforce entry of a permutation in disjoint cycle format - perm("(1,2)(2,3)") parses OK even though the docs say the string argument should be disjoint cycle notation.

My solution was to get the following code running instead of the builtin marking algorithm. This code is based on Daniel Mansfield and Laure Helme-Guizon’s work on this question.

var unwrap = Numbas.jme.unwrapValue;
var istype = Numbas.jme.isType;

try {
    var ans = this.studentAnswer;

    if (!ans) { // they've submitted an empty box
        return;}

    if (!istype(this.question.scope.evaluate(ans), "list")) {
        // numbas jme objects have a .type attribute, isType compares
        // it with a string you provide
        this.setCredit(0, "Your answer must be a list of lists, like [[1,2],[3,4]].");
    } else if (unwrap(this.question.scope.evaluate("not_disjoint(" + ans + ")" ))) {
        this.setCredit(0, "Those cycles aren't disjoint.");
    } else {
      var isok = this.question.scope.evaluate("list_of_lists_to_perm_1(" + ans + ") = s"); // s is the perm they're putting in disjoint cyc form
      if (unwrap(isok)) {
        this.setCredit(1, "Good.");
      } else {
        this.setCredit(0, "Your permutation isn't the same as the one given.");}}
         
} catch(e) {
    this.setCredit(0);  // the student's answer isn't a valid expression, give 0 credit
    this.markingComment(e);
    alert(e);}

This calls some JME functions defined as follows:

list_of_lists_to_perm_1(llis) = perm(join(map(list_to_perm_string_1(x), x, llis), ''))
list_to_perm_string_1(lis) = "(" + join(map(string(x), x, lis), ',') + ")"
not_disjoint(ll) = contains_elt_gt_1(mults(ll))
contains_elt_gt_1(ll) = length(filter(x > 1, x, ll)) > 0
mults(L) = map(sum(map(mult(j, l), l, L)), j, list(1..10))

Students have to enter [[1,2], [3,4]] for \((1,2)(3,4)\) but this isn’t any worse than perm("(1,2)(3,4)") which is what they’d need to type if the answer box used the JME permutation extension directly. For the correct answer you can use {map(map(x+1, x, l), l, nontrivial_cycles(s))} - the inner map is necessary because internally the permutations act on \(\{0, 1, \ldots, n-1\}\).