Welcome to xemc3’s documentation!

xemc3 provides an interface for collecting the output data from a EMC3 simulation into an xarray dataset, as well as accessor methods for common EMC3 analysis and plotting tasks.

Currently only in alpha (until 1.0 released) so please report any bugs, and feel free to raise issues asking questions or making suggestions.

Getting started

Installing

To get the full install including plotting run:

pip install --user 'xemc3[full]'

If you want a minimal install without plotting capabilities you can run:

pip install --user xemc3

Note that without matplotlib plotting will not be available.

Basic tasks

Checking convergence

If you have run a simulations, typically the first thing you want to do, is check the *_INFO files to check whether the simulation is converged.

This can be done by reading in the info files, and plotting the traces, to have a look at the variation:

import xemc3
import matplotlib.pyplot as plt
ds = xemc3.load.file("path_to_simu/ENERGY_INFO")
for key in ds:
    ds[key].plot()
plt.show()

See an example notebook for more info on reading these files.

Reading the output

xemc3 assumes that all files are called the way that also EMC3 is expecting them to be named. If you use different names, ensure that the files are linked to the EMC3 names, before loading the files with xemc3.

Once the simulation is sufficiently converged, further analysis can start. For this xemc3 allows to read in all the files, and store the data as a netcdf file. This has the advantage that successive reads are very fast, and is especially convenient if the data is stored in the netcdf file after running the simulation. There is a command line version, that can be conveniently called from shell, xemc3-to-netcdf that is roughly equivalent to the following python snippet:

import xemc3
ds = xemc3.load.all(path)
ds.to_netcdf(path + ".nc")

Besides faster loads, the netcdf also makes it easier to share the data for analysis, as all data is stored in a single file. This also allows to unlink the EMC3 names, or share the data with users that have a different naming convention for their files. The netcdf files can then be read again via

import xarray as xr
ds = xr.open_dataset(path + ".nc")

Post processing

xarray provides a wide variety of functionality for post processing. Good documentation and tutorials are available.

Plotting

xarray handles plotting already, but xemc3 extends this with some more specific routines, for example to plot an \(R\times z\) slice. The functionally is documented here and can be accessed via the emc3 accessor of a dataset, for example the xemc3.EMC3DatasetAccessor.plot_rz can be used by calling ds.emc3.plot_rz(...) with ds an xr.Dataset. Plotting in simulation coordinates can be done using xr.DataArray.plot as e.g. ds["Te"].isel(r=-3).plot() to plot the third outermost slice of the electron temperature.

Exercises

To get an overview what is possible with xemc3, you can try the exercises.

You can find them in the docs/exercises/ folder or try them online.

Examples

[9]:
try:
    from dave_utils import jafw
except ImportError:
    print("Importing failed. Consider installing dave_utils with:")
    print("pip install --user git+https://gitlab.mpcdf.mpg.de/dave/dave_utils.git")
    jafw = None
    raise

# This will fail outside of the IPP network
flt = jafw.getSrv()

config = jafw.setCurrents([-1.74e6] * 5 + [0] * 5)

pos = flt.types.Points3D()
pos.x1 = 5.7
pos.x2 = 0
pos.x3 = 0

lineTask = flt.types.LineTracing()
lineTask.numSteps = 3000

task = flt.types.Task()
task.step = 0.01
task.lines = lineTask

res = flt.service.trace(pos, config, task, None, None)

i = 0
dat = np.array(
    [
        res.lines[i].vertices.x1,
        res.lines[i].vertices.x2,
        res.lines[i].vertices.x3,
    ]
)
mapped = ds.emc3.evaluate_at_xyz(
    dat[0],
    dat[1],
    dat[2],
    "ne",
    periodicity=5,
    updownsym=True,
    delta_phi=np.pi / 180,
)
Importing failed. Consider installing dave_utils with:
pip install --user git+https://gitlab.mpcdf.mpg.de/dave/dave_utils.git
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[9], line 2
      1 try:
----> 2     from dave_utils import jafw
      3 except ImportError:
      4     print("Importing failed. Consider installing dave_utils with:")

ModuleNotFoundError: No module named 'dave_utils'
[10]:
if jafw is not None:
    mapped["ne"].plot(figsize=(16, 6))
else:
    print("Fieldlinetracer not available ...")
Fieldlinetracer not available ...

Heatflux post processing

