--- loncom/homework/caparesponse/caparesponse.pm 2014/12/30 20:03:15 1.255
+++ 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.255 2014/12/30 20:03:15 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.='';
}
@@ -642,17 +672,13 @@ sub end_numericalresponse {
}
}
}
- if (($target eq 'web') && ($tag eq 'formularesponse')
- && ($Apache::lonhomework::type ne 'exam') && ($Apache::inputtags::status['-1'] eq 'CAN_ANSWER')
- && (&Apache::lonnet::EXT('resource.'.$partid.'_'.$id.'.turnoffeditor') ne 'yes')) {
- $result.=&Apache::response::edit_mathresponse_button($partid,$id);
- }
-
+
&Apache::response::setup_prior_tries_hash(\&format_prior_response_numerical);
} elsif ($target eq 'edit') {
$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;
@@ -665,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)));
@@ -758,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;
@@ -778,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";
}
@@ -795,7 +840,16 @@ sub end_numericalresponse {
$tag,$parstack,
$safeeval,1);
$error=&mt("Computer's answer is incorrect ([_1]).",'"'.join(', ',@$response).'"').' ';
- if ($sigline ne '') {
+ 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 '') {
$error.=&mt('It is likely that the tolerance range [_1] or significant figures [_2] need to be adjusted.',$tolline,$sigline);
} else {
$error.=&mt('It is likely that the tolerance range [_1] needs to be adjusted.',$tolline);
@@ -823,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);
@@ -866,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);
@@ -896,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;
@@ -1050,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);
}
@@ -1095,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) {
@@ -1235,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']);
@@ -1277,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'} }));
@@ -1368,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';