Sophiakp1, constructed response Kp problem source code

This is the code for one of the chemical equilibrium problems presented on this web site. https://chemphys.uconn.edu/~chem12x/cgi-bin/sophiakp1.pl. It goes without saying (althought it is being said) that I am not a professional programmer, and that therefore the code presented is not a template. It is loaded with debugging detrious which may or may not be relevant.

The purpose of releasing the code is to suggest a scheme whereby a professional programmer can create computer assisted learning modules. S/he should regard the following lines as an effort to suggest approaches, rather than a prescription.

The code is centered on CGI.pm, which has been declared obsolete by Perl authors and managers. I have yet to find a substitute for CGI.pm for allowing perl code to be included in web forms in a transparent manner, and suggest that anyone preparing to use the following code as a suggeestion, should have his/her system administratopr install CGI.pm, since it is no longer included in the standard Perl library.

To make it abundently clear, CGI.pm allows one to write Perl code, which resides in the executable directory public_html/cgi-bin/ on a standard Unix server of web pages. The public_html directory contains (HTML sourced) web pages which can be loaded directly, while Perl programs located in cgi-bin need to be executed.

We start with the preliminary code:

#!/usr/local/bin/perl -- -*-perl-*-
#
# This is sophiakp1(_CAL1)
#
require  "CleanUp.pl";
require "top.pl";
require "JSCRIPT.js";
use CGI;
use CGI qw/:standard *table start_ul/;
use CGI::Carp qw(fatalsToBrowser);
#
use CAT2p4pchem;
use CGRutils3;
use lib '/home/cdavid/perl5/lib/perl5';
use Mail::Mailer;

$query = new CGI;
$qCAT = new CAT2p4pchem;
$qCGR = new CGRutils3;
$debug = 1;# set to zero to remove print statements

The real work starts here

$first = 1;#ignore box check first time
prin
print $query-&header;
$title = "Sophia's K<sub>p</sub> I. C. E. page";
print $query-&start_html($title);

$global_debug="false";
srand;
top();
$redz = "<font color=red>z</font>";
print "<h1>$title</h1>";
$scriptname = $query->script_name();#this gives the question name

$rnoby3 = $qCAT->rno(3)/3;
$temp_rno = $qCAT->rno(3)/3;
$qCAT->save_vars($query,'rnoby3',\$rnoby3);#save list for this variable
$qCAT->save_vars($query,'temp_rno',\$temp_rno);#save list for this variable

We have defined peripheral constants and we are ready to start the form which will be used to

  1. get the student's e-mail address, which we use to lock the screen until something is given,
  2. create the Perl variables for the problem at hand,
  3. state the problem,
  4. accept a student's response.

print $query->start_form();
$student_id = "?";
print <<EOD;
Messages for the author concerning errors, mistakes, bad grammar, etc., can be sent
<a href="mailto:Carl.David\@uconn.edu?subject=Sophiakp1%comment.">to Carl.David\@Uconn.edu using your own mail handler.</a> .
EOD
print "<hr>";

$var11 = 10*$qCAT->rno(3)+5;#create random number with two digits behind decimal point
$qCAT->save_vars($query,'var11',\$var11);#save list for this variable
$var12 = 10*$qCAT->rno(3)+$var11;#create random number with two digits behind decimal point
$qCAT->save_vars($query,'var12',\$var12);#save list for this variable
$var13 = 10*$qCAT->rno(3)+$var12;#create random number with two digits behind decimal point
$qCAT->save_vars($query,'var13',\$var13);#save list for this variable


$var11string = sprintf "%5.2f", $var11;
$var12string = sprintf "%5.2f", $var12;
$var13string = sprintf "%5.2f", $var13;
$var21 = "2*z";
$var23 = "-2*z";
$var31 = $var11string."+2*z";
$var32 = $var12string."+z";
$var33 = $var13string."-2*z";


