As a work around, I would load the macro loadMacros("contextLimitedPolynomial.pl"); and use Context("LimitedPolynomial-Strict")->variables->are(x=>"Real",a=>"Real"); and then have students take the square root of something that is already a square (such as sqrt(16 x^2 a^4)) so that the answer is a polynomial (such as 4 x a^2, disregarding the absolute value issues). You may also want to disable the square root function (see http://webwork.maa.org/wiki/DisableFunctions).
Good luck,
Paul Pearson
Thanks for the suggestion. However, I'm not satisfied with the idea of just omitting a whole class of standard exercises.
Actually, we're trying out WeBWorK as an alternative to an answer key--our students do not have answers at the back of their text, but by submitting answers to WeBWorK, they can still check if they have the correct answer (for the skills type of exercises).
I may resort to multiple choice.
Simplify this expression as X\sqrt{Y}.
X=
Y=
It's not pretty, but then you could check to see if it's correct.
I do fear that a large percentage of our elementary algebra students will not recognize that a placeholder like X can stand for an algebraic expression (say 2a+3b) rather than always representing a specific real number.
So I may try showing \square\sqrt{\square}, and then ask students to enter an expression that would go in Box 1 and and expression that would go in Box 2.
If you wanted them to simplify sqrt(a^3), would you expect abs(a)sqrt(a)? (I would, unless you had specifically indicated that a >= 0). Would you want to accept 3|a|^2|b^2|sqrt(3a) for your answer to the question in your problem, even though the absolute values are redundent here)?
Your answers to these questions would affect how I think you should handle the problem. Note that these will also be relevant to using the \Square\sqrt{\Square} approach as well.
I suspect that Paul has the right idea: you will want a specialized context that helps enforce the correct format. Or at least, you will want to analyze the parse tree. Depending on how strict the format is, that can be more or less difficult.
Davide
And I would also accept a*a or even a^(4/2) instead of a^2, or an exponent of 1/2 in place of a square root. But our students have not yet been exposed to rational exponents in our classes.
In this set of exercises, we're basically looking to see if the student can remove perfect squares from square roots.
loadMacros("contextLimitedPowers.pl");
###########################
#
# Subclass the numeric functions
#
package my::Function::numeric;
our @ISA = ('Parser::Function::numeric');
#
# Override sqrt() to return a special value (usually 1) when evaluated
# effectively eliminating it from the product.
#
sub sqrt {
my $self = shift;
my $value = $self->context->flag("setSqrt");
return $value+1 if $value && $_[0] == 1; # force sqrt(1) to be incorrect
return $value if $value;
return $self->SUPER::sqrt(@_);
}
#
# end of subclass
#
package main;
###########################
Context("Numeric")->variables->are(
a => ["Real", limits => [0,2]],
b => ["Real", limits => [0,2]],
);
#
# make sqrt() use our subclass
#
Context()->functions->set(sqrt=>{class=>'my::Function::numeric'});
#
# Don't allow fractional powers (avoids 1/2 power)
# [Could subclass exponentiation to handle that as well]
#
LimitedPowers::OnlyPositiveIntegers();
$F = Formula("sqrt(9a^5b^4)");
$f = Formula("3a^2b^2sqrt(a)");
BEGIN_TEXT
\(\{$F->TeX\}\) = \{ans_rule(20)\}
END_TEXT
#
# Use a custom checker to check that the answers are equivalent
# and that they are still equivalent when sqrt() is replaced by 1
# (so the stuff outside the sqrt() is equal in both)
#
ANS($f->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student); # check if equal when sqrt's are replaced by 1
Context()->flags->set(setSqrt => 0);
Value::Error("You must simplify your answer further") unless $OK;
return $OK;
}));
To make this work, we create a subclass of the single-variable functions and replace the sqrt() function so that it will return the value 1 when we set a context flag (and its usual value otherwise). When the flag is set, the sqrt() is effectively removed from the product so the result will be just the stuff outside the square root.
We set up the Numeric context with the variables we want (making sure to use limits that are non-negative), and hook the sqrt() function to our new subclass. We also use the LimitedPowers contexts to prevent the student from entering square roots as powers of 1/2. (It would be possible to subclass the exponentiation operator to handle that like the square roots, but since you said your kids don't know about that yet, I didn't bother with it here).
Once that is done, you make your formulas as usual, but use a custom answer checker to test if they are equal. In this case, you return 0 if the Preview button was used, or if the student answers isn't equivalent to the correct one as formulas. If they are equal, you go on to test if the non-sqrt parts are equal. That is done by setting the setSqrt
flag, removing the cached results of evaluating the correct and student formulas (so new values will be computed) and testing if the formulas as equal now that the sqrt() function returns 1. (We reset the flag so sqrt() will work normally again, just in case). Finally, we issue an error message if the non-sqrt parts of the formulas aren't equal (you could remove this if you just want the answers to be marked incorrect silently), and return whether the answers are equal or not.
Note that in its current form, sqrt(ab)
could be entered as sqrt(a)sqrt(b)
by the student. If you want to prevent that, use a value other than 1 for the value of setSqrt
, e.g.
Context()->flags->set(setSqrt => pi);so that
sqrt(ab)
will be replaced by pi, while sqrt(a)sqrt(b)
will be replaced by pi*pi, making the student's answer different from the professor's.
I think that should do it for you.
Davide
I think that I came in to this conversation a bit late and missed something crucial. I tried your program on sqrt(48)
and did not get what was intended. Since I started out by
blindly copying things, I probably got what I deserved, but here is the situation.
DOCUMENT();
loadMacros(
"PGstandard.pl", # Standard macros for PG language
"MathObjects.pl",
"contextLimitedPowers.pl"
);
# Print problem number and point value (weight) for the problem
TEXT(beginproblem());
# Show which answers are correct and which ones are incorrect
$showPartialCorrectAnswers = 1;
# code essentially from Davide Cervone 4/25/10
###########################
#
# Subclass the numeric functions
#
package my::Function::numeric;
our @ISA = ('Parser::Function::numeric');
#
# Override sqrt() to return a special value (usually 1) when evaluated
# effectively eliminating it from the product.
#
sub sqrt {
my $self = shift;
my $value = $self->context->flag("setSqrt");
return $value+1 if $value && $_[0] == 1; # force sqrt(1) to be incorrect
return $value if $value;
return $self->SUPER::sqrt(@_);
}
#
# end of subclass
#
package main;
###########################
Context("Numeric")->variables->are(
x => ["Real", limits => [0,2]], # only needed if x is used in the square roots
);
#
# make sqrt() use our subclass
#
Context()->functions->set(sqrt=>{class=>'my::Function::numeric'});
Context()->flags->set(reduceConstantFunctions=>0);
#
#
# Don't allow fractional powers (avoids 1/2 power)
# [Could subclass exponentiation to handle that as well]
#
LimitedPowers::OnlyPositiveIntegers();
$f = Compute("sqrt(48)");
$g=Compute("4*sqrt(3)");
BEGIN_TEXT
$BR
\(\sqrt{48}\)= \{ans_rule(20)\}$PAR
\(4 \sqrt{3}\)= \{ans_rule(20)\}
END_TEXT
#
# Use a custom checker to check that the answers are equivalent
# and that they are still equivalent when sqrt() is replaced by 1
# (so the stuff outside the sqrt() is equal in both)
#
ANS($f->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
#
# Get parsed formula for student and correct answers
#
$student = $ans->{student_formula};
$correct = $correct->{original_formula} if defined $correct->{original_formula};
#
# check if equal when sqrt's are replaced by 1
#
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student);
Context()->flags->set(setSqrt => 0);
#
Value::Error("Check to see if your answer is simplified.") unless $OK;
return $OK;
},formatStudentAnswer=>"reduced"));
ANS($g->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
#
# Get parsed formula for student and correct answers
#
$student = $ans->{student_formula};
$correct = $correct->{original_formula} if defined $correct->{original_formula};
#
# check if equal when sqrt's are replaced by 1
#
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student);
Context()->flags->set(setSqrt => 0);
#
Value::Error("Check to see if your answer is simplified.") unless $OK;
return $OK;
},formatStudentAnswer=>"reduced"));
ENDDOCUMENT()
Result:
Entered | Answer Preview | Correct | Result | Messages |
---|---|---|---|---|
4*sqrt(3) | sqrt(48) | incorrect | Check to see if your answer is simplified. | |
sqrt(48) | 4*sqrt(3) | incorrect | Check to see if your answer is simplified. |
Of course, the "check to see if your answer is simplified" message assumes that the professor's answer is simplified, and would not be appropriate for an answer of sqrt(48), so you would want to change that in that case.
If this isn't what you are looking for, then if you can describe what you need, perhaps we can achieve that.
Davide
I thought that I had followed your directions in the modified program below, but I must have missed something. $f was intended to be the correct answer but
the sqrt(48) which only appears in the answer box is accepted as correct.
DOCUMENT();
loadMacros(
"PGstandard.pl", # Standard macros for PG language
"MathObjects.pl",
"contextLimitedPowers.pl"
);
# Print problem number and point value (weight) for the problem
TEXT(beginproblem());
# Show which answers are correct and which ones are incorrect
$showPartialCorrectAnswers = 1;
# code essentially from Davide Cervone 4/25/10
###########################
#
# Subclass the numeric functions
#
package my::Function::numeric;
our @ISA = ('Parser::Function::numeric');
#
# Override sqrt() to return a special value (usually 1) when evaluated
# effectively eliminating it from the product.
#
sub sqrt {
my $self = shift;
my $value = $self->context->flag("setSqrt");
return $value+1 if $value && $_[0] == 1; # force sqrt(1) to be incorrect
return $value if $value;
return $self->SUPER::sqrt(@_);
}
#
# end of subclass
#
package main;
###########################
Context("Numeric")->variables->are(
x => ["Real", limits => [0,2]], # only needed if x is used in the square roots
);
#
# make sqrt() use our subclass
#
Context()->functions->set(sqrt=>{class=>'my::Function::numeric'});
Context()->flags->set(reduceConstantFunctions=>0);
#
#
# Don't allow fractional powers (avoids 1/2 power)
# [Could subclass exponentiation to handle that as well]
#
LimitedPowers::OnlyPositiveIntegers();
$f = Compute("sqrt(48)");
# $g=Compute("4*sqrt(3)");
# $h=Compute("3*sqrt(4)");
BEGIN_TEXT
$BR
\(\sqrt{48}\)= \{ans_rule(20)\}$PAR
\(\sqrt{48}\)= \{ans_rule(20)\}$PAR
\(\sqrt{48}\)= \{ans_rule(20)\}
END_TEXT
#
# Use a custom checker to check that the answers are equivalent
# and that they are still equivalent when sqrt() is replaced by 1
# (so the stuff outside the sqrt() is equal in both)
#
ANS($f->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
#
# Get parsed formula for student and correct answers
#
$student = $ans->{student_formula};
$correct = $correct->{original_formula} if defined $correct->{original_formula};
#
# check if equal when sqrt's are replaced by 1
#
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student);
Context()->flags->set(setSqrt => 0);
#
Value::Error("Check to see if your answer is simplified.") unless $OK;
return $OK;
},formatStudentAnswer=>"reduced"));
ANS($f->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
#
# Get parsed formula for student and correct answers
#
$student = $ans->{student_formula};
$correct = $correct->{original_formula} if defined $correct->{original_formula};
#
# check if equal when sqrt's are replaced by 1
#
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student);
Context()->flags->set(setSqrt => 0);
#
Value::Error("Check to see if your answer is simplified.") unless $OK;
return $OK;
},formatStudentAnswer=>"reduced"));
ANS($f->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
#
# Get parsed formula for student and correct answers
#
$student = $ans->{student_formula};
$correct = $correct->{original_formula} if defined $correct->{original_formula};
#
# check if equal when sqrt's are replaced by 1
#
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student);
Context()->flags->set(setSqrt => 0);
#
Value::Error("Check to see if your answer is simplified.") unless $OK;
return $OK;
},formatStudentAnswer=>"reduced"));
ENDDOCUMENT()
The result was
Entered | Answer Preview | Correct | Result | Messages |
---|---|---|---|---|
4*sqrt(3) | sqrt(48) | incorrect | Check to see if your answer is simplified. | |
sqrt(48) | sqrt(48) | correct | ||
3*sqrt(4) | sqrt(48) | incorrect |
Show correct answers
Your score was not recorded because this problem has not been assigned to you.
You have attempted this problem 0 times.
You received a score of 33% for this attempt.
You have unlimited attempts remaining.
Main Menu
CoursesThis also seems not to be what you expect. What is it that you DO expect to happen?
Davide
Note that in its current form,sqrt(ab)
could be entered assqrt(a)sqrt(b)
by the student. If you want to prevent that, use a value other than 1 for the value ofsetSqrt
, e.g.Context()->flags->set(setSqrt => pi);so thatsqrt(ab)
will be replaced by pi, whilesqrt(a)sqrt(b)
will be replaced by pi*pi, making the student's answer different from the professor's.
Note that this means that a correct answer of sqrt(8)
could be written as sqrt(4)sqrt(2)
when setSqrt
is set to 1, but not when set to something else. (A correct answer of 2sqrt(2)
could not be written sqrt(4)sqrt(2)
with either value of setSqrt
, however.) Since the original poster was asking about checking reduced roots, the question of sqrt(8)
as a correct answer was not an issue. But since you seem to want to use sqrt(48)
as a correct answer, you should be aware of these issues.
Davide
I feel particularly dense. I thought that I had changed $f. I made the appropriate changes and now things work perfectly.
Ken
Davide
loadMacros("contextLimitedPowers.pl"); # # Set up the LimitedSqrt context # sub _contextLimitedSqrt_init { my $context = $main::context{LimitedSqrt} = Parser::Context->getCopy("Numeric"); $context->flags->set(limits => [0,2]); # no negatives in the radicals $context->functions->set(sqrt=>{class=>'my::Function::numeric'}); # override sqrt() LimitedPowers::OnlyPositiveIntegers($context); # don't allow powers of 1/2 $context->{cmpDefaults}{Formula}{checker} = sub { my ($correct,$student,$ans) = @_; return 0 if $ans->{isPreview} || $correct != $student; Context()->flags->set(setSqrt => 1); delete $correct->{test_values}, $student->{test_values}; my $OK = ($correct == $student); # check if equal when sqrt's are replaced by 1 Context()->flags->set(setSqrt => 0); Value::Error("You must simplify your answer further") unless $OK; return $OK; }; } ########################### # # Subclass the numeric functions # package my::Function::numeric; our @ISA = ('Parser::Function::numeric'); # # Override sqrt() to return a special value (usually 1) when evaluated # effectively eliminating it from the product. # sub sqrt { my $self = shift; my $value = $self->context->flag("setSqrt"); return $value+1 if $value && $_[0] == 1; # force sqrt(1) to be incorrect return $value if $value; return $self->SUPER::sqrt(@_); } 1;
Then in your problem file, use
loadMacros("contextLimitedSqrt"); Context("LimitedSqrt")->variables->are(a=>"Real", b=>"Real"); $f = Formula("3a^5b^2sqrt(a)"); ... (rest of problem) ... ANS($f->cmp);
That should do it.
Davide
The correct answer "x sqrt(6)" is marked wrong for the code below. And when the code is altered by specifying that the answer to be 2*sqrt(3), the answer sqrt(12) is still marked correct, although the intent was to require the simplified radical for credit.
Bruce
-------------------
DOCUMENT();
loadMacros(
"PGstandard.pl", # Standard macros for PG language
"MathObjects.pl",
"contextLimitedPowers.pl"
);
# Print problem number and point value (weight) for the problem
TEXT(beginproblem());
# Show which answers are correct and which ones are incorrect
$showPartialCorrectAnswers = 1;
# code essentially from Davide Cervone 3/29/10
###########################
#
# Subclass the numeric functions
#
package my::Function::numeric;
our @ISA = ('Parser::Function::numeric');
#
# Override sqrt() to return a special value (usually 1) when evaluated
# effectively eliminating it from the product.
#
sub sqrt {
my $self = shift;
my $value = $self->context->flag("setSqrt");
return $value+1 if $value && $_[0] == 1; # force sqrt(1) to be incorrect
return $value if $value;
return $self->SUPER::sqrt(@_);
}
#
# end of subclass
#
package main;
###########################
Context("Numeric")->variables->are(
x => ["Real", limits => [0,2]],
);
#
# make sqrt() use our subclass
#
Context()->functions->set(sqrt=>{class=>'my::Function::numeric'});
#
# Don't allow fractional powers (avoids 1/2 power)
# [Could subclass exponentiation to handle that as well]
#
LimitedPowers::OnlyPositiveIntegers();
$f = Compute("x*sqrt(6)");
BEGIN_TEXT
$BR
9. \{ans_rule(20)\}
END_TEXT
#
# Use a custom checker to check that the answers are equivalent
# and that they are still equivalent when sqrt() is replaced by 1
# (so the stuff outside the sqrt() is equal in both)
#
ANS($f->cmp(checker => sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
Context()->flags->set(setSqrt => 1);
delete $correct->{test_values}, $student->{test_values};
my $OK = ($correct == $student); # check if equal when sqrt's are replaced
# by 1
Context()->flags->set(setSqrt => 0);
Value::Error("Check to see if your answer is simplified.") unless $OK;
return $OK;
}));
ENDDOCUMENT();
x*sqrt(6)
and 2*sqrt(3)
are different from your original example of sqrt(9a^5b^4)
in a key respect: the square roots in your latest examples are constants of, but in the former they are of formulas. (The a and b are variables in the problem, even though you think of them as constants.) My example code above did not handle this situation, since it was not part of the original example. The problem is that things like sqrt(6)
get turned into their numeric equivalents during the parsing phase, so the formula returned for the professor's answer for x*sqrt(6)
would be x*2.44949
, and the trick about evaluating the square roots would not work (since the square root is gone).
This can be resolved by making the following change: set the reduceConstantFunctions
context flag to 0, so that the square root of a constant will be retained in the formula as an actual square root (this is done automatically for student answers, but not professor formulas). This is done using:
Context()->flags->set(reduceConstantFunctions=>0);That should make your
x*sqrt(6)
question work as you desired.
For the 2*sqrt(3)
, not only is the square root a constant, the whole answer is a constant, so Compute("2*sqrt(3)")
returns a MathObject Real (approximately 3.4641), not a formula, and the student's answer of sqrt(12)
returns the same real, so they are counted as equal.
This can be resolved by adding the following two lines to the checker:
$student = $ans->{student_formula}; $correct = $correct->{original_formula} if defined $correct->{original_formula};right after the line
return 0 if $ans->{isPreview} || $correct != $student;This will use the parsed formula that the student typed rather than the final numeric answer (so
sqrt(12)
and 2*sqrt(3)
will be different formulas), and will use the original parsed formula for the correct answer, if it was created using Compute()
, but is a constant.
Note that these changes will be OK even for the x*sqrt(6)
example, and even for the original sqrt(9a^5b^4)
formula, so this is probably the best form to use for your problems.
Note that a student MUST enter sqrt(6)
rather than a numeric value, so if she enters x*2.44949
, it will be marked wrong (with the error "check to see if your answer is simplified"). Not the best error, but it would take some more work to do better.
Finally, note that the results table will show the student answer in the "entered" column as the numeric result rather than the parsed formula. You may want to change that to reflect the fact that you care about the form, not just the number. Adding formatStudentAnswer=>"reduced"
to the cmp()
call will do that, even when the correct answer is a constant.
So here is the updated version that should work for all your answers.
DOCUMENT(); loadMacros( "PGstandard.pl", # Standard macros for PG language "MathObjects.pl", "contextLimitedPowers.pl" ); # Print problem number and point value (weight) for the problem TEXT(beginproblem()); # Show which answers are correct and which ones are incorrect $showPartialCorrectAnswers = 1; # code essentially from Davide Cervone 4/25/10 ########################### # # Subclass the numeric functions # package my::Function::numeric; our @ISA = ('Parser::Function::numeric'); # # Override sqrt() to return a special value (usually 1) when evaluated # effectively eliminating it from the product. # sub sqrt { my $self = shift; my $value = $self->context->flag("setSqrt"); return $value+1 if $value && $_[0] == 1; # force sqrt(1) to be incorrect return $value if $value; return $self->SUPER::sqrt(@_); } # # end of subclass # package main; ########################### Context("Numeric")->variables->are( x => ["Real", limits => [0,2]], # only needed if x is used in the square roots ); # # make sqrt() use our subclass # Context()->functions->set(sqrt=>{class=>'my::Function::numeric'}); Context()->flags->set(reduceConstantFunctions=>0); # # Don't allow fractional powers (avoids 1/2 power) # [Could subclass exponentiation to handle that as well] # LimitedPowers::OnlyPositiveIntegers(); $f = Compute("x*sqrt(6)"); BEGIN_TEXT $BR 9. \{ans_rule(20)\} END_TEXT # # Use a custom checker to check that the answers are equivalent # and that they are still equivalent when sqrt() is replaced by 1 # (so the stuff outside the sqrt() is equal in both) # ANS($f->cmp(checker => sub { my ($correct,$student,$ans) = @_; return 0 if $ans->{isPreview} || $correct != $student; # # Get parsed formula for student and correct answers # $student = $ans->{student_formula}; $correct = $correct->{original_formula} if defined $correct->{original_formula}; # # check if equal when sqrt's are replaced by 1 # Context()->flags->set(setSqrt => 1); delete $correct->{test_values}, $student->{test_values}; my $OK = ($correct == $student); Context()->flags->set(setSqrt => 0); # Value::Error("Check to see if your answer is simplified.") unless $OK; return $OK; },formatStudentAnswer=>"reduced"));
The formatStudentAnswer
flag could be set in the Context instead of including it in cmp()
if you perfer.
Hope that does it.
Davide
I've inserted your fixes, and all appears well.
Thanks!
Bruce
For the current set, our variable expressions represent positive values, and we do not ask for the absolute values.I hope that you will make that assumption explicit in the statement of your problems (especially if you contribute to the NPL). One of my pet peeves is that incoming Freshmen believe that [math]\sqrt{x^2}=x[/math], which is not true in general, and few of them seem to have seen [math]\sqrt{x^2}=|x|[/math] which is true, at least for real [math]x[/math]. This is a major source of algebraic errors for students in beginning calculus, and is something I would like to see them have a better understanding of when then come into college.
Just my two cent's worth.
Davide