Forum archive 2000-2006

Nandor Sieben - answer evaluator taking more then one answer, factorization

Nandor Sieben - answer evaluator taking more then one answer, factorization

by Arnold Pizer -
Number of replies: 0
inactiveTopicanswer evaluator taking more then one answer, factorization topic started 6/15/2005; 12:31:52 AM
last post 6/15/2005; 8:21:25 PM
userNandor Sieben - answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 12:31:52 AM (reads: 1672, responses: 11)
Is there a way to write an answer evaluator that evaluates more than one ans_rule's. More generally, what is the best way to write problems for factorization? The existing factorization problems usually ask for the two factors A and B separately and they require certain conditions like the degree of A is smaller than the degree of B, or that the constant term of A is smaller then the constant term of B, etc. This is a little artificial. If an evaluator could swallow the ans_rule for both A and B then these conditions were not needed because the evaluator could check if (trueA=A and trueB=B) or (trueA=B and trueB=A).

Maybe A and B could be given in a single ans_rule if we had a filter that checks if the answer is really a product. For example check that it is of the form ( ? [+|-] ? ) [|*] (? [+|-] ? ) or of the form ? [|*] ( ? [+|-] ? ) etc. A couple of regular expression checks should be able to do this in simple cases.

Nandor

<| Post or View Comments |>


userNandor Sieben - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 2:33:00 AM (reads: 1935, responses: 0)
Can the parser decide if an expression is a product or a sum on the top level. That is, do we a have a function that builds an expression tree and returns the operation on the top level?

Is it possble to use a grader somehow?

Nandor

<| Post or View Comments |>


userDavide P. Cervone - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 7:33:39 AM (reads: 1927, responses: 0)
There are at least three ways to accomplish this. The first is to use the unorderedAnswer.pl macro file that is part of the Union College problem library. This lets you supply answer blanks and answers that can appear in any order. Here's how this might look for that case of your factorization:

 

    loadMacros(
"unorderedAnswer.pl",
);


TEXT(&beginproblem);
BEGIN_TEXT
Factor: \(x^2-1\) = \{ans_rule(15)\} \(\times\) \{ans_rule(15)\}.
END_TEXT


UNORDERED_ANS(fun_cmp("x-1"),fun_cmp("x+1"));

Here, UNORDERED_ANS handles matching the user answers to the two supplied checkers and the student can put the factors in either blank. See the documentation that is in the unorderedAnswer.pl file for more details.

 


