Banner

Putting Stella Models on the Web

Anne Thissen
The Shodor Education Foundation
June 1998

So... you want to put your Stella model on the web. You don't know Perl, you don't know Java, and you want to put up something more interactive than a picture of your Stella layout. You're in luck. This page contains several templates of Perl code to simplify your Stella-to-web translation. You don't need to understand the code, just to change a few variable names and select subroutines according to the type of function you have in your Stella program.

First, you will need to get the runtime equations from your Stella model. These may look like:

    HEIGHT(t) = HEIGHT(t-dt) + (UPWARD_VELOCITY) * dt
We will use a slightly different form of the equation.
    HEIGHT(t+dt) = HEIGHT(t) + UPWARD_VELOCITY(t) * dt
The two equations are equivalent, and equally useful for most purposes. You can get to these by clicking the little down-arrow on the left side of the model screen. Go up to the Equation menu and select Equation Prefs. Select the button for order of execution. This will help you later.

If you are working with either of the rock models, UPWARD_VELOCITY can be expressed as a simple function of time. The rocket model is a bit more complicated, but both are quite doable.

First, start your Perl program in SimpleText:

#!/usr/local/bin/perl
#[Title of your model]
#Created by [Your name here]
#Using code developed by Anne Thissen, Ben Davenport, 
#  Bob O'Neill, Clint Tillerson

$server_name = "scan.shodor.org";
$tmp = "/tmp";

&get_request;
&html_header ("Graphical Results for [Title of your model]");

Except for substituting in the title of your model, and your name, these lines should not change. Next, select which variables you want the user to input from the web page -- starting height of the rock? Initial upward velocity? Delta time? Length of the simulation? These variables need names you will remember. Variable names need to start with a letter and only contain letters, numbers, and _'s, no spaces. You might use, for example, init_height and init_velocity.

For each of these variables, you need a line like one of these:

	$init_height=$rqpairs{'init_height'};
	$max_time=$rqpairs{'max_time'};
	$dt=$rqpairs{'dt'};
	$ymax=$rqpairs{'ymax'};
	$style=$rqpairs{'style'}

If you want to let the user select the integration method (Euler's, Heun's, or Runge-Kutta 2 or 4), include the following line:

	$integration=$rqpairs{'integration'};

Then you will initialize the rest of the constants, the ones you choose:

	$gravity=9.8;

(unless, of course, you want your model to work on other planets). Make as many of both these lines as you need for all your constants. You also need to set functions to their initial values:

	$velocity=$init_velocity;
	$height=$init_height;

Your initializations section of your Stella equations can help you with this. Now, go down to the bottom of the program and work backwards for a while. Paste in the following LONG block of code:

&html_trailer;
exit;

sub get_request  {

 if ($ENV{'REQUEST_METHOD'} eq "POST") {
  read(STDIN, $request, $ENV{'CONTENT_LENGTH'});
 }
 else
 {
  print "Location: http://$server_name/scsimodels/my_model_name/\n\n";
  exit;
  }

 %rqpairs = &url_decode(split(/[&=]/, $request));
}

sub url_decode {

#	Decode a URL encoded string or array of strings
#  + -> space
#  %xx -> character xx

 foreach (@_) {
  tr/+/ /;
  s/%(..)/pack("c",hex($1))/ge;
 }
 @_;
}

sub html_header {

# Subroutine html_header sends to Standard Output the necessary
# material to form an HHTML header for the document to be
# returned, the single argument is the TITLE field.

 local($title) = @_;

 print "Content-type: text/html\n\n";
 print "<html><head>\n";
 print "<title>$title</title>\n";
 print "</head><body bgcolor=#ffffff>\n\n";
 print "<center><h1>$title</h1></center><hr size=5>";

}

sub html_trailer {
 print "<hr><address>The Shodor Education Foundation, 
     Inc.</address>";
 print "</body></html>\n";
}

sub interp {

   local ($number, *array1, *array2) = @_;

   for ($i=0; $i<=10; $i++) {
    
   if ($number == $array1[0] ||
		($number > $array1[$i] && $number <= $array1[$i+1]))
     {
      $proportion = ($number - $array1[$i]) / ($array1[$i+1] - $array1[$i]);
      $result = $array2[$i] + (($array2[$i+1] - $array2[$i]) * $proportion);
      return ($result);
     }
  }
}

You only need to change one thing in all that. Go up to the eleventh line and change my_model_name to something short and personalized. That's the folder your model will be in when we put it on the server.

Now you are ready to write the code for your function. This will be pretty easy if you have your Stella model runtime equations in front of you, in order of execution. This time you will use the actual runtime equations. It will look something like this:

sub function {
		local ($init_velocity, $time, $dt) = @_;

		$upward_velocity = $init_velocity - 9.8 * $time;
		$change_in_height = $upward_velocity * $dt;

		return($change_in_height);
}