Documenation of the EMC3-EIRENE is part of the EMC3-EIRENE documentation

  • map particle and density deposition ENERGY_DEPO and PARTICLE_DEPO to the target structures

  • Target heatflux contains contribution from potential energy

  • Neutrals are not included

  • plasma radiation not included

[ ]:
# Ensure the example data is present
from get_data import load_example_data

_ = load_example_data()
[ ]:
# Input file
! cat ../../example-data/emc3_example/fort.3
[ ]:
# Input file
! cat ../../example-data/emc3_example/fort.79

Run with e.g.

cd /raven/u/mp004/runs/casegroup/case_docs/
srun emc3_eirene
[4]:
# xemc3 can be used for plotting:
! xemc3-divertor ../../example-data/emc3_example/ -gls -t 'Example data'

Analysing the data

[ ]:
import xemc3
[ ]:
plates = xemc3.load.plates("../../example-data/emc3_example/")
[ ]:
plates

The data contains padding such that they can be combined into a numpy-data-block

[ ]:
plates["f_E"].isel(plate_ind=13).plot()

Using the ds.emc3[] the padding can be removed, if it not not needed for the current data, i.e. if only a single plate is included:

[ ]:
nosym = list(plates.emc3.iter_plates())
nosym[13].emc3["f_E"].plot()
nosym[13]

It is possible to get all plates, rather then just the simulated half-module using the ds.emc3.iter_plates function:

[ ]:
sym = list(plates.emc3.iter_plates(symmetry=True, segments=5))
len(sym)

Analysing traces

Often the first step after running the simulation is to ensure that the simulation is converged.

xemc3 can be used to do this.

[1]:
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import xemc3
import glob

# Matplotlib setup
import setup_plt

%matplotlib inline
[2]:
# Use local helper function to get some data
from get_data import load_example_data

path = load_example_data(get_path=True)
# If you want to use your own data use something like
# path = "path/to/mydata/"

Reading some files

The fastest way is to load just a single iteration trace *_INFO file:

[3]:
ds = xemc3.load.file(path + "/ENERGY_INFO")
[4]:
plt.figure()
ds["Te_upstream"].plot()
[4]:
[<matplotlib.lines.Line2D at 0x7fdee46dcd30>]
_images/examples_info_5_1.png

However in most cases we are only interrested in the last points of the INFO file.

[5]:
plt.figure()
ds.Te_upstream[-50:].plot()
[5]:
[<matplotlib.lines.Line2D at 0x7fdee2363820>]
_images/examples_info_7_1.png

Reading all INFO files

Besides using xemc3.load.all(path) it is also simple to read just the *_INFO files:

[6]:
ds = xr.Dataset()
for file in glob.iglob(f"{path}/*_INFO"):
    ds = xemc3.load.file(file, ds)
ds
[6]:
<xarray.Dataset>
Dimensions:                (iteration: 1000)
Coordinates:
  * iteration              (iteration) int64 -999 -998 -997 -996 ... -3 -2 -1 0
Data variables: (12/28)
    Te_change              (iteration) float64 nan nan nan ... 0.012 0.012 0.012
    Te_upstream            (iteration) float64 nan nan nan ... 208.3 208.3 208.6
    Te_down_back           (iteration) float64 nan nan nan ... 17.59 17.58 17.56
    Te_down_mean           (iteration) float64 nan nan nan ... 16.56 16.55 16.53
    Te_down_fwd            (iteration) float64 nan nan nan ... 13.63 13.65 13.64
    Ti_change              (iteration) float64 nan nan nan ... 0.018 0.018 0.018
    ...                     ...
    ionization_electron    (iteration) float64 nan nan nan ... -26.6 -26.6 -26.6
    ionization_ion         (iteration) float64 nan nan nan ... 7.404 7.404 7.399
    ionization_moment_fwd  (iteration) float64 nan nan ... -6.633e-19 -6.671e-19
    ionization_moment_bwk  (iteration) float64 nan nan ... 1.144e-18 1.152e-18
    TOTAL_FLX              (iteration) float64 nan nan nan ... 83.96 84.17 84.36
    TOTAL_RAD              (iteration) float64 nan nan nan ... 5e+04 5e+04 5e+04
