Skip to content

Superfish

superfish.Superfish

Superfish(
    automesh=None,
    problem="fish",
    use_tempdir=True,
    use_container="auto",
    container_method=None,
    interactive=False,
    workdir=None,
    verbose=True,
)

Interface to the Poisson Superfish programs.

Manages a working directory, runs the Poisson Superfish programs (natively on Windows, or through a container elsewhere), and parses their output.

Attributes:

Name Type Description
input dict

Input data with basename and automesh (lines) keys.

output dict

Parsed output, filled by :meth:load_output.

path str

Working directory where the programs run.

use_container bool

Whether commands run through a container.

container_method str or None

Selected container orchestration method.

Poisson-Superfish object

Parameters:

Name Type Description Default
automesh str

Path to an automesh input file. If given, it is loaded and the object is configured to run.

None
problem (fish, poisson)

Type of problem to run.

"fish"
use_tempdir bool

Run in a temporary directory instead of in place.

True
use_container auto or bool

Whether to run through a container. "auto" uses the native executables on Windows and a container elsewhere.

'auto'
container_method (docker, shifter, singularity)

Container orchestration method. If not given, defaults to the PYSUPERFISH_CONTAINER_METHOD environment variable; if that is also unset, the first available method is auto-detected: Singularity with an existing image, then Docker, then Shifter, then Singularity without an image.

"docker"
interactive bool

Run the container in interactive (X11) mode. macOS only.

False
workdir str

Base directory for the working directory.

None
verbose bool

Print progress messages.

True
Source code in superfish/superfish.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def __init__(
    self,
    automesh: str | None = None,
    problem: str = "fish",
    use_tempdir: bool = True,
    use_container: str | bool = "auto",
    container_method: str | None = None,
    interactive: bool = False,
    workdir: str | None = None,
    verbose: bool = True,
) -> None:
    """
    Poisson-Superfish object

    Parameters
    ----------
    automesh : str, optional
        Path to an automesh input file. If given, it is loaded and the
        object is configured to run.
    problem : {"fish", "poisson"}
        Type of problem to run.
    use_tempdir : bool
        Run in a temporary directory instead of in place.
    use_container : "auto" or bool
        Whether to run through a container. "auto" uses the native
        executables on Windows and a container elsewhere.
    container_method : {"docker", "shifter", "singularity"}, optional
        Container orchestration method. If not given, defaults to the
        PYSUPERFISH_CONTAINER_METHOD environment variable; if that is
        also unset, the first available method is auto-detected:
        Singularity with an existing image, then Docker, then Shifter,
        then Singularity without an image.
    interactive : bool
        Run the container in interactive (X11) mode. macOS only.
    workdir : str, optional
        Base directory for the working directory.
    verbose : bool
        Print progress messages.
    """
    self.configured = False
    self.problem = problem

    self.verbose = verbose
    self.use_tempdir = use_tempdir
    self.interactive = interactive
    if workdir:
        workdir = os.path.abspath(workdir)
        assert os.path.exists(workdir), f"workdir does not exist: {workdir}"
    self.workdir = workdir

    if automesh:
        self.load_input(automesh)
        self.configure()

    if container_method is None:
        container_method = self._container_method
    if container_method is None:
        container_method = self._detect_container_method()
    else:
        container_method = str(container_method).lower()
        if container_method not in self._container_commands:
            options = ", ".join(sorted(self._container_commands))
            raise ValueError(
                f"Unknown container_method {container_method!r}; "
                f"choose from: {options}"
            )
    self.container_method = container_method

    if use_container == "auto":
        if platform.system() == "Windows" and os.path.exists(
            self._windows_exe_path
        ):
            self.use_container = False
            self.vprint(f"Using executables installed in {self._windows_exe_path}")

        else:
            self.vprint(
                f"Using {self.container_method} container on {platform.system()}:"
            )
            self.vprint("    ", self.container_command)
            self.use_container = True

    else:
        self.use_container = use_container

Attributes

superfish.Superfish.automesh_name property
automesh_name

Name of the automesh input file, <basename>.AM.

superfish.Superfish.basename property
basename

Base name of the problem, from the automesh file name.

superfish.Superfish.container_command property
container_command

Command template for the selected container method, or None.

Methods:

superfish.Superfish.configure
configure()

Configure the working directory to run in.

Creates a temporary directory when use_tempdir is set; otherwise runs in workdir or in place.

Source code in superfish/superfish.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
def configure(self) -> None:
    """
    Configure the working directory to run in.

    Creates a temporary directory when ``use_tempdir`` is set;
    otherwise runs in ``workdir`` or in place.
    """

    # Set paths
    if self.use_tempdir:
        # Need to attach this to the object. Otherwise it will go out of scope.
        self.tempdir = tempfile.TemporaryDirectory(dir=self.workdir)
        self.path = self.tempdir.name

    else:
        if self.workdir:
            self.path = self.workdir
            self.tempdir = None
        else:
            # Work in place
            self.path = self.original_path

    self.vprint("Configured to run in:", self.path)

    self.configured = True
