FIRM3D (Fast Ion Reduced Models in 3D) is a software suite for modeling of energetic particle dynamics in 3D magnetic fields. The guiding center equations of motion are integrated in magnetic fields represented in Boozer coordinates, including VMEC equilibria and Alfvén eigenmodes from AE3D or FAR3D. The core routines are based on SIMSOPT, but have been extended to include additional physics and diagnostics that are not typically required in the optimization context. This standalone framework enables more modular development of FIRM3D with minimal dependencies.
📖 Documentation: Read the Docs | 📚 Examples: See the examples/ directory
All dependencies can be installed inside a conda environment using the provided install bash script located at install/macOS/install_firm3d_osx.sh.
All dependencies can be installed inside a conda environment using the provided install bash script located at install/perlmutter/install_firm3d_perlmutter.sh.
The equilibrium magnetic field in Boozer coordinates, an instance of BoozerMagneticField, can be represented using a simple analytic model, a radial interpolant of a booz_xform equilibrium , or a 3D interpolant.
An important attribute of BoozerMagneticField classes is field_type, which can be vac (vacuum field), no_k (does not retain the radial covariatn component), or an empty string (no assumptions made). By default, the field_type will determine the mode used for guiding center tracing, gc_vac, gc_noK, or gc.
Computes a BoozerMagneticField based on a first-order expansion in
distance from the magnetic axis (Landreman & Sengupta, Journal of Plasma
Physics 2018). A possibility to include a QS-breaking perturbation is added, so the magnetic field strength is expressed as,
the covariant components of equilibrium field are,
and the rotational transform is,
While formally
The magnetic field can be computed at any point in Boozer coordinates using radial spline interpolation (scipy.interpolate.make_interp_spline) and an inverse Fourier transform in the two angles. While the Fourier representation is more accurate, typically InterpolatedBoozerField is used inside the tracing loop due to its efficiency. If given a VMEC output file, performs a Boozer coordinate transformation using booz_xform. If given a booz_xform output file, the Boozer transformation must be performed with all surfaces on the VMEC half grid, and with phip, chi, pres, and phi saved in the file.
Field evaluations are parallelized over the number of Fourier harmonics over CPUs, given the communicator comm. In addition, the evaluations are parallelized over threads with OpenMP. Because the guiding center tracing routines
are also parallelized over CPUs with MPI, we don't recommend passing comm to BoozerRadialInterpolant if it is
being passed to a tracing routine.
As stated above, the booz_xform equilibrium must be performed with all surfaces on the VMEC half grid, and with phip, chi, pres, and phi saved in the file. This can be done using the C++ implementation with the main branch, by passing flux=True to read_wout():
import booz_xform as bx
b = bx.Booz_xform()
b.read_wout(wout_filename,True)
b.mboz = mboz
b.nboz = nboz
b.run()
b.write_boozmn(boozmn_filename)
Equilibria produced with the STELLOPT implementation can also be used.
This field takes an existing BoozerMagneticField instance, such as BoozerRadialInterpolant, and interpolates it on a regular grid in
Given an equilibrium field
The parameter
In the ShearAlfvenWave classes, the equilibrium field is prescribed as a BoozerMagneticField class in addition to input parameters determining
This class initializes a Shear Alfvén Wave with a scalar potential of the form:
where
Class representing a superposition of multiple Shear Alfvén Waves (SAWs).
This class models the superposition of multiple Shear Alfvén waves, combining their scalar potentials
The superposition of waves is initialized with a base wave, which defines the reference equilibrium field
See Paul et al., JPP (2023; 89(5):905890515. doi:10.1017/S0022377823001095) for more details.
Guiding center integration in Boozer coordinates is performed using equations of motion obtained from the Littlejohn Lagrangian,
where
See R. White, Theory of Tokamak Plasmas, Sec. 3.2.
The trajectory information is saved as
Various integrators, equations of motion, and solver options are detailed below.
The primary routine for unperturbed guiding center integration is trace_particles_boozer.
In the case of mode='gc_vac' we solve the guiding center equations under the vacuum assumption, i.e
where
In the case of mode='gc' we solve the general guiding center equations for an MHD equilibrium:
where primes indicate differentiation wrt mod='gc_noK', the above equations are used with
In the case of mode='gc_vac' we solve the guiding center equations under the vacuum assumption, i.e.
where
In the case of mode='gc' we solve the general guiding center equations for an MHD equilibrium:
Guiding center integration is continued until the maximum integration time, tmax, is reached, or until one of the StoppingCriteria is hit. Stopping criteria include:
-
MaxToroidalFluxStoppingCriterion: stop when trajectory reaches a maximum value of normalized toroidal flux (e.g.,$s=1$ indicates the plasma boundary) -
MinToroidalFluxStoppignCriterion: stop when trajectory reaches a minimum value of normalized toroidal flux. Sometimes a point close to the axis, e.g.$s = 10^{-3}$ , is chosen to avoid numerical issues associated with the coordinate singularity. -
ZetaStoppingCriterion: stop when the toroidal angle reaches a given value (modulus$2\pi$ ). -
VparStoppingCriterion: stop when the parallel velocity reaches a given value. For example, can be used to terminate tracing when a particle mirrors. -
ToroidalTransitStoppingCriterion: stop when the toroidal angle increases by an integer multiple of$2\pi$ . Useful for resonance detection. -
IterationStoppingCriterion: stop when a number of iterations is reached. -
StepSizeStoppingCriterion: stop when the step size gets too small. When using adaptive timestepping, can avoid particles getting "stuck" due to small step size.
There are two ways the trajectory information can be saved: by recording "hits" of user-defined coordinate planes (e.g., Poincaré sections), or by recording uniform time intervals of the trajectory. The routines trace_particles_boozer or trace_particles_boozer_perturbed return this information in the tuple (res_tys,res_hits).
- If
forget_exact_path=False, the parameterdt_savedetermines the time interval for trajectory saving. (Note that if this parameter is made too small, one may run into memory issues.) This trajectory information is returned inres_tys, which is a list (length = number of particles) of numpy arrays with shape(nsave,5). Herensaveis the number of timesteps saved. Each row contains the time and the state,[t, s, theta, zeta, v_par]. Ifforget_exact_path=True, only the state at the initial and final time will be returned. - The "hits" are defined through the input lists
zetas,omegas,vpars. Ifvparsis specified, the trajectory will be recorded when the parallel velocity hits a given value. For example, the Poincaré map for trapped particles is defined by recording the points with$v_{||} = 0$ . Ifzetasis specified, the trajectory will be recorded when$\zeta - \omega t$ hits the values given in thezetasarray, with the frequency$\omega$ given by theomegasarray. Thezetasandomegaslists must have same length. Ifomegasis not specified, it defaults to zeros. This feature is useful for defining the Poincaré map for passing particles (with or without a single-harmonic shear Alfvén wave). The hits are returned inres_hits, which is a list (length = number of particles) of numpy arrays with shape(nhits,6), wherenhitsis the number of hits of a coordinate plane or stopping criteria. Each row or the array contains[time] + [idx] + state, whereidxtells us which of the hit planes or stopping criteria was hit. Ifidx>=0andidx<len(zetas), then thezetas[idx]plane was hit. Ifidx>=len(zetas), then thevpars[idx-len(zetas)]plane was hit. Ifidx<0, thenstopping_criteria[int(-idx)-1]was hit. The state vector is[t, s, theta, zeta, v_par].
The coordinate singularity at the magnetic axis can be handled in several ways using the keyword argument axis passed to
trace_particles_boozer and trace_particles_boozer_perturbed.
- If
axis=0, the trajectory will be integrated in standard Boozer coordinates$(s,\theta,\zeta)$ . If this is used, it is recommended that one passes aMinToroidalFluxStoppingCriterionto prevent particles from passing to$s < 0$ . - If
axis=1, the trajectory will be integrated in the pseudo-Cartesian coordinates$(\sqrt{s}\cos(\theta),\sqrt{s}\sin(\theta),\zeta)$ , but all trajectory information will be saved in the standard Boozer coordinates$(s,\theta,\zeta)$ . This option prevents particles from passing to$s < 0$ . Because the equations of motion are mapped form$(s,\theta,\zeta)$ to$(\sqrt{s}\cos(\theta),\sqrt{s},\sin(\theta),\zeta)$ , a division by$\sqrt{s}$ is performed. Thus this option may be ill-behaved near the axis. - If
axis=2, the trajectory will be integrated in the pseudo-Cartesian coordinates$(s\cos(\theta),s\sin(\theta),\zeta)$ , but all trajectory information will be saved in the standard Boozer coordinates$(s,\theta,\zeta)$ . This option prevents particles from passing to$s < 0$ . No division by$s$ is required to map to this coordinate system. This option is recommended if one would like to integrate near the magnetic axis.
By default the Runge-Kutta Dormand-Prince 5(4) method implemented in Boost is used to integrate the ODEs. Adaptive time stepping is performed to satisfy the user-prescribed relative and absolute error tolerance parameters, reltol and abstol.
If solveSympl=True in the solver_options, a symplectic solver is used with step size dt. The semi-implicit Euler scheme described inAlbert, C. G., et al. (2020). Symplectic integration with non-canonical quadrature for guiding-center orbits in magnetic confinement devices. Journal of computational physics, 403, 109065 is implemented. A root solve is performed to map from non-canonical to canonical variables, with tolerance given by roottol. If predictor_step=True, the initial guess for the next step is improved using first derivative information.