Attributes:
    title:             EMC3-EIRENE Simulation data
    software_name:     xemc3
    software_version:  0.2.6.dev8+gf431467
    date_created:      2023-05-30T11:24:34.208393
    id:                8db5559c-fedc-11ed-9df3-0242ac110002
    references:        https://doi.org/10.5281/zenodo.5562265

Again, the different traces can be plotted, to get an estimate of whether the simulation is converged:

[7]:
plt.figure()
ds.dens_change[-60:].plot()
[7]:
[<matplotlib.lines.Line2D at 0x7fdee0af9640>]
_images/examples_info_11_1.png

Checking how much data

As numpy arrays need to be blocks, xemc3 uses nan-padding. np.isfinite can be used to check how much data we have

[8]:
np.isfinite(ds).sum()
[8]:
<xarray.Dataset>
Dimensions:                ()
Data variables: (12/28)
    Te_change              int64 221
    Te_upstream            int64 221
    Te_down_back           int64 221
    Te_down_mean           int64 221
    Te_down_fwd            int64 221
    Ti_change              int64 221
    ...                     ...
    ionization_electron    int64 206
    ionization_ion         int64 206
    ionization_moment_fwd  int64 206
    ionization_moment_bwk  int64 206
    TOTAL_FLX              int64 172
    TOTAL_RAD              int64 172
[ ]:

Introduction to xemc3

xemc3 is a library for reading the output fron EMC3 simulations into the xarray format.

[1]:
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import xemc3

# Matplotlib setup
import setup_plt

Quick introduction to xarray

This is just to get a quick introduction of the structure of the xarray data type.

In the cell below we generate a xarray with dimensions \((3,3)\) for variable \(x\) with coordinates \((10,20)\) and \(y\) with coordinates \((1,2,3)\).

[2]:
data = xr.DataArray(
    np.random.rand(2, 3), dims=("x", "y"), coords={"x": [10, 20], "y": [1, 2, 3]}
)
data
[2]:
<xarray.DataArray (x: 2, y: 3)>
array([[0.70594585, 0.10235316, 0.80768602],
       [0.40721037, 0.71620189, 0.77389268]])
Coordinates:
  * x        (x) int64 10 20
  * y        (y) int64 1 2 3
data.values

Returns the wrapped numpy array, in this case the return value of np.random.rand(2, 3)

[3]:
data.values
[3]:
array([[0.70594585, 0.10235316, 0.80768602],
       [0.40721037, 0.71620189, 0.77389268]])
data.dims

Returns the name of the dimensions.

[4]:
data.dims
[4]:
('x', 'y')
data.coords

Returns the coordinates for all axis directions with coordinate names and datatype of the coordinates.

[5]:
data.coords
[5]:
Coordinates:
  * x        (x) int64 10 20
  * y        (y) int64 1 2 3
data.attrs

Returns other attributes in form of a dictionary with you can easily add by generating a new value associated with a new key.

[6]:
data.attrs["units"] = "m"
data.attrs
[6]:
{'units': 'm'}
More on xarray:

EMC3 data

The prerequisite for this example to work is to have downloaded the file emc3_example.nc and have the libraries specified in this script installed in your enviroment. We recommend using netCDF4 for opening .nc files. The emc3_example.nc can be found and downloaded here: https://gitlab.mpcdf.mpg.de/dave/xemc3-data given that you have acces.

The path specified in the string in the cell below is where you have stored the emc3_example.nc locally on your computer.

[7]:
# Use local helper function to get some data
from get_data import load_example_data

ds = load_example_data()
# If you want to use your own data use something like
# ds = xemc3.load.all("path/to/mydata/")
# or if you have converted it already to a netcdf file
# ds = xr.open_dataset("path/to/mydata.nc")

ds
[7]:
<xarray.Dataset>
Dimensions:                       (r: 139, theta: 512, phi: 36, delta_r: 2,
                                   delta_theta: 2, delta_phi: 2,
                                   iteration: 1000, plate_ind: 22,
                                   plate_phi_plus1: 57, plate_x_plus1: 50,
                                   r_plus1: 140, theta_plus1: 513,
                                   phi_plus1: 37, plate_phi: 560, plate_x: 100,
                                   delta_plate_phi: 2, delta_plate_x: 2)