superfish.Superfish.container_run_cmd
container_run_cmd(*args)

Form the run command string for the container.

The container data should live in its /data/ folder.

Parameters:

Name Type Description Default
*args str

Program name and arguments, e.g. ("automesh", "TEST.AM").

()

Returns:

Type Description
str

The full shell command.

Raises:

Type Description
RuntimeError

If no container method is available.

Source code in superfish/superfish.py
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
def container_run_cmd(self, *args: str) -> str:
    """
    Form the run command string for the container.

    The container data should live in its /data/ folder.

    Parameters
    ----------
    *args : str
        Program name and arguments, e.g. ``("automesh", "TEST.AM")``.

    Returns
    -------
    str
        The full shell command.

    Raises
    ------
    RuntimeError
        If no container method is available.
    """

    cmds = " ".join(args)

    template = self.container_command
    if template is None:
        raise RuntimeError(
            "No container method available: "
            "docker, shifter, or singularity not found"
        )

    if self.interactive:
        assert platform.system() == "Darwin", "TODO interactive non-Darwin"
        cmd0 = "IP=$(ifconfig en0 | grep inet | awk '$1==\"inet\" {print $2}');xhost + $IP;"
        interactive_flags = "-e INTERACTIVE_FISH=1 -e DISPLAY=$IP:0"
    else:
        cmd0 = ""
        interactive_flags = ""

    cmd = template.format(
        local_path=self.path,
        image=self._container_image,
        interactive_flags=interactive_flags,
        cmds=cmds,
        singularity_image=self._singularity_image,
    )

    return cmd0 + cmd
superfish.Superfish.fieldmesh
fieldmesh(zmin=-100, zmax=100, nz=0, dz=0, rmin=0, rmax=100, nr=0, dr=0)

Interpolate the field over a grid, returning a FieldMesh.

Similar to :meth:interpolate, but input units are in meters.

Various combinations of the spacings and grid point numbers nz, dz, nr, dr can be used. If neither is specified, a default of 100 grid points will be used.

Parameters:

Name Type Description Default
zmin float

z extent of the grid, in meters.

-100
zmax float

z extent of the grid, in meters.

-100
nz int

Number of z points.

0
dz float

z spacing, in meters. Overrides zmax when given with nz.

0
rmin float

Radial extent of the grid, in meters.

0
rmax float

Radial extent of the grid, in meters.

0
nr int

Number of radius points.

0
dr float

Radial spacing, in meters. Overrides rmax when given with nr.

0

Returns:

Type Description
FieldMesh

An openPMD-beamphysics FieldMesh object.

Source code in superfish/superfish.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def fieldmesh(
    self,
    zmin: float = -100,
    zmax: float = 100,
    nz: int = 0,
    dz: float = 0,
    rmin: float = 0,
    rmax: float = 100,
    nr: int = 0,
    dr: float = 0,
) -> "FieldMesh":
    """
    Interpolate the field over a grid, returning a FieldMesh.

    Similar to :meth:`interpolate`, but input units are in meters.

    Various combinations of the spacings and grid point numbers
    ``nz``, ``dz``, ``nr``, ``dr`` can be used. If neither is
    specified, a default of 100 grid points will be used.

    Parameters
    ----------
    zmin, zmax : float
        z extent of the grid, in meters.
    nz : int
        Number of z points.
    dz : float
        z spacing, in meters. Overrides ``zmax`` when given with
        ``nz``.
    rmin, rmax : float
        Radial extent of the grid, in meters.
    nr : int
        Number of radius points.
    dr : float
        Radial spacing, in meters. Overrides ``rmax`` when given with
        ``nr``.

    Returns
    -------
    FieldMesh
        An openPMD-beamphysics FieldMesh object.
    """

    conv = self.param("CONV")
    fac = 100 / conv

    # Various input possibilities for the grid
    if dz and nz:
        zmax = zmin + (nz - 1) * dz
    elif dz and not nz:
        nz = int((zmax - zmin) / dz) + 1
        zmax = zmin + (nz - 1) * dz
    elif not dz and not nz:
        # Default
        nz = 100

    if dr and nr:
        rmax = rmin + (nr - 1) * dr
    elif dr and not nr:
        nr = int((rmax - rmin) / dr) + 1
        rmax = rmin + (nr - 1) * dr
    elif not dr and not nr:
        # Default
        nr = 100

    FM = interpolate2d(
        self,
        zmin=zmin * fac,
        zmax=zmax * fac,
        nz=nz,
        rmin=rmin * fac,
        rmax=rmax * fac,
        nr=nr,
        return_fieldmesh=True,
    )

    return FM
