--- loncom/homework/default_homework.lcpm 2005/02/18 22:52:50 1.94 +++ loncom/homework/default_homework.lcpm 2006/07/18 14:06:17 1.111 @@ -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.94 2005/02/18 22:52:50 albertel Exp $ +# $Id: default_homework.lcpm,v 1.111 2006/07/18 14:06:17 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -89,11 +89,15 @@ sub caparesponse_check { } else { $message .="no ws in :$response:\n"; } - if ($type eq 'cs' || $type eq 'ci' || $type eq 'mc') { + &LONCAPA_INTERNAL_DEBUG(" type is $type "); + if ($type eq 'cs' || $type eq 'ci') { #for string answers make surec all places spaces occur, there is #really only 1 space, in both the answer and the response $answer=~s/ +/ /g; $response=~s/ +/ /g; + } elsif ($type eq 'mc') { + $answer=~s/[\s,]//g; + $response=~s/[\s,]//g; } if ($type eq 'float' && $unit=~/\$/) { if ($response!~/^\$/) { return "NO_UNIT: Missing \$ "; } @@ -113,10 +117,11 @@ sub caparesponse_check { if ( $answer eq ($answer *1.0)) { $type = 2; } else { $type = 3; } } else { - if ($type eq 'cs') { $type = 4; } + if ($type eq 'cs') { $type = 4; } elsif ($type eq 'ci') { $type = 3 } elsif ($type eq 'mc') { $type = 5; } elsif ($type eq 'fml') { $type = 8; } + elsif ($type eq 'math') { $type = 9; } elsif ($type eq 'subj') { $type = 7; } elsif ($type eq 'float') { $type = 2; } elsif ($type eq 'int') { $type = 1; } @@ -145,13 +150,21 @@ sub caparesponse_check { ($sig_ubound,$sig_lbound)=&LONCAPA_INTERNAL_get_sigrange($sig); my $reterror=""; - my $result = &caparesponse_capa_check_answer($response,$answer,$type, + my $result; + if ($type eq '9') { + $result = &maxima_check(&maxima_cas_formula_fix($response),&maxima_cas_formula_fix($answer),\$reterror); + } else { + if ($type eq '8') { # fml type + $response = &capa_formula_fix($response); + $answer = &capa_formula_fix($answer); + } + $result = &caparesponse_capa_check_answer($response,$answer,$type, $tol_type,$tol, $sig_lbound,$sig_ubound, $ans_fmt,$unit,$calc,$id_list, $points,$external::randomseed, \$reterror); - + } if ($result == '1') { $result='EXACT_ANS'; } elsif ($result == '2') { $result='APPROX_ANS'; } elsif ($result == '3') { $result='SIG_FAIL'; } @@ -175,26 +188,57 @@ sub caparesponse_check { return ("$result:\nRetError $reterror:\nAnswer $answer:\nResponse $response:\n type-$type|$tol|$tol_type|$sig:$sig_lbound:$sig_ubound|$unit|\n$message",$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)=@_; +# Escape scientific notation, so 3e8 does not become 3*e*8 +# 3e8 -> 3&8; 3e-8 -> 3&-8; 3E+8 -> e&+8 + $expression=~s/(\d+)e([\+\-]*\d+)/$1\&\($2\)/gsi; +# 3x10^8 -> 3&8; 3*10^-8 -> 3&-8 + $expression=~s/(\d+)(?:x|\*)10(?:\^|\*\*)([\+\-]*\d+)/$1\&\($2\)/gsi; +# Fill in multiplication signs +# a b -> a*b + $expression=~s/\s+/\*/gs; +# 3a -> 3*a; 3( -> 3*(; 3A -> 3*a + $expression=~s/(\d)([a-zA-Z\(])/$1\*$2/gs; +# a3 -> a*3; )3 -> )*3 + $expression=~s/([a-zA-Z])(\d)/$1\*$2/gs; +# )a -> )*a + $expression=~s/\)(\w)/\)\*$1/gs; +# 3&8 -> 3e8; 3&-4 -> 3e-4 + $expression=~s/(\d+)\&\(([\+\-]*\d+)\)/$1e$2/gs; + return $expression; +} sub caparesponse_check_list { my $response=$LONCAPA::CAPAresponse_args{'response'}; - my ($result,@list); - @list=@LONCAPA::CAPAresponse_answer; - my $aresult=''; - my $current_answer; - my $answers=join(':',@list); - $result.="Got response :$answers:\n"; - &LONCAPA_INTERNAL_DEBUG("Yo! got ".join(':',%LONCAPA::CAPAresponse_args)); + my $result="Got response :".join(':',@LONCAPA::CAPAresponse_answer).":\n"; + &LONCAPA_INTERNAL_DEBUG("args ".join(':',%LONCAPA::CAPAresponse_args)); my @responselist; my $type = $LONCAPA::CAPAresponse_args{'type'}; $result.="Got type :$type:\n"; - if ($type ne '' && $#list > 0) { - (@responselist)=split /,/,$response; + if ($type ne '' && $#LONCAPA::CAPAresponse_answer > 0) { + (@responselist)=split(/,/,$response); + if (@responselist < @LONCAPA::CAPAresponse_answer) { + return 'MISSING_ANSWER'; + } + if (@responselist > @LONCAPA::CAPAresponse_answer) { + return 'EXTRA_ANSWER'; + } } else { (@responselist)=($response); } - my $unit=''; $result.="Initial final response :$responselist['-1']:\n"; + my $unit; if ($type eq '' || $type eq 'float') { #for numerical problems split off the unit if ( $responselist['-1']=~ /(.*[^\s])\s+([^\s]+)/ ) { @@ -203,28 +247,32 @@ sub caparesponse_check_list { } } $result.="Final final response :$responselist['-1']:$unit:\n"; - $result.=":$#list: answers\n"; $unit=~s/\s//; - my $i=0; - my $awards=''; - my @msgs; - for ($i=0; $i<@list;$i++) { - my $msg; - $result.="trying answer :$list[$i]:\n"; - my $thisanswer=$list[$i]; + + my ($awards, @msgs, $i); + foreach my $thisanswer (@LONCAPA::CAPAresponse_answer) { + my ($msg,$aresult); $result.="trying answer :$thisanswer:\n"; - if ($unit eq '') { - ($aresult,$msg)=&caparesponse_check($thisanswer,$responselist[$i]); + if (defined($thisanswer)) { + if ($unit eq '') { + ($aresult,$msg)=&caparesponse_check($thisanswer, + $responselist[$i]); + } else { + ($aresult,$msg)=&caparesponse_check($thisanswer, + $responselist[$i]." $unit"); + } } else { - ($aresult,$msg)=&caparesponse_check($thisanswer, - $responselist[$i]." $unit"); + $aresult='ERROR'; + $msg='answer was undefined'; } - my ($temp)=split /:/, $aresult; + &LONCAPA_INTERNAL_DEBUG("after if $aresult -- $msg"); + my ($temp)=split(/:/, $aresult); $awards.="$temp,"; $result.=$aresult; push(@msgs,$msg); + $i++; } - chop $awards; + chop($awards); return ("$awards:\n$result",@msgs); } @@ -447,14 +495,14 @@ sub random_negative_binomial { return @retArray; } -sub abs { abs(shift) } -sub sin { sin(shift) } -sub cos { cos(shift) } -sub exp { exp(shift) } -sub int { int(shift) } -sub log { log(shift) } -sub atan2 { atan2($_[0],$_[1]) } -sub sqrt { sqrt(shift) } +sub abs { CORE::abs(shift) } +sub sin { CORE::sin(shift) } +sub cos { CORE::cos(shift) } +sub exp { CORE::exp(shift) } +sub int { CORE::int(shift) } +sub log { CORE::log(shift) } +sub atan2 { CORE::atan2($_[0],$_[1]) } +sub sqrt { CORE::sqrt(shift) } sub tan { CORE::sin($_[0]) / CORE::cos($_[0]) } #sub atan { atan2($_[0], 1); } @@ -518,10 +566,15 @@ sub format { #if ($options =~ /\$/) { $dollamode=1; } #if ($options =~ /,/) { $commamode=1; } if ($options =~ /\./) { $alwaysperiod=1; } - $fmt=~s/e/E/g; - my $result=sprintf('%.'.$fmt,$value); - if ($alwaysperiod && $fmt eq '0f') { $result .='.'; } - $result=~s/(E[+-]*)0/$1/; + my $result; + if ($fmt=~/s$/i) { + $result=&format_significant_figures($value,$fmt); + } else { + $fmt=~s/e/E/g; + $result=sprintf('%.'.$fmt,$value); + if ($alwaysperiod && $fmt eq '0f') { $result .='.'; } + $result=~s/(E[+-]*)0/$1/; + } #if ($dollarmode) {$result=&dollarformat($result);} #if ($commamode) {$result=&commaformat($result);} return $result; @@ -529,24 +582,33 @@ sub format { sub chemparse { my ($reaction) = @_; - my @tokens = split(/(\s\+|\->|<=>)/,$reaction); + my @tokens = split(/(\s\+|\->|<=>|<\-|\.)/,$reaction); my $formula = ''; foreach my $token (@tokens) { if ($token eq '->' ) { $formula .= '\ensuremath{\rightarrow} '; next; } + if ($token eq '<-' ) { + $formula .= '\ensuremath{\leftarrow} '; + next; + } if ($token eq '<=>') { if ($external::target eq 'web' && &EXT('request.browser.unicode')) { $formula .= '⇌ '; } else { $formula .= &web('<=> ','\ensuremath{\rightleftharpoons} ', - '<=$gt; '); + '<=> '); } next; } - $token =~ /^\s*(\d*(?:&frac\d\d)?)(.*)/; + if ($token eq '.') { + $formula =~ s/(\ \;| )$//; + $formula .= '·'; + next; + } + $token =~ /^\s*([\d|\/]*(?:&frac\d\d)?)(.*)/; $formula .= $1 if ($1 ne '1'); # stoichiometric coefficient my $molecule = $2; @@ -558,6 +620,7 @@ sub chemparse { $molecule =~ s/\s*//g; # forced space $molecule =~ s/_/ /g; + $molecule =~ s/-/−/g; $formula .= $molecule.' '; } # get rid of trailing space @@ -575,7 +638,11 @@ sub prettyprint { if ($options =~ /\$/) { $dollarmode=1; } if ($options =~ /,/) { $commamode=1; } if ($options =~ /\./) { $alwaysperiod=1; } - if ($fmt) { $value=sprintf('%.'.$fmt,$value); } + if ($fmt=~/s$/i) { + $value=&format_significant_figures($value,$fmt); + } elsif ($fmt) { + $value=sprintf('%.'.$fmt,$value); + } if ($alwaysperiod && $fmt eq '0f') { if ($target eq 'tex') { $value .='\\ensuremath{.}'; @@ -614,12 +681,12 @@ sub prettyprint { sub commaformat { my ($number,$target) = @_; if ($number =~ /\./) { - while ($number =~ /([^\.,]+)([^\.,][^\.,][^\.,])([,0-9]*\.[0-9]*)$/) { - $number = $1.','.$2.$3; + while ($number =~ /([^0-9]*)([0-9]+)([^\.,][^\.,][^\.,])([,0-9]*\.[0-9]*)$/) { + $number = $1.$2.','.$3.$4; } } else { - while ($number =~ /([^,]+)([^,][^,][^,])([,0-9]*)$/) { - $number = $1.','.$2.$3; + while ($number =~ /^([^0-9]*)([0-9]+)([^,][^,][^,])([,0-9]*)$/) { + $number = $1.$2.','.$3.$4; } } return $number; @@ -637,6 +704,41 @@ sub dollarformat { return $number; } +# format of form ns or nS where n is an integer +sub format_significant_figures { + my ($number,$format) = @_; + return '0' if ($number == 0); + # extract number of significant figures needed + my ($sig) = ($format =~ /(\d+)s/i); + # arbitrary choice - suggestions ?? or throw error message? + $sig = 3 if ($sig eq ''); + # save the minus sign + my $sign = ($number < 0) ? '-' : ''; + $number = abs($number); + # needed to correct for a number greater than 1 (or + my $power = ($number < 1) ? 0 : 1; + # could round up. Take the integer part of log10. + my $x10 = int(log($number)/log(10)); + # find number with values left of decimal pt = # of sign figs. + my $xsig = $number*10**($sig-$x10-$power); + # get just digits left of decimal pt - also rounds off correctly + my $xint = sprintf('%.0f',$xsig); + # save any trailing zero's + my ($zeros) = ($xint =~ /(0+)$/); + # return number to original magnitude + my $numSig = $xint*10**($x10-$sig+$power); + # insert trailing zero's if have decimal point + $numSig =~ s/^(\d+)\.(\d+)(\e?(.*)?)$/$1\.$2$zeros$3/; + # put a decimal pt for number ending with 0 and length = # of sig fig + $numSig.='.' if (length($numSig) == $sig && $numSig =~ /0$/); + if (length($numSig) < $sig) { + $numSig.='.'.substr($zeros,0,($sig-length($numSig))); + } + # return number with sign + return $sign.$numSig; + +} + sub map { my ($phrase,$dest,$source)=@_; my @oldseed=&random_get_seed(); @@ -832,6 +934,30 @@ sub choose { return $_[$num]; } +#&sum1(1,$x,sub { &sum1($_[0],2*$_[0], sub { fact($_[0])**2 })}); +#sub sum1 { +# my ($start,$end,$sub)=@_; +# my $sum=0; +# for (my $i=$start;$i<=$end;$i++) { +# $sum+=&$sub($i); +# } +# return $sum +#} + +#&sum2('a',1,$x,'&sum2(\'b\',$a,2*$a, \'&factorial($b)**2\')'); +#sub sum2 { +# my ($varname,$start,$end,$line)=@_; +# my $sum=0; +# for (my $i=$start;$i<=$end;$i++) { +# my $func=sub { +# eval("\$".$varname."=$i"); +# eval($line); +# }; +# $sum+=&$func($i); +# } +# return $sum +#} + # expiremental idea sub proper_path { my ($path)=@_;