Step 3: Writing the corrector

Objects needed for the corrector

For the corrector we need the following objects:

CorrectorHelper helper;//provides methods to read and modify the datasheet
//problem objects
MInteger x1,x2,d;
MOpNumber polynomial;
//answer objects
MInteger numberOfRoots;
MDouble root1,root2,answer;

The correction process

The correction process should be implemented in the correct(Datasheet inputDatasheet, Datasheet outputDatasheet) method.

The inputDatasheet contains the problem data and the user's answers, while the outputDatasheet contains the correction that will be written at the end of the correction process.

The correction normally consists of the following steps:
  1. Create the correction helper and subtask nodes
  2. Load the problem data
  3. Load the user's answers and determine wheter or not the task is edited.
  4. Compare the answer with the expected solution
  5. Write the correction and set the grade
  6. Finish the correction helper.

Steps 3 to 5 are normally repeated for each subtask of the problem.

Step 1: Create the correction helper and subtask nodes

helper = new CorrectorHelper(inputDatasheet, outputDatasheet);//create the helper 
//input datasheet contains the problem and the answer
//output datasheet contains the correction

SubtaskNode subtask1 = helper.getMarkingDocBuilder().addSubtaskNode();//create and add a subtask node to the correction
SubtaskNode subtask2 = helper.getMarkingDocBuilder().addSubtaskNode();//create and add a subtask node to the correction

The subtaskNode is an XML document node which will be used in step 5 when we write the correction of the current subtask.

Step 2: Load the problem data

Loading the problem data in the corrector is similar to loading data in the applet. Instead using the methods of the MumieExercise class we will use the methods of the CorrectorHelper.

//load the problem
x1 = new MInteger();
x2 = new MInteger();
d = new MInteger();
polynomial = new MOpNumber();

helper.loadElement("user/problem/x1", x1);
helper.loadElement("user/problem/x2", x2);
helper.loadElement("user/problem/d", d);
helper.loadElement("user/problem/polynomial", polynomial);

Steps 3-5 for task a

Loading the user answer

