xhermes 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xhermes-0.1.0/PKG-INFO +138 -0
- xhermes-0.1.0/README.md +114 -0
- xhermes-0.1.0/setup.cfg +4 -0
- xhermes-0.1.0/setup.py +32 -0
- xhermes-0.1.0/xhermes/__init__.py +2 -0
- xhermes-0.1.0/xhermes/accessors.py +249 -0
- xhermes-0.1.0/xhermes/load.py +425 -0
- xhermes-0.1.0/xhermes/utils.py +44 -0
- xhermes-0.1.0/xhermes.egg-info/PKG-INFO +138 -0
- xhermes-0.1.0/xhermes.egg-info/SOURCES.txt +11 -0
- xhermes-0.1.0/xhermes.egg-info/dependency_links.txt +1 -0
- xhermes-0.1.0/xhermes.egg-info/requires.txt +2 -0
- xhermes-0.1.0/xhermes.egg-info/top_level.txt +1 -0
xhermes-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xhermes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Analyse data from Hermes-3 simulations using xarray
|
|
5
|
+
License: Apache
|
|
6
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
7
|
+
Classifier: Intended Audience :: Science/Research
|
|
8
|
+
Classifier: Intended Audience :: Education
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: xarray
|
|
17
|
+
Requires-Dist: xbout
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# xHermes
|
|
26
|
+
|
|
27
|
+
xHermes is a post-processing package for Hermes-3 in 1D, 2D and 3D which provides automatic conversion to SI units and many useful plotting routines.
|
|
28
|
+
|
|
29
|
+
xHermes a wrapper around [xBOUT](https://github.com/boutproject/xBOUT) to provide [Hermes-3](https://github.com/bendudson/hermes-3) specific
|
|
30
|
+
features. At the moment, xHermes is used for loading in the Hermes-3 results, performing SI unit conversion and calculating some geometry terms.
|
|
31
|
+
You may still want to use xBOUT, in particular if you are running 2D and 3D cases and want plotting routines for those.
|
|
32
|
+
|
|
33
|
+
There is also a collection of Mike Kryjak's personal post-processing scripts [sdtools](https://github.com/mikekryjak/sdtools/tree/main/hermes3) which have extensive additional functionality for 1D and 2D postprocessing.
|
|
34
|
+
These scripts are not officially supported, but feel free to take a look.
|
|
35
|
+
It is intended for all those additional features to be merged into xHermes over time.
|
|
36
|
+
|
|
37
|
+
Both xBOUT and xHermes use [Xarray](https://docs.xarray.dev/en/stable/) which provides a scalable and powerful framework
|
|
38
|
+
for dealing with large amounts of data while preserving dimensional
|
|
39
|
+
consistency. If you have ever used [Pandas](https://pandas.pydata.org/), you will find Xarray very familiar, as it is effectively Pandas in more dimensions.
|
|
40
|
+
|
|
41
|
+
While Xarray is based on Numpy arrays under the hood and the data
|
|
42
|
+
can be accessed in this way, Xarray's main benefit comes in its
|
|
43
|
+
powerful abilities to select, filter and operate on data. The
|
|
44
|
+
syntax is similar to Pandas and the basics are explained in
|
|
45
|
+
Xarray's excellent documentation.
|
|
46
|
+
If you are new to Xarray, it may be useful to start with reviewing the [selection syntax](https://docs.xarray.dev/en/stable/user-guide/indexing.html).
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Hermes-3 is pip installable. In the terminal with your Python environment enabled, run:
|
|
51
|
+
|
|
52
|
+
pip install xhermes
|
|
53
|
+
|
|
54
|
+
However, you may not be able to get the latest version this way. For now, it is highly recommended you do an editable install instead:
|
|
55
|
+
|
|
56
|
+
git clone https://github.com/boutproject/xhermes
|
|
57
|
+
cd xhermes
|
|
58
|
+
pip install -e .
|
|
59
|
+
|
|
60
|
+
In this way, pip will install the Python package from the xHermes folder you cloned. If you use git to checkout branches or modify the contents in any way, it will be automatically work.
|
|
61
|
+
|
|
62
|
+
## Loading simulations
|
|
63
|
+
|
|
64
|
+
The data from a Hermes-3 run can be loaded with just
|
|
65
|
+
|
|
66
|
+
import xhermes
|
|
67
|
+
ds = xhermes.open(hermes_simulation_path)
|
|
68
|
+
|
|
69
|
+
where the `hermes_simulation_path` directory is assumed to contain the `BOUT.dmp.*.nc`
|
|
70
|
+
files and a `BOUT.settings` file. `open` returns an instance of an
|
|
71
|
+
`xarray.Dataset` which contains BOUT- and Hermes-specific information
|
|
72
|
+
in the `attrs`, so represents a general structure for storing all of
|
|
73
|
+
the output of a simulation, including data, input options and (soon)
|
|
74
|
+
grid data.
|
|
75
|
+
|
|
76
|
+
When working with 2D or 3D simulations of tokamaks, it can be useful to
|
|
77
|
+
load the grid along with the simulation file and pass the "toroidal" flag to xBOUT:
|
|
78
|
+
|
|
79
|
+
import xhermes
|
|
80
|
+
ds = xhermes.open(hermes_simulation_path, gridfilepath = grid_file_path, geometry = "toroidal")
|
|
81
|
+
|
|
82
|
+
This loads the metric tensor and grid corner coordinates from the grid which
|
|
83
|
+
enables geometrical operations, polygonal plotting routines and additional metadata on topology.
|
|
84
|
+
|
|
85
|
+
## Working with the data
|
|
86
|
+
|
|
87
|
+
### Normalisation and geometry extraction
|
|
88
|
+
|
|
89
|
+
All variables within Hermes-3 are normalised. The normalisation factors, the units and variable names
|
|
90
|
+
are provided in the Hermes-3 output for most variables.
|
|
91
|
+
This information can be accessed for each variable DataArray:
|
|
92
|
+
|
|
93
|
+
ds["Pe"].attrs
|
|
94
|
+
|
|
95
|
+
xHermes automatically applies the unnormalisation when the simulation is loaded in.
|
|
96
|
+
|
|
97
|
+
When working with 1D and 2D tokamak simulations, it can be useful to extract
|
|
98
|
+
cell lengths, volumes and cross-sectional areas as well as some additional metadata from the model:
|
|
99
|
+
|
|
100
|
+
ds = ds.hermes.extract_1D_tokamak_geometry()
|
|
101
|
+
|
|
102
|
+
or
|
|
103
|
+
|
|
104
|
+
ds = ds.hermes.extract_2D_tokamak_geometry()
|
|
105
|
+
|
|
106
|
+
### The Dataset
|
|
107
|
+
|
|
108
|
+
The Dataset `ds` contains useful objects:
|
|
109
|
+
- A DataArray for each variable: `ds.data_vars` shows a list. `ds["Pe"]` returns the Pe DataArray.
|
|
110
|
+
- A DataArray for each coordinate: `ds.coords` shows a list. `ds["t"]` or `ds.coords["t"]` returns the t DataArray.
|
|
111
|
+
- A summary of dimensions in the model: `ds.dims`
|
|
112
|
+
- Model metadata: `ds.metadata`
|
|
113
|
+
- Model input settings: `ds.options`
|
|
114
|
+
|
|
115
|
+
### The DataArray
|
|
116
|
+
|
|
117
|
+
The DataArray (e.g. `ds["Pe"]`) contains the array with the variable data, its dimensionality and
|
|
118
|
+
attributes as well as metadata and options inherited from the parent dataset.
|
|
119
|
+
|
|
120
|
+
A visual summary of the DataArray contents and size can be obtained by simply:
|
|
121
|
+
|
|
122
|
+
ds["Pe"]
|
|
123
|
+
|
|
124
|
+
Note that this may be slow due to the Xarray overhead. To access the underlying data as a Numpy array, one can use:
|
|
125
|
+
|
|
126
|
+
ds["Pe"].values
|
|
127
|
+
|
|
128
|
+
or
|
|
129
|
+
|
|
130
|
+
ds["Pe"].data
|
|
131
|
+
|
|
132
|
+
### Plotting
|
|
133
|
+
Xarray contains a lot of built-in plotting routines which can be used to prepare [excellent 1D and 2D plots](https://docs.xarray.dev/en/stable/user-guide/plotting.html).
|
|
134
|
+
Unfortunately, it is unable to deal with 2D plots in tokamak geometry.
|
|
135
|
+
Routines to plot those are available in xBOUT which has some excellent [example notebooks](https://github.com/boutproject/xBOUT-examples).
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
xhermes-0.1.0/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# xHermes
|
|
2
|
+
|
|
3
|
+
xHermes is a post-processing package for Hermes-3 in 1D, 2D and 3D which provides automatic conversion to SI units and many useful plotting routines.
|
|
4
|
+
|
|
5
|
+
xHermes a wrapper around [xBOUT](https://github.com/boutproject/xBOUT) to provide [Hermes-3](https://github.com/bendudson/hermes-3) specific
|
|
6
|
+
features. At the moment, xHermes is used for loading in the Hermes-3 results, performing SI unit conversion and calculating some geometry terms.
|
|
7
|
+
You may still want to use xBOUT, in particular if you are running 2D and 3D cases and want plotting routines for those.
|
|
8
|
+
|
|
9
|
+
There is also a collection of Mike Kryjak's personal post-processing scripts [sdtools](https://github.com/mikekryjak/sdtools/tree/main/hermes3) which have extensive additional functionality for 1D and 2D postprocessing.
|
|
10
|
+
These scripts are not officially supported, but feel free to take a look.
|
|
11
|
+
It is intended for all those additional features to be merged into xHermes over time.
|
|
12
|
+
|
|
13
|
+
Both xBOUT and xHermes use [Xarray](https://docs.xarray.dev/en/stable/) which provides a scalable and powerful framework
|
|
14
|
+
for dealing with large amounts of data while preserving dimensional
|
|
15
|
+
consistency. If you have ever used [Pandas](https://pandas.pydata.org/), you will find Xarray very familiar, as it is effectively Pandas in more dimensions.
|
|
16
|
+
|
|
17
|
+
While Xarray is based on Numpy arrays under the hood and the data
|
|
18
|
+
can be accessed in this way, Xarray's main benefit comes in its
|
|
19
|
+
powerful abilities to select, filter and operate on data. The
|
|
20
|
+
syntax is similar to Pandas and the basics are explained in
|
|
21
|
+
Xarray's excellent documentation.
|
|
22
|
+
If you are new to Xarray, it may be useful to start with reviewing the [selection syntax](https://docs.xarray.dev/en/stable/user-guide/indexing.html).
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Hermes-3 is pip installable. In the terminal with your Python environment enabled, run:
|
|
27
|
+
|
|
28
|
+
pip install xhermes
|
|
29
|
+
|
|
30
|
+
However, you may not be able to get the latest version this way. For now, it is highly recommended you do an editable install instead:
|
|
31
|
+
|
|
32
|
+
git clone https://github.com/boutproject/xhermes
|
|
33
|
+
cd xhermes
|
|
34
|
+
pip install -e .
|
|
35
|
+
|
|
36
|
+
In this way, pip will install the Python package from the xHermes folder you cloned. If you use git to checkout branches or modify the contents in any way, it will be automatically work.
|
|
37
|
+
|
|
38
|
+
## Loading simulations
|
|
39
|
+
|
|
40
|
+
The data from a Hermes-3 run can be loaded with just
|
|
41
|
+
|
|
42
|
+
import xhermes
|
|
43
|
+
ds = xhermes.open(hermes_simulation_path)
|
|
44
|
+
|
|
45
|
+
where the `hermes_simulation_path` directory is assumed to contain the `BOUT.dmp.*.nc`
|
|
46
|
+
files and a `BOUT.settings` file. `open` returns an instance of an
|
|
47
|
+
`xarray.Dataset` which contains BOUT- and Hermes-specific information
|
|
48
|
+
in the `attrs`, so represents a general structure for storing all of
|
|
49
|
+
the output of a simulation, including data, input options and (soon)
|
|
50
|
+
grid data.
|
|
51
|
+
|
|
52
|
+
When working with 2D or 3D simulations of tokamaks, it can be useful to
|
|
53
|
+
load the grid along with the simulation file and pass the "toroidal" flag to xBOUT:
|
|
54
|
+
|
|
55
|
+
import xhermes
|
|
56
|
+
ds = xhermes.open(hermes_simulation_path, gridfilepath = grid_file_path, geometry = "toroidal")
|
|
57
|
+
|
|
58
|
+
This loads the metric tensor and grid corner coordinates from the grid which
|
|
59
|
+
enables geometrical operations, polygonal plotting routines and additional metadata on topology.
|
|
60
|
+
|
|
61
|
+
## Working with the data
|
|
62
|
+
|
|
63
|
+
### Normalisation and geometry extraction
|
|
64
|
+
|
|
65
|
+
All variables within Hermes-3 are normalised. The normalisation factors, the units and variable names
|
|
66
|
+
are provided in the Hermes-3 output for most variables.
|
|
67
|
+
This information can be accessed for each variable DataArray:
|
|
68
|
+
|
|
69
|
+
ds["Pe"].attrs
|
|
70
|
+
|
|
71
|
+
xHermes automatically applies the unnormalisation when the simulation is loaded in.
|
|
72
|
+
|
|
73
|
+
When working with 1D and 2D tokamak simulations, it can be useful to extract
|
|
74
|
+
cell lengths, volumes and cross-sectional areas as well as some additional metadata from the model:
|
|
75
|
+
|
|
76
|
+
ds = ds.hermes.extract_1D_tokamak_geometry()
|
|
77
|
+
|
|
78
|
+
or
|
|
79
|
+
|
|
80
|
+
ds = ds.hermes.extract_2D_tokamak_geometry()
|
|
81
|
+
|
|
82
|
+
### The Dataset
|
|
83
|
+
|
|
84
|
+
The Dataset `ds` contains useful objects:
|
|
85
|
+
- A DataArray for each variable: `ds.data_vars` shows a list. `ds["Pe"]` returns the Pe DataArray.
|
|
86
|
+
- A DataArray for each coordinate: `ds.coords` shows a list. `ds["t"]` or `ds.coords["t"]` returns the t DataArray.
|
|
87
|
+
- A summary of dimensions in the model: `ds.dims`
|
|
88
|
+
- Model metadata: `ds.metadata`
|
|
89
|
+
- Model input settings: `ds.options`
|
|
90
|
+
|
|
91
|
+
### The DataArray
|
|
92
|
+
|
|
93
|
+
The DataArray (e.g. `ds["Pe"]`) contains the array with the variable data, its dimensionality and
|
|
94
|
+
attributes as well as metadata and options inherited from the parent dataset.
|
|
95
|
+
|
|
96
|
+
A visual summary of the DataArray contents and size can be obtained by simply:
|
|
97
|
+
|
|
98
|
+
ds["Pe"]
|
|
99
|
+
|
|
100
|
+
Note that this may be slow due to the Xarray overhead. To access the underlying data as a Numpy array, one can use:
|
|
101
|
+
|
|
102
|
+
ds["Pe"].values
|
|
103
|
+
|
|
104
|
+
or
|
|
105
|
+
|
|
106
|
+
ds["Pe"].data
|
|
107
|
+
|
|
108
|
+
### Plotting
|
|
109
|
+
Xarray contains a lot of built-in plotting routines which can be used to prepare [excellent 1D and 2D plots](https://docs.xarray.dev/en/stable/user-guide/plotting.html).
|
|
110
|
+
Unfortunately, it is unable to deal with 2D plots in tokamak geometry.
|
|
111
|
+
Routines to plot those are available in xBOUT which has some excellent [example notebooks](https://github.com/boutproject/xBOUT-examples).
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
xhermes-0.1.0/setup.cfg
ADDED
xhermes-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Utility function to read the README file.
|
|
6
|
+
# Used for the long_description.
|
|
7
|
+
def read(fname):
|
|
8
|
+
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
setup(
|
|
12
|
+
name="xhermes",
|
|
13
|
+
version="0.1.0",
|
|
14
|
+
description="Analyse data from Hermes-3 simulations using xarray",
|
|
15
|
+
license="Apache",
|
|
16
|
+
long_description=read("README.md"),
|
|
17
|
+
long_description_content_type="text/markdown",
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Intended Audience :: Education",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: Apache Software License",
|
|
24
|
+
"Natural Language :: English",
|
|
25
|
+
"Operating System :: POSIX :: Linux",
|
|
26
|
+
"Programming Language :: Python :: 3.6",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
28
|
+
],
|
|
29
|
+
install_requires=["xarray", "xbout"],
|
|
30
|
+
packages=find_packages(),
|
|
31
|
+
include_package_data=True,
|
|
32
|
+
)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
from xarray import register_dataset_accessor, register_dataarray_accessor
|
|
2
|
+
from xbout import BoutDatasetAccessor, BoutDataArrayAccessor
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@register_dataset_accessor("hermes")
|
|
7
|
+
class HermesDatasetAccessor(BoutDatasetAccessor):
|
|
8
|
+
"""
|
|
9
|
+
Methods on Hermes-3 datasets
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, ds):
|
|
13
|
+
super().__init__(ds)
|
|
14
|
+
|
|
15
|
+
def unnormalise(self):
|
|
16
|
+
"""
|
|
17
|
+
In-place modify data, converting to SI units
|
|
18
|
+
|
|
19
|
+
Normalisation values set in load.open_hermesdataset
|
|
20
|
+
in attrs['conversion']
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Un-normalise data arrays
|
|
24
|
+
for key, da in self.data.items():
|
|
25
|
+
da.hermes.unnormalise()
|
|
26
|
+
|
|
27
|
+
# Un-normalise coordinates
|
|
28
|
+
for coord in ["dx", "dy", "dz", "t"]:
|
|
29
|
+
units_type = self.data[coord].attrs.get("units_type", "unknown")
|
|
30
|
+
if (units_type == "unknown") or (units_type == "SI"):
|
|
31
|
+
continue
|
|
32
|
+
elif units_type == "hermes":
|
|
33
|
+
# Un-normalise coordinate, modifying in place
|
|
34
|
+
self.data[coord] = (
|
|
35
|
+
self.data[coord] * self.data[coord].attrs["conversion"]
|
|
36
|
+
)
|
|
37
|
+
self.data[coord].attrs["units_type"] = "SI"
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError("Unrecognised units_type: " + units_type)
|
|
40
|
+
|
|
41
|
+
def extract_1d_tokamak_geometry(self, remove_outer = False):
|
|
42
|
+
"""
|
|
43
|
+
Process the results to generate 1D relevant geometry data:
|
|
44
|
+
- Reconstruct pos, the cell position in [m] from upstream from dy
|
|
45
|
+
- Calculate cross-sectional area da and volume dv
|
|
46
|
+
|
|
47
|
+
Notes
|
|
48
|
+
----------
|
|
49
|
+
- dy is technically in flux space, but in 1D
|
|
50
|
+
this is the same as in regular space and in units of [m]
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
----------
|
|
54
|
+
- Dataset with the new geometry
|
|
55
|
+
"""
|
|
56
|
+
ds = self.data.squeeze() # Get rid of 1-length dimensions
|
|
57
|
+
|
|
58
|
+
# Reconstruct grid position (pos, as in position) from dy
|
|
59
|
+
dy = ds.coords["dy"].values
|
|
60
|
+
n = len(dy)
|
|
61
|
+
pos = np.zeros(n)
|
|
62
|
+
pos[0] = 0.5*dy[0]
|
|
63
|
+
|
|
64
|
+
for i in range(1,n):
|
|
65
|
+
pos[i] = pos[i-1] + 0.5*dy[i-1] + 0.5*dy[i]
|
|
66
|
+
|
|
67
|
+
# Set 0 to be at first cell boundary in domain
|
|
68
|
+
if remove_outer is True:
|
|
69
|
+
pos -= (pos[2] + pos[3]) / 2
|
|
70
|
+
else:
|
|
71
|
+
pos -= (pos[1] + pos[2]) / 2
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ds["pos"] = (["y"], pos)
|
|
75
|
+
|
|
76
|
+
# Make pos the main coordinate instead of y
|
|
77
|
+
ds = ds.swap_dims({"y":"pos"})
|
|
78
|
+
ds.coords["pos"].attrs = ds.coords["y"].attrs
|
|
79
|
+
|
|
80
|
+
ds["pos"].attrs.update({
|
|
81
|
+
"conversion" : 1,
|
|
82
|
+
"units" : "m",
|
|
83
|
+
"standard_name" : "parallel position",
|
|
84
|
+
"long_name" : "Parallel connection length"})
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Derive and append metadata for the cross-sectional area
|
|
88
|
+
# and volume. The conversions are 1 because the derivation
|
|
89
|
+
# is from already-normalised parameters
|
|
90
|
+
ds["da"] = ds.dx * ds.dz * ds.J / np.sqrt(ds.g_22)
|
|
91
|
+
ds["da"].attrs.update({
|
|
92
|
+
"conversion" : 1,
|
|
93
|
+
"units" : "m2",
|
|
94
|
+
"standard_name" : "cross-sectional area",
|
|
95
|
+
"long_name" : "Cell parallel cross-sectional area"})
|
|
96
|
+
|
|
97
|
+
ds["dv"] = ds.J * ds.dx * ds.dy * ds.dz
|
|
98
|
+
ds["dv"].attrs.update({
|
|
99
|
+
"conversion" : 1,
|
|
100
|
+
"units" : "m3",
|
|
101
|
+
"standard_name" : "cell volume",
|
|
102
|
+
"long_name" : "Cell Volume"})
|
|
103
|
+
|
|
104
|
+
return ds
|
|
105
|
+
|
|
106
|
+
def extract_2d_tokamak_geometry(self):
|
|
107
|
+
"""
|
|
108
|
+
Process the results to generate 2D relevant geometry data:
|
|
109
|
+
|
|
110
|
+
Calculates
|
|
111
|
+
----------
|
|
112
|
+
- nxg and nyg, versions of ny and nx which always include guard cells
|
|
113
|
+
if they exist in the dataset, and do not include them if they don't
|
|
114
|
+
- j1_1g, j1_2g, ..., versions of jyseps which account for guards in the
|
|
115
|
+
same way as nxg and nyg
|
|
116
|
+
- x_idx, y_idx: arrays of radial and poloidal indices of all cells
|
|
117
|
+
- dv, dr, dthe, dl: cell volume and real space cell dimensions
|
|
118
|
+
- Adds target names to the metadata accounting for single or double null
|
|
119
|
+
|
|
120
|
+
Notes
|
|
121
|
+
----------
|
|
122
|
+
- Adds copies of jyseps1_1 that are shortened to j1_1 etc.
|
|
123
|
+
This is potentially annoying.
|
|
124
|
+
- The reason it's useful to have arrays of coordinate indices
|
|
125
|
+
is because Xarray is surprisingly awkward when it comes to
|
|
126
|
+
obtaining this from the coordinates.
|
|
127
|
+
- This method has a hardcoded requirement for providing the grid file.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
----------
|
|
131
|
+
- Dataset with the new geometry
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
ds = self.data.squeeze()
|
|
135
|
+
m = ds.metadata
|
|
136
|
+
|
|
137
|
+
if "topology" not in m:
|
|
138
|
+
raise Exception(
|
|
139
|
+
"2D Tokamak topology missing from metadata. Please load model with the flag geometry = 'toroidal' and provide grid")
|
|
140
|
+
|
|
141
|
+
# Add theta index to coords so that both X and theta can be accessed index-wise
|
|
142
|
+
# It is surprisingly hard to extract the index of coordinates in Xarray...
|
|
143
|
+
ds.coords["theta_idx"] = (["theta"], range(len(ds.coords["theta"])))
|
|
144
|
+
|
|
145
|
+
# Extract target names. This is done here and not in load because load is not
|
|
146
|
+
# tokamak specific.
|
|
147
|
+
if "single-null" in m["topology"]:
|
|
148
|
+
m["targets"] = ["inner_lower", "outer_lower"]
|
|
149
|
+
elif "double-null" in m["topology"]:
|
|
150
|
+
m["targets"] = ["inner_lower", "outer_lower", "inner_upper", "outer_upper"]
|
|
151
|
+
|
|
152
|
+
num_targets = len(m["targets"])
|
|
153
|
+
|
|
154
|
+
# nyg, nxg: cell counts which are always with guard cells
|
|
155
|
+
# if they exist, or not if they don't
|
|
156
|
+
if m["keep_xboundaries"] == 0:
|
|
157
|
+
m["nxg"] = m["nx"] - m["MXG"] * 2
|
|
158
|
+
m["MXG"] = 0
|
|
159
|
+
else:
|
|
160
|
+
m["nxg"] = m["nx"]
|
|
161
|
+
|
|
162
|
+
if m["keep_yboundaries"] == 0:
|
|
163
|
+
m["nyg"] = m["ny"]
|
|
164
|
+
m["MYG"] = 0
|
|
165
|
+
else:
|
|
166
|
+
m["nyg"] = m["ny"] + m["MYG"] * num_targets
|
|
167
|
+
|
|
168
|
+
# Simplified names of the separatrix indices
|
|
169
|
+
m["j1_1"] = m["jyseps1_1"]
|
|
170
|
+
m["j1_2"] = m["jyseps1_2"]
|
|
171
|
+
m["j2_1"] = m["jyseps2_1"]
|
|
172
|
+
m["j2_2"] = m["jyseps2_2"]
|
|
173
|
+
|
|
174
|
+
# Separatrix indices which account for guard cells in the same way as nxg, nyg
|
|
175
|
+
m["j1_1g"] = m["j1_1"] + m["MYG"]
|
|
176
|
+
m["j1_2g"] = m["j1_2"] + m["MYG"] * (num_targets - 1)
|
|
177
|
+
m["j2_1g"] = m["j2_1"] + m["MYG"]
|
|
178
|
+
m["j2_2g"] = m["j2_2"] + m["MYG"] * (num_targets - 1)
|
|
179
|
+
|
|
180
|
+
# Array of radial (x) indices and of poloidal (y) indices for each cell
|
|
181
|
+
# This is useful because Xarray makes it awkward to extract indices in certain cases
|
|
182
|
+
ds["x_idx"] = (["x", "theta"], np.array([np.array(range(m["nxg"]))] * int(m["nyg"])).transpose())
|
|
183
|
+
ds["y_idx"] = (["x", "theta"], np.array([np.array(range(m["nyg"]))] * int(m["nxg"])))
|
|
184
|
+
|
|
185
|
+
# Cell volume calculation
|
|
186
|
+
ds["dv"] = (["x", "theta"], ds["dx"].data * ds["dy"].data * ds["dz"].data * ds["J"].data)
|
|
187
|
+
ds["dv"].attrs.update({
|
|
188
|
+
"conversion" : 1,
|
|
189
|
+
"units" : "m3",
|
|
190
|
+
"standard_name" : "cell volume",
|
|
191
|
+
"long_name" : "Cell volume",
|
|
192
|
+
"source" : "xHermes"})
|
|
193
|
+
|
|
194
|
+
# Cell areas in real space - comes from Jacobian
|
|
195
|
+
# Note: can be calculated from flux space or real space coordinates:
|
|
196
|
+
# dV = (hthe/Bpol) * (R*Bpol*dr) * dy*2pi = hthe * dy * dr * 2pi * R
|
|
197
|
+
ds["dr"] = (["x", "theta"], ds.dx.data / (ds.R.data * ds.Bpxy.data))
|
|
198
|
+
ds["dr"].attrs.update({
|
|
199
|
+
"conversion" : 1,
|
|
200
|
+
"units" : "m",
|
|
201
|
+
"standard_name" : "radial length",
|
|
202
|
+
"long_name" : "Length of cell in the radial direction",
|
|
203
|
+
"source" : "xHermes"})
|
|
204
|
+
|
|
205
|
+
ds["hthe"] = (["x", "theta"], ds["J"].data * ds["Bpxy"].data) # h_theta
|
|
206
|
+
ds["hthe"].attrs.update({
|
|
207
|
+
"conversion" : 1,
|
|
208
|
+
"units" : "m/radian",
|
|
209
|
+
"standard_name" : "h_theta: poloidal arc length per radian",
|
|
210
|
+
"long_name" : "h_theta: poloidal arc length per radian",
|
|
211
|
+
"source" : "xHermes"})
|
|
212
|
+
|
|
213
|
+
ds["dl"] = (["x", "theta"], ds["dy"].data * ds["hthe"].data) # poloidal arc length
|
|
214
|
+
ds["dl"].attrs.update({
|
|
215
|
+
"conversion" : 1,
|
|
216
|
+
"units" : "m",
|
|
217
|
+
"standard_name" : "poloidal arc length",
|
|
218
|
+
"long_name" : "Poloidal arc length",
|
|
219
|
+
"source" : "xHermes"})
|
|
220
|
+
|
|
221
|
+
return ds
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@register_dataarray_accessor("hermes")
|
|
225
|
+
class HermesDataArrayAccessor(BoutDataArrayAccessor):
|
|
226
|
+
"""
|
|
227
|
+
Methods on Hermes-3 arrays
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def __init__(self, da):
|
|
231
|
+
super().__init__(da)
|
|
232
|
+
|
|
233
|
+
def unnormalise(self):
|
|
234
|
+
"""
|
|
235
|
+
In-place modify data, converting to SI units
|
|
236
|
+
|
|
237
|
+
Normalisation values set in load.open_hermesdataset
|
|
238
|
+
in attrs['conversion']
|
|
239
|
+
"""
|
|
240
|
+
units_type = self.data.attrs.get("units_type", "unknown")
|
|
241
|
+
|
|
242
|
+
if units_type == "SI":
|
|
243
|
+
# already un-normalised
|
|
244
|
+
return
|
|
245
|
+
elif ("units" in self.data.attrs) and ("conversion" in self.data.attrs):
|
|
246
|
+
# Normalise using values
|
|
247
|
+
self.data *= self.data.attrs["conversion"]
|
|
248
|
+
self.data.attrs["units_type"] = "SI"
|
|
249
|
+
return self
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
from xbout import open_boutdataset
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def open_hermesdataset(
|
|
7
|
+
datapath="./BOUT.dmp.*.nc",
|
|
8
|
+
inputfilepath=None,
|
|
9
|
+
chunks={},
|
|
10
|
+
run_name=None,
|
|
11
|
+
info=False,
|
|
12
|
+
unnormalise=True,
|
|
13
|
+
debug_variable_names = False,
|
|
14
|
+
**kwargs,
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Load a dataset from a set of Hermes output files
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
```
|
|
21
|
+
import xhermes
|
|
22
|
+
ds = xhermes.open_hermesdataset('data/BOUT.dmp.*.nc')
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
|
|
28
|
+
ds : xarray.Dataset
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
ds = open_boutdataset(
|
|
32
|
+
datapath=datapath,
|
|
33
|
+
inputfilepath=inputfilepath,
|
|
34
|
+
chunks=chunks,
|
|
35
|
+
run_name=run_name,
|
|
36
|
+
info=info,
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Record if guard cells read in or not
|
|
41
|
+
if "keep_xboundaries" in kwargs:
|
|
42
|
+
ds.metadata["keep_xboundaries"] = kwargs["keep_xboundaries"]
|
|
43
|
+
else:
|
|
44
|
+
ds.metadata["keep_xboundaries"] = True # Default
|
|
45
|
+
|
|
46
|
+
if "keep_yboundaries" in kwargs:
|
|
47
|
+
ds.metadata["keep_yboundaries"] = kwargs["keep_yboundaries"]
|
|
48
|
+
else:
|
|
49
|
+
ds.metadata["keep_yboundaries"] = False # Default
|
|
50
|
+
|
|
51
|
+
# Normalisation
|
|
52
|
+
meta = ds.attrs["metadata"]
|
|
53
|
+
Cs0 = meta["Cs0"]
|
|
54
|
+
Omega_ci = meta["Omega_ci"]
|
|
55
|
+
rho_s0 = meta["rho_s0"]
|
|
56
|
+
Nnorm = meta["Nnorm"]
|
|
57
|
+
Tnorm = meta["Tnorm"]
|
|
58
|
+
Bnorm = meta["Bnorm"]
|
|
59
|
+
|
|
60
|
+
# SI values
|
|
61
|
+
Mp = 1.67e-27 # Proton mass
|
|
62
|
+
e = 1.602e-19 # Coulombs
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
for varname in list(ds.data_vars) + list(ds.coords):
|
|
67
|
+
da = ds[varname]
|
|
68
|
+
if len(da.dims) == 4: # Time-evolving field
|
|
69
|
+
da.attrs["units_type"] = "hermes"
|
|
70
|
+
|
|
71
|
+
# Check if data already has units and conversion attributes
|
|
72
|
+
if ("units" in da.attrs) and ("conversion" in da.attrs):
|
|
73
|
+
if debug_variable_names is True:
|
|
74
|
+
print(varname + " already annotated")
|
|
75
|
+
continue # No need to add attributes
|
|
76
|
+
|
|
77
|
+
# Mark as Hermes-normalised data
|
|
78
|
+
da.attrs["units_type"] = "hermes"
|
|
79
|
+
|
|
80
|
+
if varname[:2] == "NV":
|
|
81
|
+
# Momentum
|
|
82
|
+
da.attrs.update(
|
|
83
|
+
{
|
|
84
|
+
"units": "kg m / s",
|
|
85
|
+
"conversion": Mp * Nnorm * Cs0,
|
|
86
|
+
"standard_name": "momentum",
|
|
87
|
+
"long_name": varname[2:] + " parallel momentum",
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
elif varname[0] == "N":
|
|
91
|
+
# Density
|
|
92
|
+
da.attrs.update(
|
|
93
|
+
{
|
|
94
|
+
"units": "m^-3",
|
|
95
|
+
"conversion": Nnorm,
|
|
96
|
+
"standard_name": "density",
|
|
97
|
+
"long_name": varname[1:] + " number density",
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
elif varname[0] == "T":
|
|
102
|
+
# Temperature
|
|
103
|
+
da.attrs.update(
|
|
104
|
+
{
|
|
105
|
+
"units": "eV",
|
|
106
|
+
"conversion": Tnorm,
|
|
107
|
+
"standard_name": "temperature",
|
|
108
|
+
"long_name": varname[1:] + " temperature",
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
elif varname[0] == "V":
|
|
113
|
+
# Velocity
|
|
114
|
+
da.attrs.update(
|
|
115
|
+
{
|
|
116
|
+
"units": "m / s",
|
|
117
|
+
"conversion": Cs0,
|
|
118
|
+
"standard_name": "velocity",
|
|
119
|
+
"long_name": varname[1:] + " parallel velocity",
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
elif varname[0] == "P":
|
|
124
|
+
# Pressure
|
|
125
|
+
da.attrs.update(
|
|
126
|
+
{
|
|
127
|
+
"units": "Pa",
|
|
128
|
+
"conversion": e * Tnorm * Nnorm,
|
|
129
|
+
"standard_name": "pressure",
|
|
130
|
+
"long_name": varname[1:] + " pressure",
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
elif varname == "phi":
|
|
134
|
+
# Potential
|
|
135
|
+
da.attrs.update(
|
|
136
|
+
{
|
|
137
|
+
"units": "V",
|
|
138
|
+
"conversion": Tnorm,
|
|
139
|
+
"standard_name": "potential",
|
|
140
|
+
"long_name": "Plasma potential",
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
# Don't know what this is
|
|
145
|
+
da.attrs["units_type"] = "unknown"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Coordinates
|
|
150
|
+
if varname == "dy":
|
|
151
|
+
# Poloidal cell angular width
|
|
152
|
+
da.attrs.update(
|
|
153
|
+
{
|
|
154
|
+
"units": "radian",
|
|
155
|
+
"conversion": 1,
|
|
156
|
+
"standard_name": "poloidal cell angular width",
|
|
157
|
+
"long_name": "Poloidal cell angular width",
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
elif varname == "dx":
|
|
161
|
+
# Radial cell width
|
|
162
|
+
da.attrs.update(
|
|
163
|
+
{
|
|
164
|
+
"units_type": "hermes",
|
|
165
|
+
"units": "Wb",
|
|
166
|
+
"conversion": rho_s0**2 * Bnorm,
|
|
167
|
+
"standard_name": "radial cell width",
|
|
168
|
+
"long_name": "Radial cell width in flux space",
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
elif varname == "dz":
|
|
172
|
+
# Radial cell width
|
|
173
|
+
da.attrs.update(
|
|
174
|
+
{
|
|
175
|
+
"units_type": "SI",
|
|
176
|
+
"units": "radian",
|
|
177
|
+
"conversion": 1,
|
|
178
|
+
"standard_name": "toroidal cell angular width",
|
|
179
|
+
"long_name": "Poloidal cell angular width",
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
elif varname == "J":
|
|
183
|
+
# Jacobian
|
|
184
|
+
da.attrs.update(
|
|
185
|
+
{
|
|
186
|
+
"units_type": "hermes",
|
|
187
|
+
"units": "m/radian T",
|
|
188
|
+
"conversion": rho_s0 / Bnorm,
|
|
189
|
+
"standard_name": "Jacobian",
|
|
190
|
+
"long_name": "Jacobian to translate from flux to cylindrical coordinates in real space",
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
elif varname == "g11":
|
|
194
|
+
# Metric tensor term
|
|
195
|
+
da.attrs.update(
|
|
196
|
+
{
|
|
197
|
+
"units_type": "hermes",
|
|
198
|
+
"units": "T2 m2",
|
|
199
|
+
"conversion": (Bnorm * rho_s0)**2,
|
|
200
|
+
"standard_name": "g11",
|
|
201
|
+
"long_name": "g11 term in the metric tensor",
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
elif varname == "g22":
|
|
205
|
+
# Metric tensor term
|
|
206
|
+
da.attrs.update(
|
|
207
|
+
{
|
|
208
|
+
"units_type": "hermes",
|
|
209
|
+
"units": "m-2",
|
|
210
|
+
"conversion": 1/(rho_s0)**2,
|
|
211
|
+
"standard_name": "g22",
|
|
212
|
+
"long_name": "g22 term in the metric tensor",
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
elif varname == "g33":
|
|
216
|
+
# Metric tensor term
|
|
217
|
+
da.attrs.update(
|
|
218
|
+
{
|
|
219
|
+
"units_type": "hermes",
|
|
220
|
+
"units": "m-2",
|
|
221
|
+
"conversion": 1/(rho_s0)**2,
|
|
222
|
+
"standard_name": "g33",
|
|
223
|
+
"long_name": "g33 term in the metric tensor",
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
elif varname == "g12":
|
|
227
|
+
# Metric tensor term
|
|
228
|
+
da.attrs.update(
|
|
229
|
+
{
|
|
230
|
+
"units_type": "hermes",
|
|
231
|
+
"units": "T",
|
|
232
|
+
"conversion": Bnorm,
|
|
233
|
+
"standard_name": "g12",
|
|
234
|
+
"long_name": "g12 term in the metric tensor",
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
elif varname == "g13":
|
|
238
|
+
# Metric tensor term
|
|
239
|
+
da.attrs.update(
|
|
240
|
+
{
|
|
241
|
+
"units_type": "hermes",
|
|
242
|
+
"units": "T",
|
|
243
|
+
"conversion": Bnorm,
|
|
244
|
+
"standard_name": "g13",
|
|
245
|
+
"long_name": "g13 term in the metric tensor",
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
elif varname == "g23":
|
|
249
|
+
# Metric tensor term
|
|
250
|
+
da.attrs.update(
|
|
251
|
+
{
|
|
252
|
+
"units_type": "hermes",
|
|
253
|
+
"units": "m-2",
|
|
254
|
+
"conversion": 1/(rho_s0)**2,
|
|
255
|
+
"standard_name": "g23",
|
|
256
|
+
"long_name": "g23 term in the metric tensor",
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
elif varname == "g_11":
|
|
260
|
+
# Metric tensor term
|
|
261
|
+
da.attrs.update(
|
|
262
|
+
{
|
|
263
|
+
"units_type": "hermes",
|
|
264
|
+
"units": "T-2m-2",
|
|
265
|
+
"conversion": 1/(Bnorm * rho_s0)**2,
|
|
266
|
+
"standard_name": "g_11",
|
|
267
|
+
"long_name": "g_11 term in the metric tensor",
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
elif varname == "g_22":
|
|
271
|
+
# Metric tensor term
|
|
272
|
+
da.attrs.update(
|
|
273
|
+
{
|
|
274
|
+
"units_type": "hermes",
|
|
275
|
+
"units": "m2",
|
|
276
|
+
"conversion": (rho_s0)**2,
|
|
277
|
+
"standard_name": "g_22",
|
|
278
|
+
"long_name": "g_22 term in the metric tensor",
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
elif varname == "g_33":
|
|
282
|
+
# Metric tensor term
|
|
283
|
+
da.attrs.update(
|
|
284
|
+
{
|
|
285
|
+
"units_type": "hermes",
|
|
286
|
+
"units": "m2",
|
|
287
|
+
"conversion": (rho_s0)**2,
|
|
288
|
+
"standard_name": "g_33",
|
|
289
|
+
"long_name": "g_33 term in the metric tensor",
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
elif varname == "g_12":
|
|
293
|
+
# Metric tensor term
|
|
294
|
+
da.attrs.update(
|
|
295
|
+
{
|
|
296
|
+
"units_type": "hermes",
|
|
297
|
+
"units": "T-1",
|
|
298
|
+
"conversion": 1/Bnorm,
|
|
299
|
+
"standard_name": "g_12",
|
|
300
|
+
"long_name": "g_12 term in the metric tensor",
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
elif varname == "g_13":
|
|
304
|
+
# Metric tensor term
|
|
305
|
+
da.attrs.update(
|
|
306
|
+
{
|
|
307
|
+
"units_type": "hermes",
|
|
308
|
+
"units": "T-1",
|
|
309
|
+
"conversion": 1/Bnorm,
|
|
310
|
+
"standard_name": "g_13",
|
|
311
|
+
"long_name": "g_13 term in the metric tensor",
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
elif varname == "g_23":
|
|
315
|
+
# Metric tensor term
|
|
316
|
+
da.attrs.update(
|
|
317
|
+
{
|
|
318
|
+
"units_type": "hermes",
|
|
319
|
+
"units": "m2",
|
|
320
|
+
"conversion": (rho_s0)**2,
|
|
321
|
+
"standard_name": "g_23",
|
|
322
|
+
"long_name": "g_23 term in the metric tensor",
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
elif varname == "t" or varname == "t_array":
|
|
326
|
+
# Time
|
|
327
|
+
da.attrs.update(
|
|
328
|
+
{
|
|
329
|
+
"units_type": "hermes",
|
|
330
|
+
"units": "s",
|
|
331
|
+
"conversion": 1/Omega_ci,
|
|
332
|
+
"standard_name": "time",
|
|
333
|
+
"long_name": "Time",
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if unnormalise:
|
|
338
|
+
ds.hermes.unnormalise()
|
|
339
|
+
|
|
340
|
+
if ds.attrs["options"] is not None:
|
|
341
|
+
# Process options
|
|
342
|
+
options = ds.attrs["options"]
|
|
343
|
+
|
|
344
|
+
# List of components
|
|
345
|
+
component_list = [
|
|
346
|
+
c.strip() for c in options["hermes"]["components"].strip(" ()\t").split(",")
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
# Turn into a dictionary
|
|
350
|
+
components = {}
|
|
351
|
+
for component in component_list:
|
|
352
|
+
if component in options:
|
|
353
|
+
c_opts = options[component]
|
|
354
|
+
if "type" in c_opts:
|
|
355
|
+
c_type = c_opts["type"]
|
|
356
|
+
else:
|
|
357
|
+
c_type = component # Type is the name of the component
|
|
358
|
+
else:
|
|
359
|
+
c_opts = None
|
|
360
|
+
c_type = component
|
|
361
|
+
components[component] = {"type": c_type, "options": c_opts}
|
|
362
|
+
ds.attrs["components"] = components
|
|
363
|
+
|
|
364
|
+
# Identify dimensions
|
|
365
|
+
dims = list(ds.squeeze().dims)
|
|
366
|
+
if "t" in dims: dims.remove("t")
|
|
367
|
+
meta["dimensions"] = len(dims)
|
|
368
|
+
|
|
369
|
+
# Identify species
|
|
370
|
+
meta["species"] = [x.split("P")[1] for x in ds.data_vars if x.startswith("P") and len(x) < 4]
|
|
371
|
+
meta["charged_species"] = [x for x in meta["species"] if "e" in x or "+" in x]
|
|
372
|
+
meta["ion_species"] = [x for x in meta["species"] if "+" in x]
|
|
373
|
+
meta["neutral_species"] = list(set(meta["species"]).difference(set(meta["charged_species"])))
|
|
374
|
+
|
|
375
|
+
# Prepare dictionary mapping recycling species pairs
|
|
376
|
+
if "recycling" in ds.attrs["components"]:
|
|
377
|
+
meta["recycling_pairs"] = dict()
|
|
378
|
+
for ion in meta["ion_species"]:
|
|
379
|
+
if "recycle_as" in ds.options[ion].keys():
|
|
380
|
+
meta["recycling_pairs"][ion] = ds.options[ion]["recycle_as"]
|
|
381
|
+
else:
|
|
382
|
+
print(f"No recycling partner found for {ion}")
|
|
383
|
+
|
|
384
|
+
return ds
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def open(
|
|
388
|
+
path,
|
|
389
|
+
**kwargs,
|
|
390
|
+
):
|
|
391
|
+
"""
|
|
392
|
+
Open a data directory containing Hermes output files.
|
|
393
|
+
|
|
394
|
+
Example:
|
|
395
|
+
```
|
|
396
|
+
import xhermes
|
|
397
|
+
ds = xhermes.open('data')
|
|
398
|
+
```
|
|
399
|
+
where 'data' is a directory.
|
|
400
|
+
|
|
401
|
+
Can also load a grid file containing geometry information:
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
```
|
|
405
|
+
bd = xhermes.open(".", geometry="toroidal", gridfilepath="../tokamak.nc")
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Parameters
|
|
409
|
+
----------
|
|
410
|
+
|
|
411
|
+
path : string
|
|
412
|
+
A directory containing BOUT.dmp.* files and a BOUT.settings file
|
|
413
|
+
|
|
414
|
+
All other parameters are passed through to `open_hermesdataset`
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
|
|
419
|
+
ds : xarray.Dataset
|
|
420
|
+
"""
|
|
421
|
+
return open_hermesdataset(
|
|
422
|
+
datapath=os.path.join(path, "BOUT.dmp.*.nc"),
|
|
423
|
+
inputfilepath=os.path.join(path, "BOUT.settings"),
|
|
424
|
+
**kwargs,
|
|
425
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import xarray
|
|
2
|
+
|
|
3
|
+
def guard_replace_1d(da):
|
|
4
|
+
"""
|
|
5
|
+
Replace the inner guard cells with the values of their respective
|
|
6
|
+
cell edges, i.e. the values at the model inlet and at the target.
|
|
7
|
+
This is done by interpolating the value between the two neighbouring
|
|
8
|
+
cell centres. Checks for presence of guard cells if passed a DataArray.
|
|
9
|
+
|
|
10
|
+
Cell order at target:
|
|
11
|
+
... | last | guard | second guard (unused)
|
|
12
|
+
^target
|
|
13
|
+
| -3 | -2 | -1
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
- da: Numpy array or Xarray DataArray with guard cells
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
----------
|
|
21
|
+
- Numpy array with guard replacement
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
da = da.copy()
|
|
26
|
+
|
|
27
|
+
if type(da) == xarray.core.dataarray.DataArray:
|
|
28
|
+
if da.metadata["keep_yboundaries"] is False:
|
|
29
|
+
raise ValueError("Cannot guard replace if y-boundaries are not kept")
|
|
30
|
+
|
|
31
|
+
if da.name in da.coords:
|
|
32
|
+
raise ValueError("Cannot guard replace DataArray if it is a coordinate, try passing da.values() instead")
|
|
33
|
+
|
|
34
|
+
da[{"pos" : -2}] = (da[{"pos" : -2}] + da[{"pos" : -3}])/2
|
|
35
|
+
da[{"pos" : 1}] = (da[{"pos" : 1}] + da[{"pos" : 2}])/2
|
|
36
|
+
|
|
37
|
+
da = da.isel(pos = slice(1, -1))
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
da[-2] = (da[-2] + da[-3])/2
|
|
41
|
+
da[1] = (da[1] + da[2])/2
|
|
42
|
+
da = da[1:-1]
|
|
43
|
+
|
|
44
|
+
return da
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xhermes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Analyse data from Hermes-3 simulations using xarray
|
|
5
|
+
License: Apache
|
|
6
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
7
|
+
Classifier: Intended Audience :: Science/Research
|
|
8
|
+
Classifier: Intended Audience :: Education
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: xarray
|
|
17
|
+
Requires-Dist: xbout
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# xHermes
|
|
26
|
+
|
|
27
|
+
xHermes is a post-processing package for Hermes-3 in 1D, 2D and 3D which provides automatic conversion to SI units and many useful plotting routines.
|
|
28
|
+
|
|
29
|
+
xHermes a wrapper around [xBOUT](https://github.com/boutproject/xBOUT) to provide [Hermes-3](https://github.com/bendudson/hermes-3) specific
|
|
30
|
+
features. At the moment, xHermes is used for loading in the Hermes-3 results, performing SI unit conversion and calculating some geometry terms.
|
|
31
|
+
You may still want to use xBOUT, in particular if you are running 2D and 3D cases and want plotting routines for those.
|
|
32
|
+
|
|
33
|
+
There is also a collection of Mike Kryjak's personal post-processing scripts [sdtools](https://github.com/mikekryjak/sdtools/tree/main/hermes3) which have extensive additional functionality for 1D and 2D postprocessing.
|
|
34
|
+
These scripts are not officially supported, but feel free to take a look.
|
|
35
|
+
It is intended for all those additional features to be merged into xHermes over time.
|
|
36
|
+
|
|
37
|
+
Both xBOUT and xHermes use [Xarray](https://docs.xarray.dev/en/stable/) which provides a scalable and powerful framework
|
|
38
|
+
for dealing with large amounts of data while preserving dimensional
|
|
39
|
+
consistency. If you have ever used [Pandas](https://pandas.pydata.org/), you will find Xarray very familiar, as it is effectively Pandas in more dimensions.
|
|
40
|
+
|
|
41
|
+
While Xarray is based on Numpy arrays under the hood and the data
|
|
42
|
+
can be accessed in this way, Xarray's main benefit comes in its
|
|
43
|
+
powerful abilities to select, filter and operate on data. The
|
|
44
|
+
syntax is similar to Pandas and the basics are explained in
|
|
45
|
+
Xarray's excellent documentation.
|
|
46
|
+
If you are new to Xarray, it may be useful to start with reviewing the [selection syntax](https://docs.xarray.dev/en/stable/user-guide/indexing.html).
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Hermes-3 is pip installable. In the terminal with your Python environment enabled, run:
|
|
51
|
+
|
|
52
|
+
pip install xhermes
|
|
53
|
+
|
|
54
|
+
However, you may not be able to get the latest version this way. For now, it is highly recommended you do an editable install instead:
|
|
55
|
+
|
|
56
|
+
git clone https://github.com/boutproject/xhermes
|
|
57
|
+
cd xhermes
|
|
58
|
+
pip install -e .
|
|
59
|
+
|
|
60
|
+
In this way, pip will install the Python package from the xHermes folder you cloned. If you use git to checkout branches or modify the contents in any way, it will be automatically work.
|
|
61
|
+
|
|
62
|
+
## Loading simulations
|
|
63
|
+
|
|
64
|
+
The data from a Hermes-3 run can be loaded with just
|
|
65
|
+
|
|
66
|
+
import xhermes
|
|
67
|
+
ds = xhermes.open(hermes_simulation_path)
|
|
68
|
+
|
|
69
|
+
where the `hermes_simulation_path` directory is assumed to contain the `BOUT.dmp.*.nc`
|
|
70
|
+
files and a `BOUT.settings` file. `open` returns an instance of an
|
|
71
|
+
`xarray.Dataset` which contains BOUT- and Hermes-specific information
|
|
72
|
+
in the `attrs`, so represents a general structure for storing all of
|
|
73
|
+
the output of a simulation, including data, input options and (soon)
|
|
74
|
+
grid data.
|
|
75
|
+
|
|
76
|
+
When working with 2D or 3D simulations of tokamaks, it can be useful to
|
|
77
|
+
load the grid along with the simulation file and pass the "toroidal" flag to xBOUT:
|
|
78
|
+
|
|
79
|
+
import xhermes
|
|
80
|
+
ds = xhermes.open(hermes_simulation_path, gridfilepath = grid_file_path, geometry = "toroidal")
|
|
81
|
+
|
|
82
|
+
This loads the metric tensor and grid corner coordinates from the grid which
|
|
83
|
+
enables geometrical operations, polygonal plotting routines and additional metadata on topology.
|
|
84
|
+
|
|
85
|
+
## Working with the data
|
|
86
|
+
|
|
87
|
+
### Normalisation and geometry extraction
|
|
88
|
+
|
|
89
|
+
All variables within Hermes-3 are normalised. The normalisation factors, the units and variable names
|
|
90
|
+
are provided in the Hermes-3 output for most variables.
|
|
91
|
+
This information can be accessed for each variable DataArray:
|
|
92
|
+
|
|
93
|
+
ds["Pe"].attrs
|
|
94
|
+
|
|
95
|
+
xHermes automatically applies the unnormalisation when the simulation is loaded in.
|
|
96
|
+
|
|
97
|
+
When working with 1D and 2D tokamak simulations, it can be useful to extract
|
|
98
|
+
cell lengths, volumes and cross-sectional areas as well as some additional metadata from the model:
|
|
99
|
+
|
|
100
|
+
ds = ds.hermes.extract_1D_tokamak_geometry()
|
|
101
|
+
|
|
102
|
+
or
|
|
103
|
+
|
|
104
|
+
ds = ds.hermes.extract_2D_tokamak_geometry()
|
|
105
|
+
|
|
106
|
+
### The Dataset
|
|
107
|
+
|
|
108
|
+
The Dataset `ds` contains useful objects:
|
|
109
|
+
- A DataArray for each variable: `ds.data_vars` shows a list. `ds["Pe"]` returns the Pe DataArray.
|
|
110
|
+
- A DataArray for each coordinate: `ds.coords` shows a list. `ds["t"]` or `ds.coords["t"]` returns the t DataArray.
|
|
111
|
+
- A summary of dimensions in the model: `ds.dims`
|
|
112
|
+
- Model metadata: `ds.metadata`
|
|
113
|
+
- Model input settings: `ds.options`
|
|
114
|
+
|
|
115
|
+
### The DataArray
|
|
116
|
+
|
|
117
|
+
The DataArray (e.g. `ds["Pe"]`) contains the array with the variable data, its dimensionality and
|
|
118
|
+
attributes as well as metadata and options inherited from the parent dataset.
|
|
119
|
+
|
|
120
|
+
A visual summary of the DataArray contents and size can be obtained by simply:
|
|
121
|
+
|
|
122
|
+
ds["Pe"]
|
|
123
|
+
|
|
124
|
+
Note that this may be slow due to the Xarray overhead. To access the underlying data as a Numpy array, one can use:
|
|
125
|
+
|
|
126
|
+
ds["Pe"].values
|
|
127
|
+
|
|
128
|
+
or
|
|
129
|
+
|
|
130
|
+
ds["Pe"].data
|
|
131
|
+
|
|
132
|
+
### Plotting
|
|
133
|
+
Xarray contains a lot of built-in plotting routines which can be used to prepare [excellent 1D and 2D plots](https://docs.xarray.dev/en/stable/user-guide/plotting.html).
|
|
134
|
+
Unfortunately, it is unable to deal with 2D plots in tokamak geometry.
|
|
135
|
+
Routines to plot those are available in xBOUT which has some excellent [example notebooks](https://github.com/boutproject/xBOUT-examples).
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
xhermes/__init__.py
|
|
4
|
+
xhermes/accessors.py
|
|
5
|
+
xhermes/load.py
|
|
6
|
+
xhermes/utils.py
|
|
7
|
+
xhermes.egg-info/PKG-INFO
|
|
8
|
+
xhermes.egg-info/SOURCES.txt
|
|
9
|
+
xhermes.egg-info/dependency_links.txt
|
|
10
|
+
xhermes.egg-info/requires.txt
|
|
11
|
+
xhermes.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xhermes
|