Coordinates:
    R_bounds                      (r, theta, phi, delta_r, delta_theta, delta_phi) float64 ...
    z_bounds                      (r, theta, phi, delta_r, delta_theta, delta_phi) float64 ...
    phi_bounds                    (phi, delta_phi) float64 ...
  * iteration                     (iteration) int64 -999 -998 -997 ... -2 -1 0
    plate_z                       (plate_ind, plate_phi_plus1, plate_x_plus1) float64 ...
    plate_phi                     (plate_ind, plate_phi_plus1) float64 ...
    plate_R                       (plate_ind, plate_phi_plus1, plate_x_plus1) float64 ...
    plate_z_bounds                (plate_ind, plate_phi, plate_x, delta_plate_phi, delta_plate_x) float64 ...
    plate_phi_bounds              (plate_ind, plate_phi, plate_x, delta_plate_phi, delta_plate_x) float64 ...
    plate_R_bounds                (plate_ind, plate_phi, plate_x, delta_plate_phi, delta_plate_x) float64 ...
Dimensions without coordinates: r, theta, phi, delta_r, delta_theta, delta_phi,
                                plate_ind, plate_phi_plus1, plate_x_plus1,
                                r_plus1, theta_plus1, phi_plus1, plate_x,
                                delta_plate_phi, delta_plate_x
Data variables: (12/93)
    _plasma_map                   (r, theta, phi) int64 ...
    ne                            (r, theta, phi) float64 ...
    nZ1                           (r, theta, phi) float64 ...
    nZ2                           (r, theta, phi) float64 ...
    nZ3                           (r, theta, phi) float64 ...
    nZ4                           (r, theta, phi) float64 ...
    ...                            ...
    f_E                           (plate_ind, plate_phi, plate_x) float64 ...
    avg_n                         (plate_ind, plate_phi, plate_x) float64 ...
    avg_Te                        (plate_ind, plate_phi, plate_x) float64 ...
    avg_Ti                        (plate_ind, plate_phi, plate_x) float64 ...
    tot_n                         (plate_ind) float64 ...
    tot_P                         (plate_ind) float64 ...
Attributes:
    title:             EMC3-EIRENE Simulation data
    software_name:     xemc3
    software_version:  0.2.2.dev5+g7311151
    date_created:      2022-10-27T13:16:46.836400
    id:                9bda9808-55f9-11ed-9228-00001029fe80
    references:        https://doi.org/10.5281/zenodo.5562265
dataset (ds) explanation

When running the codeline ds on the last line of a cell you get an overview of what the xarray object consist of.

ds.coords[‘R_bounds’]

R_bounds represents the coordinates of the vertices at the gridcells in the radial direction in the \(xy\)-plane. with \(R = \sqrt{x^2 + y^2}\).

ds.coords[‘z_bounds’]

z_bounds represents the coordinates of the vertices of the gridcells in the \(z\)-direction.

ds.coords[‘phi_bounds’]

phi_bounds represents the coordinates of the vertices of the gridcells in the \(\phi\)-direction.

[8]:
# The shape is 6 dimensional, as we include 3 dimensions for the vertices
ds.coords["R_bounds"].shape
[8]:
(139, 512, 36, 2, 2, 2)
[9]:
# Note that units are m - xemc3 prefers SI, with only eV as exception
ds.coords["z_bounds"]
[9]:
<xarray.DataArray 'z_bounds' (r: 139, theta: 512, phi: 36, delta_r: 2,
                              delta_theta: 2, delta_phi: 2)>
[20496384 values with dtype=float64]
Coordinates:
    R_bounds    (r, theta, phi, delta_r, delta_theta, delta_phi) float64 ...
    z_bounds    (r, theta, phi, delta_r, delta_theta, delta_phi) float64 ...
    phi_bounds  (phi, delta_phi) float64 ...
Dimensions without coordinates: r, theta, phi, delta_r, delta_theta, delta_phi
Attributes:
    xemc3_type:  geom
    units:       m
Toroidal slice

A toroidal slice is defined as the grid of \((R,z)\)-values at a fixed angle \(\phi\). The values of the \(\phi\)-angles used in the W7X grid can be found in the paragraph below and demonstrated in the next cell.

ds.coords[‘phi_bounds’]

Running the cell below gives you an array of the \(\phi\) angles.

[10]:
ds.coords["phi_bounds"]
[10]:
<xarray.DataArray 'phi_bounds' (phi: 36, delta_phi: 2)>
[72 values with dtype=float64]
Coordinates:
    phi_bounds  (phi, delta_phi) float64 ...
