00001 #! /usr/bin/perl 00002 # Implementation of Simplex Algorithm 00003 # © Mark Hutchinson 2004 00004 00005 use CGI qw/:standard *table *center/; 00006 00007 @tableau=(); 00008 @maxmin=(); 00009 @names=(); 00010 @remove=(); 00011 00012 # Note: vertical counts from 0, horizontal from 1 (to allow space for 00013 # displayed calculations) 00014 00015 sub data_error; 00016 00017 # Alas, would that I knew how to import things... 00018 00019 ######################### Start of fraction library ######################### 00020 00021 # Fraction routines 00022 # © Mark Hutchinson 2004 00023 00024 sub f_split { 00025 ($_[0] =~ m!^([0-9\-]+)/([0-9\-]+)$!) or return ($_[0],'1'); 00026 return ($1,$2); 00027 } 00028 00029 sub f_join { 00030 return $_[0]."/".$_[1]; 00031 } 00032 00033 sub f_is_neg { 00034 my $neg; 00035 ($_[0] != 0) or return 0; 00036 $neg=$_[0]/abs($_[0]); 00037 $neg *= ($_[1]/abs($_[1])); 00038 return $neg; 00039 } 00040 00041 sub f_toggle_neg { 00042 my $neg; 00043 my $num=$_[0]; 00044 ($num != 0) or return '0/1'; 00045 $neg=$num/abs($num); 00046 return ('-'.$num,$_[1]) if $neg==1; 00047 return (substr($num,1),$_[1]); 00048 } 00049 00050 sub f_simplify { 00051 my @parts=&f_split($_[0]); 00052 &_f_simplify(@parts); 00053 } 00054 00055 sub _f_simplify { 00056 my $neg,$a,$b,$c,$num,$denom; 00057 ($num,$denom)=@_; 00058 return "0/1" if $num==0; 00059 &data_error("Division by zero") if $denom==0; 00060 $neg=&f_is_neg(@_); 00061 $num=abs $num; 00062 $denom=abs $denom; 00063 if($num<$denom) { 00064 $a=$num; 00065 $b=$denom; 00066 } else { 00067 $a=$denom; 00068 $b=$num; 00069 } 00070 do { 00071 $c=-(int($a/$b)*$b-$a); 00072 if($c!=0){ 00073 $a=$b; 00074 $b=$c; 00075 } 00076 } until ($c==0); 00077 $num/=$b; 00078 $denom/=$b; 00079 return &f_join($neg*$num,$denom); 00080 } 00081 00082 sub f_proper_parts { 00083 my $num,$denom,$count,$neg,$proper; 00084 ($num,$denom)=&f_split($_[0]); 00085 return ('0','0/1') if $num==0; 00086 $neg=&f_is_neg($num,$denom); 00087 $num=abs $num; 00088 $denom= abs $denom; 00089 for($count=0;$num>=$denom;$count++){ 00090 $num -= $denom; 00091 } 00092 $count *= $neg; 00093 return ('0',$_[0]) if $count==0; 00094 return ($count,'0/1') if $num==0; 00095 $proper=&f_join($num,$denom); 00096 return ($count,$proper); 00097 } 00098 00099 sub f_make_proper { 00100 return &f_make_improper($_[0]) if $display_type eq 'Display as improper fractions'; 00101 return &f_fract_to_num($_[0]) if $display_type eq 'Display as decimals'; 00102 return &_f_make_proper($_[0]); 00103 } 00104 00105 sub _f_make_proper { 00106 my $int,$fract,$num,$denom,$sign; 00107 if($display_sign eq 'on'){ 00108 if(&f_lt($_[0],'0/1')){ 00109 $sign='-'; 00110 }else{ 00111 $sign='+'; 00112 } 00113 } 00114 ($int,$fract)=&f_proper_parts($_[0]); 00115 ($num,$denom)=&f_split($fract); 00116 return $int if $num==0; 00117 return '<sup>'.$num.'</sup>/<sub>'.$denom.'</sub>' if $int==0; 00118 return $int.$sign.'<sup>'.$num.'</sup>/<sub>'.$denom.'</sub>'; 00119 } 00120 sub f_make_improper { 00121 my $num,$denom; 00122 ($num,$denom)=&f_split($_[0]); 00123 return '0' if ($num==0); 00124 return $num if ($denom==1); 00125 return '<sup>'.$num.'</sup>/<sub>'.$denom.'</sub>'; 00126 } 00127 00128 sub f_add { 00129 my @a,@b,$denom; 00130 @a=&f_split($_[0]); 00131 @b=&f_split($_[1]); 00132 $denom=$a[1]*$b[1]; 00133 $a[0] *= $b[1]; 00134 $b[0] *= $a[1]; 00135 return &_f_simplify($a[0]+$b[0],$denom); 00136 } 00137 00138 sub f_sub { 00139 my $bn,@b=&f_split($_[1]); 00140 @b=&f_toggle_neg(@b); 00141 $bn=&f_join(@b); 00142 return &f_add($_[0],$bn); 00143 } 00144 00145 sub f_mult { 00146 my @a,@b; 00147 @a=&f_split($_[0]); 00148 @b=&f_split($_[1]); 00149 return &_f_simplify($a[0]*$b[0],$a[1]*$b[1]); 00150 } 00151 00152 sub f_div { 00153 my @b=&f_split($_[1]); 00154 return &f_mult($_[0],&f_join($b[1],$b[0])); 00155 } 00156 00157 sub f_fract_to_num { 00158 my @a=&f_split($_[0]); 00159 return $a[0]/$a[1]; 00160 } 00161 00162 sub f_eq { 00163 my $res=&f_sub($_[0],$_[1]); 00164 return ($res==0); 00165 } 00166 00167 sub f_ne { 00168 my $res=&f_sub($_[0],$_[1]); 00169 return ($res!=0); 00170 } 00171 00172 sub f_lt { 00173 my $res=&f_sub($_[0],$_[1]); 00174 return ($res<0); 00175 } 00176 00177 sub f_le { 00178 my $res=&f_sub($_[0],$_[1]); 00179 return ($res<=0); 00180 } 00181 00182 sub f_gt { 00183 my $res=&f_sub($_[0],$_[1]); 00184 return ($res>0); 00185 } 00186 00187 sub f_ge { 00188 my $res=&f_sub($_[0],$_[1]); 00189 return ($res>=0); 00190 } 00191 00192 sub f_num_to_fract { 00193 my $int,$no,$r,$s,$t,$num,$denom,$s,$a=$_[0]; 00194 $int=int $a; 00195 $a -= $int; 00196 do { 00197 $no++; 00198 $r=$a*(10**$no); 00199 $s=int $r; 00200 $t=$r-$s; 00201 } until($t==0 or $no>=8); 00202 $num=$s+($int*(10**$no)); 00203 $denom=10**$no; 00204 return &_f_simplify($num,$denom); 00205 } 00206 00207 sub f_int_to_fract { 00208 return &f_join($_[0],1); 00209 } 00210 00211 sub f_all_to_fract { 00212 # Handles integers, reals, and proper, improper and mixed fractions 00213 ($_[0] != "") or return "0/1"; 00214 ($_[0] !~ m!^[0-9\-]+/[0-9\-]+$!) or return $_[0]; 00215 ($_[0] !~ m/^[0-9\-]+$/) or return &f_int_to_fract($_[0]); 00216 00217 if($_[0] !~ m!^([0-9\-]+)/([0-9\-]+)/([0-9\-]+)$!){ 00218 ($_[0] =~ m/^[0-9\-]*\.[0-9\-]+$/) or &data_error("Fraction entered incorrectly"); 00219 return &f_num_to_fract($_[0]); 00220 } 00221 return &f_add(&f_int_to_fract($1),&f_join($2,$3)); 00222 } 00223 00224 ########################## End of fraction library ########################## 00225 00226 sub data_error { 00227 my $arg; 00228 $arg=$_[0]; 00229 print header, 00230 start_html("Error: $arg"), 00231 h1("Error: $arg"), 00232 p( 00233 "There is a problem with the data supplied to the script ", 00234 i("simplex.pl"), 00235 "."), 00236 p( 00237 "Please ", 00238 a({-href=>"javascript:history.go(-1)"},"return to the previous page"), 00239 " and try again."), 00240 end_html; 00241 exit; 00242 } 00243 00244 sub simplex_error { 00245 my $arg=$_[0]; 00246 print h3("Error: $arg"), 00247 p( 00248 "There has been an error in applying the simplex algorithm: $arg."), 00249 end_html; 00250 exit; 00251 } 00252 00253 sub print_tableau { 00254 my $title,$i,$j; 00255 $title=$_[0]; 00256 print h3($title), 00257 start_center, 00258 start_table({-width=>'100%',-cols=>$vars+2,-border=>1,-cellspacing=>0,-cellpadding=>0}), 00259 '<tr align="center" valign="top">'; 00260 for ($i=0;$i<=$vars;$i++){ 00261 print td({-align=>'center'},$names[$i]); 00262 } 00263 print td({-align=>'center'},'RHS'), 00264 '</tr>'; 00265 for ($j=0;$j<($objs+$cons);$j++){ 00266 print '<tr align="center" valign="top"'; 00267 print ' bgcolor="#DDDDDD"' if($j<$objs); 00268 print '>'; 00269 print td{-align=>'center'},$tableau[$j][0]; 00270 for ($i=1;$i<=$vars+1;$i++){ 00271 print td({-align=>'center'},&f_make_proper($tableau[$j][$i])); 00272 } 00273 print '</tr>'; 00274 } 00275 print end_table, 00276 end_center; 00277 } 00278 00279 # Max/min-sensitive improvement function 00280 sub imp_poss { 00281 if($_[0]==1){ 00282 return &f_lt($_[1],$_[2]); 00283 } 00284 return &f_gt($_[1],$_[2]); 00285 } 00286 00287 sub display_solution { 00288 my $a,$one_one,$i,$j,$value,$row; 00289 for($i=0;$i<($objs+$cons);$i++){ 00290 $tableau[$i][0]="R<sub>$i</sub>"; 00291 } 00292 if($objs>1){ 00293 &print_tableau("At end of stage $curr_o"); 00294 return; 00295 } else { 00296 &print_tableau("Final tableau"); 00297 } 00298 print h3("Solution"), 00299 p; 00300 for($i=1;$i<=$vars;$i++){ 00301 $one_one=0; 00302 $value=0; 00303 $row=0; 00304 for($j=0;$j<($objs+$cons);$j++){ 00305 $a=$tableau[$j][$i]; 00306 ($a != -1) or ($a = -2); 00307 $one_one += abs($a); 00308 $row=$j if $a==1; 00309 } 00310 $value=$tableau[$row][$RHS] if $one_one==1; 00311 $value=&f_make_proper($value); 00312 print "$names[$i] = $value", 00313 br; 00314 } 00315 } 00316 00317 00318 ############################### Read in data ################################ 00319 &data_error("No data") if(param() eq ""); 00320 00321 ($vars,$objs,$cons)=(param('novars'),param('noobjs'),param('nocons')); 00322 foreach $a ($vars,$objs,$cons){ 00323 &data_error("Incomplete data") if $a==0; 00324 } 00325 push @maxmin,""; 00326 for ($i=0;$i<$objs;$i++){ 00327 $a=param('m'.$i); 00328 if($a eq 'Maximise'){ 00329 push @maxmin,1; 00330 }elsif($a eq 'Minimise'){ 00331 push @maxmin,0; 00332 }else{ 00333 &data_error("Incomplete data"); 00334 } 00335 } 00336 $display_type=param('fdisp'); 00337 $display_sign=param('showsign'); 00338 push @remove,-1; 00339 push @names,' '; 00340 for ($i=1;$i<=$vars;$i++){ 00341 $a=param('n'.$i); 00342 while ($a =~ s?\\?<sub>?){}; 00343 while ($a =~ s?([^<])/?$1</sub>?){}; 00344 push @names,$a; 00345 $a=param('r'.$i); 00346 if($a eq 'Never'){ 00347 push @remove,-1; 00348 }else{ 00349 push @remove,$a; 00350 } 00351 } 00352 for ($i=0;$i<($objs+$cons);$i++){ 00353 @temp=("R<sub>$i</sub>"); 00354 for ($j=1;$j<=$vars+1;$j++){ 00355 $temp=param('d'.$i.'_'.$j); 00356 $temp=&f_all_to_fract($temp); 00357 push @temp,$temp; 00358 } 00359 $tableau[$i]=[ @temp ]; 00360 } 00361 print header, 00362 start_html('Simplex results'), 00363 h1('Simplex results'), 00364 p; 00365 00366 ############################## Simplex algorithm ############################ 00367 # need for loop for removal of objective rows after 00368 $copy_objs=$objs; 00369 00370 outer_for: for($curr_o=1;$curr_o<=$copy_objs;$curr_o++,$objs--){ 00371 print h2("Stage $curr_o"), 00372 p; 00373 &print_tableau("Initial tableau"); 00374 $RHS=$vars+1; 00375 $pivot_no=1; 00376 00377 outer_while: while(1) { 00378 $temp="0/1"; 00379 for($i=$objs+1;$i<=$vars;$i++){ 00380 if(&imp_poss($maxmin[$curr_o],$tableau[0][$i],$temp)){ 00381 $temp=$tableau[0][$i]; 00382 $pivotc=$i; 00383 } 00384 } 00385 if(&f_eq($temp,"0/1")){ 00386 &display_solution; 00387 last outer_while; 00388 } 00389 00390 if (&f_gt($tableau[$objs][$pivotc],"0/1") or (&f_lt($tableau[$objs][$pivotc],"0/1") and &f_lt($tableau[$objs][$RHS],"0/1"))){ 00391 $pivotv = &f_div($tableau[$objs][$RHS],$tableau[$objs][$pivotc]); 00392 } else { 00393 $pivotv = '-1/1'; 00394 } 00395 $pivotr=$objs; 00396 for($i=$objs+1;$i<($objs+$cons);$i++){ 00397 next if(&f_eq($tableau[$i][$pivotc],'0/1')); 00398 $temp=&f_div($tableau[$i][$RHS],$tableau[$i][$pivotc]); 00399 if(&f_gt($temp,'0/1') and (&f_lt($pivotv,'0/1') or &f_lt($temp,$pivotv))) { 00400 $pivotv=$temp; 00401 $pivotr=$i; 00402 } 00403 } 00404 ($pivotv != '-1/1') or &simplex_error("Unable to find pivot element (no feasible solution)"); 00405 $value=$tableau[$pivotr][$pivotc]; 00406 00407 for($i=1;$i<=$RHS;$i++){ 00408 $tableau[$pivotr][$i]=&f_div($tableau[$pivotr][$i],$value); 00409 } 00410 $tableau[$pivotr][0]="R<sub>$pivotr</sub>"; 00411 for($j=0;$j<($objs+$cons);$j++){ 00412 next if $j==$pivotr; 00413 $tosub=$tableau[$j][$pivotc]; 00414 $char='- '; 00415 $char='+ ' if &f_lt($tosub,'0/1'); 00416 if(not(&f_eq($tosub,'1/1') or &f_eq($tosub,'-1/1'))) { 00417 my $a=$tosub; 00418 $a=&f_join(&f_toggle_neg(&f_split($a))) if &f_lt($a,'0/1'); 00419 $char=$char.&f_make_proper($a); 00420 } 00421 $char=$char."R<sub>$pivotr</sub>"; 00422 $char="" if &f_eq($tosub,'0/1'); 00423 $tableau[$j][0]="R<sub>$j</sub> ".$char; 00424 for($i=1;$i<=$RHS;$i++){ 00425 $tableau[$j][$i]=&f_sub($tableau[$j][$i],&f_mult($tosub,$tableau[$pivotr][$i])); 00426 } 00427 } 00428 &print_tableau("Pivot $pivot_no: row R<sub>$pivotr</sub>, column $names[$pivotc]."); 00429 $pivot_no++; 00430 } 00431 00432 @new_remove=(-1); 00433 @new_names=(""); 00434 @new_tableau=(); 00435 $new_vars=$vars; 00436 00437 last outer_for if($objs==1); 00438 00439 if(($tableau[0][$RHS] != 0) and (@maxmin[$curr_o] == 0)){ 00440 print p( 00441 "(Note: the first objective function has not reduced to 0. This suggests that there is no feasible solution to the problem, and that the rest of this algorithm may be invalid. Caution is necessary in interpreting the remaining stages.)"); 00442 } 00443 00444 00445 for($i=1;$i<=$vars;$i++){ 00446 $new_vars-- if($remove[$i] == $curr_o); 00447 } 00448 00449 for($j=1;$j<($objs+$cons);$j++){ 00450 $k=$j-1; 00451 @temp=("R<sub>$k</sub>"); 00452 for($i=1;$i<=$vars+1;$i++){ 00453 if($remove[$i] != $curr_o){ 00454 push @temp,$tableau[$j][$i]; 00455 push @new_names,$names[$i]; 00456 push @new_remove,$remove[$i]; 00457 } 00458 } 00459 @new_tableau[$j-1] = [ @temp ]; 00460 } 00461 00462 @tableau=@new_tableau; 00463 @remove=@new_remove; 00464 @names=@new_names; 00465 $vars=$new_vars; 00466 00467 } 00468 $url=url(-relative=>1,-query=>1); 00469 $url =~ s/simplex\.pl/simplex_table\.pl/; 00470 00471 print p( 00472 a({-href=>"$url"},"Alter initial tableau and reprocess"),br, 00473 a({-href=>"../simplex.html"},"Return to the simplex page")), 00474 hr, 00475 font({-size=>'-1'},"This implementation of the Simplex algorithm is © Mark Hutchinson 2004."), 00476 end_html; 00477