Calling External Programs¶
There are various ways that SIMION can call external code or programs:
Calling external programs via the command-line interface, using Lua functions like
os.execute()
orio.popen()
. The program must support a command-line interface. There is the overhead of starting up the program, so this method is typically not so suitable if you need to call the code, say, thousands of times per second. Details are given below.Calling external programs via COM (luacom). The program must support a COM interface (e.g. like Excel), and this is only supported in Windows. The call may be in-process or out-of-process depending on the object, but in either case it is relatively efficient for doing that. SIMION Example: excel uses this extensively.
Writing some code in a language like C, compiling it to a DLL, and loading that into Lua. This method can be very efficient, with very little (though not zero) overhead between calls, but it requires some coding and compiling. See the SIMION Example: extension example and C API (Lua 5.1 Reference Manual).
Using TCP/IP sockets. You can use the Lua library luasockets to send/receive data with some service listening on a TCP/IP socket, or even a web server. This library is bundled with SIMION 8.1.
Using ZeroMQ (ZMQ). For parallel processing, such as communication between multiple SIMION instances, or other programming languages, it can be preferrable to use ZeroMQ rather than TCP/IP sockets. ZeroMQ may provide a more efficient and simpler interface to TCP/IP sockets (and often implemented over TCP/IP sockets). It is possible for SIMION to communicate with other SIMION instances or with other programming languages (e.g. Perl, Python, Java, C#, etc.) via ZeroMQ. The ZeroMQ library is bundled with SIMION 8.2. See SIMION Example: multiprocess_zmq (8.2) for usage.
Using named pipes: Although usable via Windows too, this is primarily suggested for Linux. Named pipes can be read/written just like normal files with the Lua
io
library, but the data is sent from/to a program. SIMION Example: gnuplot uses named pipes on Linux.
Using the Command-Line Interface¶
The Lua os.execute()
function can be used to run an external program from SIMION.
The Lua io.popen()
function is similar but captures any data
written to standard output as a string (or writes a string to standard input).
API details on these functions are in the Lua Reference Manual.
For example, either of these will open a certain file in Windows Notepad:
os.execute 'c:/windows/notepad.exe "c:/Program Files/SIMION-8.1/README.html" '
os.execute 'notepad.exe "c:/Program Files/SIMION-8.1/README.html" '
(The shorter form can be used since the directory c:\windows\
is in the
system PATH
environment variable.)
This will start Windows Notepad but not block SIMION:
os.execute 'start notepad.exe "c:/Program Files/SIMION-8.1/README.html" '
This will load the given file in whatever is the default program
in the Windows file associations (e.g. .txt
probably with a text editor,
.html
probably with a web browser, and .csv
probably with Excel):
os.execute ' "c:/Program Files/SIMION-8.1/README.html" '
os.execute ' "c:/Program Files/SIMION-8.1/examples/field_array/solenoid.csv" '
Be careful about backslashes \
, forward slashes /
, and spaces. For strings surrounded
by single or double quotes, backslashes must be escaped with an additional backslash.
You can often avoid this by just using forward slashes or quote strings with [[ ]]
, which
ignores backslash escape sequences. However, arguments with spaces must themselves be quoted.
The following three commands are all valid:
os.execute 'notepad.exe "c:\\Program Files\\SIMION-8.1\\README.html" '
os.execute 'notepad.exe "c:/Program Files/SIMION-8.1/README.html" '
os.execute [[notepad.exe "c:\Program Files\SIMION-8.1\README.html" ]]
Here’s executing SIMION itself:
os.execute([[""c:/Program Files/SIMION-2020/simion.exe" fly "c:/Program Files/SIMION-2020/examples/einzel/einzel.iob""]])
You can use the DOS “start” command to begin executing a command but not wait for it complete. Here’s launching three instances of SIMION simultaneously:
for i=1,3 do os.execute([[start "" "c:/Program Files/SIMION-2020/simion.exe" fly "c:/Program Files/SIMION-2020/examples/einzel/einzel.iob"]]) end
In some cases, the Windows program or shell does not recognize forward slashes and
you need to instead use backslashes.
Be careful also about spaces in program arguments;
for example, the space in Program Files
requires the full path to be quoted.
According to Windows conventions, some of these quoting rules are a little obscure,
and Linux has somewhat different rules.
Some resort to writing a batch file (.bat
) to disk and then just invoking
that batch file without arguments.
The command line interface may also be used to execute scripts in other languages, like Python/Perl/Matlab/Octave:
os.execute 'python myprogram.py 1 2 3'
os.execute 'perl myprogram.pl 1 2 3'
os.execute 'c:/cygwin/bin/perl.exe myprogram.pl 1 2 3'
The full path must be specified (like the last example above) if the folder
containing the program is not listed in the system PATH
environment variable,
which you can display like this:
print(os.getenv('PATH'))
Depending on the interface supported by the called program, you may pass data
through command-line arguments or files (e.g. 1 2 3
), including perhaps
a file name.
If the program reads from standard input, then appending
e.g. <input.txt
sends the contents of the file input.txt
through
standard input.
If the program writes to standard output, then appending
e.g. >output.txt
writes standard output to the file output.txt
:
os.execute 'c:/cygwin/bin/perl.exe myprogram.pl my.txt <input.txt >output.txt'
Some helper functions may be useful:
local function read_file(path)
local fh = assert(io.open(path))
data = fh:read'*a'
fh:close()
return data
end
local function write_file(path, data)
local fh = assert(io.open(path, 'w'))
fh:write(data)
fh:close()
end
local function exec(cmd, input)
local input_path = os.getenv('TEMP') .. os.tmpname()
local output_path = os.getenv('TEMP') .. os.tmpname()
os.remove(input_path)
os.remove(output_path)
write_file(input_path, input)
cmd = cmd .. ' <"' .. input_path .. '" >"' .. output_path .. '"'
assert(os.execute(cmd) == 0, cmd)
local output = read_file(output_path)
return output
end
print(exec('c:/cygwin/bin/perl.exe -ne "print $_ * 2"', "1\n2\n3"))
-- uses Perl do double the numbers, printing "246"
An alternate function io.popen()
can also be used to execute a program
and either capture standard output to a string or send a string to its standard input.
One advantage is that it does so without reading/writing to a temporary file,
but the disadvantage is that it doesn’t both read and write at the same time.
Some helper functions may be useful:
local function execr(cmd)
local fh = assert(io.popen(cmd))
local data = fh:read('*a')
fh:close()
return data
end
print(execr [[c:/cygwin/bin/perl.exe -e 'for my $x (1..3) { print "$x\n"; } ']])
-- prints "1 2 3" to the SIMION Log window.
local function execw(cmd, data)
local fh = assert(io.popen(cmd, 'w'))
fh:write(data)
fh:close()
end
execw ([[c:/cygwin/bin/perl.exe -e 'for my $x (<STDIN>) { print $x*2, "\n" for 1..1000000; } ' >out.txt]], "1\n2\n3")
-- writes "1 2 3" to the file out.txt.
But if you need to both send and receive extensive data to/from the
command, you generally need to instead just have the command read/write
temporary files, like the exec
function above.
Another advantage of io.popen
is that it can read/write multiple chunks of
data from/to the program over time, while keeping the program in memory.
The SIMION Example: gnuplot uses this to invoke the gnuplot plotting program and
update the plot over time by sending more data.
Using COM¶
See luacom.
Using a DLL¶
See SIMION Example: extension example and C API (Lua 5.1 Reference Manual). Not only C code, but other languages can be compiled as DLLs (e.g. Fortran).
Using LuaSockets¶
The following example uses LuaSockets to retrieves a web page using raw TCP/IP:
local socket = require "socket"
local client = socket.tcp()
assert(client:connect('www.google.com', 80))
client:send('GET / HTTP/1.1\nHost: www.google.com\nConnection: close\n\n')
local result = client:receive'*a'
client:close()
print(result)
There are higher-level interfaces that implement the HTTP protocol (over TCP/IP),
like socket.http
, if you want that:
local http = require "socket.http"
local data,code,headers,status = http.request("http://www.google.com/")
print(data)
print('code=', code, 'status=', status)
if headers then for k,v in pairs(headers) do print('header', k,v) end end
SIMION Example: multiprocess used sockets, though ZeroMQ is now preferred.
Using ZeroMQ (ZMQ)¶
The ZeroMQ library is bundled with SIMION 8.2. See SIMION Example: multiprocess_zmq (8.2) for usage.
SIMION Example: multiprocess_zmq/client_server also shows simple client-server implementations in multiple languages (Lua/Python/Perl/C/C#/F#).
Using Named Pipes (Linux)¶
Here is an example of creating and writing to a named pipe that is read by another program (in Linux):
os.execute'mkfifo /tmp/mypipe'
local fh = assert(io.open('/tmp/mypipe', 'w'))
fh:write('test')
fh:close()
-- Then read the pipe in another program, like
-- cat /tmp/mypipe
Matlab/Octave¶
simion_pa_save.m - MATLAB function to save a MATLAB array to SIMION PA file.
simion_call_example.m - MATLAB script calling SIMION from MATLAB via the command-line interface.
matlab_call_example.lua - SIMION Lua script calling MATLAB from SIMION via the command-line interface.
matlab_com_example.lua - SIMION Lua script calling MATLAB from SIMION via the COM automation API.
To call SIMION from Matlab, the command-line interface is the primary approach: system Run External Commands, Scripts, and Programs. Other methods include TCP/IP Communication