The SIMION SL™ Toolkit (version 1.2.1.0 - 2004-11-09)

The Simple SIMION Language (SL)™: Language Reference

(c) 2003-2004 Scientific Instrument Services, Inc. Created November 2003 by David Manura. Updated: $Date: 2004/07/16 19:42:02 $.


Introduction and Abstract

This document describes the SL user programming language provided by the SIMION SL Toolkit for use in the SIMION 3D Ion and Electron Optics simulation program (http://www.simion.com). SL is an alternative, higher-level language than the standard user programming language built into SIMION (referred to here as ``PRG'' code due to the file extension given to the program files).

First, why would you need a programming language (such as PRG or SL) when simulating ion optics? Well, to customize the calculation, dynamically change or oscillate voltages on potential arrays, respond to events, perform intermediate calculations, or perturb ion paths in unique ways. In fact, user programs are used in a number of the examples that come with SIMION, such as the Buncher lens and the quadrupole, which change/oscillate potential array voltages and record events via simple SIMION PRG programs (these are the BUNCHER.PRG, QUAD.PRG, QUADIN.PRG, and QUADOUT.PRG files).

The problem though, which the SL Compiler solves, is that the PRG language built into SIMION is limited, albeit functional. For those familiar, using PRG code can be likened to programming in the Forth language, using a Hewlett-Packard Reverse-Polish Notation (RPN) calculator, or writing assembly on a stack-based virtual CPU. In other words, it could be simpler. The SL compiler is intended to make writing user programs in SIMION much simpler by allowing you to use high-level programming language constructs as you'd expect in any power scientific application such as Excel or Matlab. In fact, SL even allows you to call custom remotely running C++ code during the simulation through the SL Remote interface (other languages may be supportable as well upon request). So, you can even write parts of your custom code in a general purpose programming language (but with some performance penalty compared to SL or PRG due to the interprocess communication overhead).

For SIMION to understand your SL code, you need the SL Compiler (http://www.simion.com/sl), which is a tool that translates SL code such as

 if x[n+1] + 2 > velocity * exp(t)
   print("Operating limits have been exceeded! velocity=#", velocity)
 else
   print("Everything ok")
 endif

into equivalent PRG code such as

  RCL n 1 + ARCL x 2 + RCL velocity RCL t E^X *
  X>Y GTO __label1
  MESS ;# Everything ok
  GTO __label2
  RCL velocity
  MESS ;# Operating limits have been exceeded! velocity=#
  LBL __label1
  LBL __label2

The latter can be understood and executed directly by SIMION. As programs get large, it is easily seen how the former notation is much easier to understand and maintain. This document does not describe the SL compiler tool (see the Tutorial--compiler_tut.html--for compiler usage) but instead describes only the SL language.

Compatibility note: SL is compatible with SIMION 7.0. It has not been tested with SIMION 6.0, although 6.0 should work as well with few, if any, changes.

A Simple Example Program: The Buncher Lens

Just to give a taste of SL, below is an real-world example program written in the SL language. It can be used as an exact replacement for the BUNCHER.PRG program in the Buncher lens example (http://www.simion.com/examples/buncher/) that comes with SIMION but of course is much easier to read. In summary, the Buncher lens is used to focus dispersed ions approaching it by cutting off the electric field as the ions get close to the lens. The effect is that ions arriving in the lens first are decelerated more than ions arriving later so that all ions subsequently converge at a certain point. The below program achieves this as follows. At the start of simulation, the program updates the potential energy (PE) surface, it sits and does nothing until time switch_time, and then it then turns off the lens voltage (adj_elect01), changes the ion color (ion_color) to blue (color 3), marks the ion position on the display, and causes the PE surface display to be again updated.

 # main buncher control program

 adjustable switch_time = 1.7     # switch time in microseconds
 adjustable buncher_voltage = 900 # buncher deceleration voltage

 static update_flag = 1           # pe surface update flag

 # adjust time step
 sub tstep_adjust
     # Let's make sure a time step ends right on the switch time.
     if ion_time_of_flight < switch_time
         ion_time_step = min(ion_time_step,
                             switch_time-ion_time_of_flight)
     endif
 endsub

 # buncher voltage control
 sub fast_adjust
     adj_elect01 = if(ion_time_of_flight < switch_time,
                      buncher_voltage, 0)
 endsub

 # display update
 sub other_actions
     if switch_time == ion_time_of_flight
         ion_color = 3      # switch ion color to blue
         mark()             # mark transition location of ion
         update_flag = 1    # set pe surface update on next time step
     elseif update_flag == 1
         update_flag = 0
         update_pe_surface = 1
     endif
 endsub

Listing #1.

Details of the individual statements above will be described throughout this document.

The Design of SL

The are many ways in which the SL language could have been designed differently. The following following major design criteria have been used in SL.

Anatomy of a Program

You should first read some of the introductory section in the SIMION 7.0 manual on user programming (pages I-1 to I-3, I-6, and possibly also I-17 to I-34) before proceeding with the section. Many of the concepts described there also apply to SL (e.g. the various segments and reserved variables defined). A summary of some of these concepts will be provided below as well.

SIMION allows the user to associate a unique SIMION program (*.PRG file) with each potential array. The PRG program responds to events and dynamically adjusts the flight and environment of ions traversing though that potential array as time progresses. For example, you might use a PRG file to cause the voltage potential of an electrode to oscillate according to a sinusoidal waveform or print out some calculation results at the end of ion flight.

When writing a custom program in SL rather than in PRG, you instead use the SL compiler to automatically generate the corresponding PRG file from your SL file. This generated PRG file can then be used and executed by SIMION as usual.

Subroutines (a.k.a. segments)

As shown in Listing #1 above, an SL program, like a PRG program, consists of one or more subroutines (referred to as a segment in PRG code), possibly proceeded by variable declarations and optionally interspersed with comments. A subroutine in SL is in many ways similar to a function or subroutine in other languages. It is a block of custom code with an identifying name.

SIMION gives special meaning to the below subroutine names. If you define a subroutine having one of these names, the SIMION calculation engine will execute that subroutine at various points during the calculation (and hence you are able to customize the calculation). Refer to the User Programming section of the SIMION manual for details on these subroutines (a.k.a. segments).

You may also define subroutines with other names and call them from the standard subroutines.

Variables

As usual, variables are used in your program to store temporary data and maintain state. In SIMION, declared variables can be of either the adjustable or static variety. This concept of adjustable v.s. static is specific to SIMION and is not seen in general purpose programming languages (think of it as different types of global scoping), so special attention must be given to it. Adjustable and static variables differ in their lifetime and in how they are presented to the users running the program.

Adjustable variables
Adjustable variables maintain their values throughout the entire simulation run or series of reruns. They are also visible to the user and can be ``adjusted'' via a GUI before the simulation. As an example, the initial voltage on an electrode might be stored in an adjustable variable so that the user running the simulation can easily ``adjust'' that voltage to any value before running the simulation.

Static variables
Static variables maintain their values only during the run of the current ion or (for grouped runs) ion group. They are not visible to the user running the simulation. Static variables are suitable internal results persisting only for the current ion flight and not directly presented to the user.

Adjustable and static variables must be defined as the beginning of your program (as seen with the ``adjustable'' and ``static'' statements in Listing #1 above). Adjustable and static variables are similar in that they both are globally visible to all concurrently running user programs. That is, one program can read and modify the adjustable and static variables of another running program.

In addition to adjustable and static variables, SIMION supports reserved and temporary variables.

Reserved variables
Reserved variables are similar to static variables except that they are ``built-in''--SIMION already defines them so you don't have to (i.e. no ``adjustable'' or ``static'' statements needed or allowed). For example, in Listing #1 above, ion_time_of_flight, ion_time_step, adj_elect01, and update_pe_surface are all reserved variables.

Temporary variables
Temporary variables (also called ``local variables'' in most languages) are variables that have a lifetime only for the duration of the currently running program segment (subroutine). They are much shorter lived than global variables (static and adjustable). Temporary variables also need not be declared (i.e. no ``adjustable'' or ``static'' statement is needed). A typical use for temporary variables is to store a temporary results that can be discard once the subroutine ends. In the below code, ``factor'' is a temporary variable:

 sub fast_adjust
     factor = cos(ion_time_of_flight + 0.5*3.14)*10 + 1
     adj_elect01 = adj_elect01 * factor
     adj_elect02 = adj_elect02 * factor
 endsub

Syntax

The previous section describes what a program is. The next question is, How do I write one? As in any language, a number of grammatical rules must be obeyed to keep the reader (i.e. the computer) happy.

We'll get to the other rules throughout this document, but this should be a start.

Simple Variable Declarations

Static and adjustable variables must be declared outside of any subroutine and should be specified near the top of your program:

 adjustable switch_time = 5
 static test = 2.2E-5 

Can can assign an intial value to these variables as shown above. If the assignment value is ommited, it defaults to zero:

 adjustable color         # same as adjustable color = 0

Assignment Statements and Expressions

Assignment statements assign the value of the right-hand expression to the variable named on the left-hand side.

 x = 5
 y = x                         # x now is 5
 x = 3 + 2*(8/4 + 5*(2-3))     # x now is -3
 x = sin(abs(x) + y)           # x now is sin(8), or ~0.9894
 x = min(9,max(4,5))           # x now is 5
 (radius, azimuth, elevation) = rect3d_to_polar3d(x, y, z)
                               # function returning multiple values
 (x, y, z) = (1, 2, 3+4)       # x=1, y=2, z=7

A special case is shown in the rect3d_to_polar3d example. This function takes three arguments as input (x, y, and z) and returns three values (a ``tuple'') as output (radius, azimuth, and elevation). So, it is possible to assign the result of a function to a tuple if the function returns a tuple.

Arrays

Arrays store a collection of numbers that are referenced by an index. Arrays are declared at the top of your program as such:

 # declare an adjustable array "stuff" of size 10
 adjustable[10] stuff
 # declare a static array "more_stuff" of size 10
 static[10] more_stuff

(PRG note: This is equivalent to the ADEFA and ADEFS commands in PRG code.) You must specify an array bound.

Despite the desires of programming purists, arrays are 1-indexed as in SIMION (i.e. the first element is at location 1 not location 0). Array elements are referenced as such:

 stuff[5] = 2               # set the fifth element of stuff to 5
 x = stuff[5+y] + stuff[1]  # set x to the sum of the (5+y)-th element
                            #   and first elements of stuff.
 stuff[x+y*2] = stuff[x+2]  # a more complex example

Side note: in PRG code, the above would be accomplished as such:

 2 5 ASTO stuff
 5 y + ARCL stuff 1 ARCL stuff STO x
 x 2 + ARCL stuff x y 2 * + ASTO stuff

You can also save and load an array to/from an file:

 array_load(stuff, "myfile.txt")
 array_save(stuff, "myfile.txt")

Refer to the SIMION manual on ALOAD and ASAVE opcodes for file format information (p I-11).

Note: SL, like PRG code, provides no intrinsic support for multidimensional arrays. A multidimensional arrays can be simulated with a single dimensional array:

  three[(z * ysize + y) * xsize + x   + 1] = 5

which is analogous to setting element (x,y,z) to 5, assuming three is a three-dimensional array of dimensions xsize, ysize, zsize that is also zero-indexed.

Operators

The following binary operators are supported: +, -, *, /, and % with their usual semantics. The '%' operator is for modular arithmetic. ``a % b'' is the remainder of a divided by b. Note: the result is considered undefined if b is not positive or either operand is non-integer. Note the behavior if the divisor is negative: ``-7 % 4'' gives 1 since -7 + 2*4 = 1.

Strings

A few commands require strings, which are delimited by double quote (``) characters.

 print("Hello World!")
 array_load(stuff, "myfile.txt")
 array_load(stuff, "myfile.txt")

A few special cases are worth noting.

 print("Test,#", 1) # a test

As expected, the pound (#) inside the string is considered part of the string, not as the beginning of a comment.

SIMION recognizes a few escape characters. For example in,

 print("123\n234\t345")

the \n is interpeted as ``new-line'' and \t is interpreted as ``tab''.

Note: \\ and \`` displays as empty strings (SIMION restriction).

Conditionals

SL supports the standard conditional statements, as well as Boolean logic.

 if x == 3
     print("ok")
 endif

 if x < 3
     print("ok")
 else
     print("test")
 endif

 if x < 3
     print("ok")
 elseif x < 2
     print("123")
 else
     print("test")
 endif

Conditionals can also contain compound Boolean logic:

 if x < 3 or not y < 2 and z > 3
     print("test")
 endif

Note that, as usual, 'not' has higher precedence than 'and', which in turn has higher precedence than 'or', so the above is equivalent to

 if x < 3 or ((not y < 2) and z > 3)
     print("test")
 endif

Note: The endif is always required, so this is INCORRECT:

 if x < 3           # invalid syntax: missing endif
     print("ok")

Functional if

An alternate syntax for conditionals is to use the functional if:

 x = if(y>0,log(y),0)

This is equivalent to

 if y > 0
     x = log(y)
 else
     y = 0
 endif

The conciseness of the former is sometimes convenient. Cascading functional if's are also supported:

 x=if(x==1,11,if(x==2,12,13))

Advanced note: ``short-circuiting'' is supported in that the following

 sub initialize
     x = if(1==0, f(1), f(2))
 endsub
 sub f(n) returns(m)
     print("#", n)
     m = 0
 endsub

outputs simply ``2\n'' not ``1\n2\n''.

C++ Note: the SL functional-if is similar to the ``condition ? y : z'' construct in the C++ language.

Loops

SL also has standard support for loops:

 # prints integers 1 to 10 inclusive
 for x = 1 to 10
     print("x=#", x)
 endfor

 # prints integers 1 to 10 inclusive in reverse
 for x = 10 to 1 step -1
     print("x=#", x)
 endfor

 # prints numbers 10, 9.5, 8, 8.5, ... 1
 for x = 10 to 1 step -0.5
     print("x=#", x)
 endfor

 # a nested for with a limit being an expression
 for x = 1 to 5
     for y = 1 to x+1
         print("x=#,y=#", x, y)
     endfor
 endfor

The limits and step are evaluated once before the loop starts, so modifications to these values inside the loop have no effect on the loop itself. For example,

 y = 5
 n = 1
 for x = 1 to y step n
     print("#", x)
     y=1
     n=2
 endfor

still outputs ``1\n\2\n3\n4\n5\n''.

Here's some more examples:

 # another way to print integers 1 to 10 inclusive.
 # This also sums the numbers.
 x=1
 while x <= 10
     print("x=#", x)
     x = x + 1
 endwhile
 print("sum=#", x)

 # a loop using a compound conditional
 x=1
 y=1
 while x*x <= 25 and y < 5
     print("x=#", x)
     x = x + 1
     y = x - 1
 endwhile

Printing

To print text to the screen (or to a file if output is being saved to a file), use the ``print'' command.

 print("Hello World!")

The print command can also print numbers and variables interpolated into the string:

 print("Velocity=# and mass=#", velocity, mass)

So, if velocity contained 100 and mass contained 2, the above would print

 Velocity=100 and mass=2

C/C++ note: This somewhat resembles the printf function in the C language. It is based on the MESS command in PRG code. In SL, ``mess'' is actually synonymous with ``print'', but use of the latter is preferred.

Built-in subroutines

SIMION comes with various built-in subroutines for performing mathematical computations, coordinate transformations, and data manipulation.

 u = exp(sqrt(2)) + sin(3)

Some of these function input and output multiple values:

 # convert 3D rectangular coordinates to 3D polar coordinates.
 (radius, azimuth, elevation) = rect3d_to_polar3d(x, y, z)

A full list of built-in subroutines is given in the Built-in and Utility Subroutines section below.

Custom Subroutines

SL allows you to define your own custom subroutines in addition to the built-in ones.

 sub terminate
     x = 4
     y = x*x
     alert(x,y)
 endsub

 sub alert(a,b)
     print("Here are the results:")
     print("a=#, b=#, sub=#", a, b, a+b)
 endsub

Subroutines can also return values:

 sub terminate
     x = (-b + descriminant(a,b,c)) / (2*a)
 endsub

 sub descriminant(a,b,c) returns(d)
     d = sqrt(b*b - 4*a*c)
 end

Subroutines can return multiple values (like the built-in rect3d_to_polar3d function):

 sub terminate
     (x, y, z) = f(1, 2, 3)
     print("#,#,#", x, y, z)
 endsub
 sub f(x,y,z) returns(x2,y2,z2)
     (x2, y2, z2) = (cos(x), cos(y), cos(z))
 endsub

Subroutines can even by recursive, as shown in the illustrative example below. (Warning: these function are most efficiently coded using iterative algorithms, not recursive ones--this is only for demonstration.)

 sub terminate
     print("5!=#", factorial(5))
     for n=0 to 7
         print("fibonacci(#)=#", n, fibonacci(n))
     endfor
 endsub

 sub factorial(n) returns(n)
     if n > 1
         n = n * factorial(n-1)
     else
         n = 1
     endif
 endsub

 sub fibonacci(n) returns(n)
     if n >= 2
         n = fibonacci(n-1) + fibonacci(n-2)
     endif
 endsub

When executed, the above outputs

 5!=120
 fibonacci(0) = 0
 fibonacci(1) = 1
 fibonacci(2) = 1
 fibonacci(3) = 2
 fibonacci(4) = 3
 fibonacci(5) = 5
 fibonacci(6) = 8
 fibonacci(7) = 13

Note: In PRG code, subroutines and labels are local to a segment, so you cannot call a subroutine defined in one segment from another segment. In SL, this restriction is removed--a subroutine is defined by itself and may be called by any other subroutine.

 adjustable x = 0
 adjustable y = 3
 sub initialize
     x = cube(y)
 endsub
 sub terminate
     x = cube(y)
 endsub
 sub cube(n) returns(a)
     a = n*n*n
 endsub

Although SL fades the boundary between SIMION segments and subroutines, a few technical differences remain. Mainly, a segment or subroutine cannot call another segment.

 sub initialize      # a standard "segment" subroutine
     print("init")
 endsub
 sub terminate       # a standard "segment" subroutine
     test()          # ok
     initialize()    # invalid! not allowed
 endsub
 sub test            # a custom subroutine
     print("test")
     initialize()    # invalid! not allowed
 endsub

Limitation: custom subroutines can take and return no more than eight parameters at a time (a largely SIMION-imposed limitation).

Built-in Subroutines

The following subroutines are built into SIMION. Their behavior in SL is very similar to their behavior in PRG code except that they are accessed using the simplified SL notation. For details on these subroutines, refer to the SIMION manual (p. I-7).

pow10(a) returns(b)
10 raised to the power a. (same as ``10^X'' PRG command)

pa_coords_to_array_coords(x,y,z) returns(x,y,z)
convert volume coordinates (grid units) to actual coordinates (grid units).

azimuth_rotate(angle,x,y,z) returns(x,y,z)
rotate 3D vector in azimuth direction (around y axis).

degrees(angle) returns(angle)
convert radians to degrees (same as ``>DEG'' PRG command)

elevation_rotate(angle,x,y,z) returns(x,y,z)
rotate 3D vector in elevation direction (around z axis).

speed_to_ke(speed, mass) returns(ke)
convert speed and mass to a kinetic energy value (with relativistic correction). (same as ``>KE'' PRG command)

rect_to_polar(x,y) returns(radius,theta)
convert 2D rectangular coordinates to polar coordinates.

rect3d_to_polar3d(x,y,z) returns(r,azimuth,elevation)
convert 3D rectangular coordinates to polar coordinates.

wb_coords_to_pa_coords(x,y,z) returns(x,y,z)
convert workbench coordinates (mm) to potential arary volume coordinates (gu).

wb_orient_to_pa_orient(x,y,z) returns(x,y,z)
convert workbench orientation to potential array volume orientation.

polar_to_rect(radius,theta) returns(x,y)
convert 2D polar coordinates to 2D rectangular coordinates.

polar3d_to_rect3d(radius,azimuth,elevation) returns(x,y,z)
convert 3D polar coordinates to 3D rectangular coordinates.

radians(angle) returns(angle)
convert angle from degrees to radians. (same as ``>RAD'' PRG command)

ke_to_speed(ke,mass) returns(speed)
convert kinetic energy and mass to a speed value (with relativistic correction). (same as ``>SPD'' PRG command)

pa_coords_to_wb_coords(x,y,z) returns(x,y,z)
convert potential array volume coordinates to workbench coordinates.

pa_orient_to_wb_orient(x,y,z) returns(x,y,z)
convert potential array volume orientation to workbench orientation.

abs(a) returns(b)
compute absolute value of a.

acos(a) returns(b)
computes arccos of a.

array_load(array_name, path)
load array from file.
 array_load(v, "myarray.txt")

array_save(array_name, path)
save array to file.
 array_save(v, "myarray.txt")

asin(a) returns(b)
compute arcsin of a.

atan(a) returns(b)
compute arctan of a.

beep()
make beep sound.

click()
make click sound

cos(a) returns(b)
compute cosine of a

exp(a) returns(b)
compute e raised to the a, where e=2.718281....

frac(a) returns(b)
return fractional part of a.

int(a) returns(b)
return integer part of a.

key() returns(b)
returns key code if available, else 0

ln(a) returns(b)
compute natural log of a.

log(a) returns(b)
compute base-10 log of a.

mark()
mark current ion's position on display.

print(``...'', ...)
print to screen/file (see usage above). This is synonymous with ``mess''.

nint(a) returns(b)
compute nearest integer of a.

nop()
do nothing (except kill time)

run_stop()
halt execution while waiting for user input. (same as ``R/S'' PRG command)

rand()
compute random number between 0 and 1.

redraw_screen()
redraw current view.

seed(a)
set new seed for the random number generator.

sin(a) returns(b)
compute sine of a.

sqrt(a) returns(b)
compute square root of a.

tan(a) returns(b)
compute tangent of a.

Utility Subroutines

SL supports a number of built-in utility functions in addition to the functions supported by the PRG language.

min(a,b) returns(c) - return the minimum of a and b.
Example:
 x = min(3,5)    # x set to 3

max(a,b) returns(c) - return the maximum of a and b
Example:
 x = max(3,5)    # x set to 5

floor(a) returns(b) - return the floor of a (i.e. smallest integer no greater than a)
Example:
 print("#,#,#,#,#,#,#", floor(2,1.9,1.1,0,-0.1,-0.9,-1))

outputs ``2,1,1,0,-1,-1,-1''. Compare this to int(x), which would output ``2,1,1,0,0,0,-1''.

ceil(x) - return the ceiling of x (i.e. smallest integer no less than x)
Example:
 print("#,#,#,#,#,#,#", ceil(2,1.9,1.1,0,-0.1,-0.9,-1))

outputs ``2,2,2,0,0,0,-1''.

Import/Include Mechanism

You can import symbols from one SL file into another SL file by using an ``import'' statement:

 # first.sl
 import "second.sl"
 sub initialize
    x = 5
    test()
 endsub

where the file ``second.sl'' might contain

 # second.sl
 adjustable x
 sub test
     print("#", x)
 endsub

Importing can be useful when files become large or when multiple files need to share the same code.

The path specified in the import statement is a relative path with respect to the file containing the import statement. So, if second.sl was actually in the parent directory of first.sl, you would instead do

 import "../second.sl"

Languages comparison note: The import mechanism is similar in purpose to the import statement in other programming languages (e.g. Java/Python). It is also similar to a lesser degree to the ``include'' statement in C/C++ but is more intelligent. ``include'' statements are typically implemented by a preprocessor that is run before compilation, while ``import'' statements are typically built into the language itself.

Advanced Concepts: Remotely calling C++ code:

During the simulation, SL code can call a subroutine defined in a remotely running C++ program (other languages may soon be supported as well) provide you declare the subroutine using the ``declaresub'' statement:

 declaresub change_velocity(ionnum, tof, vx, vy) ...
     returns(vx, vy) remote(1)

 declaresub get_color(x, y, z) ...
     returns(color) remote(2)

 sub other_actions
     (ion_vx_mm, ion_vy_mm) = change_velocity(ion_number,
         ion_time_of_flight, ion_vx_mm, ion_vy_mm)
     color = get_color(ion_px_gu, ion_py_gu, ion_pz_gu)
 endsub

In the above, the subroutines ``change_velocity'' and ``get_color'' are implemented as C++ code in a separate program created by you. The declaresub statements identify the number of input parameters, number of return values, and something called a ``remote id'' to be explained here. Since your custom program in C++ might define multiple functions, and SL does not know the names of your C++ function, there must be some way for SL to distinguish between these C++ functions. This is what the remote ID does. In your C++ program, you assign a remote ID (an integer) to each remotely callable C++ function. Then you specify those remote IDs in the corresponding ``declaresub'' statements in your SL program.

There may be a variable number of input parameters and return values, just as in non-remote subroutines defined with the ``sub'' statement. Input parameters are sent to the remote program. Return values are filled with data received from the remote program.

Warning: the remote call adds interprocess communication overhead and will slow your simulation down. Therefore, it is preferred that you use remote calls sparingly and instead write most your code in SL. Calling remote code only a few times during the simulation (e.g. at the ``terminate'' segment``) will have little or no noticable effect on performance. However, calling remote code at each time step (or multiple times per timestep, such as in the ''fast_adjust`` segment) will have a moderate effect (10-100x slowdown).

Writing C++ code that can be called by SIMION is explained in a separate article in the SL Remote documentation, but basically the C++ would look like this:

 #include <simion/remote.h>
 // uncomment below line or link in SL remoting implementation
 //#include <simion/remote.cpp>
 #include <iostream>
 
 using namespace std;
 using namespace simion;
 
 void change_velocity(
     double ion_num, double tof, double vx, double vy,
     double& vx_out, double vy_out&)
 { 
     vx_out = vx * ion_num + tof;
     vy_out = vy * ion_num + tof;
     cout << "velocity changed" << endl;
 }
 
 void get_color(
     double x, double y, double z, double& color)
 {
     color = (x + y + z) % 16;
 }
 
 int main()
 {
     // create remote object
     SLRemote remote;
 
     // register remotely callable C++ functions 
     remote.handler(1, slremote_handler(change_velocity));
     remote.handler(2, slremote_handler(get_color));
 
     // wait and handle remote calls 
     remote.run();
 
     remote.wait_for_run_complete();
     return 0;
 }

Advanced Concepts: Embedded Perl

This section is for more advanced users familiar with the Perl programming language and is not needed to use SL.

You can embed Perl code into an SL program by proceeding such code with the % character as in this example:

 % sub if_test
 % {
 %    my($id, $test) = @_;
 %    my $code = <<TEXT;
 %if $test
 %    num_good = num_good + 1
 %else
 %    print("[$id] fail")
 %endif
 %num_tests = num_tests + 1
 %TEXT
 %    return $code;
 % }
 %
 
 sub terminate
     num_good = 0
     num_tests = 0
 
     % &if_test('t1', '1+3 == 2+2')
 endsub

The embedded Perl code is executed at compile time (not at runtime) and the results of each embedded code snipped is inserted as a string into the SL program. Therefore, the above code is transformed into the following before compilation into SIMION opcodes:

 sub terminate
     num_good = 0
     num_tests = 0

     if 1+3 == 2+2
     num_good = num_good + 1
 else
     print("[t1] fail")
 endif
 num_tests = num_tests + 1
 endsub

One way we have used this, as hinted above, is to automate the creation of some of the regression tests on the compiler. You may find other uses of it as well (e.g. use it as a macro language).

Since the embedded notation (%) can be ugly, you may prefer importing most of your Perl code from a separate file as such:

 % require 'tests.pl'; undef

The special ``undef'' is required since SL by default inserts the result of each embedded code snipped as a string into your SL program. Since this is not desired in this case, we append an undef to make the snippet evaluate to nothing.

Beware that (%) lines next to each other merge and are evaluated together. The following are not the same:

 % 1;
 % 2;

v.s.

 % 1;
 % 2;

The first case is one snippet of code that evaluates to 2. The second case is two separate pieces of code that evaluate to 1 and 2 respectively.

SIMION Opcodes - SL Cross-Reference

Many of the built-in functions and statements in PRG code and SL are analogous. A few differences are mentioned below. Although PRG typically recognizes two names (a short form and a long form) for each function, SL typically only allows one cannonical name as shown in the list of Built-in Subroutines above. Names are kept alphanumeric and as short as is sufficiently unambiguous and readable.

Superceded opcodes

Some PRG opcodes are no longer relevant and have no exact replacement function in SL since SL syntax provides a much cleaner mechanism for achieving the same functionality.

Printing operations: MESS
MESS is replaced with the ``print'' command described previously.

Special arithmetic operations: 1/X and CHS (change sign)
Standard division (1 / x) and unary negation (- x) operations may be used instead.

Variable declaration/definition: DEFA, DEFS, ADEFA, ADEFS
These have been replaced with the ``adjustable'' and ``segment'' syntax as shown in Listing #1 above and the Arrays section. Loading an array from a file is not permitted within an array declaration (unlike in PRG using 'ADEFA myarray ;``myfile.txt'''). Instead, use a separate ``array_load'' command to initialize an array from a file. (Note: the SL syntax more resembles the C language.)

Array operations: ARCL and ALOAD
The SL array syntax, e.g. myarray[5], is used instead to read or set and array element. Refer to the Array section above.

Stack operations: ENTR, RCL, RLDN, RLUP, STO, X><Y
SL programs do not expose the stack to the programmer, so these stack operations are no longer necessary.

Code jumping: GSB, GTO, LBL, RTN, SEG, EXIT
GTO and LBL are not supported in SL as they defeat the purpose of structured programming. However, limitations in structured programming sometimes necessitate these. GSB and RTN have been replaced with the custom subroutine syntax as described in the Custom Subroutines section. SEG is no longer needed, as SL makes little distinction between custom subroutines and segments. In SL, segments are defined using the custom subroutine syntax. In SL, ``EXIT'' is a statement that terminates the current segment.

Conditionals: X=Y, X=0, X>Y, ...
These have been replaced with the if statement and conditional operators mentioned previously. Special note: Although X=Y uses a single equal sign, the equality operator in the if statement uses two equal signs:
 if x==y
     print("they are equal")
 endif

As in many programming languages, this distinguishes the equality operator from the assignment statement (which is a very different thing:

 x = y

Getting More Information

For additional information on the SL language, refer to the SL example programs.

Please report any errors/comments regarding this web page:
  Name/e-mail/phone (optional):
 
The SIMION SL Toolkit™ and documentation is (c) 2003-2004 Scientific Instrument Services, Inc. All Rights Reserved.