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

David Manura, Scientific Instrument Services, Inc. November 2003. Updated $Date: 2004/07/14 20:48:32 $. ## AbstractSIMION Potential arrays files (PA/PA#) can be defined using the SIMION Modify GUI or, for more complicated tasks, GEM files or the CAD import features of SL Tools. However, sometimes even these methods are too limited, such as when you wish to create or modify a geometry according to a mathemetical equation or arbitrary logic. In such cases, it it useful to create the PA files directly using a general purpose programming language. This article explains how to do so. Specifically, we show how to generate a swirl-like shape using the C++, Python, and Perl programming languages. To simplify this task, the examples make use of the SL Libraries from the SIMION SL Toolkit. ## ProblemOur task is to generate a potential array of the following shape: |

Although the SIMION GUI and GEM files provide means to generate
surfaces of revolution, this shape is not a simple surface of revolution
but rather can be described a rectangular prism revolved around an axis while
*simultaneously* moving the prism uniformly along the axis of
revolution. As such, this shape cannot be easily generated using the
various GEM file primitives (one could define each 3D pixel
individually, but that is not easy to do by hand). You might alternately
describe this shape as a sinusoidal projected onto the lateral side
of the cylinder. However, GEM files don't provide intrinsic support
for sinusoidal shapes either (rather, ellipses, lines, hyperbolas, etc.).
You might be able to generate the above geometry in a CAD program,
but we will not examime that possibility in this article.

After exhausting the above possibilities, the solution is to generate the PA file directly during using a general programming language to provide full flexibility. The easy way to do this is to use the C++, Python, or Perl classes in the SL Libraries, which come bundled with the SIMION SL Toolkit. The SL Libraries already implement for you the functions necessary to read and write PA files and easilly perform a variety of other manipulations on PA files as well. If you wish to use another programming language not supported, and you are familiar with binary file I/O, you can alternately write the PA files yourself and skip the SL Libraries. PA files are in a binary file format described on page D-5 of the SIMION 7.0 manual. This article takes the former approach, but you can still get some useful ideas out of this article if taking the latter approach.

The following sections describe generating a PA file using the C++ language, then doing the same thing using the Python, and finally doing the same using the Perl language. Although the code is not described in this article, Eric Saltzman has contributed a code sample of creating a somewhat similiarly shaped potential array in the Visual Basic 5.0 language (manually, not using the SL Libraries).

**Compiler/interpreter availability:**
To write potential arrays programatically, you will first need to acquire a compiler
for the particular language you want to program in.
Python
compilers and Perl
compilers are freely available,
while both free C++ compilers (Borland,
free version and g++ (available in Cygwin)) and commerical C++ compilers
(Microsoft Visual
C++) are available. The SIS SL compiler is itself bundled with
a copy of Perl.

## The C++ solution

The below code is the solution is C++.

/** * swirl_test.cpp * This generates a potential array file describing a swirl-like * shape (e.g. a ribbon wrapped around a cylinder). * * David Manura, Scientific Instrument Services, Inc. 2003-11 */ #include <iostream> #include <cmath> #include <simion/pa.h> // SL potential array class //#include <simion/pa.cpp> using namespace std; using namespace simion; const double pi = 3.141592; int main() { cout << "Generating swirl.pa# file....\n"; // create array in memory PA pa(PAArgs().nx(100).ny(100).nz(100)); // iterate over all points for(int x = 0; x < pa.nx(); x++) { for(int y = 0; y < pa.ny(); y++) { for(int z = 0; z < pa.nz(); z++) { //-- compute point info // compute polar coordinates int dx = x - 50; int dy = y - 50; double radius = sqrt((double)(dx * dx + dy * dy)); double theta = atan2((double)dy, (double)dx); // -PI..PI double omega = pi + theta + (double)z/5; // wrap omega to range 0..2*PI while(omega >= 2*pi) omega -= 2*pi; bool is_electrode = (radius > 30 && radius < 35 && omega < 2); double voltage = 1; // set point value if(is_electrode) { pa.point(x, y, z, true, voltage); } }}} // end loops // save to file pa.save("swirl.pa#"); cout << "done\n"; return 0; }

A brief description of the code follows.

First we deal with the specifics of C++. We include standard C++ libraries ("iostream" for printing to the display console and "math" for trigonometric and other math operations). We then include the "pa.h" header file. This provides PA class from the SL Libraries. When you compile, you will also need to link in the pa.cpp file's object code, which implements this class described in pa.h, but if you have difficulty doing this, you can simply include it into your main program by uncommenting the #include "pa.cpp" line. As in most modern C++ programs, we use the standard (std) namespace, but we also use the simion namespace. (No need to ask why--it just works--but be careful if you have a very old C++ compiler, which will likely choke on this namespace construct. Modern compilers such as Microsoft Visual C++ 6.0 (and above) and g++ work fine with the PA class, and recent version of Borland C++ should work too.) We also define the constant PI.

Inside of the main loop, we display a message to the screen ("Generating swirl.pa# file..."). Then we construct a new potential array object in memory (pa) and initialize its array to a size of 100 x 100 x 100 grid units. By default, the array is assumed to be a electrostatic (not magnetic) array of planar symmetry and no mirroring. If this were not the case, you would have to specify these parameters as well, such as

pa.field(MAGNETIC); pa.symmetry(CYLINDRICAL); pa.mirror_y(true);

