WeBWorK Problems

using contexts for grade school answers

using contexts for grade school answers

by Kenneth Appel -
Number of replies: 10
At the grade school level one wants to require answers in a form
that might be ridiculous at the college level. In particular, conversion from ratios to fractions requires that students actually
write answers as fractions. In the pre-context methods one might
use $ans3="$a3="$a3"."/"."$b3"; and then &ANS(str_cmp($ans3));

How would best form $ans3 and which context would one use
for the answer so that the / sign would be accepted (and required) but the
answer would be evaluated somewhat more flexibly.
In reply to Kenneth Appel

Re: using contexts for grade school answers

by Davide Cervone -
I think there is an error in your code sample, and that you really meant
    $ans3 = "$a3"."/"."$b3";
so that $ans4 is supposed to be a fraction.

(BTW, this is more complicated than it needs to be. $ans3="$a3/$b3"; is easier to read and just as good.)

If you want to force the student to enter a fraction, use

    Context("LimiteNumeric-StrictFraction");
    $ans3 = Compute("$a3/$b3");
    ANS($ans3->cmp);
This uses a context in which decimals are not allowed, and fractions must be entered using the division sign. No other operations (other than negation) is allowed. But unlike the string comparison you suggested, the student could answer 8/10 or 4/5 and still be marked correct.

Hope that helps.

Davide

In reply to Davide Cervone

Re: using contexts for grade school answers

by Kenneth Appel -
Davide,
I may have read too much into your response and tried to apply it too
broadly. Here is the current complete code. As you may note, I leave
LimitedNumeric-Strict Fraction because the next line originally drew an error
concerning the + sign. But WeBWorK is unhappy with my try at remedying the
problem.

4
5 DOCUMENT();
6 loadMacros(
7 "PGstandard.pl",
8 "PGchoicemacros.pl",
9 "PGgraphmacros.pl",
10 "MathObjects.pl",
11 "compoundProblem.pl",
12 "unionLists.pl",
13 "unionMacros.pl",
14 "contextInequalities.pl"
15 );
16 TEXT(beginproblem());
17 $showPartialCorrectAnswers = 1;
18 Context("Numeric");
19 $b=random( 4,8,2);
20 $a=$b*random(3,7,2);
21 $c=random(20,50,5);
22 $d1= random(3,7,1);
23 $a1= random(2,5,1);
24 $c1=random(4,9,1);
25 $b1=$c1+$d1*random(3,6,1);
26 $ans1=$a/$b+$c;
27 $ans2=$a1*($b1-$c1)/$d1;
28 @vars=('x','y','z');
29 @nums=('zero ','one ','two ','three ', 'four ', 'five ', 'six ',
30 'seven ', 'eight ', 'nine ');
31 @ops=('multiplied by ','divided by ', ' added to ', 'subtracted from ');
32
33 $a3=random(6,9,1);
34 $b3=random(2,5,1);
35 $c3=random(1,3,1);
36 Context("LimitedNumeric-StrictFraction");
37 if($c3==1){$ans3=Compute("$a3/$b3");}
38 Context("LimitedNumeric");
39 elsif ($c3==2){$ans3=Compute("$a3+$b3");}
40 elsif ($c3==3){$ans3=Compute("$a3-$b3");}
41 else {$ans3=Compute("$a3*$b3");}
42 $a4=random(6,9,1);
43 $b4=random(2,5,1);
44 $c4=random(1,3,1);
45
46 Context("LimitedNumeric-StrictFraction");
47 if($c4==1){$ans4=Compute("$a4/$b4");}
48 Context("LimitedNumeric");
49 elsif ($c4==2){$ans4=Compute("$a4+$b4");}
50 elsif ($c4==3){$ans4=Compute("$a4-$b4");}
51 else {$ans4=Compute("$a4*$b4");}
52 $m=random(6,10,2);
53 $n=random(4,8,2);
54 $p=random(2,5,1);
55 $nn=random(2,5,1);
56 $ans5=$m+$nn*$n;
57 $q=random(2,5,1);
58 $ans6=$m*$n/$q;
59 $uu=random(5,9,1);
60 $uuu=random(0,2,1);
61 $uuuu="$vars[$uuu]";
62 Context("Inequalities");
63 $ans7= Compute("$uu<$uuuu");
64
65 Context()->texStrings;
66 BEGIN_TEXT
67 $PAR
68 Find the value of each expression
69
70 $PAR
71 \($a\div $b+$c=\) \{ans_rule(4)\}.
72
73 $PAR
74 \($a1( $b1-$c1)\div $d1=\) \{ans_rule(4)\}.
75 $PAR
76 Write a numerical expression for each verbal phrase.
77 $PAR
78 $nums[$a3] $ops[$c3] $nums[$b3] \{ans_rule(10)\}
79 $PAR
80 $nums[$a4] $ops[$c4] $nums[$b4] \{ans_rule(10)\}
81 $PAR
82 Evaluate each expression if n=$n, m=$m, p=$p.
83 $PAR
84 m+$nn n=\{ans_rule(4)\}.
85
86 $PAR
87 \(\frac{mn}{$q}=\)\{ans_rule(4)\}.
88
89 $PAR
90 Write \( \underline{$nums[$uu]\ less\ than\ $vars[$uuu]}\)
91 as an algebraic expression: \{ans_rule(7)\}.
92
93
94
95
96 END_TEXT
97
98 ANS($ans1->cmp);
99 ANS($ans2->cmp);
100 ANS($ans3->cmp);
101 ANS($ans4->cmp);
102 ANS($ans5->cmp);
103 ANS($ans6->cmp);
104 ANS($ans7->cmp);
105
106
107 ENDDOCUMENT()