//load and correct subtask1
numberOfRoots = new MInteger();
root1 = new MDouble();
root2 = new MDouble();
boolean edited = false;//initialize edited flag to false
if(helper.userElementExists(1,"numOfRoots")){//answer exists?
  helper.loadUserElement(1, "numOfRoots", numberOfRoots);
  helper.loadUserElement(1, "root1", root1);
    helper.loadUserElement(1, "root2", root2);
  edited = root1.isEdited() || root2.isEdited();//edited when any roots are edited

Compare the answer with expected solution

Checking the correctness of the answer is similar to what we did in the training part of the applet.

//now determine if the answer is correct (similar to training)
boolean correct = false;//answer is correct?
boolean x1Correct = false;//x1 is in answer?
boolean x2Correct = false;//x2 is in answer?
boolean isEdited = false;//are the answers edited?
if(x1!=x2){// 2 different roots were generated
  isEdited = root1.isEdited() && root2.isEdited(); //check if both roots are edited
  x1Correct = equalCheck(x1.getDouble(), root1.getDouble()) || equalCheck(x1.getDouble(), root2.getDouble());
  x2Correct = equalCheck(x2.getDouble(), root1.getDouble()) || equalCheck(x2.getDouble(), root2.getDouble());
  correct = isEdited && x1Correct && x2Correct;//only correct if both roots are edited and both x1 and x2 are found in the answer
  correct = equalCheck(x1.getDouble(), root1.getDouble()) && root1.isEdited();//correct if root1 is edited and root1 equals x1 

Note: We also need to implement the equalCheck method in the corrector.

 * checks whether or not two real numbers are equal with 0.005 precision
 * @return true if |a-b| < 0.005
private boolean equalCheck(double a, double b){
  return Math.abs(a-b)<0.005; //check with 0.005 precision

Write the correction and set the grade

Now that we have determined the correctness of the user's answer, we can write the correction sheet for this subtask.

In the above image we see how a typical correction sheet are displayed in the mumie.
Each subtaskNode defines a row in the table and consists of the following column:
  1. Answer - here we can write the status of the answer (correct, partially correct, false, not edited) followed by the user's submitted answer
  2. Solution - here we display the expected solution
  3. Explanation - here we can add explanations and feedback for the student

Each of the column also represents an xml document node which we can access from the subtask node with the method getAnswerNode(), getSolutionNode() and getExplanationNode().

Each of the nodes provides us with the methods neeeded for writing the correction:
  1. addParagraphNode(String text) - Adds a par node which can contain a text paragraph.
  2. addMathObject(MathMLSerializable obj) - Adds a math node containing the object's content.
  3. addMathObject(String preLabel, MathMLSerializable obj) - Adds a math node containing the label and the object's content as MathML-Node.
  4. addMathObject(String preLabel, MathMLSerializable obj, String postLabel) -Adds a math node containing the first label, the object's content and the second label as MathML-Node.
  5. addMathObjects(MathMLSerializable[] objects) - Adds the content of multiple objects to a single line.

Before we begin writing the exception we should first prepare the presentation of the user's input regarding the roots of f(x). We will pack the roots in one array so that we can later use the fifth method listed above.

//prepare the roots 
MathMLSerializable[] rootPresentation; //we will pack the roots in one array
  rootPresentation = new MathMLSerializable[]{
        new MString("Root(s) of f(x) = {"),//create new MString to add text
    root1,//add the first root
    new MString("}")//create new MString to add text
  rootPresentation = new MathMLSerializable[]{
    new MString("Root(s) of f(x) = {"),
    root1,//add the first root
    new MString(","),
    root2,//add the second root
    new MString("}")

Now that we have the array, we can proceed with writing the correction:

if(correct){//in case answer is correct
  helper.addToScore(0.5);//adds half of the total points
  subtask1.getAnswerNode().addParagraphNode("Correctly solved!");
  subtask1.getAnswerNode().addParagraphNode("Your solution:");
  subtask1.getAnswerNode().addMathObject("Number of root(s) = ", numberOfRoots);
  subtask1.getAnswerNode().addMathObjects(rootPresentation);//simply add the prepared rootPresentation
  subtask1.getSolutionNode().addParagraphNode("see left.");//expected solution is the same as answer
}else{//answer is not correct
  if(edited){//display answer if task is edited
    subtask1.getAnswerNode().addParagraphNode("Not correct!");
    subtask1.getAnswerNode().addParagraphNode("Your solution:");
    subtask1.getAnswerNode().addMathObject("Number of root(s) = ", numberOfRoots);
  }else{//if not, there is nothing to display
    subtask1.getAnswerNode().addParagraphNode("Not edited!");
  //now write the expected solution
  int correctNumOfRoots = 1;
  if(x1!=x2) correctNumOfRoots = 2;
  subtask1.getSolutionNode().addParagraphNode("Correct is:");
  subtask1.getSolutionNode().addParagraphNode("Number of root(s) = "+correctNumOfRoots);
    subtask1.getSolutionNode().addParagraphNode("Root(s) of f(x) = {"+x1.getIntValue()+"}");
    subtask1.getSolutionNode().addParagraphNode("Root(s) of f(x) = {"+x1.getIntValue()+", "+x2.getIntValue()+"}");
//display the given function in the explanation code
subtask1.getExplanationNode().addParagraphNode("Given function was:");
subtask1.getExplanationNode().addMathObject("f(x) = ",polynomial);

Steps 3 to 5 for task b

Now we similarly repeat the steps for task b:

//load and correct task b
answer = new MDouble();
edited = false;
  helper.loadUserElement(2, "answer", answer);
  edited = answer.isEdited();
correct = false;
double correctAnswer = polynomial.getOperation().evaluate(d.getIntValue());
correct = edited && equalCheck(correctAnswer, answer.getDouble());
  subtask2.getAnswerNode().addParagraphNode("Correctly solved!");
  subtask2.getAnswerNode().addParagraphNode("Your solution:");
  subtask2.getAnswerNode().addMathObject("f("+d.getIntValue()+") = ", answer);
  subtask2.getSolutionNode().addParagraphNode("see left.");
    subtask2.getAnswerNode().addParagraphNode("Not correct!");
    subtask2.getAnswerNode().addParagraphNode("Your solution:");
    subtask2.getAnswerNode().addMathObject("f("+d.getIntValue()+") = ", answer);
    subtask2.getAnswerNode().addParagraphNode("Not edited!");
  //write the solution
  subtask2.getSolutionNode().addParagraphNode("Correct is:");
  subtask2.getSolutionNode().addParagraphNode("f("+d.getIntValue()+") = "+correctAnswer);

Step 6: Finish the corrector helper

The last step is to tell the correctorHelper that the correction process has finished. We should not forget to add the following code because only then the corrector will be written into the output datasheet.

helper.finish();//notifies the helper to finish and write the content into the output datasheet

Compile and Test the corrector

To compile the corrector in MIAU, select the corrector in the checkin tree and click on the compile button.

To test the corrector, you can select the problem tex in the checkin tree and click on Corrector button.

This ends the tutorial. You can find the complete source code of the corrector here

correction.png (15.9 KB) Marek Grudzinski, 04/07/2013 11:49 AM

Add picture from clipboard (Maximum size: 500 MB)