Maths with QTI – Item Body

The itemBody section is where the question text, interactions, buttons and feedback are defined. Questions, solutions, etc will vary hugely depending on the question being asked, but I have included some standard sections that tend to be used regularly.

Question Text

Question text displaying an equation using variables defined using Maxima in templateProcessing:

<div class="QTEXT">
   <p>Solve the equation
      <m:math display="block">
         <m:mrow>
            <m:mi>mLHS1</m:mi>
            <m:mo>=</m:mo>
            <m:mi>mRHS1</m:mi>
         </m:mrow>
      </m:math>
      giving your answer as an integer or as a fraction in its lowest terms.
   </p>
</div>

Displaying Variables

Maths variable: <printedVariable identifier="mVar"/>
Integer: <printedVariable identifier="oQansVars" format="%.0f"/>
Number (to 2 decimal places – change the number before the f in the format attribute to change the number of decimal places): <printedVariable identifier="oQansVars" format="%.2f"/>

Interactions

Text/numerical entry interaction

<textEntryInteraction responseIdentifier="RESPONSE" expectedLength="2" />

Mathematical expression entry interaction

<customInteraction id="customInteraction1" ma:syntax="text/x-maxima" ma:printIdentifier="printMath" responseIdentifier="RESPONSE"/>

Show Hint button

<feedbackBlock showHide="show" identifier="askhint" outcomeIdentifier="ASKHINT">
   <p>
      <endAttemptInteraction responseIdentifier="HINTREQUEST" title="Show Hint"/>
   </p>
</feedbackBlock>

Show Solution button

<feedbackBlock showHide="show" identifier="asksolution" outcomeIdentifier="ASKSOLUTION">
   <p>
      <endAttemptInteraction responseIdentifier="SOLREQUEST" title="Show Solution"/>
   </p>
</feedbackBlock>

Feedback

Correct

<feedbackBlock outcomeIdentifier="FEEDBACK" identifier="RESPONSE-CORRECT" showHide="show">
   <div>Correct</div>
</feedbackBlock>

Incorrect

<feedbackBlock outcomeIdentifier="FEEDBACK" identifier="RESPONSE-INCORRECT" showHide="show">
   <div>Incorrect, try again. <br /><br /> </div>
</feedbackBlock>

No answer entered

<feedbackBlock outcomeIdentifier="FEEDBACK" identifier="RESPONSE-NULL" showHide="show">
   <div>Please enter an answer<br /><br /></div>
</feedbackBlock>

Hint

<feedbackBlock identifier="HINT" outcomeIdentifier="FEEDBACK" showHide="show">
   <div>Solution goes here<br /><br /></div>
</feedbackBlock>

Solution

<feedbackBlock identifier="SOLUTION" outcomeIdentifier="FEEDBACK" showHide="show">
   <div>Solution goes here<br /><br /></div>
</feedbackBlock>

MathML

I won’t cover MathML in any detail here, but the University of Edinburgh’s LaTeX to MathML converter is a very useful tool to help with this. This section contains a few tips for achieving some things that aren’t immediately obvious in MathML.

Greek Letters in MathML