Dimensions without coordinates: phi, delta_phi
Attributes:
    xemc3_type:  geom
    units:       radian
ds.emc3.plot_rz(key, phi = \(\phi\))

The key is given as a string, None can be passed as a key if you want to plot the mesh. An example is the angle phi \(= \phi\) which is the angle given in radians as float.

For this particular example(.nc file) the floats of the angle \(\phi\) can be found in the dictionary defined by ds.coords[‘phi_bounds’] which has 2 dimensions; one for either side of the cell for a given angle \(\phi\). There are 37 different values for \(\phi\) since W7-X has a five-fold symmetry which is divided in two up-down symmetric parts and we use a resolution \(1^{\circ}\).

In the cells below are some examples of the parameter electron temperature \(T_e\) plotted in toroidal slices for phi index \(n_{\phi} = [0,18,35]\).

[11]:
# the parameter can be plotted by a one-liner
plt.figure()
ds.emc3.plot_rz("Te", phi=0)
[11]:
<matplotlib.collections.QuadMesh at 0x7fa3ac814190>
_images/examples_introduction_21_1.png
[12]:
# for several angles and control
import ipywidgets as widgets

fig = plt.figure()


def plot_Te(ip):
    fig.clear()
    ds.emc3.plot_rz("Te", phi=ip)


ip = widgets.FloatSlider(min=0, max=np.pi / 5, value=0, step=0.01)
widgets.interact(plot_Te, ip=ip)
<Figure size 1000x1000 with 0 Axes>
[12]:
<function __main__.plot_Te(ip)>
Simplifying of grid structure

The parameter values are defined in each grid cell, but the center or the mean of the vertices of the gridcell: \(\mathbf{r}_{param} = \langle \mathbf{r}_{vertex} \rangle\). A simplified analogy is the centerpoint of a 3D cube.

You can get the center coordinates ds.<direction>_bounds.mean(dim=("delta_r", "delta_theta", "delta_phi"), ignore_missing=True) by averaging over the delta_* dimensions of the variable, as that contains the cell extend in each direction.

[13]:
R = ds.R_bounds.mean(dim=("delta_r", "delta_theta", "delta_phi"))
z = ds.z_bounds.mean(dim=("delta_r", "delta_theta", "delta_phi"))
phi = ds.phi_bounds.mean(dim="delta_phi")
x = R * np.cos(phi)
y = R * np.sin(phi)
x, ds.Te
[13]:
(<xarray.DataArray (r: 139, theta: 512, phi: 36)>
 array([[[5.90687195, 5.90143282, 5.89057613, ..., 4.27387008,
          4.21925933, 4.16544375],
         [5.90692225, 5.90157835, 5.89081476, ..., 4.27443602,
          4.21980501, 4.16596973],
         [5.90697495, 5.90172636, 5.89105602, ..., 4.27501305,
          4.22036168, 4.16650662],
         ...,
         [5.90673533, 5.90101113, 5.88987631, ..., 4.27223898,
          4.2176884 , 4.1639313 ],
         [5.90677846, 5.90114916, 5.89010684, ..., 4.27277148,
          4.21820095, 4.16442445],
         [5.90682402, 5.90128975, 5.89034014, ..., 4.27331521,
          4.21872462, 4.16492863]],

        [[5.88039947, 5.87478717, 5.86353702, ..., 4.22636359,
          4.17204559, 4.11853643],
         [5.88048206, 5.87503477, 5.86394578, ..., 4.22725027,
          4.17290028, 4.11936057],
         [5.88056438, 5.87528231, 5.86435491, ..., 4.22815464,
          4.17377244, 4.12020195],
 ...
         [5.64755073, 5.63505485, 5.61354696, ..., 3.81421366,
          3.76543708, 3.71779086],
         [5.64922161, 5.63819013, 5.61807245, ..., 3.81753395,
          3.76870465, 3.7210289 ],
         [5.65069075, 5.64113616, 5.6224387 , ..., 3.82111107,
          3.77222496, 3.7245137 ]],

        [[5.59414493, 5.58958172, 5.57387846, ..., 3.75650956,
          3.70552132, 3.65647075],
         [5.59422224, 5.59117652, 5.57709579, ..., 3.76073063,
          3.70983664, 3.66088151],
         [5.59408138, 5.5925006 , 5.5799317 , ..., 3.76497918,
          3.71417615, 3.66525302],
         ...,
         [5.59027424, 5.58100907, 5.56053413, ..., 3.7435559 ,
          3.69303175, 3.64369176],
         [5.59192682, 5.58422348, 5.56529866, ..., 3.74788951,
          3.69714638, 3.64790418],
         [5.59337562, 5.58724114, 5.56988255, ..., 3.75223099,
          3.701277  , 3.65213093]]])
 Dimensions without coordinates: r, theta, phi,
 <xarray.DataArray 'Te' (r: 139, theta: 512, phi: 36)>
 [2562048 values with dtype=float64]
 Dimensions without coordinates: r, theta, phi
 Attributes:
     xemc3_type:  mapped
     long_name:   Electron temperature
     units:       eV)
