Time-Dependent Field

SIMION can handle low-frequency, time-dependent electric and magnetic fields.

The term “low-frequency” refers to frequencies that are low enough such that radiation effects are not significant (the quasi-static approximation to Maxwell’s equations, in which time derivatives are omitted). Generally, for this to hold, the lengths of the electromagnetic waves (lambda = c / frequency, where c is the speed of light) should be much larger than the maximum length of your system. A number of the SIMION examples such as the quadrupole and ion trap run at frequencies on in the megahertz range. Simulating higher-frequency (radiation) effects requires different software using different methods.

Usually, fields in SIMION are generated by applying voltages to electrodes. A user program may change electrode voltages during the simulation so that the field becomes time-dependent. A user program can apply any waveform desired, including sinusoidal or stepped. Regular PA files allow all voltages to be proportionately scaled by the same factor. To independently (non-proportionately) adjust potentials on individual electrodes, you must use a fast-adjustable potential array (PA# file), which makes use of the principle of superposition in an efficient way.

A number of the SIMION examples make use of time-dependent fields in this way:

The following simple SIMION user programs applies a sinusoidal voltage to electrode #1 in a fast-adjust potential array.

-- Lua example (SIMION 8 required)
simion.workbench_program()
adjustable ac_voltage = 500  -- AC voltage
adjustable dc_voltage = 10   -- DC voltage
adjustable omega = 100       -- angular frequency (radians per microsecond)
function segment.fast_adjust()
   adj_elect01 = ac_voltage * sin(omega * ion_time_of_flight) + dc_voltage
end

Note: The sin function expects an angle in radians, not degrees, and ion_time_of_flight has units of microseconds. So

  • sin(2 * math.pi * ion_time_of_flight) would have a period of 1 microsecond.
  • sin(2 * math.pi * 1E-6 * ion_time_of_flight) would have a period of 1 second.
  • sin(2 * math.pi * 1E-6 * f * ion_time_of_flight) would have a frequency of f Hz.

Some example code for generating an arbitrary stepped or segmented voltage profile is given in Applying a non-repeating segmented waveform potential.

Another method of generating fields is by setting the field vectors directly via a user program (efield_adjust and mfield_adjust segments). These fields can be static or time-dependent. This method is typically used if an analytical expression is known for the field or if the field is defined by an external data file.

When oscillating fields, make sure that your trajectory time-steps are small enough so that your particles “see” the oscillations. There should be at least a few time-steps per field oscillation.

For full details on time-dependent fields, see the SIMION manual, specifically the sections on fast-adjustable PA# files and user programming appendix concerning fast_adjust, efield_adjust, and mfield_adjust segments. See also the examples mentioned above.

Square waveforms

Here are two different quick approaches to generating square or rectangular waveforms.

The first approach utilizes the sinusoidal function. If the sin value is above some threshold (some number between -1 and 1), then one voltage is used; otherwise, another voltage is used. Here is a 50% duty cycle:

simion.workbench_program()
adjustable hz = 1000000  -- frequency in Hz
function segment.fast_adjust()
  local f = math.sin((2*math.pi*hz*1E-6) * ion_time_of_flight)
  adj_elect01 = (f >= 0) and 200 or -300
end

Here is for an arbitrary duty cycle:

simion.workbench_program()
adjustable hz = 1000000  -- frequency in Hz
adjustable duty = 0.8
function segment.fast_adjust()
  local f = math.sin((2*math.pi*hz*1E-6) * ion_time_of_flight)
  adj_elect01 = (f >= math.cos(math.pi * duty)) and 200 or -300
end

The second approach uses the remainder (%) of the time divided by the period. If this value is above a certain value, then one voltage is used; otherwise, another voltage is used. This approach probably is more straightforward, at least as the pulse duration is defined directly.

simion.workbench_program()
adjustable hz = 1000000  -- frequency in Hz
function segment.fast_adjust()
  local period = 1E6 / hz   -- waveform period in microseconds
  local f = ion_time_of_flight % period  -- microseconds within current period
  adj_elect01 = (f >= 0.3*period) and 200 or -300
end

Please note that the above two approaches do not adjust time-steps to accurately end on voltage transitions. Rather, time-steps may straddle voltage transitions. To improve this, you would need a tstep_adjust segment, or code like SIMION Example: waveform which does it for you with care. Alternately, just use small time-steps (large T.Qual) or even dynamically reducing time-step sizes (positive T.Qual) to mitigate the error somewhat without additional code (but experiment to see to what extent varying the T.Qual value affects results to see if error is significant).

A square wave can also be approximated by combinations of sine waves, and this may be done also in practice in the electronics, like approximating a square wave with a bisinuoidal waveform (see SIMION Example: faims (bisinusoidalwavelib.lua)).

Triangular waveforms

A sawtooth waveform based on right triangles:

adj_elect01 = (ion_time_of_flight / (1E6/_frequency_hz)) % 1

The (1E6/_frequency_hz) is the period in microseconds. “% 1” is the fractional remainder when divided by one, thereby generating the sawtooth pattern.

A sawtooth waveform based on isosceles triangles:

adj_elect01 = math.abs(4 * ((ion_time_of_flight / (1E6/_frequency_hz)) % 1) - 2) - 1

Applying a non-repeating segmented waveform potential

These user programs apply a non-repeating wave-form voltage to an electrode such that the waveform consists of a series of line segments. Both SIMION 8 (Lua) and SIMION 8 (PRG) versions of the program are included.

-- SIMION 8.0 user program (Lua) to create non-repeating
-- waveform of line segments.
--
-- Note: for better accuracy, also ensure that your time-steps are
-- sufficiently small relative to the waveform.  For example, impose a
-- sufficiently large negative (or positive) trajectory quality control
-- value, enable time step markers, or add a tstep_adjust segment here.
--
-- D.Manura-2006-11--Rev.2
simion.workbench_program()

-- Here's where the waveform is defined as a list
-- of ordered pairs (t, v) of times (t) and voltages (v).
local wave = {
  {0, 20},       -- initial
  {10, 20},
  {20, 60},
  {30, 60},
  {30, 30},
  {math.huge, 30} -- infinity
}

function segment.fast_adjust()
  -- For improved accuracy, define time t at the mid-point of the
  -- time-step.  The computed potential at this time will then
  -- be the accumulated average potential over the entire time-step.
  local t = ion_time_of_flight + ion_time_step * 0.5

  -- Locate current line segment [n-1, n] of the waveform.
  local n
  for m = 1, #wave do
    n = m; if wave[m][1] > t then break end
  end

  -- Obtain points (t1,v1) and (t2,v2) of that line segment.
  local t1, t2, v1, v2 = wave[n-1][1], wave[n][1], wave[n-1][2], wave[n][2]

  -- Linearly interpolate potential over the line segment.
  local v = v1 + (t - t1) * ((v2-v1)/(t2-t1))

  -- Store voltage.
  adj_elect01 = v
end

See also SIMION Example: waveform.

Square Waveform with Gradual Rise/Decay (Sigmoid)

simion.workbench_program()

adjustable period = 2
adjustable duty = 0.5
adjustable V2 = 100
adjustable scale = 50

function voltage(time)
  local f = time % period
  local f1 = duty * 0.5
  local f2 = (duty + 1)*0.5
  if f < duty then
    return V2 * (1/(1 + exp(-(f - f1)*scale)))
  else
    return V2 * (1 - 1/(1 + exp(-(f - f2)*scale)))
  end
end

function segment.fast_adjust()
  adj_elect[1] = voltage(ion_time_of_flight)
  if ion_number == 1 then
  end
end

function segment.initialize_run()
  fh = assert(io.open("wave.csv", "w"))
  for i=0,1000 do
    local t = i/10
    fh:write(t, ",", voltage(t), "\n")
  end
  fh:close()
end

This uses a sigmoid function (Wikipedia:Sigmoid_function) in each half-period. One is centered around duty * 0.5. The other is centered around (duty + 1)*0.5 and inverted. It is all defined in terms of the variable “f” which cycles between 0 and 1, using the modulus operator (%).

Older Code Examples

SIMION 7.0 version of sinusoidal field:

; PRG example (SIMION 7 compatible)
DEFA ac_voltage 500  ; AC voltage
DEFA dc_voltage 10   ; DC voltage
DEFA omega 100       ; angular frequency (radians per microsecond)
SEG fast_adjust
  RCL omega RCL ion_time_of_flight * SIN RCL ac_voltage * RCL dc_voltage +
  STO adj_elect01

# SL example (SIMION 7+SL compatible)
adjustable ac_voltage = 500  # AC voltage
adjustable dc_voltage = 10   # DC voltage
adjustable omega = 100       # angular frequency (radians per microsecond)
sub fast_adjust
   adj_elect01 = ac_voltage * sin(omega * ion_time_of_flight) + dc_voltage
endsub

SIMION 7.0 version of waveform defined by line segments.

; SIMION 7.0 user program (PRG) to create non-repeating
; waveform of line segments.
;
; Note: for better accuracy, also ensure that your time-steps are
; sufficiently small relative to the waveform.  For example, impose a
; sufficiently large negative (or positive) trajectory quality control
; value, enable time step markers, or add a tstep_adjust segment here.
;
; D.Manura-2006-09--Rev.1

DEFS  wave_num_points 6  ; size of array
ADEFS wave_times      6  ;   note: array values in microseconds
ADEFS wave_voltages   6  ;   note: array values in volts
DEFS  is_initialized  0

SEG fast_adjust
  ; Here's where the waveform is defined as a list
  ; of ordered pairs (t, v) of times (t) and voltages (v).
  ; if not is_initialized then
  RCL is_initialized X!=0 GTO skip1
    1      STO  is_initialized
    0    1 ASTO wave_times 20  1 ASTO wave_voltages
    10   2 ASTO wave_times 20  2 ASTO wave_voltages
    20   3 ASTO wave_times 60  3 ASTO wave_voltages
    30   4 ASTO wave_times 60  4 ASTO wave_voltages
    30   5 ASTO wave_times 30  5 ASTO wave_voltages
    1E99 6 ASTO wave_times 30  6 ASTO wave_voltages ; infinity
  LBL skip1
  ; end

  ; For improved accuracy, define time t at the mid-point of the
  ; time-step.  The computed potential at this time will then
  ; be the accumulated average potential over the entire time-step.
  ; t = ion_time_of_flight + ion_time_step * 0.5
  RCL ion_time_of_flight RCL ion_time_step 0.5 * + STO t

  ; Locate current line segment [n-1, n] of the waveform.
  ; for n = 1, wave_num_points do
  1 STO n LBL loop1
  RCL wave_num_points RCL n X>Y GTO skip2
    ; if wave_times[n] > t then break
    RCL t RCL n ARCL wave_times
    X>Y GTO skip2
    ; n = n + 1
    RCL n 1 + STO n
    GTO loop1
  LBL skip2
  ; end

  ; Obtain points (t1,v1) and (t2,v2) of that line segment.
  RCL n 1 - ARCL wave_times    STO t1  ; t1 = wave_times[n-1]
  RCL n ARCL wave_times        STO t2  ; t2 = wave_times[n]
  RCL n 1 - ARCL wave_voltages STO v1  ; v1 = wave_times[n-1]
  RCL n ARCL wave_voltages     STO v2  ; v2 = wave_times[n]

  ; Linearly interpolate potential over the line segment.
  ; v = v1 + (t - t1) * ((v2-v1)/(t2-t1))
  RCL v1
  RCL t RCL t1 -
  RCL v2 RCL v1 - RCL t2 RCL t1 - /
  * +
  STO v

  ; Store voltage.
  RCL v STO adj_elect01  ; v = adj_elect01

See Also