Normalisation conventions#

Different gyrokinetic codes use different default Normalisation conventions and pyrokinetics allows for the conversion between different conventions. Where possible variables in pyrokinetics have assigned units via the Pint python library. Currently these objects all have assigned units.

Using Units#

When loading in the Equilibrium and Kinetics objects variables will be in SI units Quantity. Objects with units must be treated consistently, meaning that it is not possible to add two Quantity objects with different Units

from pyrokinetics import Pyro, template_dir

# Equilibrium file
eq_file = template_dir / "test.geqdsk"
# Kinetics data file
kinetics_file = template_dir / "jetto.cdf"

# Load up pyro object
pyro = Pyro(
    eq_file=eq_file,
    kinetics_file=kinetics_file,
)

# Has units of  / meter ** -3
density = pyro.kinetics.species_data.electron.get_dens(0.5)
print(f"Density = {density}")

Running the above results in the following where density is a Quantity object

2.0617094330890322e+20 / meter ** 3

Using .m on a Quantity will return the magnitude of the Quantity without units. Using .to allows you to convert to another pint Unit

print(f"Electron density magnitude = {density.m}")
print(f"Electron density (/cm^3) = {density.to('cm **-3')}")
Electron density magnitude = 2.0617094330890322e+20
Electron density (/cm^3)) = 206170943308903.25 / centimeter ** 3

Reference values#

In gyrokinetics we often work with normalised quantities with a reference value. For example densities are usually defined relative to the electron density. In pyrokinetics these normalised quantities are given “units” to allow conversion between different conventions.

So any local parameters will be defined in these reference units. When creating a pyro object from a kinetics/equilibrium object then it is possible to map these values back in to SI units using to_base_units or to back to reference values

pyro.load_local(psi_n=0.5, local_geometry="Miller")

electron_density = pyro.local_species.electron.dens

print(f"Electron density (normalised) = {electron_density}")
print(f"Electron density (un-normalised) = {electron_density.to_base_units()}")

deuterium_density = pyro.local_species.deuterium.dens
print(f"Deuterium density (normalised) = {deuterium_density}")
print(f"Deuterium density (un-normalised) = {deuterium_density.to_base_units()}")
Electron density (normalised) = 1.0 nref_electron_test0000
Electron density (un-normalised) = 2.0617094330890322e+20 / meter ** 3
Deuterium density (normalised) = 0.5057294099957877 nref_electron_test0000
Deuterium density (un-normalised) = 1.0426670951788664e+20 / meter ** 3

Each code has a different default normalisation and it is possible to map from one code to another by “converting” the units. For example below we see that the collisionality has different units for different codes with different magnitude

electron_collisionality = pyro.local_species.electron.nu
print(f"Electron collisionality (Pyro units) {electron_collisionality}")
print(f"Electron collisionality (CGYRO units) {electron_collisionality.to(pyro.norms.cgyro)}")
print(f"Electron collisionality (GS2 units) {electron_collisionality.to(pyro.norms.gs2)}")
print(f"Electron collisionality (GENE units) {electron_collisionality.to(pyro.norms.gene)}")
Electron collisionality (Pyro units) 0.050877383651849475 vref_nrl_test0000 / lref_minor_radius_test0000
Electron collisionality (CGYRO units) 0.050877383651849475 vref_nrl_test0000 / lref_minor_radius_test0000
Electron collisionality (GS2 units) 0.03597574298925234 vref_most_probable_test0000 / lref_minor_radius_test0000
Electron collisionality (GENE units) 0.09411557703006325 vref_nrl_test0000 / lref_major_radius_test0000

When loading a Pyro object directly from a gyrokinetic input file, the physical reference values are often not stored. In this scenario it is only possible to convert quantities between different conventions but not back to SI units.

The following reference values are defined in pyrokinetics under pyro.norms, with each code convention being stored within that i.e. CGYRO conventions/normalisations are under pyro.norms.cgyro

Table 2 Pyrokinetic references#

Reference value

Location in Norms

Pyrokinetics convention default

\(m_{ref}\): Reference mass

pyro.norms.mref

Deuterium mass

\(n_{ref}\): Reference density

pyro.norms.nref

Electron density

\(T_{ref}\): Reference temperature

pyro.norms.tref

Electron temperature

\(v_{ref}\): Reference velocity

pyro.norms.vref

Sound speed \(c_s = \sqrt{T_e/m_D}\)

\(B_{ref}\): Reference magnetic field

pyro.norms.bref

\(B_0 = f/ R_{maj}\)

\(L_{ref}\): Reference length

pyro.norms.lref

Minor radius

\(\rho_{ref}\): Reference Larmor radius

pyro.norms.rhoref

\(c_s / \Omega_i\) where \(\Omega_i = eB_0/m_D\)

Outputs#

Pyrokinetics stores everything in its own normalisation. When reading/write GKInput and GKOutput, the data is read in and then converted into pyrokinetics normalisation. The output data is stored in the format of an xarray Dataset so to convert all of the output into a different convention do the following

from pyrokinetics import Pyro, template_dir

# Point to CGYRO input file
cgyro_template = template_dir / "outputs/CGYRO_linear/input.cgyro"

# Load in file
pyro = Pyro(gk_file=cgyro_template, gk_code="CGYRO")

# Load in CGYRO output data
pyro.load_gk_output()

# Data current in pyrokinetics units
data = pyro.gk_output

# This converts the data to CGYRO units
data.to(pyro.norms.cgyro)

Code-specific conventions#

While pyrokinetics uses a single internal normalisation, different gyrokinetic codes adopt different definitions for their reference quantities. These differences are encoded in normalisation conventions, and are used when converting quantities to or from a given code’s units.

