################################################################################
# WeBWorK Online Homework Delivery System
# Copyright © 2000-2013 The WeBWorK Project, http://openwebwork.sf.net/
# $CVSHeader: pg/macros/parserPrime.pl,v 1.2 2009/10/03 15:58:49 dpvc Exp $
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of either: (a) the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version, or (b) the "Artistic License" which comes with this package.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
# Artistic License for more details.
################################################################################
=head1 NAME
contextLimitedRadical.pl - defines a root(n,x) function for n-th root of x, and
allows for specification of forms of radical answers
=head1 DESCRIPTION
This file enables a context LimitedRadical in which students and pg-authors may use root(n,x) for the nth root of x. The function checks for n=0, and also allows for negative x only if n is an odd integer.
The context requires that radical expressions (using either sqrt or root) meet the form of the professor's answer with respect to radical simplification, rationalization of denominators, and to a large extent fraction reduction.
To accomplish this, objects like root(3,2) should be created as Formulas, not with Compute, or they will be treated as Reals and reduced. Compute("root(3,x)") should be fine though.
To check that radicals themselves are fully simplified, during a check, both sqrt(x) and root(n,x) are temporarily replaced by the constant function 1. The student and author answers have to agree both before and after this change. Any radical applied to 1 is declared unsimplified as well.
Also during a check, +, -, *, and / are temporarily replaced with bizarro versions that nearly make for a bizarro field structure on R. The student and professor answers are checked with bizarro arithmetic in place to check that the student has simplified things like each of the following: 1+2, sqrt(2)+sqrt(2), 2sqrt(2)*2, 2sqrt(2)/2.
If a student's answer is numerically equal to the author's, but not equal under bizarro arithmetic, students get the message that their answer is not fully simplified. So care should be taken by the problem author when defining answers.
The near-field structure of the bizarro versions means that 1+sqrt(2)+sqrt(3) can be entered as sqrt(3)+sqrt(2)+1, etc, and not be declared "unsimplified".
The near-field structure also means that (1+sqrt(2))/2 can either be entered that way or as 1/2+sqrt(2)/2 without being declared "unsimplified". However, (1+2sqrt(2))/2 *will* be declared different from 1/2+sqrt(2), even though both are generally considered fully simplified. If an author can foresee this arising, consider using parserOneOf.pl to allow for multiple forms.
Technically both sqrt(3)/3 and 1/sqrt(3) are fully simplified, although you may want rational denominators. This context will declare them equal but not equivlent. The author should either accept both as correct using parserOneOf.pl, or give the message that the student's answer does not have a rational denominator using answerHints.pl.
Note that there is nothing here that is actually doing any reducing - so authors need to make sure their answers are reduced. Also note that the special checks for like terms being grouped together and to see that fractions are reduced etc. are not honestly checking for those things. So if the correct answer is sqrt(2) and the student enters sqrt(3)+sqrt(3) or 4sqrt(2)/2, they will just be told they are wrong - not that their (incorrect) answer is unsimplified.
Lastly, there are certain circumstances where usual(a+b) = bizarro(a+b). The worst example of this is with 2+2. These instances are rare, but can arise. One could try to make them more rare by making the bizarro operators more weird, but care needs to be given toward making the bizarro operators respect these pseudo-field axioms:
a+0 != a
a+0 !constant
a+b = b+a
(a+b)+c = a+(b+c)
a-b = a+(-b) (a minus b equals a plus _negative_ b)
a*1 != a
a*1 !constant
a*b = b*a
(a*b)*c = a*(b*c)
a/b = a*(b^-1)
a*(b+c) = a*b + a*c
(b + c)/a = b/a + c/a
The current settings for bizarro operators are given on the left, with their meanings on the right:
a+b = a*b (2+2 = 2*2 is one problem)
a-b = a*(-b) (1/2 - 1 = 1/2*(-1) is one problem)
a*b = exp(log(abs(a)) * log(abs(b)))
a/b = exp(log(abs(a)) * log(abs(b^-1)))
=cut
loadMacros(
"contextLimitedPowers.pl",
);
#
# Set up the LimitedRadical context
#
sub _contextLimitedRadical_init {
my $context = $main::context{LimitedRadical} = Parser::Context->getCopy("Numeric");
Parser::Number::NoDecimals($context);
$context->operators->set(
'+' => {class => 'my::BOP::add', isCommand => 1}, # override +
'-' => {class => 'my::BOP::subtract', isCommand => 1}, # override -
'*' => {class => 'my::BOP::multiply', isCommand => 1}, # override *
'/' => {class => 'my::BOP::divide', isCommand => 1}, # override /
);
$context->functions->set(sqrt=>{class=>'my::Function::numeric'}); # override sqrt()
main::PG_restricted_eval('sub root {Parser::Function->call("root",@_)}');
$context->functions->add(
root => {class => 'parser::Root::Function::numeric2'},
);
$context->flags->set(limits => [0,100]); # no negatives in the radicals
$context->flags->set(reduceConstantFunctions=>0,reduceConstants=>0);
$context->flags->set(formatStudentAnswer =>parsed);
LimitedPowers::OnlyPositiveIntegers($context); # don't allow powers of 1/2, 1/3, etc
$context->{cmpDefaults}{Formula}{checker} = sub {
my ($correct,$student,$ans) = @_;
return 0 if $ans->{isPreview} || $correct != $student;
$student = $ans->{student_formula};
$correct = $correct->{original_formula} if defined $correct->{original_formula};
$student = Formula("$student"); $correct = Formula("$correct"); #ensure both are Formula objects
Context()->flags->set(setRoot => exp(1)/ln(2), checkAddSub => 1, checkDiv => 1, checkMul => 1); #Don't use 1 for setSqrt, setRoot, or else a*sqrt(b) always bizarro evaluates to 1. exp(1)/ln(2) is hopefully not going to arise in problems
delete $correct->{test_values}, $student->{test_values};
my $OK = (($correct == $student) or ($student == $correct)); # check if equal when sqrt's are replaced by 1
Context()->flags->set(setRoot => 0, checkAddSub => 0, checkDiv => 0, checkMul => 0);
Value::Error("You must simplify your answer further") unless $OK;
return $OK;
};
}
###########################
#
# Create root(n, x)
#
package parser::Root::Function::numeric2;
our @ISA = ('Parser::Function::numeric2');
sub root {
my $self = shift; my $n = shift; my $x = shift;
$self->Error("Can't take 0th roots") if ($n == 0);
$self->Error("Can't take general roots of negative numbers") if (($x < 0) and (($n-1)/2 != int(($n-1)/2)));
my $value = $self->context->flag("setRoot");
return $value+1 if $value && $x == 1; # force root(n,1) to be incorrect
return $value+1 if $value && $x == $value; # force root(n,root(m,x))) to be incorrect
return $value if $value;
return ($x)**(1/$n) if (($x > 0) and ($n != 0));
return -(abs($x)**(1/$n)) if (($x < 0) and (($n-1)/2 == int(($n-1)/2)));
}
sub TeX {
my $self = shift;
my ($n,$x) = ($self->{params}[0],$self->{params}[1]);
return '\sqrt['.$n->TeX."]{".$x->TeX."}";
}
###########################
#
# Subclass the numeric functions
#
package my::Function::numeric;
our @ISA = ('Parser::Function::numeric');
#
# Override sqrt() to return a special value 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+1 if $value && $_[0] == $value; # force sqrt(sqrt(x)) to be incorrect
return $value if $value;
return $self->SUPER::sqrt(@_);
}
###########################
#
# Subclass the addition and subtraction
#
package my::BOP::add;
our @ISA = ('Parser::BOP::add');
#
# Return a * b when checkAddSub is set
#
sub _eval {
my $self = shift;
my $context = $self->context;
my ($a,$b) = @_;
if ($context->flag("checkAddSub")) {
return $a*$b;
} else {
return $a + $b;
}
}
sub call {(shift)->_eval(@_)}
#
# Return a * (-b) when checkAddSub is set
#
package my::BOP::subtract;
our @ISA = ('Parser::BOP::subtract');
sub _eval {
my $self = shift;
my $context = $self->context;
my ($a,$b) = @_;
if ($context->flag("checkAddSub")) {
return $a * (-$b);
} else {
return $a - $b;
}
}
###########################
#
# Subclass the multiplication
#
package my::BOP::multiply;
our @ISA = ('Parser::BOP::multiply');
#
# Return exp(log|a|*log|b|) when checkMul is set
# or 1 if either a or b is 0
#
sub _eval {
my $self = shift;
my $context = $self->context;
my ($a,$b) = @_;
if ($context->flag("checkMul")) {
return CORE::exp(CORE::log(CORE::abs($a))*CORE::log(CORE::abs($b))) unless ($a*$b == 0);
return 1;}
else {
return $a * $b;
}
}
sub call {(shift)->_eval(@_)}
###########################
#
# Subclass the division
#
package my::BOP::divide;
our @ISA = ('Parser::BOP::divide');
#
# Return exp(log|a|*log|b^-1|) when checkDiv is set
# or 1 if a is 0
#
sub _eval {
my $self = shift;
my $context = $self->context;
my ($a,$b) = @_;
if ($context->flag("checkDiv")) {
return CORE::exp(CORE::log(CORE::abs($a))*CORE::log(CORE::abs(($b)**(-1)))) unless ($a == 0);
return 1;}
else {
return $a / $b;
}
}
sub call {(shift)->_eval(@_)}
1;