Reading and plotting nonlinear outputs

Reading and plotting nonlinear outputs#

This is a step-by-step guide on how to use pyrokinetics to read the output of a nonlinear gyrokinetic simulation and create some simple plots. In this guide, we will use the example nonlinear output from CGYRO. The same ideas apply for any gyrokinetic codes supported in pyrokinetics,

Let’s first import pyrokinetics and define our nonlinear input file.

>>> from pyrokinetics import Pyro, template_dir
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> gk_file = template_dir / "outputs/CGYRO_nonlinear/input.cgyro"
>>> pyro = Pyro(gk_file=gk_file)

The gyrokinetic file input.cgyro is our input file template where we set all the extra flags that have been used in the simulation.

We can then read the nonlinear simulation into a dataset using pyrokinetics which is stored as a gk_output object

>>> pyro.load_gk_output(load_fields=True, load_fluxes=True, load_moments=True)
>>> data = pyro.gk_output.data

In this we have decided to load in the fluxes and full 3D fields and the moments of the distribution function using the kwargs load_fluxes, load_fields and load_moments respectively, all of which are optional. Here we initialise the utility base dataclass GKOutputArgs which is used to pass quantities to GKOutput. Derived classes include Coords, Fields, Fluxes, Moments etc. This class contains features such as automatic unit conversion and a dict-like interface to quantities. Derived classes should define an InitVar[Tuple[str, ...]] called dims, which sets the dimensionality of each quantity, e.g. ("kx", "ky", "time").

It is helpful to view the different data stored in the pyro object along with the dimensions of each quantity.

>>> print(pyro.gk_output)
<xarray.Dataset>
Dimensions:      (theta: 16, kx: 256, ky: 7, time: 2, species: 2, field: 3,
                  energy: 8, pitch: 32, flux: 3, moment: 3)
Coordinates:
  * time         (time) float64 0.1 0.2
  * kx           (kx) float64 -9.731 -9.655 -9.579 -9.503 ... 9.503 9.579 9.655
  * ky           (ky) float64 0.0 0.141 0.282 0.423 0.564 0.705 0.846
  * theta        (theta) float64 -3.142 -2.794 -2.435 ... 2.058 2.435 2.794
  * energy       (energy) float64 0.002008 0.05163 0.2767 ... 3.334 5.312 7.308
  * pitch        (pitch) float64 -0.9973 -0.9856 -0.9648 ... 0.9856 0.9973
  * species      (species) <U8 'electron' 'ion1'
  * field        (field) <U4 'phi' 'apar' 'bpar'
  * flux         (flux) <U8 'particle' 'heat' 'momentum'
  * moment       (moment) <U11 'density' 'temperature' 'velocity'
Data variables:
    phi          (theta, kx, ky, time) complex64 [([tref] / [mref])**(0.5) * [mref] / [bref_B0])·tref_electron/e/lref_minor_radius] ...
    apar         (theta, kx, ky, time) complex64 [([tref] / [mref])**(0.5) * [mref] / [bref_B0])²·bref_B0/lref_minor_radius] ...
    bpar         (theta, kx, ky, time) complex64 [([tref] / [mref])**(0.5) * [mref] / [bref_B0])·bref_B0/lref_minor_radius] ...
    density      (theta, kx, species, ky, time) complex64 [([tref] / [mref])**(0.5) * [mref] / [bref_B0])·nref_electron/lref_minor_radius] ...
    temperature  (theta, kx, species, ky, time) complex128 [([tref] / [mref])**(0.5) * [mref] / [bref_B0])·tref_electron/lref_minor_radius] ...
    velocity     (theta, kx, species, ky, time) complex64 [([tref] / [mref])**(0.5)·([tref] / [mref])**(0.5) * [mref] / [bref_B0])/lref_minor_radius] ...
    particle     (field, species, ky, time) float32 [([tref] / [mref])**(0.5)·([tref] / [mref])**(0.5) * [mref] / [bref_B0])²·nref_electron/lref_minor_radius²] ...
    heat         (field, species, ky, time) float32 [([tref] / [mref])**(0.5)·([tref] / [mref])**(0.5) * [mref] / [bref_B0])²·nref_electron·tref_electron/lref_minor_radius²] ...
    momentum     (field, species, ky, time) float32 [([tref] / [mref])**(0.5) * [mref] / [bref_B0])²·nref_electron·tref_electron/lref_minor_radius] ...
Attributes: (12/13)
    linear:            False
    gk_code:           CGYRO
    input_file:        N_ENERGY = 8\nE_MAX = 8\nN_XI = 32\nN_THETA = 16\nN_RA...
    attribute_units:   {}
    title:             GKOutput
    software_name:     Pyrokinetics
    ...                ...
    object_type:       GKOutput
    object_uuid:       8d9f0d00-65d9-4857-9c17-45b0e23e4240
    object_created:    2024-03-26 12:01:42.976982
    session_uuid:      24613d9b-80a3-4eae-a8ce-b00417e58d61
    session_started:   2024-03-26 11:59:01.793200
    netcdf4_version:   1.6.4

The outputs are stored as a xarray Dataset which allows for fast manipulation of data using the built-in methods of xarray. Note that all the data variables have units. Whilst reading in outputs, all data is converted to the pyrokinetics normalisation convention. Details regarding normalisations and units can be found in Normalisation conventions.

Let’s suppose we want to plot the ky spectrum of the electron heat fluxes from our nonlinear simulation. First, we need to extract the relevant quantities from the dataset. After examining the data set, we see that the heat and particle fluxes are given as functions of ("field", "species", "ky", "time"). In order to plot the fluxes as a function of ky we must select out the relevant species, field and time.

>>> electron_heat_flux_ky = data["heat"].sel(field="phi", species="electron").isel(time=-1)
>>> electron_heat_flux_ky.plot()
>>> plot.show()
../_images/CGYRO_ky_flux_spectrum.png

We could of course choose to average over the time dimension instead. Similarly if we want to plot the total heat flux as a function of time we would need to sum over the field, species and ky to obtain this.

>>> total_heat_flux_time = data["heat"].sum(dim=["field", "species", "ky"])
>>> total_heat_flux_time.plot()
>>> plot.show()
../_images/CGYRO_total_heat_timetrace.png

We can also plot the field data. Below we plot the electrostatic potential phi as a function of kx and ky for at the final time slice when theta = 0.0. Note here we want to plot log(phi) but as phi currently has units we need to remove them using the .pint.dequantify() method

>>> phi = data["phi"].sel(theta=0.0, method="nearest", drop=True).isel(time=-1, drop=True)
>>> log_phi = np.log(np.abs(phi).pint.dequantify())
>>> log_phi.plot(x="kx", y="ky")
>>> plt.show()
../_images/CGYRO_phi_kxky.png