Ken

In reply to Kenneth Appel

Re: using contexts for grade school answers

by Davide Cervone -
It looks like the error is at line 38: you can't put a command between the if statement and the following elsif, which is what WeBWorK is complaining about.

You could set the context within the if clause where you set $ans3. You will also need to set it back right afterward.

I also think you may be having trouble because of using the LimitedNumeric context, which does not allow any operations, including the plus and minus.

In order to check that the student has actually entered a plus or minus, you would need to use a custom answer checker that looks at the student's parsed answer to see that it is a sum or difference. That is a little more sophisticated, but could be done. In any case, the LimitedNumeric context is not the right one.

Davide

P.S., thanks for including the code, but it would help to have the error message that WeBWorK generated as well.
In reply to Davide Cervone

Re: using contexts for grade school answers

by Kenneth Appel -
Davide,
I corrected the previous error (which occurred in two places)
and now get the error message:

Can't call method "cmp" without a package or object reference at line 97 of [TMPL]/patsheets/ch1test.pg

I was not really surprised by that kind of error since in two different
situations I have seen the ->cmp used in two different ways and did not
really understand the difference. Where is the answer mechanism explained?

I assume that in order to deal with +, - and * I will have to write
something like "LimitedNumeric-StrictFraction" . If so, where would I
find the code for that, if not, where should I look for a model?
Thanks for your patience.
Ken
In reply to Kenneth Appel

Re: using contexts for grade school answers

by Davide Cervone -
The cmp method can only be used for MathObejcts (those items created by Compute() or Formula() or Real() or one of the other MathObject constructors), not native perl numbers. Your $ans1, $ans2, $ans5 and $ans6 are not MathObjects but plain old perl numbers. They don't have cmp methods to call, and that is what the error message is complaining about.

You should use Real() to turn them into MathObjects. E.g., $ans1 = Real($a/$b+$c); instead.

Davide
In reply to Kenneth Appel

Re: using contexts for grade school answers

by Davide Cervone -
I assume that in order to deal with +, - and * I will have to write something like "LimitedNumeric-StrictFraction"

It would be possible to make a context for this purpose, but that probably isn't the best way to go in this case. I'd recommend a custom answer checker instead that looks through the student's parsed answer to see that it's structure is correct.