$varz = $var13*$qCAT->rno(3)/3;
$qCAT->save_vars($query,'varz',\$varz);#save list for this variable
$varzstring = sprintf "%5.2f", $varz;
$answerKpusingQp = Qp(($var13-2*$varz), ($var11+2*$varz),($var12+$varz));
$error = 0;
$student_id = "?";
print "To unlock this page, please enter your e-mail address:",  $query->textfield( -name=>'student_id',-default=>'?',-size=>40,-maxlength=>50);
$qCAT->save_vars($query,'student_id',\$student_id);#save
print <<EOF;
<br>
The equilibrium constant for the reaction of NO(g) with O<sub>2</sub>(g) to
form NO<sub>2</sub>(g) is unknown.
EOF

The following code gives the Kp expression as long as this site stays alive.

print <<EOF;
<center>
<img src="http://www.sciweavers.org/tex2img.php?eq=NO%28g%29%20%2B%20%0A%20%5Cfrac%7B1%7D%7B2%7DO_2%28g%29%20%5Crightleftharpoons%0ANO_2%28g%29%20&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0" align="center" border="0" alt="NO(g) +  \frac{1}{2}O_2(g) \rightleftharpoonsNO_2(g) " width="221" height="43" />
</center>
In a recent experiment,  the initial pressure of NO(g) was $var11, while the initial pressure of
O<sub>2</sub>(g) was $var12, and the initial pressure of NO<sub>2</sub>(g) was
$var13.
Please fill in the following tableau (remember to include the '*' sign when multiplying): 
EOF

We've stated the problem (at least part of it) and now we are going to switch from text to a table, i.e., us HTML explicitly. Here is where the discussion of separating the HTML from the Perl makes no sense to me. The HTML flows perfectly, and templating it makes no sense, since the next problem will require a different kind of table, with its own coding.

The tableau code follows, with the table entries indicating the box indexing scheme which will be lost as the student fills in the boxes. Perhaps coding these (x,y) couples into the table with persistence, would be preferable, but this works as the simplest way of beginning.

print  "<center>";
print table({-border=>undef},
  caption('I.C.E. Table'),
  Tr({-align=>'CENTER',-valign=>'TOP'},
    [
    th(['', 'P<sub>NO</sub>','P<sub>O<sub>2</sub></sub>','P<sub>NO<sub>2</sub></sub>']),
    td(['Initial' ,
      $query->textfield( -name=>'box11',
        -default=>'1,1',
        -size=>5,
        -maxlength=>7
      ),
      $query->textfield(-name=>'box12',
        -default=>'1,2',
        -size=>5,
      -maxlength=>7),
      $query->textfield(-name=>'box13',
        -default=>'1,3',
        -size=>5,
      -maxlength=>7),
    ]),
    td(['Change' ,
      $query->textfield(-name=>'box21',
        -default=>'2,1',
        -size=>5,
      -maxlength=>7),
      $redz,
      $query->textfield(-name=>'box23',
        -default=>'2,3',
        -size=>5,
      -maxlength=>7),
    ]),
    td(['Equilibium'   ,
      $query->textfield(-name=>'box31',
        -default=>'3,1',
        -size=>5,
      -maxlength=>9),
      $query->textfield(-name=>'box32',
        -default=>'3,2',
        -size=>5,
      -maxlength=>9),
      $query->textfield(-name=>'box33',
        -default=>'3,3',
        -size=>5,
      -maxlength=>9)
    ])
  ] )
);

When the submit button is pressed, the contents of the boxes are assigned to Perl variables:

$ans11 = $query->param('box11');
$ans12 = $query->param('box12');
$ans13 = $query->param('box13');
$ans21 = $query->param('box21');
$ans22 = $query->param('box22');
$ans23 = $query->param('box23');
$ans31 = $query->param('box31');
$ans32 = $query->param('box32');
$ans33 = $query->param('box33');

