--- loncom/homework/caparesponse/caparesponse.pm 2016/02/18 18:40:05 1.257 +++ loncom/homework/caparesponse/caparesponse.pm 2025/02/20 06:36:00 1.266 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # caparesponse definition # -# $Id: caparesponse.pm,v 1.257 2016/02/18 18:40:05 raeburn Exp $ +# $Id: caparesponse.pm,v 1.266 2025/02/20 06:36:00 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -327,7 +327,17 @@ sub start_numericalresponse { sub set_answertext { my ($name,$target,$token,$tagstack,$parstack,$parser,$safeeval, $response_level) = @_; - &add_in_tag_answer($parstack,$safeeval,$response_level); + my $stringify; + if (scalar(@$tagstack)) { + if ($$tagstack[-1] eq 'formularesponse') { + if (&Apache::lonxml::get_param('samples',$parstack,$safeeval)) { + $stringify = 1; + } + } elsif ($$tagstack[-1] eq 'numericalresponse') { + $stringify = 1; + } + } + &add_in_tag_answer($parstack,$safeeval,$stringify,$response_level); if ($name eq '' || !ref($answer{$name})) { if (ref($answer{$tag_internal_answer_name})) { @@ -401,7 +411,7 @@ sub setup_capa_response { if (ref($response)) { $$args_ref{'response'}=dclone($response); } else { - $$args_ref{'response'}=dclone([$response]); + $$args_ref{'response'}=dclone(["$response"]); } } @@ -409,6 +419,7 @@ sub check_submission { my ($response,$partid,$id,$tag,$parstack,$safeeval,$ignore_sig)=@_; my @args = ('type','tol','sig','format','unit','calc','samples','preprocess'); my $args_ref = &setup_capa_args($safeeval,$parstack,\@args,$response); + my $stringify; my $hideunit= &Apache::lonnet::EXT('resource.'.$partid.'_'.$id.'.turnoffunit'); @@ -428,18 +439,20 @@ sub check_submission { if ($tag eq 'formularesponse') { if ($$args_ref{'samples'}) { $$args_ref{'type'}='fml'; + $stringify = 1; } else { $$args_ref{'type'}='math'; } } elsif ($tag eq 'numericalresponse') { $$args_ref{'type'}='float'; + $stringify = 1; } elsif ($tag eq 'stringresponse') { if ($$args_ref{'type'} eq '') { $$args_ref{'type'} = 'ci'; } } - &add_in_tag_answer($parstack,$safeeval); + &add_in_tag_answer($parstack,$safeeval,$stringify); if (!%answer) { &Apache::lonxml::error("No answers are defined"); @@ -514,11 +527,14 @@ sub stringresponse_gradechange { } sub add_in_tag_answer { - my ($parstack,$safeeval,$response_level) = @_; + my ($parstack,$safeeval,$stringify,$response_level) = @_; my @answer=&Apache::lonxml::get_param_var('answer',$parstack,$safeeval, $response_level); &Apache::lonxml::debug('answer is'.join(':',@answer)); if (@answer && $answer[0] =~ /\S/) { + if ($stringify) { + @answer = map { $_ .= ''; } @answer; + } $answer{$tag_internal_answer_name}= {'type' => 'ordered', 'answers' => [\@answer] }; } @@ -544,7 +560,7 @@ sub end_numericalresponse { } my $partid = $Apache::inputtags::part; my $id = $Apache::inputtags::response[-1]; - my $tag; + my ($tag,$stringify); my $safehole = new Safe::Hole; $safeeval->share_from('capa',['&caparesponse_capa_check_answer']); @@ -562,12 +578,19 @@ sub end_numericalresponse { &Apache::lonxml::debug($$parstack[-1] . "\n
"); if ( &Apache::response::submitted('scantron')) { - &add_in_tag_answer($parstack,$safeeval); - my ($values,$display)=&make_numerical_bubbles($partid,$id, - $target,$parstack,$safeeval); - $response=$values->[$response]; - } - $Apache::lonhomework::results{"resource.$partid.$id.submission"}=$response; + if ($tag eq 'numericalresponse') { + $stringify = 1; + } elsif ($tag eq 'formularesponse') { + if (&Apache::lonxml::get_param('samples',$parstack,$safeeval)) { + $stringify = 1; + } + } + &add_in_tag_answer($parstack,$safeeval,$stringify); + my ($values,$display)=&make_numerical_bubbles($partid,$id,$target, + $parstack,$safeeval); + $response=$values->[$response]; + } + $Apache::lonhomework::results{"resource.$partid.$id.submission"}=$response; my ($ad,$msg,$name)=&check_submission($response,$partid,$id, $tag,$parstack, $safeeval); @@ -601,7 +624,14 @@ sub end_numericalresponse { } } } elsif ($target eq 'web' || $target eq 'tex') { - &check_for_answer_errors($parstack,$safeeval); + if ($tag eq 'numericalresponse') { + $stringify = 1; + } elsif ($tag eq 'formularesponse') { + if (&Apache::lonxml::get_param('samples',$parstack,$safeeval)) { + $stringify = 1; + } + } + &check_for_answer_errors($parstack,$safeeval,$stringify); my $award = $Apache::lonhomework::history{"resource.$Apache::inputtags::part.solved"}; my $status = $Apache::inputtags::status['-1']; if ($Apache::lonhomework::type eq 'exam') { @@ -623,10 +653,10 @@ sub end_numericalresponse { if ($previous eq $bubble_values->[$ind]) { $checked=" checked='on' "; } - $result.=''.$alphabet[$ind].': '. - $bubble_display->[$ind].''; + $bubble_display->[$ind].''; } $result.=''; } @@ -648,6 +678,7 @@ sub end_numericalresponse { $result.=''.&Apache::edit::end_table; } elsif ($target eq 'answer' || $target eq 'analyze') { my $part_id="$partid.$id"; + my $samples; if ($target eq 'analyze') { push (@{ $Apache::lonhomework::analyze{"parts"} },$part_id); $Apache::lonhomework::analyze{"$part_id.type"} = $tag; @@ -660,11 +691,26 @@ sub end_numericalresponse { if (scalar(@$tagstack)) { &Apache::response::setup_params($tag,$safeeval); } - &add_in_tag_answer($parstack,$safeeval); + if ($tag eq 'numericalresponse') { + $stringify = 1; + } elsif ($tag eq 'formularesponse') { + $samples=&Apache::lonxml::get_param('samples',$parstack,$safeeval); + if ($samples) { + $stringify = 1; + } + } + &add_in_tag_answer($parstack,$safeeval,$stringify); my (@formats)=&Apache::lonxml::get_param_var('format',$parstack,$safeeval); my $unit=&Apache::lonxml::get_param_var('unit',$parstack,$safeeval); - + my ($needsdollar,$needscomma); + if ($unit =~ /\$/) { + $needsdollar = 1; + } + if ($unit =~ /\,/) { + $needscomma = 1; + } + if ($target eq 'answer') { $result.=&Apache::response::answer_header($tag,undef, scalar(keys(%answer))); @@ -753,17 +799,21 @@ sub end_numericalresponse { my $ans=$answers[$i]; my $fmt=$formats[0]; if (@formats && $#formats) {$fmt=$formats[$i];} - foreach my $element (@$ans) { + my @answers; + if (ref($ans) eq 'ARRAY') { + @answers = (@{$ans}); + } + foreach my $element (@answers) { if ($fmt && $tag eq 'numericalresponse') { $fmt=~s/e/E/g; if ($unit=~/\$/) { $fmt="\$".$fmt; $unit=~s/\$//g; } if ($unit=~/\,/) { $fmt="\,".$fmt; $unit=~s/\,//g; } $element = &format_number($element,$fmt,$target, $safeeval); - if ($fmt=~/\$/ && $unit!~/\$/) { $element=~s/\$//; } + if ($fmt=~/\$/ && !$needsdollar) { $element=~s/\$//; } } } - push(@fmt_ans,join(',',@$ans)); + push(@fmt_ans,join(',',@answers)); } my $response=\@fmt_ans; @@ -773,7 +823,7 @@ sub end_numericalresponse { ! ($Apache::lonhomework::type eq 'exam' || lc($hideunit) eq "yes") ) { my $cleanunit=$unit; - $cleanunit=~s/\$\,//g; + $cleanunit=~s/[\$,]//g; foreach my $ans (@fmt_ans) { $ans.=" $cleanunit"; } @@ -790,7 +840,13 @@ sub end_numericalresponse { $tag,$parstack, $safeeval,1); $error=&mt("Computer's answer is incorrect ([_1]).",'"'.join(', ',@$response).'"').' '; - if ($ad eq 'UNIT_INVALID_STUDENT') { + if (($ad eq 'NO_UNIT') && $needsdollar) { + $error.=&mt('The unit attribute includes [_1] but the answer format does not.','$').' '. + &mt('Either remove the [_1] from the unit or prepend [_1] to the answer format.','$'); + } elsif (($ad eq 'COMMA_FAIL') && $needscomma) { + $error.=&mt('The unit attribute includes [_1] but the answer format does not.',',').' '. + &mt('Either remove the [_1] from the unit or prepend [_1] to the answer format.',','); + } elsif ($ad eq 'UNIT_INVALID_STUDENT') { $error.=&mt('Unable to interpret units. Computer reads units as "[_1]".',$msg).' '. &mt('The unit attribute in the numericalresponse item needs to be a supported physical unit.'); } elsif ($sigline ne '') { @@ -821,7 +877,6 @@ sub end_numericalresponse { } } if ($tag eq 'formularesponse' && $target eq 'answer') { - my $samples=&Apache::lonxml::get_param('samples',$parstack,$safeeval); $result.=&Apache::response::answer_part($tag,$samples); } $result.=&Apache::response::next_answer($tag,$name); @@ -864,8 +919,8 @@ sub format_prior_response_numerical { } sub check_for_answer_errors { - my ($parstack,$safeeval) = @_; - &add_in_tag_answer($parstack,$safeeval); + my ($parstack,$safeeval,$stringify) = @_; + &add_in_tag_answer($parstack,$safeeval,$stringify); my %counts; foreach my $name (keys(%answer)) { push(@{$counts{scalar(@{$answer{$name}{'answers'}})}},$name); @@ -894,7 +949,7 @@ sub get_table_sizes { my $cell_width=0; foreach my $member (@$rbubble_values) { my $cell_width_real=0; - if ($member=~/(\+|-)?(\d*)\.?(\d*)\s*\$?\\times\s*10\^{(\+|-)?(\d+)}\$?/) { + if ($member=~/(\+|-)?(\d*)\.?(\d*)\s*\$?\\times\s*10\^\{(\+|-)?(\d+)}\$?/) { $cell_width_real=(length($2)+length($3)+length($5)+7)*$scale; } elsif ($member=~/(\d*)\.?(\d*)(E|e)(\+|-)?(\d*)/) { $cell_width_real=(length($1)+length($2)+length($5)+9)*$scale; @@ -1048,7 +1103,23 @@ sub make_numerical_bubbles { &Math::Random::random_uniform_integer(1,1,10); } for ($ind=0;$ind<$number_of_bubbles;$ind++) { - $bubble_values[$ind] = $answerfactor*($factor**($power-$powers[$#powers-$ind])); + my $exponent = $power-$powers[$#powers-$ind]; + $bubble_values[$ind] = $answerfactor*($factor**$exponent); + + # If bubble is for correct answer (i.e., exponent = 0), and value + # of $answerfactor * factor**$exponent is an integer with more than + # 15 digits, assign $answerfactor itself as bubble value. + # This prevents a "use fewer digits" issue on 64bit servers + # when correct answer is >= 1e+16, and when correct bubble is A. + + if ($exponent == 0) { + if ($bubble_values[$ind] =~ /^-?(\d+)$/) { + if (length($1) > 15) { + $bubble_values[$ind] = $answerfactor; + } + } + } + $bubble_display[$ind] = &format_number($bubble_values[$ind], $format,$target,$safeeval); } @@ -1093,7 +1164,9 @@ sub make_horizontal_latex_bubbles { my $j=0; my $cou=0; $result.='\vskip 2mm \noindent '; - $result .= '\textbf{'.$Apache::lonxml::counter.'.} \vskip -3mm '; + if ($Apache::lonhomework::type eq 'exam') { + $result .= '\textbf{'.$Apache::lonxml::counter.'.} \vskip -3mm '; + } for (my $i=0;$i<$number_of_tables;$i++) { if ($i == 0) { @@ -1233,6 +1306,8 @@ sub end_stringresponse { if (!$Apache::lonxml::default_homework_loaded) { &Apache::lonxml::default_homework_load($safeeval); } + my $tag; + if (scalar(@$tagstack)) { $tag=$$tagstack[-1]; } if ( $target eq 'grade' && &Apache::response::submitted() ) { &Apache::response::setup_params('stringresponse',$safeeval); $safeeval->share_from('capa',['&caparesponse_capa_check_answer']); @@ -1275,7 +1350,8 @@ sub end_stringresponse { if ($$args_ref{'type'} eq '') { $$args_ref{'type'} = 'ci'; } - &add_in_tag_answer($parstack,$safeeval); + my $stringify = 1; + &add_in_tag_answer($parstack,$safeeval,$stringify); my (@final_awards,@final_msgs,@names,%ansstring); foreach my $name (keys(%answer)) { &Apache::lonxml::debug(" doing $name with ".join(':',@{ $answer{$name}{'answers'} })); @@ -1366,7 +1442,11 @@ sub end_stringresponse { } } } elsif ($target eq 'answer' || $target eq 'analyze') { - &add_in_tag_answer($parstack,$safeeval); + my $stringify; + if ($type ne 're') { + $stringify = 1; + } + &add_in_tag_answer($parstack,$safeeval,$stringify); if ($target eq 'analyze') { push (@{ $Apache::lonhomework::analyze{"parts"} },"$part.$id"); $Apache::lonhomework::analyze{"$part.$id.type"} = 'stringresponse';