Here's a sample. Suppose you want to check for $a + $b for some variables $a and $b. Then you can use:

  ANS(Compute("$a+$b")->cmp(checker=>sub {
    my ($correct,$student,$ans) = @_;
    my $f = $ans->{student_formula};
    return 0 if $ans->{message} || $ans->{isPreview} || !defined($f);
    $f = $f->{tree};
    Value->Error("Your answer should be two numbers joined by an operation") 
        unless $f->class eq 'BOP' && $f->{lop}->class eq 'Number' && $f->{rop}->class eq 'Number';
    my ($A,$B) = (Real($f->{lop}{value}),Real($f->{rop}{value}));
    return $f->{bop} eq '+' && (($A == $a && $B == $b) || ($A == $b && $B == $a));
  }));
Here, we use a custom answer checker that extracts the parsed student answer ($ans->{student_formula}) and gets its parse tree ($f->{tree}) if it is defined and there isn't already an error message and we are not previewing answers. We give an error if the structure of the answer is not a binary operator between two numbers (you could just return 0 if you don't want the error message). The test would have to be more complicated if you wanted to allow negative operads. Then we extract the two numbers from the student answer and turn them into Real MathObjects (so that the comparisons will use the real number tolerances). Finally, we return a true value when the operator is a plus and the two operands are the given ones (in either order). Alternatively, you could issue more detailed error messages when the operation isn't plus (for example), or when the numbers aren't correct, but I'll leave that to you as an exercise.

If you are going to use this frequently, you could make a more general subroutine that works in more cases. For example, it could compare the structure of the student answer to the structure of the correct answer. This should get you a start, at least.

Davide

In reply to Davide Cervone

Re: using contexts for grade school answers

by Kenneth Appel -
Davide,
It seems totally ingracious to quibble with such a complete answer, but I wonder if there is a way to do this somewhere other than in the ANS area.
The code that requires the answer is based on a random selection of
operator:

$c3=random(0,3,1);
if($c3==0){$ans3=Compute("$a3*$b3");}
elsif ($c3==2){$ans3=Compute("$a3+$b3");}
elsif ($c3==3){$ans3=Compute("$a3-$b3");}
else
{Context("LimitedNumeric-StrictFraction");
$ans3=Compute("$a3/$b3");Context("Numeric");}

Thus the answer is determined earlier.

If I take this full load of code and put it into the ANS part that you have given
me (if this is even possible) I suspect that it will offset later answers.

I am not sure that my compulsion to write essentially every problem with
random data is rational so you can certainly suggest that I just avoid this
type of situation, but right now I am really probing the capabilities of your
parser.
Ken

In reply to Kenneth Appel

Re: using contexts for grade school answers

by Davide Cervone -
OK, here's a more generic version:
  #
  #  A custom checker that compares a student formula to the correct 
  #  formula for identical structure.  Both must be two numbers separated
  #  by an operation.
  #
  $LimitedOperation = sub {
      my ($correct,$student,$ans) = @_;
      my $f = $ans->{student_formula}{tree};
      return 0 if $ans->{message} || $ans->{isPreview} || !defined($f);
      #
      #  Check that the form is correct
      #
      Value->Error("Your answer should be two numbers joined by an operation") 
          unless $f->class eq 'BOP' && isNumber($f->{lop}) && isNumber($f->{rop});
      #
      #  Get the student's operands
      #
      my ($a,$b) = ($f->{lop}->eval,$f->{rop}->eval);
      #
      #  Get the correct answer's formula and operands
      #
      my $F = Parser::Formula($ans->{correct_ans})->{tree};
      my ($A,$B) = ($F->{lop}->eval,$F->{rop}->eval);
      #
      #  Check that the answers are equal, that the operators are the same,
      #    and that the operands are the same.
      #
      return $correct == $student &&
        $f->{bop} eq $F->{bop} &&
        (($A == $a && $B == $b) || ($A == $b && $B == $a));
  };

  #
  #  Check if a node is a Number or a negative Number
  #    (with no operations involved)
  #
  sub isNumber {
    my $n = shift;
    return $n->class eq 'Number' ||
      ($n->class eq 'UOP' && $n->{uop} eq 'u-' && $n->{op}->class eq 'Number');
  }

  $a = -1; $b = 3;
  $ans = Compute("$a+$b");
  ANS($ans->cmp(checker=>$LimitedOperation));
