--- loncom/homework/default_homework.lcpm 2009/12/27 19:40:42 1.146 +++ loncom/homework/default_homework.lcpm 2019/04/03 22:46:30 1.172.2.1 @@ -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.146 2009/12/27 19:40:42 www Exp $ +# $Id: default_homework.lcpm,v 1.172.2.1 2019/04/03 22:46:30 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -143,7 +143,6 @@ sub caparesponse_check { my $sig_lbound=''; #done my $sig_ubound=''; #done - #type's definitons come from capaParser.h #remove leading and trailing whitespace @@ -152,10 +151,8 @@ sub caparesponse_check { } if ($response=~ /^\s|\s$/) { $response=~ s:^\s+|\s+$::g; - &LONCAPA_INTERNAL_DEBUG("Removed ws now :$response:"); } - #&LONCAPA_INTERNAL_DEBUG(" type is $type "); if ($type eq 'cs' || $type eq 'ci') { #for string answers make sure all places spaces occur, there is #really only 1 space, in both the answer and the response @@ -179,7 +176,6 @@ sub caparesponse_check { if (length($response) > 500) { return ('TOO_LONG',undef); } if ($type eq '' ) { - &LONCAPA_INTERNAL_DEBUG("Didn't find a type :$type: defaulting"); if ( $answer eq ($answer *1.0)) { $type = 2; } else { $type = 3; } } else { @@ -199,7 +195,6 @@ sub caparesponse_check { #formula type setup the sample points if ($type eq '8') { ($id_list,$points)=split(/@/,$samples); - &LONCAPA_INTERNAL_DEBUG("Found :$id_list:$points: points in $samples"); } if ($tol eq '') { $tol=0.0; @@ -258,8 +253,6 @@ sub caparesponse_check { elsif ($result =='15') { $result='UNIT_IRRECONCIBLE'; } else {$result = "ERROR: Unknown Result:$result:$@:";} - &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); } @@ -270,7 +263,10 @@ sub caparesponse_check_list { my $type = $LONCAPA::CAPAresponse_args{'type'}; my $answerunit=$LONCAPA::CAPAresponse_args{'unit'}; &LONCAPA_INTERNAL_DEBUG("Got type :$type: answer unit :$answerunit:\n"); - + + my $preprocess=$LONCAPA::CAPAresponse_args{'preprocess'}; + $preprocess=~s/^\&//; + my $num_input_lines = scalar(@{$LONCAPA::CAPAresponse_answer->{'answers'}}); @@ -307,33 +303,72 @@ sub caparesponse_check_list { } } - &LONCAPA_INTERNAL_DEBUG("Initial final response :$responses->[0][-1]:"); my $unit; + my ($allowalgebra)=($LONCAPA::CAPAresponse_args{'allowalgebra'}=~/^(yes|1|on)$/i); if ($type eq 'float' || $type eq '') { #for numerical problems split off the unit -# 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:"); + my $part1; + my $part2; + if ($allowalgebra) { + ($part1,$part2)=($responses->[0][-1]=~ /^(.*[^\s])\s+([^\s]+)$/); + } else { + ($part1,$part2)=($responses->[0][-1]=~ /^([\d\.\,\s\$]*(?:(?:[xX\*]10[\^\*]*|[eE]*)[\+\-]*\d*)*(?:^|\S)\d+)([\$\s\w\^\*\/\(\)\+\-]*[^\d\.\s\,][\$\s\w\^\*\/\(\)\+\-]*)$/); + } + if (defined($part1) && defined($part2)) { + $responses->[0][-1]=$part1; + $unit=&capa_formula_fix($part2); + my $customunits=$LONCAPA::CAPAresponse_args{'customunits'}; + if ($customunits =~ /\S/) { + foreach my $replacement (split(/\s*\,\s*/,$customunits)) { + my ($which,$what)=split(/\s*\=\s*/,$replacement); + if ((defined($which)) && (defined($what))) { + $what=&capa_formula_fix($what); + $unit=~s/$which/\($what\)/g; + } + } + } } } - &LONCAPA_INTERNAL_DEBUG("Final final response :$responses->[0][-1]:$unit:"); $unit=~s/\s//; + my $error; foreach my $response (@$responses) { - foreach my $element (@$response) { + foreach my $element (@$response) { + # See if we have preprocessor + if ($preprocess=~/\S/) { + if (defined(&$preprocess)) { + no strict 'refs'; + $element=&$preprocess($element,$unit); + use strict 'refs'; + } + } if (($type eq 'float') || (($type eq '') && ($unit ne ''))) { $element =~ s/\s//g; } my $appendunit=$unit; +# Deal with percentages +# unit is unit entered by student, answerunit is unit by author +# Deprecated: divide answer by 100 if student entered percent, +# but author did not. Too much confusion # if (($unit=~/\%/) && ($answerunit ne '%')) { # $element=$element/100; # $appendunit=~s/\%//; # } - if (($element==0) && ($unit!~/\w/) && ($answerunit=~/\w/)) { +# Author entered percent, student did not + if (($unit!~/\%/) && ($answerunit=~/\%/)) { + $element=$element*100; + $appendunit='%'.$appendunit; + } +# Zero does not need a dimension + if (($element =~ /^[0\.]+$/) && ($unit!~/\w/) && ($answerunit=~/\w/)) { $appendunit=$answerunit; } - $element .= " $appendunit"; +# Do the math for the student if allowed + if ($allowalgebra) { + $element=&cas('maxima',$element); + } + if ($appendunit ne '') { + $element .= " $appendunit"; + } &LONCAPA_INTERNAL_DEBUG("Made response element :$element:"); } } @@ -344,6 +379,23 @@ sub caparesponse_check_list { } } + my $allow_control_char = 0; + my $control_chars_removed = 0; + my $ansstring; + if ($type eq 'cs' || $type eq 'ci') { + if (ref($LONCAPA::CAPAresponse_answer->{'answers'}) eq 'ARRAY') { + foreach my $strans (@{$LONCAPA::CAPAresponse_answer->{'answers'}}) { + if (ref($strans) eq 'ARRAY') { + $ansstring = join("\0",@{$strans}); + foreach my $item (@{$strans}) { + if ($item =~ /[\000-\037]/) { + $allow_control_char = 1; + } + } + } + } + } + } # &LONCAPA_INTERNAL_DEBUG(&LONCAPA_INTERNAL_Dumper($responses)); my %memoized; @@ -353,9 +405,30 @@ sub caparesponse_check_list { my $response = $responses->[$i]; my $key = "$answer\0$response"; my (@awards,@msgs); - for (my $j=0; $j[$j] =~ /[\000-\037]/) { + $response->[$j] =~ s/[\000-\037]//g; + $control_chars_removed = 1; + } + } + } + # See if we have preprocessor for string responses + if (($preprocess=~/\S/) && ($type eq 'cs' || $type eq 'ci')) { + if (defined(&$preprocess)) { + no strict 'refs'; + $response->[$j]=&$preprocess($response->[$j]); + use strict 'refs'; + } + } + my ($award,$msg) = &caparesponse_check($answer->[$j], $response->[$j]); + if ($type eq 'cs' || $type eq 'ci') { + $error = &verify_stringresponse($type,$award,$response->[$j], + $answer->[$j]); + } push(@awards,$award); push(@msgs, $msg); } @@ -380,8 +453,29 @@ sub caparesponse_check_list { } else { my (@awards,@msgs); for (my $j=0; $j[$j] =~ /[\000-\037]/) { + $response->[$j] =~ s/[\000-\037]//g; + $control_chars_removed = 1; + } + } + } + # See if we have preprocessor + if (($preprocess=~/\S/) && ($type eq 'cs' || $type eq 'ci')) { + if (defined(&$preprocess)) { + no strict 'refs'; + $response->[$j]=&$preprocess($response->[$j]); + use strict 'refs'; + } + } + my ($award,$msg) = &caparesponse_check($answer->[$j], $response->[$j]); + if ($type eq 'cs' || $type eq 'ci') { + $error = &verify_stringresponse($type,$award,$response->[$j], + $answer->[$j]); + } push(@awards,$award); push(@msgs, $msg); } @@ -428,7 +522,29 @@ sub caparesponse_check_list { &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); + return ($final_award,$final_msg,$error,$control_chars_removed,$ansstring); +} + +sub verify_stringresponse { + my ($type,$award,$resp,$ans) = @_; + return if ($award eq 'EXACT_ANS'); + my $error; + if ($resp =~ /^\s|\s$/) { + $resp =~ s{^\s+|\s+$}{}g; + } + if ($ans =~ /^\s|\s$/) { + $ans =~ s{^\s+|\s+$}{}g; + } + if ($type eq 'ci') { + $resp = lc($resp); + $ans = lc($ans); + } + if ($resp eq $ans) { + if ($award eq 'INCORRECT') { + $error = 'MISGRADED'; + } + } + return $error; } sub cas { @@ -516,16 +632,29 @@ sub hinton { sub random { my ($start,$end,$step)=@_; if ( ! $hidden::RANDOMINIT ) { - if ($external::randomseed == 0) { $external::randomseed=1; } - if ($external::randomseed =~/,/) { - my ($num1,$num2)=split(/,/,$external::randomseed); - &random_set_seed(1,abs($num1)); - } elsif ($external::randomseed =~/:/) { - my ($num1,$num2)=split(/:/,$external::randomseed); - &random_set_seed(abs($num1),abs($num2)); - } else { - &random_set_seed(1,int(abs($external::randomseed))); - } + if ($external::randomseed == 0) { $external::randomseed=1; } + if ($external::randomseed =~/,/) { + my ($num1,$num2) = map { abs($_); } split(/,/,$external::randomseed); + if ((!$num1) || ($num1 > 2147483398)) { + &random_set_seed_from_phrase($external::randomseed); + } else { + &random_set_seed(1,$num1); + } + } elsif ($external::randomseed =~/:/) { + my ($num1,$num2) = map { abs($_); } split(/:/,$external::randomseed); + if ((!$num1) || (!$num2) || ($num1 > 2147483562) || ($num2 > 2147483398)) { + &random_set_seed_from_phrase($external::randomseed); + } else { + &random_set_seed($num1,$num2); + } + } else { + my $num1 = int(abs($external::randomseed)); + if ((!$num1) || ($num1 > 2147483398)) { + &random_set_seed_from_phrase($external::randomseed); + } else { + &random_set_seed(1,$num1); + } + } &math_random_uniform(); $hidden::RANDOMINIT=1; } @@ -700,6 +829,7 @@ sub cos { CORE::cos(shift) } sub exp { CORE::exp(shift) } sub int { CORE::int(shift) } sub log { CORE::log(shift) } +sub ln { CORE::log(shift) } sub atan2 { CORE::atan2($_[0],$_[1]) } sub sqrt { CORE::sqrt(shift) } @@ -785,11 +915,19 @@ sub chemparse { my $formula = ''; foreach my $token (@tokens) { if ($token eq '->' ) { - $formula .= '\ensuremath{\rightarrow} '; + if ($external::target eq 'web') { + $formula .= '→ '; + } else { + $formula .= '\ensuremath{\rightarrow} '; + } next; } if ($token eq '<-' ) { - $formula .= '\ensuremath{\leftarrow} '; + if ($external::target eq 'web') { + $formula .= '← '; + } else { + $formula .= '\ensuremath{\leftarrow} '; + } next; } if ($token eq '<=>') { @@ -926,16 +1064,47 @@ sub format_significant_figures { 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))); + if ($numSig =~ /^(\d+)\.(\d+)/) { + # insert trailing zero's if have decimal point + my @digarray = split('',$1.$2); + my $sigcount; + while (@digarray > 0) { + my $item = shift(@digarray); + if ($item) { + $sigcount = 1 + @digarray; + last; + } + } + if (($sigcount) && ($sig >= $sigcount)) { + $zeros = substr($zeros,0,($sig - $sigcount)); + } + $numSig =~ s/^(\d+)\.(\d+)(\e?(.*)?)$/$1\.$2$zeros$3/; + } else { + if ($numSig =~ /^(\d+)e([\+\-]\d+)$/i) { + my $pre_exp = $1; + my $exponent = $2; + $numSig = $pre_exp.'.'.$zeros.'E'.$exponent; + } elsif ($numSig =~ /0$/) { + # add decimal pt for number ending with 0 and length == # of sig figs + if (length($numSig) == $sig) { + $numSig.='.'; + } elsif (length($numSig) > $sig) { + # exponential form for number ending with 0 and length > # of sig figs + my $fmtsig = $sig-1; + if ($fmtsig) { + $numSig = sprintf('%.'.$fmtsig.'E',$numSig); + } + } elsif (length($numSig) < $sig) { + $numSig.='.'.substr($zeros,0,($sig-length($numSig))); + } + } else { + if (length($numSig) < $sig) { + $numSig.='.'.substr($zeros,0,($sig-length($numSig))); + } + } } # return number with sign return $sign.$numSig; - } sub map { @@ -1049,12 +1218,24 @@ sub class { return $course; } +sub classid { + my $courseid = &EXT('request.course.id'); + $courseid = '' if $courseid eq ""; + return $courseid; +} + sub firstname { my $firstname = &EXT('environment.firstname'); $firstname = '' if $firstname eq ""; return $firstname; } - + +sub middlename { + my $middlename = &EXT('environment.middlename'); + $middlename = '' if $middlename eq ""; + return $middlename; +} + sub lastname { my $lastname = &EXT('environment.lastname'); $lastname = '' if $lastname eq ""; @@ -1068,10 +1249,78 @@ sub sec { } sub submission { - my ($partid,$responseid,$subnumber)=@_; + my ($partid,$responseid,$subnumber,$encode,$cleanupnum,$mapalias)=@_; my $sub=''; if ($subnumber) { $sub=$subnumber.':'; } - return &EXT('user.resource.'.$sub.'resource.'.$partid.'.'.$responseid.'.submission'); + my $output = + &EXT('user.resource.'.$sub.'resource.'.$partid.'.'.$responseid.'.submission',$mapalias); + if (ref($output) eq 'ARRAY') { + my @items = @{$output}; + if ($encode) { + @items = map { &encode_response($_); } @items; + } + if (ref($cleanupnum) eq 'HASH') { + @items = map { &cleanup_numerical_response($cleanupnum,$_); } @items; + } + return \@items; + } else { + if ($encode) { + $output = &encode_response($output); + } + if (ref($cleanupnum) eq 'HASH') { + $output = &cleanup_numerical_response($cleanupnum,$output); + } + return $output; + } +} + +sub encode_response { + my ($value) = @_; + $value =~ s/&/&/g; + $value =~ s//>/g; + $value =~ s/"/"/g; + return $value; +} + +sub cleanup_numerical_response { + my ($cleanupnum,$value) = @_; + if (ref($cleanupnum) eq 'HASH') { + if ($cleanupnum->{exponent}) { + if ($value =~ m{^(.*)[\*xX]\s*10\s*\^\s*(\+|\-)?\s*(\d+)(.*)$}) { + my $pre_exp = $1; + my $sign = $2; + my $exponent = $3; + my $post_exp = $4; + if ($pre_exp !~ /\./) { + $pre_exp .= '.'; + } + if ($sign eq '') { + $sign = '+'; + } + $value = $pre_exp.'E'.$sign.$exponent.$post_exp; + } + } + if ($cleanupnum->{comma}) { + $value =~ s{(\d+),(\d+)}{$1$2}; + } + if ($cleanupnum->{letterforzero}) { + $value =~ s/^\s*o(\.\d+)/0$1/i; + } + if ($cleanupnum->{spaces}) { + $value =~ s{^\s+|\s+$}{}g; + if ($value =~ m{^(.*)\.\s+(\d+)(.*)$}) { + my $pre_pt = $1; + my $decimal = $2; + my $post_dec = $3; + $value = $pre_pt.'.'.$decimal.$post_dec; + } + } + if ($cleanupnum->{format} =~ /^\d+s$/i) { + $value = &format_significant_figures($value,$cleanupnum->{format}); + } + } + return $value; } sub currentpart { @@ -1120,6 +1369,40 @@ sub answer_date_epoch { return &EXT('resource.'.$partid.'.answerdate'); } +sub parameter_setting { + my ($which,$partid)=@_; + unless ($partid) { $partid=0; } + return &EXT('resource.'.$partid.'.'.$which); +} + +sub stored_data { + my ($which,$partid)=@_; + unless ($partid) { $partid=0; } + return &EXT('user.resource.resource.'.$partid.'.'.$which); +} + +sub wrong_bubbles { + my ($correct,$lower,$upper,$step,@given)=@_; + my @array=(); + my %hash=(); + foreach my $new (@given) { + $hash{$new}=1; + } + my $num=int(¶meter_setting('numbubbles',¤tpart())); + unless ($num) { $num=8; } + if ($num>1) { + for (my $i=0;$i<=500;$i++) { + my $new=&random($lower,$upper,$step); + if ($hash{$new}) { next; } + if (abs($new-$correct)<$step) { next; } + $hash{$new}=1; + @array=keys(%hash); + if ($#array+2>=$num) { last; } + } + } + return @array; +} + sub array_moments { my @input=@_; my (@output,$N); @@ -1196,3 +1479,8 @@ sub proper_path { } } +sub input_id { + my ($part_id, $response_id, $textline_id) = @_; + return 'HWVAL_'.$part_id.'_'.$response_id.'_'.$textline_id; +} +