Each convention is defined by specifying which reference quantities differ from the pyrokinetics defaults. Any reference not explicitly overridden uses the pyrokinetics convention.

The available conventions are accessible via pyro.norms:

pyro.norms.pyrokinetics
pyro.norms.cgyro
pyro.norms.gs2
pyro.norms.gene
pyro.norms.stella
pyro.norms.gx
pyro.norms.gkw
pyro.norms.tglf
pyro.norms.neo
pyro.norms.imas

Pyrokinetics default choice#

By default, pyrokinetics uses:

  • Electron reference density \(n_{ref} = n_e\)

  • Electron reference temperature \(T_{ref} = T_e\)

  • Reference mass \(m_{ref} = m_D\)

  • Reference velocity \(v_{ref} = c_s = \sqrt{T_e / m_D}\)

  • Reference length \(L_{ref}\) = minor radius

  • Reference magnetic field \(B_{ref} = B_0\)

  • Reference Larmor radius \(\rho_{ref} = c_s / \Omega_i\)

Other conventions override a subset of these choices.

Summary of code conventions#

Some codes (e.g. GS2, STELLA, GKW, IMAS) use the most probable thermal velocity as the reference velocity. In pyrokinetics this is defined as

\[v_{\mathrm{mp}} = \sqrt{\frac{2 T_{ref}}{m_{ref}}}\]

This differs from the sound-speed–based convention

\[c_s = \sqrt{\frac{T_{ref}}{m_{ref}}}\]

by a factor of \(\sqrt{2}\). The distinction is purely a normalisation choice; conversion between conventions is always possible within pyrokinetics and does not require additional physical reference values.

For all supported gyrokinetic codes, the reference Larmor radius is defined as

\[\rho_{ref} = \frac{v_{ref}}{q_{ref} B_{ref} / m_{ref}}\]

As a result, the defining choices for a normalisation convention are the reference velocity \(v_{ref}\), reference length \(L_{ref}\), and reference magnetic field \(B_{ref}\). Together, these determine the spatial, velocity, and gyroradius scaling of the system.

The remaining reference quantities (such as \(n_{ref}\) and \(T_{ref}\)) may vary between codes, but do not affect the fundamental normalisation of lengths and velocities.

Some codes use a magnetic field normalisation based on the equilibrium flux gradient rather than the on-axis field. In these cases,

\[B_{ref} = B_{\mathrm{unit}} = \frac{q}{r}\,\frac{d\psi}{dr}\]

For GS2, the geometric magnetic field \(B_{\mathrm{geo}}\) only appears explicitly when the geometric major radius used in the field-line description differs from the reference major radius. In practice, this occurs when the GS2 namelist satisfies

nml["theta_grid_parameters"]["r_geo"] != nml["theta_grid_parameters"]["rmaj"]

In this case, the geometric field is related to the reference magnetic field by

\[\frac{B_{\mathrm{geo}}}{B_0} = \frac{r_{\mathrm{maj}}}{r_{\mathrm{geo}}}\]

When \(r_{\mathrm{geo}} = r_{\mathrm{maj}}\), the geometric and reference magnetic fields are identical and no separate \(B_{\mathrm{geo}}\) normalisation is introduced.

The table below summarises the reference choices used by each supported code.

Table 3 Normalisation conventions by code#

Code

\(v_{ref}\)

\(L_{ref}\)

\(B_{ref}\)

CGYRO

\(c_s\)

\(a_{\mathrm{minor}}\)

\(B_{\mathrm{unit}}\)

GS2

\(v_{th} = \sqrt{2 T_{ref} / m_{ref}}\)

\(a_{\mathrm{minor}}\)

\(B_0\) or \(B_{geo}\)

STELLA

\(v_{th}\)

\(a_{\mathrm{minor}}\)

\(B_0\)

GX

\(c_s\)

\(a_{\mathrm{minor}}\)

\(B_0\)

GENE

\(c_s\)

\(R_{\mathrm{major}}\)

\(B_0\)

GKW

\(v_{th}\)

\(R_{\mathrm{major}}\)

\(B_0\)

IMAS

\(v_{th}\)

\(R_{\mathrm{major}}\)

\(B_0\)

TGLF

\(c_s\)

\(a_{\mathrm{minor}}\)

\(B_{\mathrm{unit}}\)

NEO

\(c_s\)

\(a_{\mathrm{minor}}\)

\(B_{\mathrm{unit}}\)

Conversion to and from SI units (contexts)#

Conversion between simulation (normalised) units and SI units is only possible when the underlying physical reference values are known. This typically occurs when a Pyro object is constructed from equilibrium and kinetics data, where quantities such as \(n_{ref}\), \(T_{ref}\), \(B_{ref}\), and \(L_{ref}\) are explicitly defined.

Even when these reference values are available, conversion between SI units and simulation units must be performed using a normalisation context. These contexts are stored under pyro.norms.context and must be explicitly supplied when converting quantities.

For example:

nu = pyro.local_species.electron.nu

# Convert between simulation conventions (requires context)
nu_gs2 = nu.to(pyro.norms.gs2, pyro.norms.context)

# Convert to SI units (also requires context)
nu_si = nu.to_base_units(pyro.norms.context)

The context provides the transformation rules linking simulation reference units (e.g. vref, lref, rhoref) to physical units. Without an active context, conversions between physical and simulation units are not permitted.

If the reference values are not known (for example when loading a gyrokinetic input file without equilibrium or kinetic profiles), conversion between different simulation conventions remains possible, but conversion to or from SI units will raise an error. The reference values can be specified using pyro.set_reference_values()

Warning

Conversion between physical (SI) units and simulation units always requires an explicit context. Calling .to() without supplying pyro.norms.context will raise an error if physical reference values are involved.