This one uses the correct answer to determine the structure that the student must supply, but be warned that the original equation MUST be created using Compute(), not through some other MathObject constructor. You can use the $LimitedOperation checker on each of the answers without change. I also took care of checking for negative numbers, in case you want to use those, too.

See if that works better for you.

Davide

In reply to Davide Cervone

Re: using contexts for grade school answers

by Kenneth Appel -
Davide,
For some stupid reason my mouse copy would not work on this material
so, after what I thought was careful copying and then two hours of bug checking
on that "careful copying" I got things to compile into the problem. Here is
the code (note the error below the code before reading the code).

DOCUMENT();
loadMacros(
"PGstandard.pl",
"PGchoicemacros.pl",
"PGgraphmacros.pl",
"MathObjects.pl",
"compoundProblem.pl",
"unionLists.pl",
"unionMacros.pl",
"contextInequalities.pl"
);
TEXT(beginproblem());
$showPartialCorrectAnswers = 1;
# A custom checker that compares a student formula to the correct
# formula for identical structure. Both must be two numberes
# separated by an operator.
#
$LimitedOperation = sub {
my($correct,$student,$ans) = @_;
my $f = $ans->{student_formula}{tree};
return 0 if $ans->{message} || $ans->{isPreview} ||
!defined($f);
#
# Check that the form is correct
#
Value->Error("Your answer should be two numbers with
either a +, -, *, or / between them")
unless $f->class eq 'BOP' && isNumber($f->{lop}) &&
isNumber($f->{rop});
#
# Get the student's operands
#
my($a,$b)=($f->{lop}->eval, $f->{rop}->eval);
# Get the correct answer's formula and operands.
#
#my $F = Parser::Formula($ans->{correct_ans})->{tree};
my ($A,$B) =($F->{lop}->eval, $F->{rop}->eval);
#
#Check that the answers are equal, that the operators are
# are the same and that the operands are the same.
#
return $correct == $student &&
$f->{bop} eq $F->{bop} &&
(($A == $a && $B == $b) || ($A == $b && $B == $a));
};
#
# Check if a node is a Number or a negative Number
# (with no operations involved)
#
sub isNumber{
my $n = shift;
return $n->class eq 'Number' ||
($n->class eq 'UOP' && $n->{uop} eq 'u-' &&
$n->{op}->class eq 'Number');
}



Context("Numeric");
$b=random( 4,8,2);
$a=$b*random(3,7,2);
$c=random(20,50,5);
$d1= random(3,7,1);
$a1= random(2,5,1);
$c1=random(4,9,1);
$b1=$c1+$d1*random(3,6,1);
$ans1=Real($a/$b+$c);
$ans2=Compute($a1*($b1-$c1)/$d1);
@vars=('x','y','z');
@nums=('zero ','one ','two ','three ', 'four ', 'five ', 'six ',
'seven ', 'eight ', 'nine ');
@ops=('multiplied by ','divided by ', ' added to ', 'subtracted from ');

$a3=random(6,9,1);
$b3=random(2,5,1);$c3=
random(0,3,1);

if($c3==0){$ans3=Compute("$a3*$b3");}
elsif ($c3==2){$ans3=Compute("$a3+$b3");}
elsif ($c3==3){$ans3=Compute("$a3-$b3");}
else
{Context("LimitedNumeric-StrictFraction");
$ans3=Compute("$a3/$b3");Context("Numeric");}

$a4=random(6,9,1);
$b4=random(2,5,1);
$c4=random(0,3,1);


if($c4==0) {$ans4=Compute("$a4*$b4");}
elsif ($c4==2){$ans4=Compute("$a4+$b4");}
elsif ($c4==3){$ans4=Compute("$a4-$b4");}
else
{Context("LimitedNumeric-StrictFraction");
$ans4=Compute("$a4/$b4");Context("Numeric");}

$m=random(6,10,2);
$n=random(4,8,2);
$p=random(2,5,1);
$nn=random(2,5,1);
$ans5=Real($m+$nn*$n);
$q=random(2,5,1);
$ans6=Compute($m*$n/$q);
$uu=random(5,9,1);
$uuu=random(0,2,1);
$uuuu="$vars[$uuu]";
Context("Inequalities");
$ans7= Compute("$uu<$uuuu");

Context()->texStrings;
BEGIN_TEXT
$PAR
Find the value of each expression

$PAR
\($a\div $b+$c=\) \{ans_rule(4)\}.

$PAR
\($a1( $b1-$c1)\div $d1=\) \{ans_rule(4)\}.
$PAR
Write a numerical expression for each verbal phrase.
$PAR
$nums[$a3] $ops[$c3] $nums[$b3] \{ans_rule(10)\}
$PAR
$nums[$a4] $ops[$c4] $nums[$b4] \{ans_rule(10)\}
$PAR
Evaluate each expression if n=$n, m=$m, p=$p.
$PAR
m+$nn n=\{ans_rule(4)\}.

$PAR
\(\frac{mn}{$q}=\)\{ans_rule(4)\}.

$PAR
Write \( \underline{$nums[$uu]\ less\ than\ $vars[$uuu]}\)
as an algebraic expression: \{ans_rule(7)\}.




END_TEXT

ANS($ans1->cmp);
ANS($ans2->cmp);
ANS($ans3->cmp(checker=>$LimitedOperation));
ANS($ans4->cmp(checker=>$LimitedOperation));
ANS($ans5->cmp);
ANS($ans6->cmp);
ANS($ans7->cmp);


ENDDOCUMENT()

You may notice that I changed nothing but the error message - I thought
that yours was a bit sophisticated for seventh graders. Then I tried some
false and correct answers. It correctly rejected some false answers with
the proper message, but the following puzzles me.


Entered Answer Preview Correct Result Messages
53 incorrect
15 incorrect
32 8\cdot 4 8*4 incorrect An error occurred while checking your answer:
Can't call method "eval" on an undefined value at line 41 of [TMPL]/patsheets/ch1test.pg
28 7\cdot 4 7+4 incorrect An error occurred while checking your answer:
Can't call method "eval" on an undefined value at line 41 of [TMPL]/patsheets/ch1test.pg
18 incorrect
Notice that entered and preview disagree on problems 3 and 4. Also answer
3 should be incorrect and answer 4 should be incorrect (wrong operator)
Ken


In reply to Kenneth Appel

Re: using contexts for grade school answers

by Davide Cervone -
Looks to me like the error is the line
   #my $F = Parser::Formula($ans->{correct_ans})->{tree};
where you have commented out the command, so $F never gets defined, and then in the next line $F->{lop} is also undefined, and so $F->{lop}->eval is trying to call eval on an undefined value. That is what the error message suggests, and certainly having this command commented out is a copying error.

You say that the entered and preview answer disagree, but they look like they agree to me. The usual result of a numeric answer checker is to make the "entered" field be the result of formula the student answered, and the "preview" field be the typeset version of the parsed answer. That is what is happening here.

You can control how the student answer is shown using the formatStudentAnswer context flag. Since you are switching contexts so much, it might be easier to use it on the MathObject itself:

    $ans3 = Compute("$a3/$b3")->with(formatStudentAnswer=>"parsed");
and the same for $ans4. That will cause the "entered" field to show the full equation rather than the result of the equation. That is probably what you are after.

Davide