--- loncom/homework/default_homework.lcpm 2006/07/17 21:11:21 1.110 +++ loncom/homework/default_homework.lcpm 2008/09/27 07:26:45 1.141 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # used by lonxml::xmlparse() as input variable $safeinit to Apache::run::run() # -# $Id: default_homework.lcpm,v 1.110 2006/07/17 21:11:21 www Exp $ +# $Id: default_homework.lcpm,v 1.141 2008/09/27 07:26:45 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -33,6 +33,72 @@ $pi=atan2(1,1)*4; $rad2deg=180.0/$pi; $deg2rad=$pi/180.0; $"=' '; +use strict; +{ + my $n = 0; + my $total = 0; + my $num_left = 0; + my @order; + my $type; + + sub init_permutation { + my ($size,$requested_type) = @_; + @order = (0..$size-1); + $n = $size; + $type = $requested_type; + if ($type eq 'ordered') { + $total = $num_left = 1; + } elsif ($type eq 'unordered') { + $total = $num_left = &factorial($size); + } else { + die("Unkown type: $type"); + } + } + + sub get_next_permutation { + if ($num_left == $total) { + $num_left--; + return \@order; + } + + # Find largest index j with a[j] < a[j+1] + + my $j = scalar(@order) - 2; + while ($order[$j] > $order[$j+1]) { + $j--; + } + + # Find index k such that a[k] is smallest integer + # greater than a[j] to the right of a[j] + + my $k = scalar(@order) - 1; + while ($order[$j] > $order[$k]) { + $k--; + } + + # Interchange a[j] and a[k] + + @order[($k,$j)] = @order[($j,$k)]; + + # Put tail end of permutation after jth position in increasing order + + my $r = scalar(@order) - 1; + my $s = $j + 1; + + while ($r > $s) { + @order[($s,$r)]=@order[($r,$s)]; + $r--; + $s++; + } + + $num_left--; + return(\@order); + } + + sub get_permutations_left { + return $num_left; + } +} sub check_commas { my ($response)=@_; @@ -60,6 +126,7 @@ sub check_commas { return 1; } + sub caparesponse_check { my ($answer,$response)=@_; #not properly used yet: calc @@ -78,20 +145,19 @@ sub caparesponse_check { #type's definitons come from capaParser.h - my $message=''; + #remove leading and trailing whitespace if (!defined($response)) { $response=''; } if ($response=~ /^\s|\s$/) { $response=~ s:^\s+|\s+$::g; - $message .="Removed ws now :$response:\n"; - } else { - $message .="no ws in :$response:\n"; + &LONCAPA_INTERNAL_DEBUG("Removed ws now :$response:"); } - &LONCAPA_INTERNAL_DEBUG(" type is $type "); + + #&LONCAPA_INTERNAL_DEBUG(" type is $type "); if ($type eq 'cs' || $type eq 'ci') { - #for string answers make surec all places spaces occur, there is + #for string answers make sure all places spaces occur, there is #really only 1 space, in both the answer and the response $answer=~s/ +/ /g; $response=~s/ +/ /g; @@ -100,7 +166,7 @@ sub caparesponse_check { $response=~s/[\s,]//g; } if ($type eq 'float' && $unit=~/\$/) { - if ($response!~/^\$/) { return "NO_UNIT: Missing \$ "; } + if ($response!~/^\$|\$$/) { return ('NO_UNIT', undef); } $response=~s/\$//g; } if ($type eq 'float' && $unit=~/\,/ && (&check_commas($response)<0)) { @@ -110,10 +176,10 @@ sub caparesponse_check { $unit=~s/[\$,]//g; if ($type eq 'float') { $response=~s/,//g; } - if (length($response) > 500) { return "TOO_LONG: Answer too long"; } + if (length($response) > 500) { return ('TOO_LONG',undef); } if ($type eq '' ) { - $message .= "Didn't find a type :$type: defaulting\n"; + &LONCAPA_INTERNAL_DEBUG("Didn't find a type :$type: defaulting"); if ( $answer eq ($answer *1.0)) { $type = 2; } else { $type = 3; } } else { @@ -125,7 +191,7 @@ sub caparesponse_check { elsif ($type eq 'subj') { $type = 7; } elsif ($type eq 'float') { $type = 2; } elsif ($type eq 'int') { $type = 1; } - else { return "ERROR: Unknown type of answer: $type" } + else { return ('ERROR', "Unknown type of answer: $type") } } my $points; @@ -133,7 +199,7 @@ sub caparesponse_check { #formula type setup the sample points if ($type eq '8') { ($id_list,$points)=split(/@/,$samples); - $message.="Found :$id_list:$points: points in $samples\n"; + &LONCAPA_INTERNAL_DEBUG("Found :$id_list:$points: points in $samples"); } if ($tol eq '') { $tol=0.0; @@ -151,6 +217,7 @@ sub caparesponse_check { my $reterror=""; my $result; + if ((($type eq '9') || ($type eq '8')) && ($response=~/\=/)) { return ('BAD_FORMULA','Please submit just an expression, not an equation.'); } if ($type eq '9') { $result = &maxima_check(&maxima_cas_formula_fix($response),&maxima_cas_formula_fix($answer),\$reterror); } else { @@ -185,86 +252,188 @@ sub caparesponse_check { elsif ($result =='15') { $result='UNIT_IRRECONCIBLE'; } else {$result = "ERROR: Unknown Result:$result:$@:";} - return ("$result:\nRetError $reterror:\nAnswer $answer:\nResponse $response:\n type-$type|$tol|$tol_type|$sig:$sig_lbound:$sig_ubound|$unit|\n$message",$reterror); + &LONCAPA_INTERNAL_DEBUG("RetError $reterror: Answer $answer: Response $response: type-$type|$tol|$tol_type|$sig:$sig_lbound:$sig_ubound|$unit|"); + &LONCAPA_INTERNAL_DEBUG(" $answer $response $result "); + return ($result,$reterror); } -sub maxima_cas_formula_fix { - my ($expression)=@_; - return &implicit_multiplication($expression); -} - -sub capa_formula_fix { - my ($expression)=@_; - return &implicit_multiplication($expression); -} - -sub implicit_multiplication { - my ($expression)=@_; - $expression=~s/(\d+)e([\+\-]*\d+)/$1\&\($2\)/gsi; - $expression=~s/(\d+)(?:x|\*)10(?:\^|\*\*)([\+\-]*\d+)/$1\&\($2\)/gsi; - $expression=~s/\s+/\*/gs; - $expression=~s/(\d)([a-zA-Z\(])/$1\*$2/gs; - $expression=~s/([a-zA-Z])(\d)/$1\*$2/gs; - $expression=~s/\)(\w)/\)\*$1/gs; - $expression=~s/(\d+)\&\(([\+\-]*\d+)\)/$1e$2/gs; - return $expression; -} sub caparesponse_check_list { - my $response=$LONCAPA::CAPAresponse_args{'response'}; - my $result="Got response :".join(':',@LONCAPA::CAPAresponse_answer).":\n"; + my $responses=$LONCAPA::CAPAresponse_args{'response'}; &LONCAPA_INTERNAL_DEBUG("args ".join(':',%LONCAPA::CAPAresponse_args)); - my @responselist; my $type = $LONCAPA::CAPAresponse_args{'type'}; - $result.="Got type :$type:\n"; - if ($type ne '' && $#LONCAPA::CAPAresponse_answer > 0) { - (@responselist)=split(/,/,$response); - if (@responselist < @LONCAPA::CAPAresponse_answer) { + my $answerunit=$LONCAPA::CAPAresponse_args{'unit'}; + &LONCAPA_INTERNAL_DEBUG("Got type :$type: answer unit :$answerunit:\n"); + + my $num_input_lines = + scalar(@{$LONCAPA::CAPAresponse_answer->{'answers'}}); + + if ($type ne '' ) { + if (scalar(@$responses) < $num_input_lines) { return 'MISSING_ANSWER'; } - if (@responselist > @LONCAPA::CAPAresponse_answer) { + if (scalar(@$responses) > $num_input_lines) { + return 'EXTRA_ANSWER'; + } + + } + + foreach my $which (0..($num_input_lines-1)) { + my $answer_size = + scalar(@{$LONCAPA::CAPAresponse_answer->{'answers'}[$which]}); + if ($type ne '' + && $answer_size > 1) { + $responses->[$which]=[split(/,/,$responses->[$which])]; + } else { + $responses->[$which]=[$responses->[$which]]; + } + } + foreach my $which (0..($num_input_lines-1)) { + my $answer_size = + scalar(@{$LONCAPA::CAPAresponse_answer->{'answers'}[$which]}); + my $response_size = + scalar(@{$responses->[$which]}); + if ($answer_size > $response_size) { + return 'MISSING_ANSWER'; + } + if ($answer_size < $response_size) { return 'EXTRA_ANSWER'; } - } else { - (@responselist)=($response); } - $result.="Initial final response :$responselist['-1']:\n"; + + &LONCAPA_INTERNAL_DEBUG("Initial final response :$responses->[0][-1]:"); my $unit; - if ($type eq '' || $type eq 'float') { + if ($type eq 'float' || $type eq '') { #for numerical problems split off the unit - if ( $responselist['-1']=~ /(.*[^\s])\s+([^\s]+)/ ) { - $responselist['-1']=$1; - $unit=$2; +# if ( $responses->[0][-1]=~ /(.*[^\s])\s+([^\s]+)/ ) { + if ( $responses->[0][-1]=~ /^([\d\.\,\s\$]*(?:(?:[xX\*]10[\^\*]*|[eE]*)[\+\-]*\d*)*(?:^|\S)\d+)([\$\s\w\^\*\/\(\)\+\-]*[^\d\.\s\,][\$\s\w\^\*\/\(\)\+\-]*)$/ ) { + $responses->[0][-1]=$1; + $unit=&capa_formula_fix($2); + &LONCAPA_INTERNAL_DEBUG("Found unit :$unit:"); } } - $result.="Final final response :$responselist['-1']:$unit:\n"; + &LONCAPA_INTERNAL_DEBUG("Final final response :$responses->[0][-1]:$unit:"); $unit=~s/\s//; + foreach my $response (@$responses) { + foreach my $element (@$response) { + if (($type eq 'float') || (($type eq '') && ($unit ne ''))) { + $element =~ s/\s//g; + } + my $appendunit=$unit; + if (($unit=~/\%/) && ($answerunit ne '%')) { + $element=$element/100; + $appendunit=~s/\%//; + } + if (($element==0) && ($unit!~/\w/) && ($answerunit=~/\w/)) { + $appendunit=$answerunit; + } + $element .= " $appendunit"; + &LONCAPA_INTERNAL_DEBUG("Made response element :$element:"); + } + } + + foreach my $thisanswer (@{ $LONCAPA::CAPAresponse_answer->{'answers'} }) { + if (!defined($thisanswer)) { + return ('ERROR','answer was undefined'); + } + } - my ($awards, @msgs, $i); - foreach my $thisanswer (@LONCAPA::CAPAresponse_answer) { - my ($msg,$aresult); - $result.="trying answer :$thisanswer:\n"; - if (defined($thisanswer)) { - if ($unit eq '') { - ($aresult,$msg)=&caparesponse_check($thisanswer, - $responselist[$i]); - } else { - ($aresult,$msg)=&caparesponse_check($thisanswer, - $responselist[$i]." $unit"); + +# &LONCAPA_INTERNAL_DEBUG(&LONCAPA_INTERNAL_Dumper($responses)); + my %memoized; + if ($LONCAPA::CAPAresponse_answer->{'type'} eq 'ordered') { + for (my $i=0; $i{'answers'}[$i]; + my $response = $responses->[$i]; + my $key = "$answer\0$response"; + my (@awards,@msgs); + for (my $j=0; $j[$j], + $response->[$j]); + push(@awards,$award); + push(@msgs, $msg); } - } else { - $aresult='ERROR'; - $msg='answer was undefined'; + my ($award,$msg) = + &LONCAPA_INTERNAL_FINALIZEAWARDS(\@awards,\@msgs); + $memoized{$key} = [$award,$msg]; } - &LONCAPA_INTERNAL_DEBUG("after if $aresult -- $msg"); - my ($temp)=split(/:/, $aresult); - $awards.="$temp,"; - $result.=$aresult; - push(@msgs,$msg); - $i++; + } else { + #FIXME broken with unorder responses where one is a + # and the other is a (need to delay parse til + # inside the loop?) + foreach my $response (@$responses) { + my $response_size = scalar(@{$response}); + foreach my $answer (@{ $LONCAPA::CAPAresponse_answer->{'answers'} }) { + my $key = "$answer\0$response"; + my $answer_size = scalar(@{$answer}); + my ($award,$msg); + if ($answer_size > $response_size) { + $award = 'MISSING_ANSWER'; + } elsif ($answer_size < $response_size) { + $award = 'EXTRA_ANSWER'; + } else { + my (@awards,@msgs); + for (my $j=0; $j[$j], + $response->[$j]); + push(@awards,$award); + push(@msgs, $msg); + } + ($award,$msg) = + &LONCAPA_INTERNAL_FINALIZEAWARDS(\@awards,\@msgs); + } + $memoized{$key} = [$award,$msg]; + } + } + } + + my ($final_award,$final_msg); + &init_permutation(scalar(@$responses), + $LONCAPA::CAPAresponse_answer->{'type'}); + + # possible FIXMEs + # - significant time is spent calling non-safe space routine + # from safe space + # - early outs could be possible with classifying awards is to stratas + # and stopping as so as hitting the top strata + # - some early outs also might be possible with check ing the + # memoized hash of results (is correct even possible? etc.) + + my (@final_awards,@final_msg); + while( &get_permutations_left() ) { + my $order = &get_next_permutation(); + my (@awards, @msgs, $i); + foreach my $thisanswer (@{ $LONCAPA::CAPAresponse_answer->{'answers'} }) { + my $key = "$thisanswer\0".$responses->[$order->[$i]]; + push(@awards,$memoized{$key}[0]); + push(@msgs,$memoized{$key}[1]); + $i++; + + } + &LONCAPA_INTERNAL_DEBUG(" all awards ".join(':',@awards)); + + my ($possible_award,$possible_msg) = + &LONCAPA_INTERNAL_FINALIZEAWARDS(\@awards,\@msgs); + &LONCAPA_INTERNAL_DEBUG(" pos awards ".$possible_award); + push(@final_awards,$possible_award); + push(@final_msg,$possible_msg); + } + + &LONCAPA_INTERNAL_DEBUG(" all final_awards ".join(':',@final_awards)); + my ($final_award,$final_msg) = + &LONCAPA_INTERNAL_FINALIZEAWARDS(\@final_awards,\@final_msg,undef,1); + return ($final_award,$final_msg); +} + +sub cas { + my ($system,$input,$library)=@_; + my $output; + if ($system eq 'maxima') { + $output=&maxima_eval($input,$library); + } else { + $output='Error: unrecognized CAS'; } - chop($awards); - return ("$awards:\n$result",@msgs); + return $output; } sub tex { @@ -841,46 +1010,75 @@ sub class { return $course; } +sub firstname { + my $firstname = &EXT('environment.firstname'); + $firstname = '' if $firstname eq ""; + return $firstname; +} + +sub lastname { + my $lastname = &EXT('environment.lastname'); + $lastname = '' if $lastname eq ""; + return $lastname; +} + sub sec { my $sec = &EXT('request.course.sec'); $sec = '' if $sec eq ""; return $sec; } +sub submission { + my ($partid,$responseid,$subnumber)=@_; + my $sub=''; + if ($subnumber) { $sub=$subnumber.':'; } + return &EXT('user.resource.'.$sub.'resource.'.$partid.'.'.$responseid.'.submission'); +} + +sub currentpart { + return $external::part; +} + +sub eval_time { + my ($timestamp)=@_; + unless ($timestamp) { return ''; } + return &locallocaltime($timestamp); +} + sub open_date { - my @dc = split(/\s+/,localtime(&EXT('resource.0.opendate'))); - return '' if ($dc[0] eq "Wed" and $dc[2] == 31 and $dc[4] == 1969); - my @hm = split(/:/,$dc[3]); - my $ampm = " am"; - if ($hm[0] > 12) { - $hm[0]-=12; - $ampm = " pm"; - } - return $dc[0].', '.$dc[1].' '.$dc[2].', '.$dc[4].' at '.$hm[0].':'.$hm[1].$ampm; -} - -sub due_date { - my @dc = split(/\s+/,localtime(&EXT('resource.0.duedate'))); - return '' if ($dc[0] eq "Wed" and $dc[2] == 31 and $dc[4] == 1969); - my @hm = split(/:/,$dc[3]); - my $ampm = " am"; - if ($hm[0] > 12) { - $hm[0]-=12; - $ampm = " pm"; - } - return $dc[0].', '.$dc[1].' '.$dc[2].', '.$dc[4].' at '.$hm[0].':'.$hm[1].$ampm; + my ($partid)=@_; + unless ($partid) { $partid=0; } + return &eval_time(&EXT('resource.'.$partid.'.opendate')); +} + +sub due_date { + my ($partid)=@_; + unless ($partid) { $partid=0; } + return &eval_time(&EXT('resource.'.$partid.'.duedate')); } sub answer_date { - my @dc = split(/\s+/,localtime(&EXT('resource.0.answerdate'))); - return '' if ($dc[0] eq "Wed" and $dc[2] == 31 and $dc[4] == 1969); - my @hm = split(/:/,$dc[3]); - my $ampm = " am"; - if ($hm[0] > 12) { - $hm[0]-=12; - $ampm = " pm"; - } - return $dc[0].', '.$dc[1].' '.$dc[2].', '.$dc[4].' at '.$hm[0].':'.$hm[1].$ampm; + my ($partid)=@_; + unless ($partid) { $partid=0; } + return &eval_time(&EXT('resource.'.$partid.'.answerdate')); +} + +sub open_date_epoch { + my ($partid)=@_; + unless ($partid) { $partid=0; } + return &EXT('resource.'.$partid.'.opendate'); +} + +sub due_date_epoch { + my ($partid)=@_; + unless ($partid) { $partid=0; } + return &EXT('resource.'.$partid.'.duedate'); +} + +sub answer_date_epoch { + my ($partid)=@_; + unless ($partid) { $partid=0; } + return &EXT('resource.'.$partid.'.answerdate'); } sub array_moments {