################################################################################
# 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 and rationalization of denominators.
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 on the
other hand.
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 to check that the student
has grouped like terms well.
Lastly during a check, / is temporarily replaced with a bizarro version to check that instances of
things like 4sqrt(2)/2 do not appear.
If a student's answer is numerically equal to the author's, but not in the same form, students get the
message that their answer is not fully simplified. So care should be taken when defining author answers.
Technically this will not be a correct message in the situation where the answer is sqrt(3)/3 and the student
enters 1/sqrt(3). 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 their
answers reduced. Also note that the special checks for like terms being grouped together and to see that fractions
are reduced are not honestly checking for that. 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.
=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::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,2]); # no negatives in the radicals
$context->flags->set(reduceConstantFunctions=>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};
Context()->flags->set(setSqrt => 1, setRoot => 1, checkAddSub => 1, checkDiv => 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, setRoot => 0, checkAddSub => 0, checkDiv => 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 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 (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(@_);
}
###########################
#
# Subclass the addition and subtraction
#
package my::BOP::add;
our @ISA = ('Parser::BOP::add');
#
# Return a + b + sin(a) + sin(b) + /sqrt(2) when checkAddSub is set
#
sub _eval {
my $self = shift;
my $context = $self->context;
my ($a,$b) = @_;
if ($context->flag("checkAddSub")) {
return $a+$b+CORE::sin($a)+CORE::sin($b)+CORE::exp(1)/CORE::sqrt(2);
} else {
return $a + $b;
}
}
sub call {(shift)->_eval(@_)}
#
# Return a - b + sin(a) + sin(-b) + /sqrt(2) 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+CORE::sin($a)+CORE::sin(-$b)+CORE::exp(1)/CORE::sqrt(2);
} else {
return $a - $b;
}
}
###########################
#
# Subclass the division
#
package my::BOP::divide;
our @ISA = ('Parser::BOP::divide');
#
# Return a/(b^3(2+sin(b)) when checkDiv is set
#
sub _eval {
my $self = shift;
my $context = $self->context;
my ($a,$b) = @_;
if ($context->flag("checkDiv")) {
return $a/(($b)**3*(2+CORE::sin($b)));}
else {
return $a / $b;
}
}
sub call {(shift)->_eval(@_)}
1;