#begin judging box answers:
if($query->param('student_id') ne "?"){

In order to unlock the screen, we force the "?" to have been changed.

Then each box is checked and the error messages printed.

  if (grade_student_txt_against_correct($ans11,$var11,"Does not seem right ","z") == 0){
    print "<br>box 1,1 appears to be wrong";
    $error = 1;
  }else{print"box 1,1 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans12,$var12,"Does not seem right ","z") == 0){
    print "<br>box 1,2 appears to be wrong ";
    $error = 1;
  }else{print"box 1,2 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans13,$var13,"Does not seem right ","z") == 0){
    print "<br>box 1,3 appears to be wrong ";
    $error = 1;
  }else{print"box 1,3 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans21,$var21,"Does not seem right ","z") == 0){
    print "<br>box 2,1 appears to be wrong ";
    $error = 1;
  }else{print"box 2,1 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans23,$var23,"Does not seem right ","z") == 0){
    print "<br>box 2,3 appears to be wrong ";
    $error = 1;
  }else{print"box 2,3 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans31,$var31,"Does not seem right ","z") == 0){
    print "<br>box 3,1 appears to be wrong ";
    $error = 1;
  }else{print"box 3,1 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans32,$var32,"Does not seem right ","z") == 0){
    print "<br>box 3,2 appears to be wrong ";
    $error = 1;
  }else{print"box 3,2 appears to be right <br>";}
  if (grade_student_txt_against_correct($ans33,$var33,"Does not seem right ","z") == 0){
    print "<br>box 3,3 appears to be wrong ";
    $error = 1;
  }else{print"box 3,3 appears to be right<br> ";}
  $first = 0;#enable box checking
#
  print "<hr>";
  
  
  $ans11 = $query->param('box11');
  $ans12 = $query->param('box12');
  $ans13 = $query->param('box13');
  $ans21 = $query->param('box21');
  $ans22 = $query->param('box22');
  $ans23 = $query->param('box23');
  $ans31 = $query->param('box31');
  $ans32 = $query->param('box32');
  $ans33 = $query->param('box33');
  $NO2_eq = $ans13-2*$varz;
  $NO_eq = $ans11+2*$varz;
  $O2_eq = $ans12+$varz;
# NO_2 then NO and then O_2
  $Kp_correct = &Qp($NO2_eq,$NO_eq,$O2_eq);
  $ans = $Kp_correct;

When all boxes are correct, we present the real problem.

  if ($error == 0) {
    print "<br>If the value of  $redz  = $varzstring , what is the value of
    K<sub>p</sub>?  ";
#=========start
    my $ret = $qCAT->qnd($query,'t0',$ans,2,$filename,'');#to be extended

Above is the request for a student numberical answer. The function qnd is a 3 significant figure comparer. qCAT.pm contains a set of support subroutines which are commonly used in this kind of work. The text for qCAT.pm is included in this "book" elsewhere.

    if($ret == 0){ #here if answer wrong
      print "<img src=../icons/RAIN_LINE.gif>";
      print "
      <br>Do you want to see how this problem is done (or at least a hint)?
      If so, please check this box (and resubmit) ",$query->checkbox('to')," enable this feature.
      ";

The text of help, is solely in the programmer's discretion.

      if ($query->param('to') eq 'on'){
        print <<EOF;
        <br>
        Once the boxes are correctly filled in, the
        the equilibrium pressure of NO<sub>2</sub> = $NO2_eq,
        the equilibrium pressure of NO = $NO_eq,
        and the equilibrium pressure of O<sub>2</sub>  = $O2_eq.
        Knowing $redz one can compute these values, and substitute them into
        the K<sub>p</sub> expression to finish the problem.
EOF
      }
      
    }
    else {
#here if answer right

Again, when the student gets the answer correct, the programmer can "reward" him/her as s/he sees fit. In our case here, while the question is being circulated in a campaign to get Perl based cgi-bin modules to be added to all computer assisted learning modules, the "reward" text is aimed at educators rather than students.


      print "Great";
      print <<EOF;
      <br>This is the ultimate formulation of computer assisted learning that I can produce.
      It offers no help whatsoever in terms of filling in the boxes in the tableau, but tells students when they are wrong.
      Suggestions about interventions when students make mistakes would certainly be appreciated.
EOF
$student_id = $query->param('student_id');
    print "<br><img src=../icons/RAIN_LINE.gif><br>";

When the student gets the right answer, the programmer needs to record this event. This can be done by adding remarks to a database of student activity, (as one `can record wrong responses, for analysis and improvement purposes).

In this case, I wanted to monitor whether or not anyone (i.e., an educator visiting the site) had actually carried out the computation required to finish the problem, hence the use of an e-mail to me.

    $mailer = new Mail::Mailer;
    $email = 'carl.david@uconn.edu';
    $mailer->open( {
        To       => $email,
        From     => 'The Webmaster <chem12x@chemphys.uconn.edu>',
        Subject  => 'Web Site Feedback using Mail::Mailer'
    } );
    
    print $mailer <<END_OF_MESSAGE;
    This is the Mail::Mailer message sent by $student_id using the page $scriptname.
END_OF_MESSAGE
    };
  }
}
$first = 0;
$qCAT->save_vars($query,'first',\$first);#save list for this variable
print $query->submit;

print $qusery->end_form;

This calculator (below) can easily be omitted.

&JSCRIPT();
print $query->end_html;


This is specific to the chemistry problem.

sub Qp{
# NO_2 then NO and then O_2
#print "dump  = ";print Dumper(@_);print "<br>";
  
  return(($_[0])/(($_[1])*sqrt($_[2])));
}

This is a constructed response evaluating subroutine, which in our case is being used for the box fillin section of the problem.

sub grade_student_txt_against_correct
#note no input in this subroutine
# calling sequence, required!!!!!
{
#      my ($query) = $_[1];# this seems to work only in the newer version of CGI.pm
# this is a subroutine which gets a query, an answer, a student answer, and a list
# of variables and returns 1 (True) if the student answer is OK, and 0 (False) otherwise.
#      my $q = $_[2];#passing unique textual identifier
#
  $total_args = scalar(@_);
  
  my $student = $_[0];#passing a student answwer
  my $correct = $_[1];#passing the correct answer
  my $error_response = $_[2];#response for a wrong answer
  my $variables1 = $_[3];#passing list of variables
  my $dump = 0;
  $variables = $variables1;
#print "<br>inside subroutine, global_debug = $global_debug";
  if ( $global_debug eq "true"){$dump = 1};
  if($dump){
    print "<br> total_args = $total_args";
    print "<br> student = $student";
    print "<br> correct = $correct";
    print "<br> variables = $variables";
  }
#      if ($query->param($q)ne ''){
  my $ans1 = $student;
  
  my $saved_stu_ans1 = $ans1;
  if($dump){
    print "<br> ans1 = $ans1";
    print "<br> saved_stu_ans1= $saved_stu_ans1";
  }
  $escape = '?!';
  if(substr($ans1,0,length($escape)) eq $escape){print "<br> test mode, answer expected = $correct";}
  
  &CleanUp($ans1);
  &CleanUp2($ans1);#someone else's attempt, lost reference
  &CleanUp($correct);
  if($dump){
    print "<br> ans1 (after CleanUp)   = $ans1";
    print "<br> saved_stu_ans1 (after CleanUp)  = $correct";
  }
  $var = $variables1;
  if($dump){
    print "<br> substitution variable var = $var";
  }
  $rno = 1+rand();
  $ans1 =~ s/$var/$rno/gi;
  $correct =~ s/$var/$rno/g;
  if($dump){
    print "<br> rno = $rnob";
    print "<br> after substitution variable var = $var";
    print "<br> after a cycle of substitutions, ans1 = $ans1, and correct = $correct";
  }
  if($dump){
    print "<br> criterion = ",abs(eval($correct)-eval($ans1));
    print "<br> correct = ",eval($correct);
    print "<br> ans1 = ",eval($ans1);
  }

This is the core of the scheme. The expressions, with the same random variable in both the student's and the author's answer are compared.

  if(abs(eval($correct) -eval( $ans1)) < 0.0001){
    print "<img src=../icons/check.gif>";
    return 1
  }
  else{
    print "<br><img src=../icons/checkno.gif>";
#print "<br>You did not submit a correct answer.<br>";
    print $error_response;
    return 0
  }
#      }#end of if param
  if($global_debug eq "true"){ $dump = 0; $global_debug = "false"};
}#end of subroutine  grade_student_txt_against_correct

sub CleanUp2{#Author's name and address unfortunately lost. Not my code.
  $_ = shift;
  s/\-+(.*)/\1/g;
  s/(.*)[ \t]+\-(.*)/\1\2/g;
  tr/\$\'\`\"\<\>\/\;\!\|/_/;
  return($_);
}