Skip to content

Commit d416767

Browse files
authored
Merge pull request #3 from entity-toolkit/0.5.0rc
v0.5.0 Release Candidate
2 parents c248359 + f57b3ea commit d416767

24 files changed

+2170
-1073
lines changed

.gitignore

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,6 @@ dmypy.json
151151
# Cython debug symbols
152152
cython_debug/
153153

154-
# PyCharm
155-
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
156-
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
157-
# and can be added to the global gitignore or merged into this file. For a more nuclear
158-
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
159-
#.idea/
160154
test/
161-
temp/
155+
temp/
156+
*.bak

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 3-Clause License
22

3-
Copyright (c) 2023, Entity toolkit
3+
Copyright (c) 2025, Entity development team
44

55
Redistribution and use in source and binary forms, with or without
66
modification, are permitted provided that the following conditions are met:

README.md

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,114 @@
11
## nt2.py
22

3-
Python package for visualization and post-processing of the [`Entity`](https://github.com/entity-toolkit/entity) simulation data. For usage, please refer to the [documentation](https://entity-toolkit.github.io/entity/howto/vis/#nt2py). The package is distributed via [`PyPI`](https://pypi.org/project/nt2py/):
3+
Python package for visualization and post-processing of the [`Entity`](https://github.com/entity-toolkit/entity) simulation data. For usage, please refer to the [documentation](https://entity-toolkit.github.io/wiki/getting-started/vis/#nt2py). The package is distributed via [`PyPI`](https://pypi.org/project/nt2py/):
44

55
```sh
66
pip install nt2py
77
```
88

9+
### Usage
10+
11+
The Library works both with single-file output as well as with separate files. In either case, the location of the data is passed via `path` keyword argument.
12+
13+
```python
14+
import nt2
15+
16+
data = nt2.Data(path="path/to/data")
17+
# example:
18+
# data = nt2.Data(path="path/to/shock.h5") : for single-file
19+
# data = nt2.Data(path="path/to/shock") : for multi-file
20+
```
21+
22+
The data is stored in specialized containers which can be accessed via corresponding attributes:
23+
24+
```python
25+
data.fields # < xr.Dataset
26+
data.particles # < dict[int : xr.Dataset]
27+
data.spectra # < xr.Dataset
28+
```
29+
30+
#### Examples
31+
32+
Plot a field (in cartesian space) at a specific time (or output step):
33+
34+
```python
35+
data.fields.Ex.sel(t=10.0, method="nearest").plot() # time ~ 10
36+
data.fields.Ex.isel(t=5).plot() # output step = 5
37+
```
38+
39+
Plot a slice or time-averaged field quantities:
40+
41+
```python
42+
data.fields.Bz.mean("t").plot()
43+
data.fields.Bz.sel(t=10.0, x=0.5, method="nearest").plot()
44+
```
45+
46+
Plot in spherical coordinates (+ combine several fields):
47+
48+
```python
49+
e_dot_b = (data.fields.Er * data.fields.Br +\
50+
data.fields.Eth * data.fields.Bth +\
51+
data.fields.Eph * data.fields.Bph)
52+
bsqr = data.fields.Br**2 + data.fields.Bth**2 + data.fields.Bph**2
53+
# only plot radial extent of up to 10
54+
(e_dot_b / bsqr).sel(t=50.0, method="nearest").sel(r=slice(None, 10)).polar.pcolor()
55+
```
56+
57+
You can also quickly plot the fields at a specific time using the handy `.inspect` accessor:
58+
59+
```python
60+
data.fields\
61+
.sel(t=3.0, method="nearest")\
62+
.sel(x=slice(-0.2, 0.2))\
63+
.inspect.plot(only_fields=["E", "B"])
64+
# Hint: use `<...>.plot?` to see all options
65+
```
66+
67+
Or if no time is specified, it will create a quick movie (need to also provide a `name` in that case):
68+
69+
```python
70+
data.fields\
71+
.sel(x=slice(-0.2, 0.2))\
72+
.inspect.plot(name="inspect", only_fields=["E", "B", "N"])
73+
```
74+
75+
You can also create a movie of a single field quantity (can be custom):
76+
77+
```python
78+
(data.fields.Ex * data.fields.Bx).sel(x=slice(None, 0.2)).movie.plot(name="ExBx", vmin=-0.01, vmax=0.01, cmap="BrBG")
79+
```
80+
81+
You may also combine different quantities and plots (e.g., fields & particles) to produce a more customized movie:
82+
83+
```python
84+
def plot(t, data):
85+
fig, ax = mpl.pyplot.subplots()
86+
data.fields.Ex.sel(t=t, method="nearest").sel(x=slice(None, 0.2)).plot(
87+
ax=ax, vmin=-0.001, vmax=0.001, cmap="BrBG"
88+
)
89+
for sp in range(1, 3):
90+
ax.scatter(
91+
data.particles[sp].sel(t=t, method="nearest").x,
92+
data.particles[sp].sel(t=t, method="nearest").y,
93+
c="r" if sp == 1 else "b",
94+
)
95+
ax.set_aspect(1)
96+
data.makeMovie(plot)
97+
```
98+
99+
> If using Jupyter notebook, you can quickly preview the loaded metadata by simply running a cell with just `data` in it (or in regular python, by doing `print(data)`).
100+
101+
### Dashboard
102+
103+
Support for the dask dashboard is still in beta, but you can access it by first launching the dashboard client:
104+
105+
```python
106+
import nt2
107+
nt2.Dashboard()
108+
```
109+
110+
This will output the port where the dashboard server is running, e.g., `Dashboard: http://127.0.0.1:8787/status`. Click on it (or enter in your browser) to open the dashboard.
111+
9112
### Features
10113

11114
1. Lazy loading and parallel processing of the simulation data with [`dask`](https://dask.org/).
@@ -16,4 +119,4 @@ pip install nt2py
16119

17120
- [ ] Unit tests
18121
- [ ] Plugins for other simulation data formats
19-
- [ ] Usage examples
122+
- [x] Usage examples

dist/nt2py-0.5.0-py3-none-any.whl

25.6 KB
Binary file not shown.

dist/nt2py-0.5.0.tar.gz

21 KB
Binary file not shown.

nt2/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
__version__ = "0.4.1"
1+
__version__ = "0.5.0"
2+
3+
from nt2.data import Data as Data
4+
from nt2.dashboard import Dashboard as Dashboard

nt2/containers/__init__.py

Whitespace-only changes.

nt2/containers/container.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import os
2+
import h5py
3+
import numpy as np
4+
from typing import Any
5+
6+
7+
def _read_attribs_SingleFile(file: h5py.File):
8+
attribs = {}
9+
for k in file.attrs.keys():
10+
attr = file.attrs[k]
11+
if type(attr) is bytes or type(attr) is np.bytes_:
12+
attribs[k] = attr.decode("UTF-8")
13+
else:
14+
attribs[k] = attr
15+
return attribs
16+
17+
18+
class Container:
19+
"""
20+
* * * * Container * * * *
21+
22+
Parent class for all data containers.
23+
24+
Args
25+
----
26+
path : str
27+
The path to the data.
28+
29+
Kwargs
30+
------
31+
single_file : bool, optional
32+
Whether the data is stored in a single file. Default is False.
33+
34+
pickle : bool, optional
35+
Whether to use pickle for reading the data. Default is True.
36+
37+
greek : bool, optional
38+
Whether to use Greek letters for the spherical coordinates. Default is False.
39+
40+
dask_props : dict, optional
41+
Additional properties for Dask [NOT IMPLEMENTED]. Default is {}.
42+
43+
Attributes
44+
----------
45+
path : str
46+
The path to the data.
47+
48+
configs : dict
49+
The configuration settings for the data.
50+
51+
metadata : dict
52+
The metadata for the data.
53+
54+
mesh : dict
55+
Coordinate grid of the domain (cell-centered & edges).
56+
57+
master_file : h5py.File
58+
The master file for the data (from which the main attributes are read).
59+
60+
attrs : dict
61+
The attributes of the master file.
62+
63+
Methods
64+
-------
65+
plotGrid(ax, **kwargs)
66+
Plots the gridlines of the domain.
67+
68+
"""
69+
70+
def __init__(
71+
self, path, single_file=False, pickle=True, greek=False, dask_props={}
72+
):
73+
super(Container, self).__init__()
74+
75+
self.configs: dict[str, Any] = {
76+
"single_file": single_file,
77+
"use_pickle": pickle,
78+
"use_greek": greek,
79+
}
80+
self.path = path
81+
self.metadata = {}
82+
self.mesh = None
83+
if self.configs["single_file"]:
84+
try:
85+
self.master_file: h5py.File | None = h5py.File(self.path, "r")
86+
except OSError:
87+
raise OSError(f"Could not open file {self.path}")
88+
else:
89+
field_path = os.path.join(self.path, "fields")
90+
file = os.path.join(field_path, os.listdir(field_path)[0])
91+
try:
92+
self.master_file: h5py.File | None = h5py.File(file, "r")
93+
except OSError:
94+
raise OSError(f"Could not open file {file}")
95+
96+
self.attrs = _read_attribs_SingleFile(self.master_file)
97+
98+
self.configs["ngh"] = int(self.master_file.attrs.get("NGhosts", 0))
99+
self.configs["layout"] = (
100+
"right" if self.master_file.attrs.get("LayoutRight", 1) == 1 else "left"
101+
)
102+
self.configs["dimension"] = int(self.master_file.attrs.get("Dimension", 1))
103+
self.configs["coordinates"] = self.master_file.attrs.get(
104+
"Coordinates", b"cart"
105+
).decode("UTF-8")
106+
if self.configs["coordinates"] == "qsph":
107+
self.configs["coordinates"] = "sph"
108+
109+
if not self.configs["single_file"]:
110+
self.master_file.close()
111+
self.master_file = None
112+
113+
def __del__(self):
114+
if self.master_file is not None:
115+
self.master_file.close()
116+
117+
def plotGrid(self, ax, **kwargs):
118+
from matplotlib import patches
119+
120+
xlim, ylim = ax.get_xlim(), ax.get_ylim()
121+
options = {
122+
"lw": 1,
123+
"color": "k",
124+
"ls": "-",
125+
}
126+
options.update(kwargs)
127+
128+
if self.configs["coordinates"] == "cart":
129+
for x in self.attrs["X1"]:
130+
ax.plot([x, x], [self.attrs["X2Min"], self.attrs["X2Max"]], **options)
131+
for y in self.attrs["X2"]:
132+
ax.plot([self.attrs["X1Min"], self.attrs["X1Max"]], [y, y], **options)
133+
else:
134+
for r in self.attrs["X1"]:
135+
ax.add_patch(
136+
patches.Arc(
137+
(0, 0),
138+
2 * r,
139+
2 * r,
140+
theta1=-90,
141+
theta2=90,
142+
fill=False,
143+
**options,
144+
)
145+
)
146+
for th in self.attrs["X2"]:
147+
ax.plot(
148+
[
149+
self.attrs["X1Min"] * np.sin(th),
150+
self.attrs["X1Max"] * np.sin(th),
151+
],
152+
[
153+
self.attrs["X1Min"] * np.cos(th),
154+
self.attrs["X1Max"] * np.cos(th),
155+
],
156+
**options,
157+
)
158+
ax.set(xlim=xlim, ylim=ylim)

0 commit comments

Comments
 (0)