Use of NaN values in xEMC3

Not all gridcells have a defined parameter value attached to it. This is mostly the outer and inner region of the grid where the values of many parameter has been ignored because in the specific regions the quantity is not evolved. This is illustrated in the above plot example of the electron temperature \(T_e\). In the cell below you can see how large a fraction of the total number of gridpoints the mesh for the electron temperature that has NaN as a value.

[14]:
n_nans_Te = np.sum(np.isnan(ds.Te)).data
print("How many nans in Te field?", n_nans_Te)
print(f"Fraction of nans with respect to gridcells: {n_nans_Te/ds.Te.size*100:.2f} %")
How many nans in Te field? 584972
Fraction of nans with respect to gridcells: 22.83 %
Grid

In the cell below there is an interactive plot of the grid. You can use the slider to iterate through all toroidal slices(all \(\phi\) angles).

[15]:
import ipywidgets

fig = plt.figure()


def plot_emc3grid(ip):
    fig.clear()
    ds.emc3.plot_rz(None, phi=ip)


ip = ipywidgets.FloatSlider(min=0, max=np.pi / 5, value=0, step=0.01)
ipywidgets.interact(plot_emc3grid, ip=ip)
<Figure size 1000x1000 with 0 Axes>
[15]:
<function __main__.plot_emc3grid(ip)>
Grid with boundaries

Interactive plot of the grid, here you can use the ipywidget slider to iterate through all toroidal slices, the rmin and rmax to set the boundaries in r direction, and the zmin and zmax to set the boundaries in z direction.

[16]:
plt.figure()
ds.emc3.plot_rz("Te", phi=np.pi / 5, Rmin=5.8, Rmax=6.1, zmin=0, zmax=0.3)
[16]:
<matplotlib.collections.QuadMesh at 0x7fa3ac4d26d0>
_images/examples_introduction_30_1.png
Periodic boundary conditions for plotting

Naturally the data does not have periodic boundary conditions, which means that the last dataframe would be equal to the first. In the case of emc3 data the periodicity is in the theta direction. For plotting the dimension of the theta grid is increased by one and set to the first values in the theta direction. This is for tha plot to “complete the orbit” in the theta direction for it to be closed.

[17]:
fig = plt.figure()


def plot_Te_zoomed(ip):
    fig.clear()
    c = plt.pcolormesh(
        ds.emc3["R_corners"][:, :, ip],
        ds.emc3["z_corners"][:, :, ip],
        ds.Te[:, :, ip],
        cmap=plt.cm.jet,
        shading="auto",
    )
    plt.colorbar(c)


phislider = ipywidgets.IntSlider(min=0, max=35)
ipywidgets.interact(plot_Te_zoomed, ip=phislider)
<Figure size 1000x1000 with 0 Axes>
[17]:
<function __main__.plot_Te_zoomed(ip)>

Loading the raw data is slow

As several text files have to be read and parsed, this is rather time consuming.

See Loading of data for a more convienent way to work with EMC3 data.

[1]:
import xemc3
[2]:
%%time

ds = xemc3.load.all("../../example-data/emc3_example/")
CPU times: user 1min 1s, sys: 301 ms, total: 1min 1s
Wall time: 1min 2s

Loading of data

The most convinient way of working with EMC3 data is from netcdf files. This way all metadata and all available fields are known, and are loaded into memory as needed.

Netcdf files are also beneficial for sharing simulation results, as they contain metadata and are thus easy to use. While xemc3 only exists for python, netcdf library implementation are present in most languages, so you can easily do your analysis in your preferred language.

[1]:
import xemc3

import xarray as xr
import numpy as np

