From: dpvc v. a. <we...@ma...> - 2005-06-02 22:52:19
|
Log Message: ----------- Modified the Formula answer checker to allow checking of points where the functions are undefined (i.e., attempt to check that the domains agree). This can be enabled by setting the checkundefinedPoints flag in the Context(), or in the formula itself. Test points where the function is undefined will be retained and the student's answer will have to be undefined at the same points. If the points are selected at random, the points where the function is undefined are kept in addition to the ones where it is defined, so the required number of test points where the function IS defined will still need to be found. The number of undefined points can be up to the number of defined points. This number can be reduced by setting the max_undefined flag. If you want to guarantee that a specific undefined point be tested, you can provide that point using the test_points field of the formula. For example: Context()->flags->set(checkUndefinedPoints=>1); $f = Formula("(x^2-1)/(x-1)"); $f->{test_points} = [[-1],[0],[1],[2]]; ANS($f->cmp); will guarantee that the singularity at x=1 is tested, so the answer x+1 will not be marked as correct. If an answer matches at all the test points where the functions are both defined, but some of the undefind points differ between the two functions, the answer checker will generate an error message indicating that the domains of the functions don't agree. (This is ONLY generated with the functions match except for that.) This can be controlled by setting the showDomainErrors flag in the formula's cmp() call: ANS($f->cmp(showDomainErrors=>0)); The default is to show these errors. Modified Files: -------------- pg/lib/Value: Formula.pm AnswerChecker.pm Revision Data ------------- Index: AnswerChecker.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/AnswerChecker.pm,v retrieving revision 1.43 retrieving revision 1.44 diff -Llib/Value/AnswerChecker.pm -Llib/Value/AnswerChecker.pm -u -r1.43 -r1.44 --- lib/Value/AnswerChecker.pm +++ lib/Value/AnswerChecker.pm @@ -748,19 +748,24 @@ return ( Value::Union::cmp_defaults($self,@_), typeMatch => Value::Formula->new("(1,2]"), + showDomainErrors => 1, ) if $self->type eq 'Union'; my $type = $self->type; $type = ($self->isComplex)? 'Complex': 'Real' if $type eq 'Number'; $type = 'Value::'.$type.'::'; - return (&{$type.'cmp_defaults'}($self,@_), upToConstant => 0) - if defined(%$type) && $self->type ne 'List'; + return ( + &{$type.'cmp_defaults'}($self,@_), + upToConstant => 0, + showDomainErrors => 1, + ) if defined(%$type) && $self->type ne 'List'; return ( Value::List::cmp_defaults($self,@_), removeParens => $self->{autoFormula}, typeMatch => Value::Formula->new(($self->createRandomPoints(1))[1]->[0]{data}[0]), + showDomainErrors => 1, ); } @@ -800,7 +805,8 @@ 'C0|' . $context->{_variables}->{pattern}; $context->update; $context->variables->add('C0' => 'Parameter'); my $f = Value::Formula->new('C0')+$self; - for ('limits','test_points','test_values','num_points','granularity','resolution') + for ('limits','test_points','test_values','num_points','granularity','resolution', + 'checkUndefinedPoints','max_undefined') {$f->{$_} = $self->{$_} if defined($self->{$_})} $cmp->ans_hash(correct_value => $f); Parser::Context->current(undef,$current); @@ -831,7 +837,12 @@ sub cmp_postprocess { my $self = shift; my $ans = shift; return unless $ans->{score} == 0 && !$ans->{isPreview}; - return if $ans->{ans_message} || !$ans->{showDimensionHints}; + return if $ans->{ans_message}; + if ($self->{domainMismatch} && $ans->{showDomainErrors}) { + $self->cmp_Error($ans,"The domain of your function doesn't match that of the correct answer"); + return; + } + return if !$ans->{showDimensionHints}; my $other = $ans->{student_value}; return if $ans->{ignoreStrings} && (!Value::isValue($other) || $other->type eq 'String'); return unless $other->type =~ m/^(Point|Vector|Matrix)$/; Index: Formula.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Formula.pm,v retrieving revision 1.22 retrieving revision 1.23 diff -Llib/Value/Formula.pm -Llib/Value/Formula.pm -u -r1.22 -r1.23 --- lib/Value/Formula.pm +++ lib/Value/Formula.pm @@ -5,6 +5,8 @@ package Value::Formula; my $pkg = 'Value::Formula'; +my $UNDEF = bless {}, "UNDEF"; + use strict; use vars qw(@ISA); @ISA = qw(Parser Value); @@ -158,7 +160,7 @@ ## FIXME: Check given points for consistency my $points = $l->{test_points} || $r->{test_points} || $l->createRandomPoints; my $lvalues = $l->{test_values} || $l->createPointValues($points,1); - my $rvalues = $r->createPointValues($points); + my $rvalues = $r->createPointValues($points,0,$l->{checkUndefinedPoints}); # # Note: $l is bigger if $r can't be evaluated at one of the points return 1 unless $rvalues; @@ -186,15 +188,17 @@ } # - # Look through the two lists to see if they are equal. + # Look through the two lists of values to see if they are equal. # If not, return the comparison of the first unequal value # (not good for < and >, but OK for ==). # + my $domainError = 0; foreach $i (0..scalar(@{$lvalues})-1) { + if (ref($lvalues->[$i]) eq 'UNDEF' ^ ref($rvalues->[$i]) eq 'UNDEF') {$domainError = 1; next} $cmp = $lvalues->[$i] <=> $rvalues->[$i]; return $cmp if $cmp; } - return 0; + $l->{domainMismatch} = $domainError; } # @@ -208,15 +212,16 @@ my @params = $self->{context}->variables->parameters; my @zeros = (0) x scalar(@params); my $f = $self->{f}; $f = $self->{f} = $self->perlFunction(undef,[@vars,@params]) unless $f; + my $checkUndef = scalar(@params) == 0 && (shift || $self->getFlag('checkUndefinedPoints',0)); my $values = []; my $v; foreach my $p (@{$points}) { $v = eval {&$f(@{$p},@zeros)}; - if (!defined($v)) { + if (!defined($v) && !$checkUndef) { return unless $showError; Value::Error("Can't evaluate formula on test point (".join(',',@{$p}).")"); } - push @{$values}, Value::makeValue($v); + push @{$values}, (defined($v)? Value::makeValue($v): $UNDEF); } $self->{test_points} = $points; $self->{test_values} = $values; @@ -265,18 +270,27 @@ my $f = $self->{f}; $f = $self->{f} = $self->perlFunction(undef,[@vars,@params]) unless $f; my $seedRandom = $self->{context}->flag('random_seed')? 'PGseedRandom' : 'seedRandom'; my $getRandom = $self->{context}->flag('random_seed')? 'PGgetRandom' : 'getRandom'; + my $checkUndef = scalar(@params) == 0 && $self->getFlag('checkUndefinedPoints',0); + my $maxUndef = $self->getFlag('max_undefined',$num_points); $self->$seedRandom; - my $points = []; my $values = []; + my $points = []; my $values = []; my $num_undef = 0; my (@P,@p,$v,$i); my $k = 0; - while (scalar(@{$points}) < $num_points && $k < 10) { + while (scalar(@{$points}) < $num_points+$num_undef && $k < 10) { @P = (); $i = 0; foreach my $limit (@limits) { @p = (); foreach my $I (@{$limit}) {push @p, $self->$getRandom(@{$I})} push @P, $make[$i++]->make(@p); } $v = eval {&$f(@P,@zeros)}; - if (!defined($v)) {$k++} else { + if (!defined($v)) { + if ($k == 0 && $checkUndef) { + push @{$points}, [@P]; + push @{$values}, $UNDEF; + $num_undef++; + } + $k++; + } else { push @{$points}, [@P]; push @{$values}, Value::makeValue($v); $k = 0; # reset count when we find a point |