Manipulating SIMION Potential Arrays Programmatically

Abstract

SIMION 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, Perl, and Lua programming languages. To simplify this task, the examples make use of the SL Libraries from SIMION 8 (or the SL Toolkit) or the simion.pas Lua library in SIMION 8.1 (or 8.0.4-earlyaccess).

Problem

Our task is to generate a potential array of the following shape:

helix shaped electrode viewed in SIMION

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 SIMION 8 (previously with the 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 in Appendix F.5 of the SIMION 8.0 manual (or 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 (http:resources/sllibrariestut/spiral-vb5.zip) 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 interpreters and Perl interpreters are freely available, while both free and commercial C++ compilers are available (including Microsoft Visual C++ Express Edition and g++ (also included in Cygwin). (The SL Toolkit, though not SIMION 8.0, bundled a copy of Perl.)

There is also an experimental Lua version of doing this. The Lua version has some advantages because it runs inside the SIMION process and requires no additional compiler. The Lua interface to potential arrays is in SIMION 8.1. For details on the Lua version see simion.pas.

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, Adaptas Solutions, LLC 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>`http:cpp/index.html <http:cpp/index.html>`_.

Finally, after setting all points, we save the PA. You can now import the PA file into SIMION. The generated <swirl.pa#>`http:resources/sllibrariestut/swirl.pa%23.zip <http:resources/sllibrariestut/swirl.pa%23.zip>`_ file (zipped) is provided here for reference.

For additional details see the simion::PA - PA Library in C++ reference.

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, Adaptas Solutions, LLC 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 SIMION.PA - PA Library in 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, Adaptas Solutions, LLC 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 SIMION::PA - PA Library in Perl reference.

The Lua Solution

The following solution in Lua will require SIMION 8.1. Create the below swirl.lua file, click the “Run Lua Program” button on the SIMION main screen and select the swirl.lua file. The potential array will be created in memory, and you can view it in Modify or View.

-- swirl.lua
-- Generates a swirl-like SIMION potential array
--   (like a ribbon wrapped around a cylinder)
-- David Manura, Adaptas Solutions, LLC 2007-10.

print "Generating swirl.pa-- file....\n";

-- create new potential array
local pa = simion.pas:open()
pa.symmetry = 'planar'
pa:size(100,100,100)

-- iterate over all points
for x = 0, pa.nx-1 do
for y = 0, pa.ny-1 do
for z = 0, pa.nz-1 do
    -- compute polar coordinates
    local dx = x - 50
    local dy = y - 50
    local radius = sqrt(dx * dx + dy * dy)

    local theta
    if dx == 0 and dy == 0 then -- atan2 would fail on this
        theta = 0
    else
        theta = atan2(dy, dx);  -- -PI..PI
    end

    -- this is what generates the rotation along the axis.
    omega = math.pi + theta + z/5.0
    -- wrap around omega to range 0..2*PI
    while omega >= 2*math.pi do
        omega = omega - 2*math.pi
    end

    -- compute point value
    local is_electrode = (radius > 30 and radius < 35 and omega < 2)
    local voltage = 1

    -- set point value
    if is_electrode then
        pa:point(x, y, z, voltage,true)
    end
end
end
end

-- write file
pa:save("swirl.pa#")

print "done"

For additional details, see the simion.pas 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, Lua and Python generally have the simplest and most beginner-friendly syntax, and the Lua version (in SIMION 8.1/8.0.4-earlyaccess) is the simplest because there is no compiler setup and it runs inside of SIMION, 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.