|
The "Exec" actor is a component in Ptolemy II that executes an arbitrary command in the underlying operating system — basically we can think of it as a wrapper for a Unix process. Tokens received on the actor's "input" port are converted to strings and sent to the process's standard input, and the process's standard output and standard error streams are output from the actor as StringTokens. The beauty of the Exec actor is that it provides a fairly general interface between Ptolemy and Unix.
In some respects, Ptolemy is not so different from Unix. Unix schedules the execution of a bunch of processes and facilitates communication between them through such means as named or anonymous pipes (FIFO's). Ptolemy schedules the execution of actors and facilitates communication between them through 'relations' (pipes with types). In Ptolemy, the scheduling algorithm can be chosen by the user (as 'Directors'), and actors expose many more ports in a standard way than just the standard in, standard out, and standard error ports of a Unix process.
We have to provide a little wrapper around the unix process to translate Ptolemy tokens into input it will understand, and likewise to parse its output into tokens that Ptolemy will understand. In my previous notebook entry I wrote about plotting an array of points {{x=double, y=double}} (obtained from a Datascope database with db2ptolemy, but that's irrelevent) using the standard Ptolemy XYPlotter. How about we plot this data with gnuplot instead?
We have to turn a Ptolemy expression like the following:
data = {{x = 0, y = 0}, {x = 1, y = 1}, {x = 2, y = 4}, {x = 3, y = 9}, {x = 4, y = 16}}
into a gnuplot command, that might look like this:
plot '-'
0 0
1 1
2 4
3 9
4 16
e
First, we can write a function to turn one tuple {x=double,y=double} into the gnuplot format:
gnuplotify = function (tuple:{x=double,y=double}) tuple.x + " " + tuple.y + "\n"
And then we can map this function over the array of tuples, and provide the necessary header and footer:
"plot '-'\n" + map(gnuplotify, data) + "e\n"
That command should have the desired output. Modifying the ArrayStationLocationPlotter demo to use this paradigm, we end up with a workflow (called ArrayStationLocationGnuplot) that plots sensor locations using gnuplot:
![[screenshot of workflow ${KEPLER}/workflows/orb/ArrayStationLocationGnuplot.xml]](http://mercali.ucsd.edu/~tobin/ArrayStationLocationGnuplot.png)
Unix leverages the ability to compose many small, special purpose programs into "pipelines" to easily realize complex processing tasks. We regularly form pipelines of grep, awk, and perl, or dbsort, dbjoin, dbselect. Pipelines and shell programming are the swiss army knifes of Unix, and the Exec actor in Ptolemy allows us to graft in all of this functionality into Ptolemy. With the Exec actor, any little Unix program that transforms its input suddenly becomes a Ptolemy actor that does the same. For instance, say we want an actor that takes an integer and outputs a list of its prime factors. We could write this from scratch in Java. Or we could just drag-and-drop an instance of the Exec actor, enter "factor" as the command, and utilize the extant "GNU factor" utility.
This time we don't have to translate the input to the program, but we have to translate its output to be digestable by Ptolemy. The output of factor looks like 42: 2 3 7, and we might want to represent it as a Ptolemy array, which would be written as {2, 3, 7}. The Exec actor should probably feature a parameter by which we could supply a regular expression with which to tokenize the process's output, but it doesn't at this time, so we'll have to do our adaptation either in Ptolemy or in Unix. As an example, I'll accomplish this by composing the 'factor' program with a small Perl script that will translate the output of 'factor' into a Ptolemy expression:
while (<>) { # process each line of input
s/(\ [0-9]+)/$1,/g; # add commas
s/^[0-9]+: /{/g; # remove the initial 'NN:' and put in '{'
s/,$/}/g; # remove the final comma and put in '}'
print $_;
}
For example:
bohemia /home/tobin> echo 42 | factor | perl -e 'while (<>) { s/(\ [0-9]+)/$1,/g; s/^[0-9]+: /{/g; s/,$/}/g; print $_; }'
{2, 3, 7}
(Actually, there are a few more complications. In order to make Exec(factor) properly synchronous, we have to have 'factor' exit after it factors one number. We can do that via the old map-as-printf trick, to turn a number like 42 into "42\n0\n". (Attempting to factor '0' is factor's clue to terminate.) Moreover, to get the pipeline working from factor to perl, we have to execute this command line via the shell, so we enclose the whole thing as a quoted argument to bash -c.)
This idea can be taken a bit further. Unix pipes aren't just connections between programs running on one machine, but they are also used for network communication and accessing files on disk. The same Unix program that reads and writes from/to a terminal will happily read/write from a network connection with no modification — just hand it handles to a the open network connection as its stdin/stdout handles. The "inetd" program does exactly this on a demand basis, starting a process in response to incoming network connections. The "netcat" program does something similar. Run "netcat foo.bar.com 25" and it will connect to foo.bar.com on port 25, and then connect stdin/stdout to that network connection. By simply running netcat via the Exec actor, we can effectively connect Ptolemy relations to a remote network connection. Suddenly we can implement HTTP or SMTP or whatever protocol we want in Ptolemy. Here is an example workflow that performs an HTTP query, using only 'Const', 'Exec', and 'Display' actors:
Pipes are important in Unix, but Unix has a variety of other means of inter-process communication as well. Other operating systems take the idea of pipes even further. Plan 9, Bell Labs' stated successor to Unix, uses named pipes for everything, even the windowing system. There are a multitude of possibilities.
(Grid computing enthusiasts will be interested in 9grid.) |