superfish.Superfish.interpolate
interpolate(zmin=-1000, zmax=1000, nz=100, rmin=0, rmax=0, nr=1)

Interpolate the field over a grid.

Parameters:

Name Type Description Default
zmin float

z extent of the grid, in the problem's input units.

-1000
zmax float

z extent of the grid, in the problem's input units.

-1000
nz int

Number of z points.

100
rmin float

Radial extent of the grid, in the problem's input units.

0
rmax float

Radial extent of the grid, in the problem's input units.

0
nr int

Number of radius points.

1

Returns:

Type Description
FishT7Data or PoissonT7Data

t7data dict, as returned by :func:superfish.interpolate.interpolate2d.

Source code in superfish/superfish.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def interpolate(
    self,
    zmin: float = -1000,
    zmax: float = 1000,
    nz: int = 100,
    rmin: float = 0,
    rmax: float = 0,
    nr: int = 1,
) -> FishT7Data | PoissonT7Data:
    """
    Interpolate the field over a grid.

    Parameters
    ----------
    zmin, zmax : float
        z extent of the grid, in the problem's input units.
    nz : int
        Number of z points.
    rmin, rmax : float
        Radial extent of the grid, in the problem's input units.
    nr : int
        Number of radius points.

    Returns
    -------
    FishT7Data or PoissonT7Data
        t7data dict, as returned by
        :func:`superfish.interpolate.interpolate2d`.
    """

    t7data = interpolate2d(
        self, zmin=zmin, zmax=zmax, nz=nz, rmin=rmin, rmax=rmax, nr=nr
    )

    return t7data
superfish.Superfish.load_input
load_input(input_filePath)

Load an automesh input file.

Parameters:

Name Type Description Default
input_filePath str

Path to the automesh (.AM) file.

required
Source code in superfish/superfish.py
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
def load_input(self, input_filePath: str) -> None:
    """
    Load an automesh input file.

    Parameters
    ----------
    input_filePath : str
        Path to the automesh (.AM) file.
    """
    f = os.path.abspath(input_filePath)

    # Get basename. Should be upper case to be consistent with output files (that are always upper case)
    self.original_path, fname = os.path.split(f)
    basename = os.path.splitext(fname)[0].upper()
    self.input: dict[str, Any] = {"basename": basename}

    self.input["automesh"] = parsers.parse_automesh(f)
superfish.Superfish.load_output
load_output()

Load and parse the SFO output file into .output["sfo"].

Source code in superfish/superfish.py
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
def load_output(self) -> None:
    """
    Load and parse the SFO output file into ``.output["sfo"]``.
    """

    self.output = {}

    sfofile = os.path.join(self.path, self.basename + ".SFO")

    if not os.path.exists(sfofile):
        self.vprint("Warking: no SFO file to load.")
        return

    self.output["sfo"] = parsers.parse_sfo(sfofile)

    self.vprint("Parsed output:", sfofile)
superfish.Superfish.param
param(key)

Look up a parameter from the readback in the SFO file.

Parameters:

Name Type Description Default
key str

Variable or Constant name, e.g. "CONV".

required

Returns:

Type Description
int or float

The parameter value.

Source code in superfish/superfish.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def param(self, key: str) -> int | float:
    """
    Look up a parameter from the readback in the SFO file.

    Parameters
    ----------
    key : str
        Variable or Constant name, e.g. ``"CONV"``.

    Returns
    -------
    int or float
        The parameter value.
    """
    return self.output["sfo"]["header"]["variable"][key]
superfish.Superfish.param_info
param_info(key)

Look up the description of a parameter.

Parameters:

Name Type Description Default
key str

Variable or Constant name, e.g. "CONV".

required

Returns:

Type Description
str

The parameter description.

Source code in superfish/superfish.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def param_info(self, key: str) -> str:
    """
    Look up the description of a parameter.

    Parameters
    ----------
    key : str
        Variable or Constant name, e.g. ``"CONV"``.

    Returns
    -------
    str
        The parameter description.
    """
    return self.output["sfo"]["header"]["description"][key]
superfish.Superfish.plot_wall
plot_wall(units='original', **kwargs)

Plot the problem geometry from the parsed wall segments.

Parameters:

Name Type Description Default
units (original, cm)

Units for the plot axes. "cm" converts using the problem's CONV parameter.

"original"
**kwargs Any

Passed to :func:superfish.plot.plot_wall.