The second approach is to use the new MultiPart object that I added recently (in repsonse to a request here a few weeks ago). This lets you combine several answer blanks to be checked by a single answer checker, just as you requested (see http://webhost.math.rochester.edu/webworkdocs/discuss/msgReader$3300 for more details). Here is how this might be used:

 

    loadMacros(
"Parser.pl",
"parserMultiPart.pl",
);


Context("Numeric");


$mp = MultiPart("x+1","x-1")->with(
checker => sub {
my $correct = shift; my $student = shift; my $self = shift;
my ($F,$G) = @{$correct}; my ($f,$g) = @{$student};
return 1 if ($F == $f && $G == $g) || ($F == $g && $G == $f);
$self->setMessage(1,"A constant is not a valid factor")
unless ($f->isFormula);
$self->setMessage(2,"A constant is not a valid factor")
unless ($g->isFormula);
return 0;
},
singleResult => 1,
);


BEGIN_TEXT
Factor: \(x^2-1\) = \{$mp->ans_rule(15)\} \(\times\) \{$mp->ans_rule(15)\}.
END_TEXT


ANS($mp->cmp);

Here, the MultiPart produces two answer blanks, but they are checked by a single routine that has access to both student answers and both correct ones. The singleResult flag indicates that only one row will appear in the answer results area at the top of the page. We do a little extra error checking here, mostly as an example of how to send error messages to the MultiPart object.

 


A third way is to use the Parser to analyze the structure of the student's answer, as you suggest in your second message. That is a little bit delicate, but here is how it might be done:

 

    loadMacros("Parser.pl");


BEGIN_TEXT
Factor: \(x^2-1\) = \{ans_rule(30)\}.
END_TEXT


ANS(Formula("(x-1)(x+1)")->cmp(checker => sub {
my $correct = shift; my $student = shift;
return 0 unless $student->isFormula;
Value::Error("Your formula doesn't seem to be a product")
unless $student->{tree}->class eq "BOP" &&
$student->{tree}{def}{string} eq '*' ;
my ($F,$G) = (
Formula($correct->{tree}{lop}),
Formula($correct->{tree}{rop})
);
my ($f,$g) = (
Formula($student->{tree}{lop}),
Formula($student->{tree}{rop})
);
Value::Error("Neither factor can be a constant")
if ($f->isConstant || $g->isConstant);
return ($F == $f && $G == $g) || ($F == $g && $G == $f);
}));

Here, we check if the student answer is a formula having a binary operator that is multiplication as the top level, and report an error if not. Then we break the student and correct answer into its two factors, and report an error if either is constant. Finally, we check that the factors agree (in either order).

 


Hope that one of these does what you want.

Davide

PS, I don't think that pattern matching is a good approach to this, as it is very hard to get patterns that really work (your patterns don't seem to be all there, or perhaps I don't understand what you are trying to do), and the student will almost always be able to write a correct answer that fails to match your patterns.

<| Post or View Comments |>


userNandor Sieben - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 1:38:50 PM (reads: 1923, responses: 1)
Thank you, this is great. While we are at it, a similar problem is expanding (x+1)^2 to x^2+2x+1. I guess the third solution is the way to go but I am a little scared of it. How do I check if the operation on the top level is addition? Do I need to also check if it's subtraction? Nandor

<| Post or View Comments |>


userDavide P. Cervone - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 3:10:59 PM (reads: 1945, responses: 0)
Checking for x^2+2x+1 turns out to be much more complicated than checking for factorization. For the factoring problem, there were basically only two possible answers A*B or B*A. If the formula had more factors, however, things would have gotten out of hand: A*(B*C), (B*C)*A, B*(A*C), (A*C)*B, C*(A*B), (A*B)*C. The same will be true in this case, since this is a sum of three terms. If you wish to consider x^2 + x + x + 1, that makes things even worse.

If you really want the student to do exactly x^2 + 2x + 1 it might be best to ask her to enter the coefficients rather than the complete expression: [ ] x^2 + [ ] x + [ ].

It would be possible to make a specialized context (something like the contextLimitedComplex.pl and other Limted contexts) that allows the student to enter only sums of monomials (i.e., only polynomials in expanded form), so that x^2 + 1 + 2x and x + x^2 + x +1 would all be acceptable answers, but (x+1)^2 would not. That would work for this.

Let me see how hard it is to put that together.

To answer your question about checking if the operation is addition, use

 

    $f->{tree}->class eq 'BOP' && $f->{tree}{def}{string} eq '+';

And yes, you would need to handle subtraction as well. There are some internal routines for asking about whether something is a negation, but I suspect that this sort of hand-traversing of the parse tree is not going to be the best approach in the long run. The parser is not really a formal algebra system, and there are no tools for transforming one representation into another, or into a standard form, for example, so it is hard to do this kind of analysis. More could be done on that if there is interest, but it is not at the top of my list.

Davide

<| Post or View Comments |>


userJohn Jones - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 6:46:56 PM (reads: 1941, responses: 2)
Hi,

I haven't tried the Multipart object, but it looks like a nice solution to this kind of problem. Alternatively, you could ask for a list of factors in a single answer blank if you don't want to give away how many factors there are. The Parser can check an unordered list of functions. I think this would be my preference, except ...

I would want to only check each factor up to a constant factor, and then check if the product equals the right function to deal with (x/2-1/2)(2x+2). An alternative would be to ask for monic factors, but many of our students don't know what that word means. If I were using Multipart, I could hand code this variation into the checker. But, I prefer the list of factors approach, so how would I handle the extra checks with the List checker?

The expand issue seems much stickier. I think the best solution there would be the simple one given above of asking for the coefficients individually. If you don't want to give away how many there are, you could say, "Suppose (x+1)^2 is expanded to the form A x^4 + B x^3 + C x^2 + D x + E ...", and then give entry blanks for A, B, C, D, and E, with maybe a note that all blanks should be filled in, even if they are 0.

John

<| Post or View Comments |>


userNandor Sieben - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 8:02:17 PM (reads: 1938, responses: 1)
I am experimenting with the first solution:

Factor: (x^3+2x^2-x-2) = {ans_rule(15)} (\cdot) {ans_rule(15)} (\cdot) {ans_rule(15)}

Factor: (x^2-9) = {ans_rule(15)} (\cdot) {ans_rule(15)}

END_TEXT

UNORDERED_ANS(fun_cmp("x+1"),fun_cmp("x-1"),fun_cmp("x+2"));

UNORDERED_ANS(fun_cmp("x+3"),fun_cmp("x-3"));

This doesn't work. The first evalautor only takes the answer in the same order. The second evaluator works fine. There is no problem if I use only one UNORDETRED_ANS, the first one alone works fine. Two of them somehow get confused. What am I doing wrong?

Nandor

<| Post or View Comments |>


userDavide P. Cervone - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 8:03:23 PM (reads: 2209, responses: 0)
OK, I have worked out a polynomial context in which the student can only enter sums of multiples of powers of the variable (i.e., fully expanded polynomials). It is in contextLimitePolynomial.pl in the pg/macros directory. For example:

 

    loadMacros("contextLimitedPolynomial.pl");


Context("LimitedPolynomial");


BEGIN_TEXT
Expand: \((x-1)^2\) = \{ans_rule(30)\}.
END_TEXT


ANS(Formula("x^2-2x+1")->cmp);

will match only when the polynomial is fully expanded. "Helpful" error messages are issued when constructs are encountered that are not allowed.

You can also require that only one monomial can be entered of each degree, so if you want to require x^2-x-x+1 to be written as x^2-2x+1, you can do so. To require this, use

 

    Context("LimitedPolynomial")->flags->set(singlePowers=>1);

I think I've covered all the bases on this, but these sorts of things are always tricky, and there might be something I've missed. I've only done an afternoon's worth of testing.

Davide

<| Post or View Comments |>


userDavide P. Cervone - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 8:14:34 PM (reads: 2197, responses: 0)
You probably should read the documentation in the file, because there are some caveats for using it. In particular, you need to issue the UNORDERED_ANS call immediately after the answer blanks it refers to (no other answer blanks can intervene). So you would need to do

 

    BEGIN_TEXT
Factor: \(x^3+2x^2-x-2\) = \{ans_rule(15)\} \(\cdot\) \{ans_rule(15)\} \(\cdot\) \{ans_rule(15)\}
$PAR
END_TEXT


UNORDERED_ANS(fun_cmp("x+1"),fun_cmp("x-1"),fun_cmp("x+2"));


BEGIN_TEXT
Factor: \(x^2-9\) = \{ans_rule(15)\} \(\cdot\) \{ans_rule(15)\}
END_TEXT


UNORDERED_ANS(fun_cmp("x+3"),fun_cmp("x-3"));

You can use named answer blanks to avoid this limitation if you want. I had forgotten about that or I would have warned you. (This was the very first code I ever wrote for WeBWorK more than three years ago, only a month after installing WW1.6. I probably would have handled it better if I wrote it today, but I've never gone back to fix it up.)

Davide

<| Post or View Comments |>


userNandor Sieben - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 8:21:25 PM (reads: 1946, responses: 0)
Thanks Davide, this solved it. Nandor

<| Post or View Comments |>


userDavide P. Cervone - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/15/2005; 10:46:45 PM (reads: 2215, responses: 1)
John:

I hadn't thought of the factorization you suggested, and agree that it should be handled. So I rewrote my MultiPart example as follows:

 

    Context("Numeric");


$mp = MultiPart("x+1","x-1")->with(
checker => sub {
my $correct = shift; my $student = shift; my $self = shift;
my ($F,$G) = @{$correct};
my ($f,$g) = @{$student};
Value::Error('Neither factor can be constant')
unless $f->isFormula && $g->isFormula;
return 0 unless $F*$G == $f*$g;
Context()->variables->add(a=>'Parameter');
my $result = ($F*'a' == $f || $F*'a' == $g);
Context()->variables->remove('a');
return $result;
},
singleResult => 1,
);


BEGIN_TEXT
Factor: \(x^2-1\) = \{$mp->ans_rule(15)\} \(\times\) \{$mp->ans_rule(15)\}.
END_TEXT


ANS($mp->cmp);

Here, we multiply the two factors from the student and professors answers and compare the results before looking at the individual factors. If the products are OK, we add a parameter to the context and do an adaptive comparison between a multiple of the professor's first factor and the student's answers. If either matches, then since the product also matched, the remaining factor will match as well.

 


Alternatively, you ask about how to use the List checker, using a single answer rule and separating the factors by a comma. Here is one solution:

 

    Context("Numeric");


$L = List(Formula("x-1"),Formula("x+1"));


BEGIN_TEXT
List the factors of \(x^2-1\) separated
by commas \{ans_rule(40)\}
END_TEXT


ANS($L->cmp(
checker => sub {
my $correct = shift; my $student = shift;
return 0 unless $correct->isFormula && $student->isFormula;
return $correct*'a' == $student;
},
list_checker => sub {
my $context = $L->{context};
$context->variables->add(a=>'Parameter');
$context->flags->set(no_parameters => 0);
my ($score,@errors) = $L->cmp_list_compare(@_);
$context->flags->set(no_parameters => 1);
$context->variables->remove('a');
my ($correct,$student,$ans) = @_;
if ($score == scalar(@{$correct}) &&
$score == scalar(@{$student})) {
my $F = Formula('1'); my $f = Formula('1'); my $h;
foreach $h (@{$correct}) {$F = $F*$h}
foreach $h (@{$student}) {$f = $f*$h}
$score = 0 unless $F == $f;
}
return ($score,@errors);
},
showLengthHints => 1,
));

This one supplies both the list entry checker (via checker) and a global List checker (via list_checker). Here is how it works: the list_checker is called with a a reference to an array of the parsed correct answers, a reference to an array of the parsed student answers, and some other data we don't use in this case. In this routine, we install the parameter 'a' and allow parameters to be used in formulas (they are turned off during student input and haven't been turned back on yet). Then we call the standard list checker by hand. That list checker handles doing the unordered comparison, adding up scores, reporting errors, and so on, and most important, it calls our checker subroutine in order to compare professor and student answers. Our version of the checker routine gets the correct and student answers and checks that they actually are formulas, then does the adaptive comparison to see if one is a multiple of the other. If it is, it will be matched as correct (provisionally).

Once the standard list checker completes, we get back a score and a list of error messages (if any). The score is the total number of matched items in the list, so if the student matched each factor, then she has gotten them all correct up to a constant multiple, so we still need to check if the products are equal (she could still be off by a constant multiple). To check this, we loop through the correct and student answers and make a product of all the factors in each list, and then compare the results. If they aren't equal, we set the score to zero, otherwise, they have the right answer. In the former case, you might also want to add a message to the error list indicating that the result is off by a constant mutliple (unless $ans->{isPreview} is set).

The List answer checker can also take additional parameters like showLengthHints that indicate if messages should be issued when the student's answers are all correct but there are some missing, and showHints, which controls whether entries in the list should be marked as incorrect individually (so the student knows which entries he got right).

 


Hope that covers all the bases.

Davide

<| Post or View Comments |>


userDavide P. Cervone - Re: answer evaluator taking more then one answer, factorization  blueArrow
6/16/2005; 12:07:45 AM (reads: 2505, responses: 0)
PS, to get the latter of these to work, you need the latest copy of pg/lib/Value/Formula.pm, as there was a bug that I found while writing these examples.

Davide

<| Post or View Comments |>