# Matplotlib setup
import setup_plt
[2]:
%%time

ds = xr.open_dataset("../../example-data/emc3_example.nc")
CPU times: user 43.4 ms, sys: 23.8 ms, total: 67.1 ms
Wall time: 68.3 ms

Checking the present data

xarray is nicely integrated in jupyter notebook and many IDEs, so it is easy to check what quantities are included as well as units and so on.

Note that for this view the data does not need to recide in memory, which is especially beneficial for high-resolution grids or large parameter scans.

[3]:
ds
[3]:
<xarray.Dataset>
Dimensions:                       (r: 139, theta: 512, phi: 36, delta_r: 2,
                                   delta_theta: 2, delta_phi: 2,
                                   iteration: 1000, plate_ind: 22,
                                   plate_phi_plus1: 57, plate_x_plus1: 50,
                                   r_plus1: 140, theta_plus1: 513,
                                   phi_plus1: 37, plate_phi: 560, plate_x: 100,
                                   delta_plate_phi: 2, delta_plate_x: 2)
Coordinates:
    R_bounds                      (r, theta, phi, delta_r, delta_theta, delta_phi) float64 ...
    z_bounds                      (r, theta, phi, delta_r, delta_theta, delta_phi) float64 ...
    phi_bounds                    (phi, delta_phi) float64 ...
  * iteration                     (iteration) int64 -999 -998 -997 ... -2 -1 0
    plate_z                       (plate_ind, plate_phi_plus1, plate_x_plus1) float64 ...
    plate_phi                     (plate_ind, plate_phi_plus1) float64 ...
    plate_R                       (plate_ind, plate_phi_plus1, plate_x_plus1) float64 ...
    plate_z_bounds                (plate_ind, plate_phi, plate_x, delta_plate_phi, delta_plate_x) float64 ...
    plate_phi_bounds              (plate_ind, plate_phi, plate_x, delta_plate_phi, delta_plate_x) float64 ...
    plate_R_bounds                (plate_ind, plate_phi, plate_x, delta_plate_phi, delta_plate_x) float64 ...
Dimensions without coordinates: r, theta, phi, delta_r, delta_theta, delta_phi,
                                plate_ind, plate_phi_plus1, plate_x_plus1,
                                r_plus1, theta_plus1, phi_plus1, plate_x,
                                delta_plate_phi, delta_plate_x
Data variables: (12/93)
    _plasma_map                   (r, theta, phi) int64 ...
    ne                            (r, theta, phi) float64 ...
    nZ1                           (r, theta, phi) float64 ...
    nZ2                           (r, theta, phi) float64 ...
    nZ3                           (r, theta, phi) float64 ...
    nZ4                           (r, theta, phi) float64 ...
    ...                            ...
    f_E                           (plate_ind, plate_phi, plate_x) float64 ...
    avg_n                         (plate_ind, plate_phi, plate_x) float64 ...
    avg_Te                        (plate_ind, plate_phi, plate_x) float64 ...
    avg_Ti                        (plate_ind, plate_phi, plate_x) float64 ...
    tot_n                         (plate_ind) float64 ...
    tot_P                         (plate_ind) float64 ...
Attributes:
    title:             EMC3-EIRENE Simulation data
    software_name:     xemc3
    software_version:  0.2.2.dev5+g7311151
    date_created:      2022-10-27T13:16:46.836400
    id:                9bda9808-55f9-11ed-9228-00001029fe80
    references:        https://doi.org/10.5281/zenodo.5562265

Quantities can be accessed either by the [] operator like ["nZ3"] or by .nZ3. Note that assigning new quantities is only possible with the [] notation.

[4]:
ds.nZ3
[4]:
<xarray.DataArray 'nZ3' (r: 139, theta: 512, phi: 36)>
[2562048 values with dtype=float64]
Dimensions without coordinates: r, theta, phi
Attributes:
    print_before:             4\n
    xemc3_type:    mapped
    units:         m$^{-3}$
[5]:
ds.nZ3.isel(r=-2).plot()
[5]:
<matplotlib.collections.QuadMesh at 0x7f6e5a190a30>
_images/examples_load2_7_1.png
[6]:
ds.emc3.plot_rz("nZ3", phi=np.pi / 5)
[6]:
<matplotlib.collections.QuadMesh at 0x7f6e53f7b3d0>
_images/examples_load2_8_1.png