to change pa to be a magnetic array with MIRROR-Y cylindrical symmetry. These can also be placed in the constructor (where y mirroring is implicit from cylindrical symmetry):

PA pa(PAArgs().nx(100).ny(100).nz(100). field_type(MAGNETIC).symmetry(CYLINDRICAL));

Next, we traverse over all points in the array, and at each point,
we appropriately set its potential and electrode type (electrode
v.s. non-electrode). The series of for loops traverses each point in
the volume such that inside the loop, (x, y, z) represents the grid
coordinates of the current point. So, the question now is, given
point (x, y, z), how do we mathematically describe whether that point
is an electrode or not an electrode? In words, we want to include in
our electrode all points that are within a certain radius of a line
through the center of the potential array. This would generate a
cylinder. However, we further want to restrict the inclusion volume
to only *some* of the points on that cylinder (i.e. those points
touching a ribbon wrapped around the cylinder).

In order to achieve the first part, of finding points on the cylinder, we first transform (x, y, z) into a cylindrical coordinates (radius, theta, z), where the origin is taken at the potential array center x=50, y=50. That's what the dx, dy, sqrt, and atan2 statements above do. (As a side note, atan2 is a special version of the arctangent. It takes two parameters, x and y, and returns the angle that (x,y) makes with respect to the origin. If we instead used the usual "arctan" function, i.e. +-atan(y/x), we would get two angles, and if would not be immediately clear which one to use. Many programming languages besides C++ also provide this atan2 function.)

Now, to only keep the points touching the ribbon on the cylinder, we use the statments involving omega above. omega is a value that increases both as theta increases and as z increases. So, the set of points corresponding to a certain value of omega, will resemble a string (not a ribbon) wrapped around a cylinder. To get a ribbon, we want limit omega to not just a single value but to certain ranges. To do so, we "wrap" omega (almost like performing a modulus but using real numbers) onto the interval (0, 2*PI] and exclude points above a certain value of omega. We now have determined whether the point (x, y, z) is an electrode.

If we determine that (x, y, z) is on an electrode, we mark it as an electrode (true) with a certain voltage (we use 1V here since this will be a fast adjustable array). This is done using the "point" function, which simultaneously sets the electrode status and potential at the given point (x, y, z). For additional methods you can call on the PA object, see the SL Libraries for C++ API.

Finally, after setting all points, we save the PA. You can now import the PA file into SIMION. The generated swirl.pa# file (zipped) is provided here for reference.

For additional details see the SL Libraries for C++ API.

## The Python Solution

The Python solution is in many ways similar (in concept) to the C++ solution. The code is given below.

# swirl.py # Generates a swirl-like SIMION potential array # (like a ribbon wrapped around a cylinder) # David Manura, Scientific Instrument Services, Inc. 2003-11. from SIMION.PA import * from math import * print "Generating swirl.pa# file....\n"; # create new potential array pa = PA(nx=100, ny=100, nz=100) # iterate over all points for x in range(0, pa.nx()): for y in range(0, pa.ny()): for z in range(0, pa.nz()): # compute polar coordinates dx = x - 50 dy = y - 50 radius = sqrt(dx * dx + dy * dy) if dx == 0 and dy == 0: # atan2 would fail on this theta = 0 else: theta = atan2(dy, dx); # -PI..PI # this is what generates the rotation along the axis. omega = pi + theta + z/5.0 # wrap around omega to range 0..2*PI while omega >= 2*pi: omega -= 2*pi # compute point value is_electrode = (radius > 30 and radius < 35 and omega < 2) voltage = 1 # set point value if is_electrode: pa.point(x, y, z, 1, voltage) #-- end loop # write file pa.save("swirl.pa#") print "done"

For additional details, see the SL Libraries for Python reference.

## The Perl Solution

The Perl solution is also in many ways similar (in concept) to the C++ solution as well. The code is given below.

# swirl.pl # Generates a swirl-like SIMION potential array # (like a ribbon wrapped around a cylinder) # David Manura, Scientific Instrument Services, Inc. 2003-11. use strict; use POSIX; use SIMION::PA; # potential array library my $pi = 3.141592; print "Generating swirl.pa# file....\n"; # create new potential array my $pa = new SIMION::PA(nx => 100, ny => 100, nz => 100); # iterate over all points for my $x (0..$pa->nx()-1) { for my $y (0..$pa->ny()-1) { for my $z (0..$pa->nz()-1) { # compute polar coordinates my $dx = $x - 50; my $dy = $y - 50; my $radius = sqrt($dx * $dx + $dy * $dy); my $theta = atan2($dy, $dx); # -PI..PI # this is what generates the rotation along the axis. my $omega = $pi + $theta + $z/5; # wrap around omega to range 0..2*PI $omega -= 2*$pi while $omega >= 2*$pi; # compute point value my $is_electrode = ($radius > 30 && $radius < 35 && $omega < 2); my $voltage = 1; # set point value if($is_electrode) { $pa->point($x, $y, $z, 1, $voltage); } }}} # end loop # write file $pa->save("swirl.pa#");

For additional details, see the SL Libraries for Perl reference.

## Conclusion

Various ways to create SIMION potential arrays using the SL Libraries have been presented. Which language you use largely depends on which language you are most familiar with. At the cost of some CPU and memory efficiency, Python generally has the simplest and most beginner-friendly syntax, but all presented languages work adequately for this purpose, and there are many reasons you may have to use any one of the above languages.