Writing PA0 Files

Occasionally, users want to know how to generate PA0, PA1, PA2, etc. (PA0..PAn) files themselves. It is fairly straightforward to generate PA and PA# files using the SL Libraries. Generating PA0..PAn files is a little more complicated because there are more files and conventions to deal with, but it can be done with the SL Libraries as well as shown here.

Generating PA0..PAn files can be useful, for example, in importing a pre-calculated time-dependent field from an external program into SIMION. For example, let’s say you have two electrodes that you want SIMION to oscillate the potentials on. To do so, you can create the PA1 and PA2 files for each electrode. The PA1 and PA2 are just like the PA# file but with the following differences. In the PA1 file, electrode #1 is set to 10000 V, and all other electrodes are set to 0 V. In PA2 file, electrode #2 is set to 10000 V, and all other electrodes are set to 0 V. In each file, the non-electrode points are set to potential that would be generated by those electrodes (e.g. as if the file were refined in SIMION). The PA0 file can be generated as well as shown in the below code example. When SIMION fast-adjusts the PA0 file, it fills the potentials with a particular linear combination of the potentials in the PA1 and PA2 files, utilizing the principle of superposition.

The following example is a Perl program that generates PA0..PA2 files from theoretical values.

# make_quad_pa0.pl
# Perl program for generating SIMION QUAD.PA0, QUAD.PA1, and QUAD.PA2
# files directly for a 2D center section of a quadrupole.
#
# The generated files represent an ideal (hyperbolic) electrode
# quadrupole.  The generated files are not refined by SIMION; rather,
# this program directly sets the space potentials to theoretical
# values.  An interesting property of this is that the surface jags on
# the curved electrodes are not a source of error; rather, the major
# source of error is now the interpolation between grid points in
# calculating the field (that can be removed as well by applying the
# field with a user program rather than a potential array).
#
# For testing, you can use the generated files to replace the
# corresponding files in SIMION's _Quad example.  Note that the
# QUAD.PA* files in SIMION 7.0 have only Y mirroring, but the QUAD.PA*
# files generated here have both X and Y mirroring.  So, after opening
# the workbench (QUAD.IOB), you will need to change "Zwb+" from 0 to
# 7.6 mm on QUAD.PA0 so that it is properly centered.  Note that
# the original electrode used non-ideal (circular) electrodes, so the
# trajectories will differ under the new files.
#
# This program requires the SL Libraries for Perl in the SIMION SL
# Toolkit (http://www.simion.com/sl/api/perl/index.html).  Perl must
# be able to find these files.  A simple way to do this is to copy
# the "sl/lib/perl/SIMION" folder into the same folder as this program.
#
# Note: this code currently does not handle non-fast-adjustable
# electrodes (PA_ files), which would be a minor extension.
#
# Author: David Manura, Scientific Instrument Services, Inc.
#         www.simion.com, 2006-02-02/2014-11.

use strict;
use SIMION::PA;

my $scale = 1/20;  # mm per grid unit (gu), assuming 1 mm radius
                   #   circle can fit between electrodes.
my $nx = 38 + 1;   # Number of grid points in X and Y directions.
my $ny = $nx;
my $vref = 10000;  # Internal reference voltage for PA1..n files
                   #   (required by SIMION, so don't change this).


# Create the QUAD.PA1 file for the +-X pair of electrodes (#1) and the
# QUAD.PA2 file for the +-Y pair of electrodes (#2).  For each file,
# all points in the electrode represented are set to electrode points
# of voltage $vref, and all points in all other electrodes are set to
# electrode points of voltage 0 V.  All non-electrode points are set
# to potentials that would be due to the electrodes (as if this file
# were refined).
my $pa1 = SIMION::PA->new(nx => $nx, ny => $ny, mirror => 'xy');
my $pa2 = SIMION::PA->new(nx => $nx, ny => $ny, mirror => 'xy');
my @pas = ($pa1, $pa2);
my $zi = 0;
for my $n (1..2) { # for each PA
    my $pan = $pas[$n-1]; # select PA object

    # now initialize all the points ($xi, $yi).
    for my $xi (0..$nx-1) {
    for my $yi (0..$ny-1) {
        my ($x, $y) = ($xi * $scale, $yi * $scale);
        # note: 0.5 gu correction used ($scale*0.5).
        my $is_electrode1 = ($x**2 - $y**2 >= (1 - $scale*0.5)**2);
        my $is_electrode2 = ($y**2 - $x**2 >= (1 - $scale*0.5)**2);
        my $is_electrode  = $is_electrode1 || $is_electrode2;
        my $potential;
        if ($is_electrode1 && $n == 1 ||
            $is_electrode2 && $n == 2)  # is represented electrode
        {
            $potential = $vref;
        }
        elsif ($is_electrode) {  # is other electrode
            $potential = 0;
        }
        else { # is non-electrode
            # set to theoretical potentials
            my $sign = ($n == 1) ? +1 :
                       ($n == 2) ? -1 :
                       die;
            $potential = 0.5*$vref*($sign*($x**2 - $y**2) + 1);
        }
        $pan->point($xi, $yi, $zi, $is_electrode, $potential);  # set it
    }}
}
$pa1->save('quad.pa1');
$pa2->save('quad.pa2');