{}
Source code in superfish/superfish.py
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
def plot_wall(self, units: str = "original", **kwargs: Any) -> None:
    """
    Plot the problem geometry from the parsed wall segments.

    Parameters
    ----------
    units : {"original", "cm"}
        Units for the plot axes. ``"cm"`` converts using the problem's
        CONV parameter.
    **kwargs
        Passed to :func:`superfish.plot.plot_wall`.
    """
    if units == "original":
        conv = 1
    elif units == "cm":
        conv = self.param("CONV")
    else:
        raise ValueError(f"Units must be original or cm: {units}")

    plot_wall(self.output["sfo"]["wall_segments"], conv=conv, **kwargs)
superfish.Superfish.run
run()

Write input, run the problem, and load the output.

Runs autofish for fish problems, or the automesh/poisson/sfo chain for poisson problems.

Source code in superfish/superfish.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
def run(self) -> None:
    """
    Write input, run the problem, and load the output.

    Runs ``autofish`` for fish problems, or the
    ``automesh``/``poisson``/``sfo`` chain for poisson problems.
    """

    assert self.configured, "not configured to run"

    self.write_input()

    t0 = time()

    if self.problem == "fish":
        self.run_cmd("autofish", self.automesh_name)
    else:
        self.run_cmd("automesh", self.automesh_name)
        self.run_cmd("poisson")
        self.run_cmd("sfo")

    dt = time() - t0
    self.vprint(f"Done in {dt:10.2f} seconds")

    self.load_output()
superfish.Superfish.run_cmd
run_cmd(*cmds, **kwargs)

Run a Superfish program in the working directory.

Output is appended to output.log in the working directory when running through a container.

Parameters:

Name Type Description Default
*cmds str

Program name and arguments, e.g. ("automesh", "TEST.AM").

()
**kwargs Any

Passed to subprocess.call (container) or subprocess.run (native Windows).

{}

Returns:

Type Description
int or CompletedProcess

The return code (container), or the completed process (native Windows).

Examples:

>>> sf.run_cmd("automesh", "TEST.AM", timeout=1)
Source code in superfish/superfish.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def run_cmd(self, *cmds: str, **kwargs: Any) -> int | subprocess.CompletedProcess:
    r"""
    Run a Superfish program in the working directory.

    Output is appended to ``output.log`` in the working directory when
    running through a container.

    Parameters
    ----------
    *cmds : str
        Program name and arguments, e.g. ``("automesh", "TEST.AM")``.
    **kwargs
        Passed to ``subprocess.call`` (container) or
        ``subprocess.run`` (native Windows).

    Returns
    -------
    int or subprocess.CompletedProcess
        The return code (container), or the completed process (native
        Windows).

    Examples
    --------
    >>> sf.run_cmd("automesh", "TEST.AM", timeout=1)
    """
    if self.use_container:
        cmd = self.container_run_cmd(*cmds)
    else:
        cmd = self.windows_run_cmd(*cmds)

    self.vprint(f"Running: {cmd}")

    logfile = os.path.join(self.path, "output.log")

    if self.use_container:
        # Shifter and Singularity don't need volume mounting
        if self.container_method in ("shifter", "singularity"):
            cwd = self.path
        else:
            cwd = None

        with open(logfile, "a") as output:
            P = subprocess.call(
                cmd, shell=True, stdout=output, stderr=output, cwd=cwd, **kwargs
            )
    else:
        # Windows needs this
        P = subprocess.run(cmd.split(), cwd=self.path, **kwargs)

    return P
superfish.Superfish.vprint
vprint(*args)

Print only when verbose is enabled.

Source code in superfish/superfish.py
578
579
580
581
def vprint(self, *args: Any) -> None:
    """Print only when verbose is enabled."""
    if self.verbose:
        print(*args)
superfish.Superfish.windows_run_cmd
windows_run_cmd(*args)

Form the run command string for the native Windows executables.

Parameters:

Name Type Description Default
*args str

Program name and arguments, e.g. ("automesh", "TEST.AM").

()

Returns:

Type Description
str

The full command.

Source code in superfish/superfish.py
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
def windows_run_cmd(self, *args: str) -> str:
    """
    Form the run command string for the native Windows executables.

    Parameters
    ----------
    *args : str
        Program name and arguments, e.g. ``("automesh", "TEST.AM")``.

    Returns
    -------
    str
        The full command.
    """
    cmd = os.path.join(self._windows_exe_path, args[0].upper() + ".EXE")

    assert os.path.exists(cmd), f"EXE does not exist: {cmd}"

    if len(args) > 1:
        cmd = cmd + " " + " ".join(args[1:])

    cmd = f"wine {cmd}"

    return cmd
superfish.Superfish.write_input
write_input()

Write the automesh input file from .input["automesh"].

Source code in superfish/superfish.py
568
569
570
571
572
573
574
575
576
def write_input(self) -> None:
    """
    Write the automesh input file from ``.input["automesh"]``.
    """

    file = os.path.join(self.path, self.input["basename"] + ".AM")
    with open(file, "w") as f:
        for line in self.input["automesh"]:
            f.write(line)