Note that I put the re-initializations _first_, while Stella puts them last. That's because we're using the f(t+dt)=f(t)+... form, rather than the f(t)=f(t-dt)+... form Stella uses. Our time-dependent initializations need to be g(t), not g(t-dt). There is a t=t+dt step, but that doesn't happen now, so it doesn't help. Also notice that what we solved for here was the _change_ in the height, not the new height. That may seem silly now, but it's for when we implement the Runge-Kutta methods.

You might need to put more variables in your second line, if you have a different function. That's no problem, and the order doesn't really matter as long as you remember what order you used.

If you want to let your users use non-Euler methods, you'll need to include the following code:

sub euler {
	local ($height, $init_velocity, $time, $dt) = @_;
	$height = $height + &function($init_velocity, $time, $dt);
	return($height);
}
sub heun {
	local ($height, $init_velocity, $time, $dt) = @_;
	&euler($height, $init_velocity, $time, $dt);
	return($height);
}
sub runge_kutta_2 {
	local ($height, $init_velocity, $time, $dt) = @_;
	$height_a = &function($init_velocity, $time, .5*$dt);
	$height_b = &function($init_velocity, $time+.5*$dt, .5*$dt);
	$height = $height_a + $height_b;
	return($height);
}
sub runge_kutta_4 {
	local ($height, $init_velocity, $time, $dt) = @_;
	$height_a = &function($init_velocity, $time, $dt);
	$height_b = &function($init_velocity, $time+.5*$dt, $dt);
	$height_d = &function($init_velocity, $time+$dt, $dt);
	$height = ($height_a + 4*$height_b + $height_d) / 6;
	return($height);
}

Make sure the variables listed in the "&function($init_velocity...)" parentheses match exactly with the local(...) variables in your function code. You can put in other variables if you need. In all the local(...)'s here, the list of variables should be the same list with "$height," tagged onto the front.

Heun's method is identical to Euler's for the rock problem, but is quite different for the rocket problem, and that's why we include it. If you're doing the rocket problem or another model, you might need to use some of the code from here.

Now go back up into the program. WAY back up, in fact, past that big long paste that started with "&html_trailer". Between the initializations and that, put the following:

	open (HEIGHT, ">$tmp/height.dat") || die "Can't open height.dat: $!\n";

	if ($integration eq 'Euler') {
		for ($time=0; $time<=$maxtime; $time+=$dt) {
			print HEIGHT "$time $height\n";
			&euler($height, $init_velocity, $time, $dt);
		}
	} elsif ($integration eq 'Heun') {
		for ($time=0; $time<=$maxtime; $time+=$dt) {
		print HEIGHT "$time $height\n";
		&heun($height, $init_velocity, $time, $dt);
		}
	} elsif ($integration eq 'Runge Kutta 2') {
		for ($time=0; $time<=$maxtime; $time +=$dt) {
			print HEIGHT "$time $height\n";
			&runge_kutta_2($height, $init_velocity, $time, $dt);
		}
	} elsif ($integration eq 'Runge Kutta 4') {
		for ($time=0; $time<=$maxtime; $time+=$dt) {
			print HEIGHT "$time $height\n";
			&runge_kutta_4($height, $init_velocity, $time, $dt);
		}
	}

The list of variables in all four should match the locals you just did, with $height in the beginning. Keep going:

	close (HEIGHT);

		local ($gnuplot) = "/usr/bin/gnuplot";
		local ($ppmtogif) = "/home/httpd/cgi-bin/ppmtogif -interlace";

		open (PLOT, "| $gnuplot") || die "Couldn't open pipe to gnuplot";
		print PLOT "set size 1, 1\n";
		print PLOT "set terminal pbm color \n";
		print PLOT "set output \"$tmp/height$$.pbm\"\n";
		print PLOT "set xlabel 'Time' \n";
		print PLOT "set ylabel 'Height' \n";
		print PLOT "set xrange [0:$maxtime]\n";
		print PLOT "set yrange [0:$ymax]\n";
		print PLOT "set nokey \n";
		print PLOT "plot \"$tmp/height.dat\" with $style\n";

	close (PLOT);

		$imagefile = "$tmp/height$$.pbm";
		system ("chmod 777 $tmp/height.dat");
		system ("$ppmtogif < $imagefile > $tmp/height$$.gif");

    $graphwidth = 640;
    $graphheight= 480;

print <<EOT;

<br>

<center><table border=3 cellspacing=5 cellpadding=5>
<tr><td align=center valign=middle><h2>Height:</h2></td>
<tr><td align=center><img width=$graphwidth  height=$graphheight
src=http://$server_name/tmp/height$$.gif></td></tr>
</table></center>
<p>

EOT

That's it for your program. Now all that's left is to write the web interface.


Last Update: June 2, 1998
Please direct questions and comments about this page to WebMaster@shodor.org
© Copyright 1998 The Shodor Education Foundation, Inc.