One not particularly elegant solution to this is to define a custom checker that allows either of the answers you suggest:
loadMacros("MathObjects.pl");
Context("Numeric");
Context()->variables->add( t=>'Real', c1=>'Real', c2=>'Real' );
$ans1 = Compute("c1*e^t + c2*e^(-t)");
$ans2 = Compute("c2*e^t + c1*e^(-t)");
...
ANS( $ans1->cmp( checker=>sub {
my ( $correct, $student, $ansHash ) = @_;
return $student == $correct || $student == $ans2;
} ) );
This may not be terribly extensible to other general solutions, however.
Gavin
I modified your solution a bit after I realized that what I asked for was really more than I needed.
This code works:
$ans1 = Compute("c1 exp(-3t) + c2 exp(-4t)");
ANS($ans1->cmp(checker=>sub {
my ( $correct, $student, $ansHash ) = @_;
return $student == $correct ||
$student == $correct->substitute(c1=>c2,c2=>c1);
}));
But I don't understand why this code doesn't:
sub check {
my ( $correct, $student, $ansHash ) = @_;
return $student == $correct ||
$student == $correct->substitute(c1=>c2,c2=>c1);
}
$ans1 = Compute("c1 exp(-3t) + c2 exp(-4t)");
ANS($ans1->cmp(checker=>\&check));
I'm trying to abstract the checker into a separate function to make things easier, and I thought the right way to pass a reference to the function was to put \& in front of the name. I don't get any errors. When I enter the answer as in $ans1, it checks it as correct. But when I enter the answer as in $ans2, it checks it as incorrect. Obviously there's something I don't understand about Perl syntax.
Any help would be appreciated.
ANS($ans1->cmp(checker=>~~&check));Ugly but it works. You might also consider storing the code in a variable so as to avoid having to create a function reference by hand:
$check = sub { my ($correct, $student, $ansHash) = @_; return $student == $correct || $student == $correct->substitute(c1=>c2,c2=>c1); } $ans1 = Compute("c1 exp(-3t) + c2 exp(-4t)"); ANS($ans1->cmp(checker=>$check));
There are a couple of other small issues with your code. First, although c1=>c2
works, it should really be c1=>"c2"
since you are relying on Perl's automatic default to string values when nothing else makes sense, and that can be problematic.
Second, you should use the correct answer on the left and the student answer on the right of ==
checks, as the equality check is not entirely symmetric. For one thing, the limits for the domain are taken from the left-hand function (or from the Context if they are not set on the function itself), and the test points are determined by using the left-hand function. It is best to use the correct answer there because the limits are set properly for the correct answer, not the student answer, and you can easily get the "can't generate enough points for comparison" error if you use the student function on the left.
Hope that helps.
Davide
Thanks for your comments. I have been able to refine things to the following function which I have put in the PGcourse.pl file:
sub Either { my $ans = shift; return Compute($ans)->cmp(checker=>sub { $correct = shift; $student = shift; my $reverse = $correct->substitute(c1=>"c2",c2=>"c1"); return $correct == $student || $reverse == $student; }) }
ANS(Either("c1 exp(-$r1 t) + c2 exp(-$r2 t)"));
One thing that I don't understand (and I'm not sure that I really want to):
If I do:
return $correct == $student || $correct=>substitute(c1=>"c2",c2=>"c1") == $student;it doesn't work. If I reverse the operands to the second == it does work.
But when I store the reversed expression in a variable, it does work on the left.
I also had to use shift rather than @_ for the anonymous function because Perl didn't like @_ in that context.
Thanks very much to Davide and Gavin for all the help!
$correct
and $student
variables should be my
variables so they don't go into the global namespace (minor detail).
Second, you might want to allow additional parameters that get passed to the cmp()
method so that you can pass things like limits or other adjustments that might need to be included.
sub Either { my $ans = shift; return Compute($ans)->cmp(checker=>sub { my $correct = shift; my $student = shift; my $reverse = $correct->substitute(c1=>"c2",c2=>"c1"); return $correct == $student || $reverse == $student; },@_); }This way, any additional parameters to Either get passed on to the
cmp()
method, and you could do something like:
ANS(Either("c1 sqrt(x-2) + c2",limits=>[2.1,3]));to set the limits.
Finally, your observation about using the substitution in place of $reverse
turns out to be a bug where some cached values from the first comparison ($correct == $student
) where being passed on to the substituted formula. These included the test points and test values, which was what was causing the second comparison to fail. When you created $reversed
before doing the first comparison, those values weren't there to pass on, so everything worked fine.
I have fixed the problem in the latest version of pg/lib/Parser.pm
if you want to get an updated copy.
Davide
In any case the second order case is a great step - Thanks!!
David Gilliam
You could modify my Either function to handle order 3 or 4 or n by checking all permutations of c1 ... cn. But that's a pain, and furthermore, it is going to mutiply the checking time by n!. I doubt that my little server would be very happy the night before a homework set is due.
What I have done in the past, and will continue to do for order >= 3, is to ask for a fundamental set, entered as a list.
E.g. ANS(Compute("exp(x),exp(-x),exp(2x),exp(-2x)")->cmp);
The students can enter the fundamental set in any order.
I then ask separately for a particular solution and the solution meeting initial conditions.
Last year I did it this way for second order, but now I'm modifying the second order problems to use Either.
The issue of general handling of arbitrary constants for DE problems is hard, I think. With linear equations, it's mostly not bad. But with general first order, it's very problematic.
I tell students to condense constants (2C => C). It would be nice to allow constants without requiring this, but I think that mathematically it's a hard problem when C can occur in various contexts, as opposed to + C in integrals.
And the problems can be even more general. For example, I gave a problem a couple of weeks ago where the solution comes out 1/sqsr(x^2+y^2) = C if you solve it as exact, but x^2+y^2 = C if you solve it as separable. One could handle situations like this using Gavin's method, or just make the students continue simplifying. Ultimately though, you can't anticipate all the possible forms of correct answers.
A checker that allows any one of several answers to be counted as correct without obfuscated code would be nice for this situation. How about something like ANS(Compute("ans1, ans2, ans3")->cmp_require_one) that gives credit when the student answer is any one of the list? I'll bet that would be easy for someone who actually understands checkers to write.
Actually what you have suggested is exactly what I do for higher order equations. I figured that extending Either to these cases might be problematic but thought it might be worth asking.
For the problem with x^2+y^2=C and 1/(x^2+y^2)=C I had exactly the same problem a while back. Bob Byerly, here at Texas Tech, modified his ConstMultChecker to handle this case but we both agreed that there are then infinitely many such formulas which are all correct. General solutions of first order nonlinear problems are very difficult. I have found methods that work for me. I fix an exact form by giving part of an answer, e.g., f(x,y)+x^2 = C, and asking for f(x,y).
In any case thank you, Gavin and Davide for the second order case. I have already converted all my problems using Either.
David Gilliam
More specifically following his suggestion
"How about something like ANS(Compute("ans1, ans2, ans3")->cmp_require_one) that gives credit when the student answer is any one of the list?"
This sounds like a wonderful idea. I do not know how to do this but would certainly appreciate such a checker.