# Proposed redesign of basis_checker_rows() from pg/macros/MatrixCheckers.pl # to overcome problems with the behavior of that function. # The original code of pg/macros/MatrixCheckers.pl is # by Paul Pearson, Hope College, Department of Mathematics # and was the original basis and the inspiration for much # of that is in the proposed new checker. # For now the proposed new checker is called basis_checker_rows_tani() # and the local version of basis_checker_rows() has a bit of debugging # code added. sub _MatrixCheckers_init {}; # don't reload this file loadMacros("MathObjects.pl"); # ============================================================ sub concatenate_rows_into_matrix { my @r = @_; my @temp = (); for my $row (@r) { push(@temp,Matrix($row)->row(1)); } return Matrix(\@temp)->transpose; # We expect the basis vectors in COLUMNS later on in the code } # The original code more directly based on Paul Pearson's original code # was having some difficulty handling certain incorrect answers. # Space where I had problems had correct basis as (1,0,3,4) and (0,1,-1,-1) # The answer (1,2,1,2) and (sqrt(253),2sqrt(253),sqrt(253),2sqrt(253)+t). # The second of these vectors has a shift "t" in the last coordinate # from being sqrt(253) times the first vector. # For t=0 and t=0.00000001 the original code saw the vectors as dependent. # For t=0.1,0.01 the original code marked the answer as incorrect (it could # tell that the second vector was not in the space.) # However, for t in { 0.001, 0.0001, 0.00001, 0.000001, 0.0000001 } # the answer was being accepted as correct when it is NOT. # The issues seem to relate to be a result of too much "tolerance" in some # calculations. # The new version does not accept the incorrect answers accepted by the prior # version of the code. For t in { 0.1, 0.01, ..., 0.0000000001 } the second # vector is recognized as not in the space. However, for a very small values of # t, such as t=0.00000000001 the new code sees the 2 vectors in the answer as # dependent (as if t were really 0). sub basis_checker_rows_tani { my ( $correct, $student, $self, $answerHash ) = @_; my @c = @{$correct}; my @s = @{$student}; my $dimSpace = scalar( @c ); my $numStudV = scalar( @s ); # Most of the answer checking is done on integers # or on decimals like 0.24381729, so we will set the # tolerance accordingly in a local context. # the tolerance was set to be much smaller than in the old code my $context = Context()->copy; $context->flags->set( tolerance => 0.0000001, tolType => "absolute", ); return 0 if ( $numStudV < $dimSpace ); # count the number of vector inputs my $C = concatenate_rows_into_matrix(@c); my $S = concatenate_rows_into_matrix(@s); # Put $C and $S into the local context so that # all of the computations that follow will also be in # the local context. $C = Matrix($context,$C); $S = Matrix($context,$S); # $self->{ans}[0]->{ans_message} .= "C = $C$BR"; # $self->{ans}[0]->{ans_message} .= "S = $S$BR"; $rankC = rank($C); $rankS = rank($S); # Check that the professor's vectors are, in fact, linearly independent. # The original approach based on # Theorem: A^T A is invertible if and only if A has linearly independent columns. # was more likely to ignore small shifts, as the determinant would end up very small # We now use the improved rank.pl to test this. warn "Correct answer is a linearly dependent set." if ( $rankC < $dimSpace ); my @notInSpaceMessage = ( ); my @wasInSpace = (); my $notInSpace = 0; # Check each student vector to see if it is in the required space. my $j; for( $j = 0 ; $j < $numStudV ; $j++ ) { my @c1 = ( @{$correct} ); push( @c1, $s[$j] ); my $C1 = concatenate_rows_into_matrix(@c1); if ( rank( $C1 ) > $dimSpace ) { my $tmp1 = $j + 1; $notInSpace++; push( @notInSpaceMessage, "The vector of index $tmp1 in your answer does not belong to the given space.$BR" ); } elsif ( ! $s[$j]->isZero ) { push( @wasInSpace, $s[$j] ); } } # How many independent were in the space my $goodCount = 0; my $secondaryDependenceTest1 = 0; if ( @wasInSpace ) { my $C1 = concatenate_rows_into_matrix( @wasInSpace ); $goodCount = rank( $C1 ); # Add a second test for a linear dependence of this part of the students answers, # in case the rank code misbehaves. This is a revised version of the test originally used. # It was needed for the case t=0.00000000002 in the test example discussed above my $dd = (($C1->transpose) * $C1)->det; if ( ( $dd == Real(0) ) && ($goodCount == scalar( @wasInSpace ) ) ) { $secondaryDependenceTest1 = 1; # warn "secondaryDependenceTest1 turned on"; } } if ( ( $goodCount == $dimSpace ) && ( $goodCount == $numStudV ) && ($secondaryDependenceTest1 == 0 ) ) { # There are the correct number of independent vectors from the required space, and no others return 1; } my $depWarn = ""; if ( $secondaryDependenceTest1 == 1 ) { # The value of $goodCount was WRONG. Decrease it by one, and add a warning if the result is still > 1 if ( ( --$goodCount ) > 1 ) { $depWarn = "It is possible that the grading code had an error when counting the number of linearly independent vectors from the given space which were included in your answer.$BR"; } } if ( $goodCount == 1 ) { unshift( @notInSpaceMessage, "The answer you gave contains only one linearly independent vector from the given space.$BR$depWarn" ); } elsif ( $goodCount > 1 ) { unshift( @notInSpaceMessage, "Your answer contains $goodCount linearly independent vector from the given space.$BR$depWarn" ); } # Add a second test for a linear dependence of ALL of the students answers, # in case the rank code misbehaves. This is a revised version of the test originally used. my $secondaryDependenceTest2 = 0; $dd = (($S->transpose) * $S)->det; # To debug # warn "The determinant tested against zero to check linearly dependence had the value $dd"; if ( ( $dd == Real(0) ) && ( $rankS == $numStudV ) ) { $secondaryDependenceTest2 = 1; # warn "secondaryDependenceTest2 turned on"; } # Check that the student's vectors are linearly independent if ( ( $rankS < $numStudV ) || ( ( $secondaryDependenceTest1 + $secondaryDependenceTest2 ) > 0 ) ) { # There is a linear dependence among the students answers. # Sometimes the detection of linear dependence conflicts with that of a vector not in the space. # So give the message only if nothing was found to be outside the space. if ( $notInSpace == 0 ) { unshift( @notInSpaceMessage, "The vectors in your answer are linearly dependent.$BR"); } } # else { # The students vectors are linearly independent. # } $self->{ans}[0]->{ans_message} = join("", @notInSpaceMessage ) unless $ans->{isPreview}; my $score = ( $goodCount / $dimSpace ) - ( ( $notInSpace / $dimSpace / 4 ) ) ; $score = 0 if ( $score < 0 ); # in case penalties exceed credit for good vectors # $self->{ans}[0]->{ans_message} .= "$BR$BR score = $score"; # The bug which caused the need to scale was fixed in WW/PG 2.16 ## Scale due to MultiAnswer issue with fractional scores #$score *= ( $dimSpace )/( -1 + $dimSpace ); return $score; } ########################################## sub basis_checker_rows { my ( $correct, $student, $answerHash ) = @_; my @c = @{$correct}; my @s = @{$student}; # Most of the answer checking is done on integers # or on decimals like 0.24381729, so we will set the # tolerance accordingly in a local context. my $context = Context()->copy; $context->flags->set( tolerance => 0.001, tolType => "absolute", ); return 0 if scalar(@s) < scalar(@c); # count the number of vector inputs my $C = concatenate_rows_into_matrix(@c); my $S = concatenate_rows_into_matrix(@s); # Put $C and $S into the local context so that # all of the computations that follow will also be in # the local context. $C = Matrix($context,$C); $S = Matrix($context,$S); # Theorem: A^T A is invertible if and only if A has linearly independent columns. # Check that the professor's vectors are, in fact, linearly independent. $CTC = ($C->transpose) * $C; warn "Correct answer is a linearly dependent set." if ($CTC->det == 0); # Check that the student's vectors are linearly independent if ( (($S->transpose) * $S)->det == 0) { Value->Error("The vectors in your answer are linearly dependent."); return 0; } # Next 2 lines added for local testing... my $dd = (($S->transpose) * $S)->det; # warn "The determinant tested against zero to check linearly dependence had the value $dd"; # S = student, C = correct, X = change of basis matrix # Solve S = CX for X using (C^T C)^{-1} C^T S = X. $X = ($CTC->inverse) * (($C->transpose) * $S); return $S == $C * $X; } ############################################# 1;