These must be represented by the HTML decimal (e.g. &#913;) or hex codes (e.g. &#x391;), not the HTML entity codes (e.g. &Alpha;). Here’s a full list of codes.

Operators in MathML

Times symbol: <m:mo>&#xd7;</m:mo>
Space: <m:mi>&#160;</m:mi>

Maths with QTI – Template Processing

Please note that this is a work in progress! I have added blocks that I just happen to have been using since I started writing this post, so they are not necessarily being added ‘simplest’ first. I will continue to add more as and when I use them.

After declaring all of the variables in an assessment item in a QTI XML file, the next step is to do the template processing. This is where variables are set or randomised, answers are calculated etc. There are too many possibilities for this section to get anywhere near specifying everything that is possible, and I won’t cover what can be done using Maxima in this post, but hopefully some of the blocks of code here can be used as they are, or will act as good starting points for doing other things.

templateProcessing Start and End

<templateProcessing>
   <!-- Template processing goes here -->
</templateProcessing>

Set Fixed values

Setting a Fixed Integer value

<setTemplateValue identifier="iA">
   <baseValue baseType="integer">2</baseValue>
</setTemplateValue>

Setting a Fixed Float value

<setTemplateValue identifier="fFloat">
   <baseValue baseType="float">3.14159</baseValue>
</setTemplateValue>

Set Randomised values

Set a positive Randomised Integer value from a range

<setTemplateValue identifier="iA">
   <randomInteger min="2" max="9"/>
</setTemplateValue>

Set a positive or negative Randomised Integer value from a range

<setTemplateValue identifier="iC">
   <product>
      <randomInteger min="1" max="10"/>
      <randomInteger min="-1" max="1" step="2"/>
   </product>
</setTemplateValue>

Note that the step attribute means that the second randomInteger will be selected only every other number in the specified range. In this case, that means it can only be -1 or 1, and not 0, so this defines whether the value is positive or negative.

Set an integer to a random value from within a range

<setTemplateValue identifier="iB">
   <random>
      <multiple>
         <baseValue baseType="integer">2</baseValue>
         <baseValue baseType="integer">3</baseValue>
         <baseValue baseType="integer">5</baseValue>
         <baseValue baseType="integer">8</baseValue>
         <baseValue baseType="integer">13</baseValue>
         <baseValue baseType="integer">21</baseValue>
      </multiple>
   </random>
</setTemplateValue>

Rounding

Round to a number of significant figures

<setTemplateValue identifier="fFloatRounded">
   <roundTo roundingMode="significantFigures" figures="3">
      <variable identifier="fFloat"/>
   </roundTo>
</setTemplateValue>

Round to a number of decimal places

<setTemplateValue identifier="fFloatRounded">
   <roundTo roundingMode="decimalPlaces" figures="2">
      <variable identifier="fFloat"/>
   </roundTo>
</setTemplateValue>

 

Constraints

Set a value to not equal a fixed value

<templateConstraint>
   <not>
      <equal toleranceMode="exact">
         <variable identifier="iA"/>
         <baseValue baseType="integer">7</baseValue>
      </equal>
   </not>
</templateConstraint>

Ensure Greatest Common Denomiator of 2 values is 1 (i.e. the values share no common factors other than 1)

<templateConstraint>
   <equal toleranceMode="exact">
      <gcd>
         <variable identifier="iA"/>
         <variable identifier="iB"/>
      </gcd>
      <baseValue baseType="integer">1</baseValue>
   </equal>
</templateConstraint>

Ensure 2 values are not equal

<templateConstraint>
   <not>
      <equal toleranceMode="exact">
         <variable identifier="iA"/>
         <variable identifier="iB"/>
      </equal>
   </not>
</templateConstraint>

Ensure one value is larger than another

<templateConstraint>
   <gt>
      <variable identifier="iB"/>
      <variable identifier="iC"/>
   </gt>
</templateConstraint>

Constraint to ensure a – (b + c) != 0, i.e. a != (b + c)

<templateConstraint>
   <not>
      <equal toleranceMode="exact">
         <subtract>
            <variable identifier="iA"/>
            <sum>
               <variable identifier="iB"/>
               <variable identifier="iC"/>
            </sum>
         </subtract>
         <baseValue baseType="integer">0</baseValue>
      </equal>
   </not>
</templateConstraint>

Conditions

Set iB to equal 6, 8 or 10 if iA < 5, otherwise iB equal to 2, 4 or 6

<templateCondition>
   <templateIf>
      <lt>
         <variable identifier="iA"/>
         <baseValue baseType="integer">5</baseValue>
      </lt>
      <setTemplateValue identifier="iB">
         <randomInteger min="6" max="10" step="2"/>
      </setTemplateValue>
   </templateIf>
   <templateElse>
      <setTemplateValue identifier="iB">
         <randomInteger min="2" max="6" step="2"/>
      </setTemplateValue>
   </templateElse>
</templateCondition>

Constants

<mathConstant name="pi"/>

Maxima Script Dummy Variable

I won’t cover what is possible with Maxima and hence what goes in this variable, but this is the code used for setting the dummy variable:

<setTemplateValue identifier="tDummy">
   <customOperator class="org.qtitools.mathassess.ScriptRule" ma:syntax="text/x-maxima">
      <baseValue baseType="string"><![CDATA[
         mAns : ev(iA*x^2iB,simp);
      ]]></baseValue>
   </customOperator>
</setTemplateValue>

Maths with QTI – Issues

Please note that this is a work in progress, and we will add issues as we find them, or remove them as they are fixed (or we find out that they aren’t really issues).

As discussed in other posts, we’re using QTI (Question and Test Interoperability) to create and deliver maths questions. The majority of the questions are being written by modifying (directly in the XML) questions from Dr Sue Milne’s collection. Uniqurate is being used to put questions together into tests, and QTIWorks is being used to deliver the questions.

We have found all the parts of the system – Uniqurate, QTIWorks and the QTI specification in general – very useful, usable and flexible. However, as is inevitable with such a complex and extensive specification and combination of creation and delivery systems, we have come across some issues. None of these are major problems that we cannot work with or around, but I thought I would continue my recent theme of writing “work in progress” blogs, and write one containing all of the issues that we have with each part of the system/process.

QTI (inc. Maxima, MathAssess engine)

  • Rounding floats doesn’t seem to work properly. After rounding, there seems to often be an arbitrary (small) value added to or subtracted from the rounded value, giving a value to >10 decimal places. For example, rounding 41.28947 to 2 decimal places, which should give 41.29, might give 41.29000014723.
  • There can be issues with recognising 2 equally valid correct answers as such. For example, if 1/x^3 is the correct answer to a question, x^-3 might be considered not fully simplified, even though it is at least as simple and arguably simpler. Another example would be requiring -(3x^2)/y, when (-3x^2)/y or -3x^2/y, or even -3x^2y^-1 would be equally correct. It might be possible to fix this within the XML, but would ideally be built into the Maths comparison engine.

Uniqurate

  • Questions are sometimes added to the wrong section of a test, or are not added at all (the latter seems to happen after a question has just been deleted from the test section). Generally returning to the Main Menu and then editing the test again gets rid of the issue temporarily.
  • It would be great to be able to add multiple questions to a test section in one go, rather than having to add them one at a time.
  • It is possible (and not that difficult) to create invalid combinations of of the “Number of questions to choose from section” and “Show questions only once/Repeat Questions” settings. If you tell it to show more questions that you have added, but leave it set to use each question only once, the test is invalid. It would be good if Uniqurate stopped you from doing this. To me, it seems as though there is significant overlap between these settings, at least when you tell it to show more question instances that there are questions in the section.
  • Can only specify the total number of questions in a section, not the number of each question.
  • The “Add question to section” link only works when the mouse cursor is an arrow. Depending on the browser (I’m talking about IE10 on Windows), the pointer will be one of an arrow, a pointer (hand with pointed figure) or a text cursor when you hover over the “Add question to section” link, depending on which part of the link you are over. It only seems to actually work as a link when it is the arrow.
  • Related to the above point, if you choose to show more questions than there are in the section, and hence set it to repeat questions, you are not guaranteed one of each question. E.g. if you have 2 question, and choose to show 3 questions, you could get 3 of one question and none of the other.
  • IDs given to sections are random strings, which makes them hard to identify when looking at results in QTIWorks. It would be good to be able to specify the ID for a section.
  • If questions are repeated in a section, the total score for the test is calculated as if each question was to be shown only once.

QTIWorks

  • The maths input help box could do with some improvements – see my post about a Help Guide for Maths Entry Questions.
  • Would like to be able to display multiple questions on a single page, rather than one question per page. This would be possible to set up by creating multiple questions in a single XML file, but it would be much nicer to be able to do it through the delivery settings.
  • Right clicking on a MathML block, then going to Settings > Math Renderer > MathML sometimes shows a simplified version of what is displayed, which for some questions (e.g. division of fractions) might actually be the answer. While it is unlikely someone would realise and do this, and it is only for certain questions where it would give the answer, it is something that could be looked at.
  • When a single part question is answered, it shows as “Answered” (in blue). However, when a multi-part question is answered, it is shown as “Finished” (in grey).
  • Similarly, if you answer a question (so it is marked as “Answered” and in blue), but then look at the solution, it is then marked as finished. This makes sense if the answer given was incorrect, but if the answer was correct and then the student looked at the solution, it would be good if the status remained as “Answered”.

Fixed/Non Issues

  • Would like scores, feedback and/or solutions to be shown when reviewing a test – Feedback and model solutions can be shown when reviewing by adding the following to the testPart section in the assessment.xml file for a test:
    <itemSessionControl showFeedback="true" showSolution="true" allowReview="true" />

    showFeedback means that the last feedback that the user was shown when attempting the question is shown when they review it. showSolution provides a “Show Solution” button, which puts the correct response (as defined in the response declaration, or by using setCorrectResponse in templateProcessing) in the answer box. However, it seems that if a maths variable is set as the correct response to a question, this will not  be displayed properly as the solution when reviewing.

Maths with QTI – Variable Declarations

Please note that this is a work in progress!

The first thing that you need to do in an assessment item, in a QTI XML file, is to declare your variables. These declarations come in 3 types – responseDeclarationoutcomeDeclaration and templateDeclaration.

responseDeclarations

This is where you declare variables used for responses and response processing. The following should be useful building blocks:

User Response

These are variables used to store the user’s response to a question.

<responseDeclaration identifier="RESPONSE_INTEGER" cardinality="single" baseType="integer"/><!-- integer response -->
<responseDeclaration identifier="RESPONSE_FLOAT" cardinality="single" baseType="float"/><!-- float response -->
<responseDeclaration identifier="RESPONSE_STRING" cardinality="single" baseType="string"/><!-- string response -->
<responseDeclaration identifier="RESPONSE_MATHS" cardinality="record"/><!-- maths response -->

printMath

This is a variable into which Presentation MathML form of a RESPONSE expression is copied.

<responseDeclaration identifier="printMath" cardinality="single" baseType="string"/>

Hint and Solution Request

These can be set to true when the hint or solution is requested, and should be tested at the start of the responseProcessing.

<responseDeclaration identifier="HINTREQUEST" cardinality="single" baseType="boolean"/>
<responseDeclaration identifier="SOLREQUEST" cardinality="single" baseType="boolean"/>

outcomeDeclarations

These are used for identifying and recording the outcomes from a user’s interactions, e.g. score and feedback.

Correct Response

These can be used to store the correct response to a question, for comparison with the user’s response.

<outcomeDeclaration identifier="iAnswer" cardinality="single" baseType="integer"/>
<outcomeDeclaration identifier="mAnswer" cardinality="record"/>

Score and Feedback

<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float" normalMaximum="2">
   <defaultValue>
      <value>0</value>
   </defaultValue>
</outcomeDeclaration>
<outcomeDeclaration identifier="FEEDBACK" cardinality="multiple" baseType="identifier"/><!-- multiple cardinality - container for multiple values. For feedback, this means we can show multiple feedback sections at once -->
<outcomeDeclaration baseType="identifier" cardinality="single" identifier="EMPTY"/><!-- Empty value, for emptying FEEDBACK container -->

Hint and Solution Related

<outcomeDeclaration baseType="boolean" cardinality="single" identifier="seenSolution">
   <defaultValue>
      <value>false</value>
   </defaultValue>
</outcomeDeclaration>
<outcomeDeclaration baseType="boolean" cardinality="single" identifier="seenHint">
   <defaultValue>
      <value>false</value>
   </defaultValue>
</outcomeDeclaration>
<outcomeDeclaration baseType="identifier" cardinality="single" identifier="ASKHINT">
   <defaultValue>
      <value>askhint</value>
   </defaultValue>
</outcomeDeclaration>
<outcomeDeclaration baseType="identifier" cardinality="single" identifier="ASKSOLUTION">
   <defaultValue>
      <value>asksolution</value>
   </defaultValue>
</outcomeDeclaration>

templateDeclarations

These are used for defining and randomising variables used in the question itself.

Template Variables

<templateDeclaration identifier="iInt" cardinality="single" baseType="integer" mathVariable="true"/><!-- Integer -->
<templateDeclaration identifier="fFloat" cardinality="single" baseType="float" mathVariable="true"/><!-- Float -->
<templateDeclaration identifier="sString" cardinality="single" baseType="string" mathVariable="true"/><!-- String -->
<templateDeclaration identifier="mX" cardinality="record" mathVariable="true"/><!-- Maths -->

Use mathVariable=”true” to enable the variable to be used in MathML expressions.

Maxima Script Dummy Variable

<templateDeclaration identifier="tDummy" cardinality="single" baseType="boolean"/>

Maths with QTI – QTI XML Structure and Building Blocks Resource

We have recently started using QTI, specifically Uniqurate and QTIWorks, to write maths questions for delivery to students. Uniqurate can be used to write basic questions, including maths questions with numerical answers and simple randomisation of values. However, to incorporate more complex randomisation, e.g. randomising variable letters, to allow mathematical expressions to be given as answers and to do various other more complex things with maths questions, it is necessary to write (or, preferably, modify someone else’s!) QTI XML. This can get quite complicated and I have found some aspects of it difficult to get stuck into. I find that I know that something is possible, but it can take some time to pin down exactly how to do it, and I usually copy code from other questions. (Many thanks to Dr Sue Milne from ELandWeb for providing extensive help, both by giving access to her questions and answering many QTI-related queries)

One thing that I keep wishing for is a resource containing reusable blocks of QTI code that could be used to build a question. I believe that such a resource does not currently exist, so I have decided to start to write one, the links to which can be found below.

At this point I should make the disclaimer that I am still early on in my QTI journey, so I am no expert, and can by no means guarantee that this resource will be correct, but I will try to improve and extend it as I get to know the QTI specification better. The posts based on the XML snippets that I have been using, and therefore they are far from being a complete resourse. However, I hope they are useful to anyone getting started with QTI. Please feel free to suggest additions and corrections.

  1. QTI XML Basic Structure
  2. Variable Declarations
  3. Template Processing
  4. Item Body
  5. Response Processing
  6. Solution and Hint
  7. Modifying assessment.xml (for Assessments created in Uniqurate)

Maths with QTI – QTI XML Basic Structure

Please note that this is a work in progress!

This is the first of my QTI XML Building Blocks posts, where I will give the basic structure of an assessment item in a QTI XML file, that can be used as a framework that can be filled in with the building blocks that I will cover in future posts. This is very much a work in progress and I can’t promise everything is correct. At this stage I am just trying to create something that is useful to me, and putting it out there in case it is useful to anyone else!

So, here is the basic structure of a QTI XML document (or the basic structure I’ve been using/copying for writing questions):

<?xml version="1.0" encoding="UTF-8"?>

<!-- Define question properties -->
<!-- Some of these are obvious, others are XML namespace definitions - not all of these are generally needed 
   adaptive - if set to false, only one attempt is allowed, so this is best for summative items. If set to true, multiple attempts are allowed and the feedback/outcomes can be changed, and this is better for formative items, where a user can have multiple goes. This should not be confused with an adaptive test, where the questions presented change depending on a user's answers to previous questions, although a similar thing can be achieved within an adaptive item.
-->
<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"
   xmlns:m="http://www.w3.org/1998/Math/MathML"
   xmlns:xi="http://www.w3.org/2001/XInclude"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:ma="http://mathassess.qtitools.org/xsd/mathassess"
   xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 imsqti_v2p1.xsd http://mathassess.qtitools.org/xsd/mathassess mathassess.xsd">
   xml:lang="en"
   adaptive="true"
   timeDependent="false"
   identifier="questionidentifier"
   title="Question Title">

   <!-- VARIABLE DECLARATIONS -->
   <!-- Here we define all of the variables that are going to be used in the question -->

   <!-- Response Declarations -->
   <!-- These are the variables used in response processing, including those for storing the response(s) given by the user -->
   <responseDeclaration identifier="RESPONSE" cardinality="single" baseType="integer"/>

   <!-- Outcome Declarations -->
   <!-- These are the variables used for returning an outcome to the user, e.g. score, feedback -->
   <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="integer"/>

   <!-- Template Declarations -->
   <!-- These are the variables used for creating the question, e.g. the variables used for randomising values within a question -->
   <templateDeclaration identifier="iA" cardinality="single" baseType="integer" mathVariable="true"/>

   <!-- TEMPLATE PROCESSING -->
   <templateProcessing>
      <!-- Here template variables are assigned values, constraints are defined etc -->
   </templateProcessing>

   <!-- ITEM BODY -->
   <itemBody>
      <!-- The itemBody contains the content of the item, including the question, interactions, feedback sections (which are initially hidden) etc  -->
   </itemBody>

   <!-- RESPONSE PROCESSING -->
   <responseProcessing>
      <!-- Here the user's response is assessed, compared the correct answer etc, and outcome variable, e.g. score and which feedback to show, are set -->
   </responseProcessing>
</assessmentItem>

Maths with QTI – Help Guide for Maths Entry Questions

This is intended as a simple guide to entering mathematical expressions as answers to questions delivered using QTIWorks. Any comments about its usefulness/helpfulness would be most welcome.

Note that you can also view an input hints box by clicking on the blue question mark symbol help_questionmark next to the maths input box. You can also watch a brief video showing you how to enter mathematical answers.

This guide contains the following sections:

The Maths Input box ^ Top

When a mathematical expression is required as an answer to a question, a text box within a larger grey box is shown. When text is typed into the text box, it is interpreted mathematically, and the resulting mathematical expression is shown:

mathsinputbox_mod

If you click on the blue question mark symbol help_questionmark, an input hints box will pop up.

Basic Operators (+ – * / =) ^ Top

The basic mathematical operators can be represented by words or symbols, as shown in the table below.

Operation Symbol Word
Addition +
Subtraction
Multiplication * times
Division / divide
Equals =

note that multiplication is implied between a coefficient and a variable, e.g. 2x = 2*x, and between consecutive variables, i.e. letters, unless the letters make up a special character, e.g. pa = p*a, but pi = π, not p*i.

Other Operators (± > ≥ < ≤ ≠) ^ Top

Other operatores, e.g. comparison operators, can be represented as shown in the table below.

Operation Input Interpretation
Plus or minus +- ±
Greater than > >
Greater than or equal to >=
Less than < <
Less than or equal to <=
Not equal !=

Precedence (order of operations) ^ Top

The order of precedence, i.e. the order in which operations are performed/interpreted, follows the usual rules:

  1. brackets
  2. roots and exponentials
  3. multiplication and division
  4. addition and subtraction
Input Interpretation Notes
2+3*4 precedence_nobrackets equals 14
2+(3*4) precedence_unnecessarybrackets equals 14, brackets are unnecessary
(2+3)*4 precedence_brackets equals 20, brackets are required

Special Symbols and Functions ^ Top

Generally, letters just represent variables, and multiplication between consecutive letters is implied (see Basic Operations above). However, there are certain letters or words that have special meaning, as follows:

Letter/Word Meaning
e e, the natural exponential, 2.71828…
i the imaginary number, √-1
alpha…lambda…pi…etc Greek letters, α, λ, π etc
log logarithm function
ln natural logarithm function
sin, cos, tan trigonometric functions
cap, cup, in, not, notin, subset, subseteq, to,
vee, wedge…and probably others
Logic and Set Theory operators

Fractions (a/b) ^ Top

Use the divide symbol,  ‘/’, between the numerator (top) denominator (bottom) of a fraction. Brackets may be needed to give the correct numerator and denominator.

Input Interpretation
1/2 half
2x+1/3x+2 fraction_no_brackets
(2x+1)/(3x+2) fraction_brackets

Brackets (a(b+c)) ^ Top

Brackets can be used as they normally would when writing an expression.

Input Interpretation Notes
sin(2x+30)  brackets_sin  brackets not needed for sin of single value, e.g. sinx gives sinx
5(x+1)(2x-3)  brackets_2
3(2x-(2-y))  brackets_nested equivalent to 3(2x-2+y)

Powers (x^a) ^ Top

Use ^ (hat/caret symbol) to represent powers:

Input Interpretation Notes
x^2 powers_xsquared    
x^(1/3) powers_xtothird fractional powers need brackets
x^-1 powers_xtominus1 negative powers do not need brackets
e^(x(2x+1)) powers_expbrackets
e^x(2x+1) powers_expnobrackets

Roots (sqrt(x) or x^(a/b)) ^ Top

Only a square root can be represented with the root symbol. All other roots must be represented as fractional powers:

Root Input Interpretation
Square root (√x) sqrtx or sqrt(x) roots_sqrtx
Cube root (∛x) x^(1/3) roots_cubertx

Subscripts (x_a) ^ Top

Use _ (underscore) to give a subscript (similar to using ^ for powers/superscripts)

Input Interpretation
x_1 subscript_x_1
y_a subscript_y_a

“Sorry, I could not make sense of your input” ^ Top

You will see this message in a number of situations (including, but not limited to):

  • When you have an operator (e.g. + – * / ^ _ =), that does not have a value/variable before and/or after it, e.g. ‘1+’, ‘*3’, ‘x/’, ‘y^’
  • When you have two consecutive operators, e.g. ‘+*’
  • When you have opened a bracket and not (yet) closed it, e.g. ‘2(x+3’

If you see this message when you believe you have entered a complete expression, make sure you check your expression carefully, in particular that you have closed all brackets.

Timezone problems/ time offset in PHP under IIS/Windows

We’ve been struggling for a while now with an unexplained three hour offset in log files and error messages created by QuestionMark Perception – commercial software which runs under PHP on IIS. I thought it would worth sharing a few gotchas in trying to pin down what was causing the problem:

1. Make sure you are looking for the

date.timezone

setting in the correct php.ini file (there are usually at least two) – you can identify which is being used by looking in IIS Manager | PHP Manager under ‘PHP Settings’ | ‘Configuration file’.

2. Use phpinfo to check what timezone PHP is reporting. You can either use the function –

phpinfo()

– in an otherwise empty php page or by choosing IIS Manager | PHP Manager and then under ‘PHP Setup’, choose ‘Check phpinfo()’.

3. Be aware that the windows PHP installer adds a section at the end of the php.ini file: 

[WebPIChanges]

. A date.timezone setting in this section will override any earlier date.timezone settings.

4. If you make any changes to php.ini, you will need to restart IIS in order for them to be picked up. At the command prompt, type

iisreset

or use IIS Manager.

5. If it’s your own code you can override the value in php.ini with:

date_default_timezone_set('Europe/London');

This is useful if you can’t restart IIS on e.g. a production server.

Using jsTree as a radio input

While adapting existing code to create a Basic LTI connector for Questionmark Perception, we needed some way of allowing administrators to choose which one of our many hundreds of assessments students using the same link should see. As the assessments are organised into folders, jsTree seemed the obvious way to present them.

jsTree does come with a plugin which allows users to tick check boxes next to each node, and there is even an option to allow only one chcek box to be selected at a time, reproducing radio button functionality. However, check boxes just don’t say ‘Pick one’ to me. Instead, we decided to use jsTree’s UI plugin and a hidden form field to deliver something that behaves as if there were a radio button next to each leaf node the tree.

The tree is created from nested unordered lists e.g:

<div id="tree_container">
    <ul>
        <li id="73053976"><a href="#">Folder 1</a>
            <ul>
                <li id="1223099925"><a href="#">Folder 1.1</a>
                    <ul>
                        <li id="7312166358986135"><a href="#">Assessment 1.1-1</a></li>
                        <li id="0272148079818008"><a href="#">Assessment 1.1-2</a></li>
                    </ul>
                </li>
                <li id="8430544147035550"><a href="#">Assessment 1-1</a></li>
                <li id="3489131659907643"><a href="#">Assessment 1-2</a></li>
            </ul>
        </li>
        <li id="759603613"><a href="#">Folder 2</a>
            <ul>
                <li id="8842542540288867"><a href="#">Assessment 2-1</a></li>
                <li id="5658598094790998"><a href="#">Assessment 2-2</a></li>
            </ul>
        </li>
    </ul>
</div>

Each list item has an id and those which contain other assessments (i.e. are folders) are given a class of folder.

There is also a form:

<form action="mypage.php" method="POST">
     <input id="selected_node_id" type="hidden" name="selected_node_id"></input> 
     <input type="submit" id="save_button" value="Save change" disabled="disabled" />
</form>

This contains a hidden input which will be used to post selected node id.

And a span to show the currently saved node_id:

<p>Currently selected:<strong><span id="selected_node_text"></span></strong></p>

The jsTree is then created, using the HTML_DATA plugin, as follows:

var dirty=false; //keep track of whether changes need to be saved
$(function () {
    $("#tree_container")
        .jstree({ 
            "plugins" : [ "themes", "html_data", "ui"],
              "themes" : {
                   "theme" : "classic",
                "dots" : true,
                "icons" : true
                  },
              "ui" : {
                  "select_limit" : 1  //only allow one node to be selected at a time
                  <?php if(isset($_SESSION['saved_node_id'])&&$_SESSION['saved_node_id']!='') echo ', "initially_select" : ['.$_SESSION['saved_node_id'].']'; ?>
                  }
            })
        .bind("select_node.jstree", function(event,data) {
            var this_node = data.rslt.obj;
            event.preventDefault(); //stop any link from being followed
            if(this_node.attr("class").indexOf('folder') != -1){
                $("#selected_node_id").val('');  //this is a folder so empty hidden input
                return data.inst.toggle_node(this_node);   //toogle closed/open state of this node to display/hide contents
                }
            else {
                $("#selected_node_id").val(this_node.attr("id")); //this is a leaf/assessment so set hidden input value to id
                if(this_node.attr("id")!=<?php echo $saved_node_id ?>){   //selected node is not the same as that previously saved so enable save and set dirty
                    document.getElementById('save_button').disabled = false;
                    dirty=true;
                    }
                else{ //selected node hasn't cnaged from that saved so diable save and unset dirty
                    document.getElementById('save_button').disabled = true; 
                    $("#selected_node_text").text(this_node.children("a").text()); //this is really only used to set selected_node_text when jsTree loads
                    dirty=false;
                    }
                }
            })
    });

Points to note:

  • "select_limit" : 1

    indicates that only one node may be selected at any one time;

  • the value of initially_select is set from the saved_node_id (which corresponds to a node/li id) if this is present in the session. This selects the previously selected node when the jsTree is loaded;
  • .bind("select_node.jstree", function(event,data) {

    deals with events when a node is selected and is also fired if initially_select is specified.

And finally, to ensure user doesn’t leave page before changes have been saved:

window.onbeforeunload = function(e) {
    if (dirty) return "You have not saved your choice.";
    }

Watch this space for how this is integrated into our new Basic LTI connector.

BasicLTI for QuestionMark Perception

Using Sakai to connect to load-balanced QuestionMark Perception using BasicLTI.

Why?

  1. Provide single sign-on capability to avoid maintaining a separate password for each user;
  2. Sakia (at Oxford) allows ‘external’ users to be created  from their email addresses. Where these users are given permission with Sakai and where they have accounts in Perception, they would then be able to access assessments.

How?

By adapting the QuestiionMark BasicLTI connector. This is a community edition project which the very helpful Steve Lay pointed us at: http://projects.oscelot.org/gf/project/lti-qm/frs/

Process as follows (PLEASE NOTE THAT I AM BLOGGING THIS AS I GO SO I APOLOGISE FOR ANY MISUNDERSTANDINGS – I’LL UPDATE AS MY LEVEL OF UNDERSTANDING INCREASES):

  1. Create DB – as we have a load-balanced setup and a clustered MSSQL DB, we had to create another db in our MSSQL server to avoid introducing a single point of failure. Create the tables using the following (adapted from http://www.spvsoftwareproducts.com/php/lti_tool_provider/)
    CREATE TABLE lti_consumer (
     consumer_key varchar(255) NOT NULL,
     name varchar(45) NOT NULL,
     secret varchar(32) NOT NULL,
     lti_version varchar(12) DEFAULT NULL,
     consumer_name varchar(255) DEFAULT NULL,
     consumer_version varchar(255) DEFAULT NULL,
     consumer_guid varchar(255) DEFAULT NULL,
     css_path varchar(255) DEFAULT NULL,
     protected tinyint NOT NULL,
     enabled tinyint NOT NULL,
     enable_from datetime DEFAULT NULL,
     enable_until datetime DEFAULT NULL,
     last_access date DEFAULT NULL,
     created datetime NOT NULL,
     updated datetime NOT NULL,
     PRIMARY KEY (consumer_key)
    );
    
    CREATE TABLE lti_context (
     consumer_key varchar(255) NOT NULL,
     context_id varchar(255) NOT NULL,
     lti_context_id varchar(255) DEFAULT NULL,
     lti_resource_id varchar(255) DEFAULT NULL,
     title varchar(255) NOT NULL,
     settings text,
     primary_consumer_key varchar(255) DEFAULT NULL,
     primary_context_id varchar(255) DEFAULT NULL,
     share_approved tinyint DEFAULT NULL,
     created datetime NOT NULL,
     updated datetime NOT NULL,
     PRIMARY KEY (consumer_key, context_id)
    );
    
    CREATE TABLE lti_user (
     consumer_key varchar(255) NOT NULL,
     context_id varchar(255) NOT NULL,
     user_id varchar(255) NOT NULL,
     lti_result_sourcedid varchar(255) NOT NULL,
     created datetime NOT NULL,
     updated datetime NOT NULL,
     PRIMARY KEY (consumer_key, context_id, user_id)
    );
    
    CREATE TABLE lti_nonce (
     consumer_key varchar(255) NOT NULL,
     value varchar(32) NOT NULL,
     expires datetime NOT NULL,
     PRIMARY KEY (consumer_key, value)
    );
    
    CREATE TABLE lti_share_key (
     share_key_id varchar(32) NOT NULL,
     primary_consumer_key varchar(255) NOT NULL,
     primary_context_id varchar(255) NOT NULL,
     auto_approve tinyint NOT NULL,
     expires datetime NOT NULL,
     PRIMARY KEY (share_key_id)
    );
    
    ALTER TABLE lti_context
     ADD CONSTRAINT lti_context_consumer_FK1 FOREIGN KEY (consumer_key)
     REFERENCES lti_consumer (consumer_key);
    
    ALTER TABLE lti_context
     ADD CONSTRAINT lti_context_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id)
     REFERENCES lti_context (consumer_key, context_id);
    
    ALTER TABLE lti_user
     ADD CONSTRAINT lti_user_context_FK1 FOREIGN KEY (consumer_key, context_id)
     REFERENCES lti_context (consumer_key, context_id);
    
    ALTER TABLE lti_nonce
     ADD CONSTRAINT lti_nonce_consumer_FK1 FOREIGN KEY (consumer_key)
     REFERENCES lti_consumer (consumer_key);
    
    ALTER TABLE lti_share_key
     ADD CONSTRAINT lti_share_key_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id)
     REFERENCES lti_context (consumer_key, context_id);
  2. Create a user on the DB whose details you will enter below.
  3. Create a new administrator in Enterprise Manager which will be used by QMWISE
  4. Create an MD5 checksum for the QMWISE user as described here: https://www.questionmark.com/perception/help/v5/product_guides/qmwise/Content/Testing/Calculating%20an%20MD5%20Checksum.htm
  5. Change settings in config.php:
    define('CONSUMER_KEY', 'testkey');define('CONSUMER_SECRET', 'testsecret');
    define('DB_NAME', 'sqlsrv:Server=localhost;Database=testdb');
    define('DB_USERNAME', 'dbusername');
    define('DB_PASSWORD', 'dbpassword');
    define('QMWISE_URL', 'http://localhost/QMWISe5/QMWISe.asmx');
    define('SECURITY_CLIENT_ID', 'ClientIDFromQMWISETestHarness');
    define('SECURITY_CHECKSUM', 'ChceksumFromQMWISETestHarness');
    define('DEBUG_MODE', true);
    define('ADMINISTRATOR_ROLE', 'LTI_INSTRUCTOR');
    define('QM_USERNAME_PREFIX', '');
    define('WEB_PATH', '/qmlti');

    DB_NAME should adhere to DSN conventions here: http://www.php.net/manual/en/ref.pdo-sqlsrv.connection.php

    SECURITY_CLIENT_ID is the administrator which is used by QMWISE

    SECURITY_CHECKSUM is the chceksu which can be generated from the QMWISE test harness

    WEB_PATH:  we needed to change to subdirectory where we had placed lti connector code

  6. As detailed here (https://www.questionmark.com/Developer/Pages/apis_documentation004.aspx) you cannot write applications yourself that provide a trust header so switch this off under Administration | Server Management | Server Settings in Enterprise Manager. Don’t forget to reset IIS (Start | All Programs | Accessories right-click Command Prompt and Run as Administrator then type
    iisreset

    ) to ensure it picks up the change in settings.

  7. Upload qmlti folder into C:\inetpub\wwwroot on both of the load-balanced servers
  8. Copy the lti.pip file into the \repository\PerceptionRepository\pip folder on the storage area shared by both load-balanced servers
  9. At this stage, I wanted to be able to launch an assessment as an IMS ‘Learner’/QM Participant from the test_harness. I couldn’t work out how to do this so made the following changes (these will probably be superseded once I have worked out how to set up the tool as an IMS ‘Instructor’ so that the correct assessment is shown based upon the User/Context_ID):
    • added a custom parameters textarea to the test_harness.php (188):
      <div class="row">
      <div class="col1">
      Custom Parameters (each on separate line; param_name=param_value)
      </div>
      <div class="col2">
      <textarea name="custom" rows="5" cols="50" onchange="onChange();" ><?php echo htmlentities($_SESSION['custom']); ?></textarea>
      </div>
      </div>

      ..and on line 45, something to save them:

      set_session('custom');
    • Something to read them and turn them into request parameters in test_launch.php (48):
      $custom_params = explode('\n', str_replace('\n\r','\n',$_SESSION['custom']));
      foreach($custom_params as $custom_param){
      if(strpos($custom_param, '=')){
      $custom_param_parts = explode('=', $custom_param);
      $params['custom_'.$custom_param_parts[0]]=$custom_param_parts[1];
      }
      }
    • And changed the parameter read in index.php (65) to look for the assessment ID with ‘custom_’ prepended as this seems to be what the specification expects:
      $assessment_id = $tool_provider->context->getSetting(custom_ASSESSMENT_SETTING);
  10. Don’t forget that you’ll need to ‘Allow run from integration’ under ‘Control settings’ for any assessment that you want to launch with QMWISE.
  11. Now pouint your browser to http://yourserver/qmlti/test_harness.php. It should pick up your key and secret automatically from config.php so, to launch an assessment as a student, all you need to do is:
    • enter the username of a valid participant in the User Details | ID box;
    • Enter the ID of an assessment for which that student is scheduled in the new custom parameters box as follows:
      ASSESSMENT_SETTING=1234567887654321
    • Click ‘Save data’ to..save your data
    • Then click Launch

It’s worth noting that doing things like this does breaks the strict separation between the various tiers as the PHP application is running on the QPLA servers and accessing the database directly.

Finally, a huge thanks to QuestionMark’s Steve Lay and to Stephen Vickers for his Basic LTI code.