# Create the PA# and PA0 files from the PA1..n files.
# The PA# file is needed only for creating the PA0 file.
# In the PA# file, electrode points have potentials 1..n.
# In the PA0 file, electrode points have potential 0.
# In both files, non-electrode points have potential 0.
# Note: this code does not handle PA_ files.
my $pasharp = SIMION::PA->new(file => 'quad.pa1'); # use as template
my $pa0     = SIMION::PA->new(file => 'quad.pa1');
for my $xi (0..$nx-1) {
for my $yi (0..$ny-1) {
    my $is_electrode = 0;      # Default to non-electrode.
    my $pasharp_potential = 0; # Default to 0 V.
    for my $n (1..2) {         # Test each PA.
        my $pan = $pas[$n-1];  # Select PA.
        $is_electrode = $pan->electrode($xi,$yi,$zi);
        if ($is_electrode) {
            my $v = $pan->potential($xi,$yi,$zi);
            if ($v == $vref) {
                $pasharp_potential = $n;
                last;
            }
            else {
                $pasharp_potential = $v;
            }
        }
    }
    $pasharp->point($xi, $yi, $zi, $is_electrode, $pasharp_potential);
    $pa0->point($xi, $yi, $zi, $is_electrode, 0);
}}
$pasharp->save('quad.pa#'); # Note: not necessary, but I'll do this anyway.

# Create the PA0 file.
# The PA0 is not fast adjusted here--I could do that, but I'll let SIMION do
# that instead.
$pa0->pasharp($pasharp);  # PA# file is required for writing PA0 files.
$pa0->save('quad.pa0');

# done

Lua Version

A similar thing can be done more conveniently in Lua in SIMION 8.1.1.16 or above:

-- requires SIMION version >= 8.1.1.16.
-- D.Manura, 2013-02/2014-11.

simion.pas:close()  -- remove all PA's from RAM.

local R1 = 1

local pa = simion.pas:open()
pa:size(39,39,1)
pa.symmetry = '2dplanar[xy]'
pa.dx_mm = 0.05   -- mm/gu
pa.dy_mm = 0.05
pa:fill(function() return 0, true end) -- shortens refine time
pa:point(R1/pa.dx_mm,0,0, 1, true)  -- generates .pa1 file (electrode #1)
pa:point(0,R1/pa.dy_mm,0, 2, true)  -- generates .pa2 file (electrode #2)
pa:save'quad.pa#'
pcall(function() pa:refine{convergence=10} end)
  -- Refining generates .pa0, .pa1, .pa2 files.
  -- We don't actually care to refine though or if it
  -- fails to converge.
--pa:crop(0,0,0, pa.nx-1-extra, pa.ny-1, pa.nz-1)
  -- If the electrodes are not represented inside the PA
  -- volume, you will need to add them anyway and then crop
  -- them out to avoid a SIMION error message about
  -- "Failed loading fast adjust details from end of .PA0 file.
  --  expected electrode point for adjustable electrode."

local vref = 10000 -- Internal reference voltage for PA1..n files
                   --  (required by SIMION, so don't change this).
for n=0,2 do
  pa:load('quad.pa'..n)
  pa:fill{function(x,y,z)
    local is_electrode1 = (x^2 - y^2 >= R1^2)
    local is_electrode2 = (y^2 - x^2 >= R1^2)
    local is_electrode  = is_electrode1 or is_electrode2
    if is_electrode1 and n == 1 or
       is_electrode2 and n == 2
    then   -- is represented electrode
       return vref, true
    elseif is_electrode then  -- is other electrode
      return 0, true
    elseif n == 0 then -- non-electrode in .pa0 file
      return 0, false
    else  -- is non-electrode in other file
      -- set to theoretical potential
      local sign = (n == 1) and 1 or -1
      local potential = 0.5*vref*(sign*(x^2 - y^2)/R1^2 + 1)
      return potential, false
    end
  end, surface='fractional'}
  pa:save()
end

pa:load('quad.pa0')  -- reload base solution array

File Format

.PA0 files have the same format as .PA files (discussed in the manual “Files Used by SIMION” appendix) but with the following appended data:

n_adjustable - int (4 bytes) -
  Number of adjustable electrodes (fast scalable + fast adjustable).
  Range 1..1001 (SIMION 8.1.0); 1..128 (SIMION 8.0.0); 1..31 (SIMION 7)
voltage - double (8 bytes) -
  Voltage used for adjustable electrodes in PA? files [? >= 1].
  Normally 10000 V.
electrode[] - int (4 or 8 bytes) times nread -
  electrode[n] is the PA offset ((z*ny+y)*nx*x) of the first point
  belonging to electrode n in the PA, or -1 if electrode[n] is not
  represented in the PA.
  The size of this structure (nread) is max(i+1, 32)
    where i is the 0-based index of the n_adjustable'th entry
    that does not equal -1.
  Prior to 8.0.4-TEST5, the only element that could equal -1 was
    electrode[0], but this restriction was removed in 8.0.4-TEST5.
  Note: for large arrays with at least 2^31 points,
    each element here is 8 bytes rather than four bytes.  This is only
    supported on 64-bit versions of SIMION >= 8.1.0.
is_scalable - int (4 bytes) -
  whether PA uses extended fast scalable electrodes
  0=no,1=yes.  Omitting this (the default) and following data means no.
  Added in version 8.0.3.
  Note: values other than 0 or 1 here may indicate some other type of data
    is here from a newer version of SIMION, and SIMION >= 8.0 will
    give an error message.
values[] - double (8 bytes) times nread -
  values[n] is current adjusted voltage value of adjustable electrode n.
  values[n] is undefined if electrode[n] == -1.
  Exists only if is_scalable is 1.
  Added in version 8.0.3.