sdf-xarray 0.2.3__tar.gz → 0.2.4__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.
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/CITATION.cff +1 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/PKG-INFO +2 -2
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/README.md +1 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/pyproject.toml +2 -2
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/src/sdf_xarray/_version.py +2 -2
- sdf_xarray-0.2.4/src/sdf_xarray/plotting.py +205 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/src/sdf_xarray/sdf_interface.pyx +1 -1
- sdf_xarray-0.2.4/tests/example_files_2D_moving_window/0000.sdf +0 -0
- sdf_xarray-0.2.4/tests/example_files_2D_moving_window/0001.sdf +0 -0
- sdf_xarray-0.2.4/tests/example_files_2D_moving_window/0002.sdf +0 -0
- sdf_xarray-0.2.4/tests/example_files_2D_moving_window/0003.sdf +0 -0
- sdf_xarray-0.2.4/tests/example_files_2D_moving_window/0004.sdf +0 -0
- sdf_xarray-0.2.4/tests/example_files_2D_moving_window/input.deck +63 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/test_basic.py +1 -1
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/test_cython.py +1 -1
- sdf_xarray-0.2.4/tests/test_epoch_accessor.py +183 -0
- sdf_xarray-0.2.3/src/sdf_xarray/plotting.py +0 -184
- sdf_xarray-0.2.3/tests/test_epoch_accessor.py +0 -39
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.github/workflows/black.yml +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.github/workflows/build_publish.yml +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.github/workflows/lint.yml +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.github/workflows/tests.yml +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.gitignore +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.gitmodules +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/.readthedocs.yaml +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/BEAM.png +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/CMakeLists.txt +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/CONTRIBUTING.md +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/LICENCE +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/PlasmaFAIR.svg +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/.gitignore +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/_templates/custom-class-template.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/_templates/custom-module-template.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/api.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/conf.py +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/contributing.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/getting_started.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/index.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/key_functionality.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/make.bat +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0000.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0001.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0002.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0003.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0004.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0005.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0006.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0007.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0008.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0009.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0010.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0011.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0012.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0013.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0014.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0015.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0016.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0017.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0018.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0019.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0020.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0021.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0022.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0023.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0024.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0025.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0026.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0027.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0028.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0029.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0030.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0031.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0032.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0033.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0034.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0035.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0036.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0037.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0038.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0039.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/0040.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/deck.status +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/epoch1d.dat +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/input.deck +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/normal.visit +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/tutorial_dataset_1d/restart.visit +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/docs/unit_conversion.rst +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/src/sdf_xarray/__init__.py +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/src/sdf_xarray/csdf.pxd +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_array_no_grids/0000.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_array_no_grids/0001.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_array_no_grids/README.md +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_array_no_grids/input.deck +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_dist_fn/0000.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_dist_fn/0001.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_dist_fn/0002.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_dist_fn/input.deck +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0000.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0001.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0002.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0003.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0004.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0005.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0006.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0007.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0008.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0009.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/0010.sdf +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/README.md +0 -0
- {sdf_xarray-0.2.3/tests/example_files → sdf_xarray-0.2.4/tests/example_files_1D}/input.deck +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_mismatched_files/0000.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_mismatched_files/0001.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/tests/example_mismatched_files/0002.sdf +0 -0
- {sdf_xarray-0.2.3 → sdf_xarray-0.2.4}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: sdf-xarray
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Provides a backend for xarray to read SDF files as created by the EPOCH plasma PIC code.
|
|
5
5
|
Author-Email: Peter Hill <peter.hill@york.ac.uk>, Joel Adams <joel.adams@york.ac.uk>, Shaun Doherty <shaun.doherty@york.ac.uk>
|
|
6
6
|
License: Copyright 2024, Peter Hill, Joel Adams, epochpic team
|
|
@@ -45,7 +45,6 @@ Requires-Python: >=3.10
|
|
|
45
45
|
Requires-Dist: numpy>=2.0.0
|
|
46
46
|
Requires-Dist: xarray>=2024.1.0
|
|
47
47
|
Requires-Dist: dask>=2024.7.1
|
|
48
|
-
Requires-Dist: cython>=3.0
|
|
49
48
|
Provides-Extra: docs
|
|
50
49
|
Requires-Dist: sphinx>=5.3; extra == "docs"
|
|
51
50
|
Requires-Dist: sphinx_autodoc_typehints>=1.19; extra == "docs"
|
|
@@ -78,6 +77,7 @@ Description-Content-Type: text/markdown
|
|
|
78
77
|
|
|
79
78
|

|
|
80
79
|
[](https://pypi.org/project/sdf-xarray/)
|
|
80
|
+
[](https://doi.org/10.5281/zenodo.15351323)
|
|
81
81
|

|
|
82
82
|

|
|
83
83
|
[](https://sdf-xarray.readthedocs.io)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
[](https://pypi.org/project/sdf-xarray/)
|
|
5
|
+
[](https://doi.org/10.5281/zenodo.15351323)
|
|
5
6
|

|
|
6
7
|

|
|
7
8
|
[](https://sdf-xarray.readthedocs.io)
|
|
@@ -3,7 +3,7 @@ requires = [
|
|
|
3
3
|
"scikit-build-core>=0.3",
|
|
4
4
|
"setuptools_scm",
|
|
5
5
|
"numpy>=2.0.0",
|
|
6
|
-
"cython
|
|
6
|
+
"cython~=3.0",
|
|
7
7
|
]
|
|
8
8
|
build-backend = "scikit_build_core.build"
|
|
9
9
|
|
|
@@ -22,7 +22,6 @@ dependencies = [
|
|
|
22
22
|
"numpy>=2.0.0",
|
|
23
23
|
"xarray>=2024.1.0",
|
|
24
24
|
"dask>=2024.7.1",
|
|
25
|
-
"cython>=3.0",
|
|
26
25
|
]
|
|
27
26
|
description = "Provides a backend for xarray to read SDF files as created by the EPOCH plasma PIC code."
|
|
28
27
|
classifiers = [
|
|
@@ -105,4 +104,5 @@ extend-select = [
|
|
|
105
104
|
ignore = [
|
|
106
105
|
"PLR2004", # magic-comparison
|
|
107
106
|
"B9", # flake8-bugbear opinionated warnings
|
|
107
|
+
"PLR0913", # remove maximum number of arguments in a function
|
|
108
108
|
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import xarray as xr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
from matplotlib.animation import FuncAnimation
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_frame_title(
|
|
14
|
+
data: xr.DataArray,
|
|
15
|
+
frame: int,
|
|
16
|
+
display_sdf_name: bool = False,
|
|
17
|
+
title_custom: str | None = None,
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Generate the title for a frame"""
|
|
20
|
+
# Adds custom text to the start of the title, if specified
|
|
21
|
+
title_custom = "" if title_custom is None else f"{title_custom}, "
|
|
22
|
+
# Adds the time and associated units to the title
|
|
23
|
+
time = data["time"][frame].to_numpy()
|
|
24
|
+
|
|
25
|
+
time_units = data["time"].attrs.get("units", False)
|
|
26
|
+
time_units_formatted = f" [{time_units}]" if time_units else ""
|
|
27
|
+
title_time = f"time = {time:.2e}{time_units_formatted}"
|
|
28
|
+
|
|
29
|
+
# Adds sdf name to the title, if specifed
|
|
30
|
+
title_sdf = f", {frame:04d}.sdf" if display_sdf_name else ""
|
|
31
|
+
return f"{title_custom}{title_time}{title_sdf}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def calculate_window_boundaries(
|
|
35
|
+
data: xr.DataArray, xlim: tuple[float, float] | False = False
|
|
36
|
+
) -> np.ndarray:
|
|
37
|
+
"""Calculate the bounderies a moving window frame. If the user specifies xlim, this will
|
|
38
|
+
be used as the initial bounderies and the window will move along acordingly.
|
|
39
|
+
"""
|
|
40
|
+
x_grid = data["X_Grid_mid"].values
|
|
41
|
+
x_half_cell = (x_grid[1] - x_grid[0]) / 2
|
|
42
|
+
N_frames = data["time"].size
|
|
43
|
+
|
|
44
|
+
# Find the window bounderies by finding the first and last non-NaN values in the 0th lineout
|
|
45
|
+
# along the x-axis.
|
|
46
|
+
window_boundaries = np.zeros((N_frames, 2))
|
|
47
|
+
for i in range(N_frames):
|
|
48
|
+
# Check if data is 1D
|
|
49
|
+
if data.ndim == 2:
|
|
50
|
+
target_lineout = data[i].values
|
|
51
|
+
# Check if data is 2D
|
|
52
|
+
if data.ndim == 3:
|
|
53
|
+
target_lineout = data[i, :, 0].values
|
|
54
|
+
x_grid_non_nan = x_grid[~np.isnan(target_lineout)]
|
|
55
|
+
window_boundaries[i, 0] = x_grid_non_nan[0] - x_half_cell
|
|
56
|
+
window_boundaries[i, 1] = x_grid_non_nan[-1] + x_half_cell
|
|
57
|
+
|
|
58
|
+
# User's choice for initial window edge supercides the one calculated
|
|
59
|
+
if xlim:
|
|
60
|
+
window_boundaries = window_boundaries + xlim - window_boundaries[0]
|
|
61
|
+
return window_boundaries
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def compute_global_limits(
|
|
65
|
+
data: xr.DataArray,
|
|
66
|
+
min_percentile: float = 0,
|
|
67
|
+
max_percentile: float = 100,
|
|
68
|
+
) -> tuple[float, float]:
|
|
69
|
+
"""Remove all NaN values from the target data to calculate the global minimum and maximum of the data.
|
|
70
|
+
User defined percentiles can remove extreme outliers.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Removes NaN values, needed for moving windows
|
|
74
|
+
values_no_nan = data.values[~np.isnan(data.values)]
|
|
75
|
+
|
|
76
|
+
# Finds the global minimum and maximum of the plot, based on the percentile of the data
|
|
77
|
+
global_min = np.percentile(values_no_nan, min_percentile)
|
|
78
|
+
global_max = np.percentile(values_no_nan, max_percentile)
|
|
79
|
+
return global_min, global_max
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def animate(
|
|
83
|
+
data: xr.DataArray,
|
|
84
|
+
fps: float = 10,
|
|
85
|
+
min_percentile: float = 0,
|
|
86
|
+
max_percentile: float = 100,
|
|
87
|
+
title: str | None = None,
|
|
88
|
+
display_sdf_name: bool = False,
|
|
89
|
+
ax: plt.Axes | None = None,
|
|
90
|
+
**kwargs,
|
|
91
|
+
) -> FuncAnimation:
|
|
92
|
+
"""Generate an animation
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
---------
|
|
96
|
+
data
|
|
97
|
+
The dataarray containing the target data
|
|
98
|
+
fps
|
|
99
|
+
Frames per second for the animation (default: 10)
|
|
100
|
+
min_percentile
|
|
101
|
+
Minimum percentile of the data (default: 0)
|
|
102
|
+
max_percentile
|
|
103
|
+
Maximum percentile of the data (default: 100)
|
|
104
|
+
title
|
|
105
|
+
Custom title to add to the plot.
|
|
106
|
+
display_sdf_name
|
|
107
|
+
Display the sdf file name in the animation title
|
|
108
|
+
ax
|
|
109
|
+
Matplotlib axes on which to plot.
|
|
110
|
+
kwargs
|
|
111
|
+
Keyword arguments to be passed to matplotlib.
|
|
112
|
+
|
|
113
|
+
Examples
|
|
114
|
+
--------
|
|
115
|
+
>>> dataset["Derived_Number_Density_Electron"].epoch.animate()
|
|
116
|
+
"""
|
|
117
|
+
import matplotlib.pyplot as plt
|
|
118
|
+
from matplotlib.animation import FuncAnimation
|
|
119
|
+
|
|
120
|
+
kwargs_original = kwargs.copy()
|
|
121
|
+
|
|
122
|
+
if ax is None:
|
|
123
|
+
_, ax = plt.subplots()
|
|
124
|
+
|
|
125
|
+
N_frames = data["time"].size
|
|
126
|
+
global_min, global_max = compute_global_limits(data, min_percentile, max_percentile)
|
|
127
|
+
|
|
128
|
+
# Initialise plot and set y-limits for 1D data
|
|
129
|
+
if data.ndim == 2:
|
|
130
|
+
kwargs.setdefault("x", "X_Grid_mid")
|
|
131
|
+
plot = data.isel(time=0).plot(ax=ax, **kwargs)
|
|
132
|
+
ax.set_title(get_frame_title(data, 0, display_sdf_name, title))
|
|
133
|
+
ax.set_ylim(global_min, global_max)
|
|
134
|
+
|
|
135
|
+
# Initilise plot and set colour bar for 2D data
|
|
136
|
+
if data.ndim == 3:
|
|
137
|
+
kwargs["norm"] = plt.Normalize(vmin=global_min, vmax=global_max)
|
|
138
|
+
kwargs["add_colorbar"] = False
|
|
139
|
+
# Set default x and y coordinates for 2D data if not provided
|
|
140
|
+
kwargs.setdefault("x", "X_Grid_mid")
|
|
141
|
+
kwargs.setdefault("y", "Y_Grid_mid")
|
|
142
|
+
|
|
143
|
+
# Initialize the plot with the first timestep
|
|
144
|
+
plot = data.isel(time=0).plot(ax=ax, **kwargs)
|
|
145
|
+
ax.set_title(get_frame_title(data, 0, display_sdf_name, title))
|
|
146
|
+
|
|
147
|
+
# Add colorbar
|
|
148
|
+
if kwargs_original.get("add_colorbar", True):
|
|
149
|
+
long_name = data.attrs.get("long_name")
|
|
150
|
+
units = data.attrs.get("units")
|
|
151
|
+
plt.colorbar(plot, ax=ax, label=f"{long_name} [${units}$]")
|
|
152
|
+
|
|
153
|
+
# check if there is a moving window by finding NaNs in the data
|
|
154
|
+
move_window = np.isnan(np.sum(data.values))
|
|
155
|
+
if move_window:
|
|
156
|
+
window_boundaries = calculate_window_boundaries(data, kwargs.get("xlim", False))
|
|
157
|
+
|
|
158
|
+
def update(frame):
|
|
159
|
+
# Set the xlim for each frame in the case of a moving window
|
|
160
|
+
if move_window:
|
|
161
|
+
kwargs["xlim"] = window_boundaries[frame]
|
|
162
|
+
|
|
163
|
+
# Update plot for the new frame
|
|
164
|
+
ax.clear()
|
|
165
|
+
|
|
166
|
+
data.isel(time=frame).plot(ax=ax, **kwargs)
|
|
167
|
+
ax.set_title(get_frame_title(data, frame, display_sdf_name, title))
|
|
168
|
+
|
|
169
|
+
# Update y-limits for 1D data
|
|
170
|
+
if data.ndim == 2:
|
|
171
|
+
ax.set_ylim(global_min, global_max)
|
|
172
|
+
|
|
173
|
+
return FuncAnimation(
|
|
174
|
+
ax.get_figure(),
|
|
175
|
+
update,
|
|
176
|
+
frames=range(N_frames),
|
|
177
|
+
interval=1000 / fps,
|
|
178
|
+
repeat=True,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@xr.register_dataarray_accessor("epoch")
|
|
183
|
+
class EpochAccessor:
|
|
184
|
+
def __init__(self, xarray_obj):
|
|
185
|
+
self._obj = xarray_obj
|
|
186
|
+
|
|
187
|
+
def animate(self, *args, **kwargs) -> FuncAnimation:
|
|
188
|
+
"""Generate animations of Epoch data.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
args
|
|
193
|
+
Positional arguments passed to :func:`generate_animation`.
|
|
194
|
+
kwargs
|
|
195
|
+
Keyword arguments passed to :func:`generate_animation`.
|
|
196
|
+
|
|
197
|
+
Examples
|
|
198
|
+
--------
|
|
199
|
+
>>> import xarray as xr
|
|
200
|
+
>>> from sdf_xarray import SDFPreprocess
|
|
201
|
+
>>> ds = xr.open_mfdataset("*.sdf", preprocess=SDFPreprocess())
|
|
202
|
+
>>> ani = ds["Electric_Field_Ey"].epoch.animate()
|
|
203
|
+
>>> ani.save("myfile.mp4")
|
|
204
|
+
"""
|
|
205
|
+
return animate(self._obj, *args, **kwargs)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
begin:control
|
|
2
|
+
nx = 100
|
|
3
|
+
ny = 100
|
|
4
|
+
|
|
5
|
+
# final time of simulation
|
|
6
|
+
t_end = 10e-9
|
|
7
|
+
|
|
8
|
+
# size of domain
|
|
9
|
+
x_min = 0
|
|
10
|
+
x_end = 1
|
|
11
|
+
|
|
12
|
+
y_min = 0
|
|
13
|
+
y_max = 1
|
|
14
|
+
|
|
15
|
+
stdout_frequency = 10
|
|
16
|
+
end:control
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
begin:boundaries
|
|
20
|
+
bc_x_min = simple_outflow
|
|
21
|
+
bc_x_max = simple_outflow
|
|
22
|
+
bc_y_min = periodic
|
|
23
|
+
bc_y_max = periodic
|
|
24
|
+
end:boundaries
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
begin:window
|
|
28
|
+
move_window = T
|
|
29
|
+
window_start_time = 0
|
|
30
|
+
bc_x_min_after_move = simple_outflow
|
|
31
|
+
bc_x_max_after_move = simple_outflow
|
|
32
|
+
window_v_x = 2e8
|
|
33
|
+
end:window
|
|
34
|
+
|
|
35
|
+
begin:constant
|
|
36
|
+
x0 = 1.31
|
|
37
|
+
y0 = 0.5
|
|
38
|
+
r0 = 0.4^2
|
|
39
|
+
r2 = (x - x0)^2 + (y - y0)^2
|
|
40
|
+
end:constant
|
|
41
|
+
|
|
42
|
+
begin:species
|
|
43
|
+
# electron
|
|
44
|
+
name = electron
|
|
45
|
+
charge = -1.0
|
|
46
|
+
mass = 1.0
|
|
47
|
+
nparticles_per_cell = 5
|
|
48
|
+
|
|
49
|
+
number_density = if(abs(x-x0) lt 0.3, 2, 1)
|
|
50
|
+
number_density = if(abs(y-y0) lt 0.2, number_density(electron), 1)
|
|
51
|
+
|
|
52
|
+
temperature_ev = 0
|
|
53
|
+
end:species
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
begin:output
|
|
57
|
+
name = normal
|
|
58
|
+
dt_snapshot = 10e-9/4
|
|
59
|
+
|
|
60
|
+
# Properties on grid
|
|
61
|
+
grid = always
|
|
62
|
+
number_density = always + species + no_sum
|
|
63
|
+
end:output
|
|
@@ -5,7 +5,7 @@ import xarray as xr
|
|
|
5
5
|
|
|
6
6
|
from sdf_xarray import SDFPreprocess, _process_latex_name, open_mfdataset
|
|
7
7
|
|
|
8
|
-
EXAMPLE_FILES_DIR = pathlib.Path(__file__).parent / "
|
|
8
|
+
EXAMPLE_FILES_DIR = pathlib.Path(__file__).parent / "example_files_1D"
|
|
9
9
|
EXAMPLE_MISMATCHED_FILES_DIR = (
|
|
10
10
|
pathlib.Path(__file__).parent / "example_mismatched_files"
|
|
11
11
|
)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import tempfile
|
|
3
|
+
|
|
4
|
+
import matplotlib as mpl
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
import xarray as xr
|
|
8
|
+
from matplotlib.animation import PillowWriter
|
|
9
|
+
|
|
10
|
+
import sdf_xarray.plotting as sxp
|
|
11
|
+
from sdf_xarray import SDFPreprocess
|
|
12
|
+
|
|
13
|
+
mpl.use("Agg")
|
|
14
|
+
|
|
15
|
+
EXAMPLE_FILES_DIR_1D = pathlib.Path(__file__).parent / "example_files_1D"
|
|
16
|
+
EXAMPLE_FILES_DIR_2D_MW = (
|
|
17
|
+
pathlib.Path(__file__).parent / "example_files_2D_moving_window"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_animation_accessor():
|
|
22
|
+
array = xr.DataArray(
|
|
23
|
+
[1, 2, 3],
|
|
24
|
+
dims=["x"],
|
|
25
|
+
coords={"x": [0, 1, 2]},
|
|
26
|
+
attrs={"long_name": "Test Array", "units": "m"},
|
|
27
|
+
)
|
|
28
|
+
assert hasattr(array, "epoch")
|
|
29
|
+
assert hasattr(array.epoch, "animate")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_animate_headless():
|
|
33
|
+
with xr.open_mfdataset(
|
|
34
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
35
|
+
) as ds:
|
|
36
|
+
anim = ds["Derived_Number_Density_electron"].epoch.animate()
|
|
37
|
+
|
|
38
|
+
# Specify a custom writable temporary directory
|
|
39
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
40
|
+
temp_file_path = f"{temp_dir}/output.gif"
|
|
41
|
+
try:
|
|
42
|
+
anim.save(temp_file_path, writer=PillowWriter(fps=2))
|
|
43
|
+
except Exception as e:
|
|
44
|
+
pytest.fail(f"animate().save() failed in headless mode: {e}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_get_frame_title_no_optional_params():
|
|
48
|
+
with xr.open_mfdataset(
|
|
49
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
50
|
+
) as ds:
|
|
51
|
+
data = ds["Derived_Number_Density_electron"]
|
|
52
|
+
expected_result = "time = 5.47e-14 [s]"
|
|
53
|
+
result = sxp.get_frame_title(data, 0)
|
|
54
|
+
assert expected_result == result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_get_frame_title_sdf_name():
|
|
58
|
+
with xr.open_mfdataset(
|
|
59
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
60
|
+
) as ds:
|
|
61
|
+
data = ds["Derived_Number_Density_electron"]
|
|
62
|
+
expected_result = "time = 5.47e-14 [s], 0000.sdf"
|
|
63
|
+
result = sxp.get_frame_title(data, 0, display_sdf_name=True)
|
|
64
|
+
assert expected_result == result
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_get_frame_title_custom_title():
|
|
68
|
+
with xr.open_mfdataset(
|
|
69
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
70
|
+
) as ds:
|
|
71
|
+
data = ds["Derived_Number_Density_electron"]
|
|
72
|
+
expected_result = "Test Title, time = 5.47e-14 [s]"
|
|
73
|
+
result = sxp.get_frame_title(data, 0, title_custom="Test Title")
|
|
74
|
+
assert expected_result == result
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_get_frame_title_custom_title_and_sdf_name():
|
|
78
|
+
with xr.open_mfdataset(
|
|
79
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
80
|
+
) as ds:
|
|
81
|
+
data = ds["Derived_Number_Density_electron"]
|
|
82
|
+
expected_result = "Test Title, time = 5.47e-14 [s], 0000.sdf"
|
|
83
|
+
result = sxp.get_frame_title(
|
|
84
|
+
data, 0, display_sdf_name=True, title_custom="Test Title"
|
|
85
|
+
)
|
|
86
|
+
assert expected_result == result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_calculate_window_boundaries_1D():
|
|
90
|
+
with xr.open_mfdataset(
|
|
91
|
+
EXAMPLE_FILES_DIR_2D_MW.glob("*.sdf"),
|
|
92
|
+
preprocess=SDFPreprocess(),
|
|
93
|
+
combine="nested",
|
|
94
|
+
) as ds:
|
|
95
|
+
data = ds["Derived_Number_Density_electron"][:, :, 50]
|
|
96
|
+
expected_result = np.array(
|
|
97
|
+
[[0, 1], [0.49, 1.49], [0.99, 1.99], [1.49, 2.49], [1.99, 2.99]]
|
|
98
|
+
)
|
|
99
|
+
result = sxp.calculate_window_boundaries(data)
|
|
100
|
+
assert result == pytest.approx(expected_result, abs=0.1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_calculate_window_boundaries_2D():
|
|
104
|
+
with xr.open_mfdataset(
|
|
105
|
+
EXAMPLE_FILES_DIR_2D_MW.glob("*.sdf"),
|
|
106
|
+
preprocess=SDFPreprocess(),
|
|
107
|
+
combine="nested",
|
|
108
|
+
) as ds:
|
|
109
|
+
data = ds["Derived_Number_Density_electron"]
|
|
110
|
+
expected_result = np.array(
|
|
111
|
+
[[0, 1], [0.49, 1.49], [0.99, 1.99], [1.49, 2.49], [1.99, 2.99]]
|
|
112
|
+
)
|
|
113
|
+
result = sxp.calculate_window_boundaries(data)
|
|
114
|
+
assert result == pytest.approx(expected_result, abs=0.1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_calculate_window_boundaries_1D_xlim():
|
|
118
|
+
with xr.open_mfdataset(
|
|
119
|
+
EXAMPLE_FILES_DIR_2D_MW.glob("*.sdf"),
|
|
120
|
+
preprocess=SDFPreprocess(),
|
|
121
|
+
combine="nested",
|
|
122
|
+
) as ds:
|
|
123
|
+
data = ds["Derived_Number_Density_electron"][:, :, 50]
|
|
124
|
+
expected_result = np.array(
|
|
125
|
+
[[0.1, 0.9], [0.59, 1.39], [1.09, 1.89], [1.59, 2.39], [2.09, 2.89]]
|
|
126
|
+
)
|
|
127
|
+
result = sxp.calculate_window_boundaries(data, xlim=(0.1, 0.9))
|
|
128
|
+
assert result == pytest.approx(expected_result, abs=0.1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_calculate_window_boundaries_2D_xlim():
|
|
132
|
+
with xr.open_mfdataset(
|
|
133
|
+
EXAMPLE_FILES_DIR_2D_MW.glob("*.sdf"),
|
|
134
|
+
preprocess=SDFPreprocess(),
|
|
135
|
+
combine="nested",
|
|
136
|
+
) as ds:
|
|
137
|
+
data = ds["Derived_Number_Density_electron"]
|
|
138
|
+
expected_result = np.array(
|
|
139
|
+
[[0.1, 0.9], [0.59, 1.39], [1.09, 1.89], [1.59, 2.39], [2.09, 2.89]]
|
|
140
|
+
)
|
|
141
|
+
result = sxp.calculate_window_boundaries(data, xlim=(0.1, 0.9))
|
|
142
|
+
assert result == pytest.approx(expected_result, abs=0.1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_compute_global_limits():
|
|
146
|
+
with xr.open_mfdataset(
|
|
147
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
148
|
+
) as ds:
|
|
149
|
+
result_min, result_max = sxp.compute_global_limits(
|
|
150
|
+
ds["Derived_Number_Density_electron"]
|
|
151
|
+
)
|
|
152
|
+
expected_result_min = 8.07e19
|
|
153
|
+
expected_result_max = 1.17e20
|
|
154
|
+
assert result_min == pytest.approx(expected_result_min, abs=1e18)
|
|
155
|
+
assert result_max == pytest.approx(expected_result_max, abs=1e19)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_compute_global_limits_percentile():
|
|
159
|
+
with xr.open_mfdataset(
|
|
160
|
+
EXAMPLE_FILES_DIR_1D.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
161
|
+
) as ds:
|
|
162
|
+
result_min, result_max = sxp.compute_global_limits(
|
|
163
|
+
ds["Derived_Number_Density_electron"], 40, 45
|
|
164
|
+
)
|
|
165
|
+
expected_result_min = 9.84e19
|
|
166
|
+
expected_result_max = 9.94e19
|
|
167
|
+
assert result_min == pytest.approx(expected_result_min, abs=1e18)
|
|
168
|
+
assert result_max == pytest.approx(expected_result_max, abs=1e18)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_compute_global_limits_NaNs():
|
|
172
|
+
with xr.open_mfdataset(
|
|
173
|
+
EXAMPLE_FILES_DIR_2D_MW.glob("*.sdf"),
|
|
174
|
+
preprocess=SDFPreprocess(),
|
|
175
|
+
combine="nested",
|
|
176
|
+
) as ds:
|
|
177
|
+
result_min, result_max = sxp.compute_global_limits(
|
|
178
|
+
ds["Derived_Number_Density_electron"]
|
|
179
|
+
)
|
|
180
|
+
expected_result_min = 5.51e-1
|
|
181
|
+
expected_result_max = 2.70
|
|
182
|
+
assert result_min == pytest.approx(expected_result_min, abs=1e-2)
|
|
183
|
+
assert result_max == pytest.approx(expected_result_max, abs=1e-1)
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
import xarray as xr
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
import matplotlib.pyplot as plt
|
|
10
|
-
from matplotlib.animation import FuncAnimation
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def get_frame_title(data: xr.DataArray, frame: int, display_sdf_name: bool) -> str:
|
|
14
|
-
"""Generate the title for a frame"""
|
|
15
|
-
sdf_name = f", {frame:04d}.sdf" if display_sdf_name else ""
|
|
16
|
-
time = data["time"][frame].to_numpy()
|
|
17
|
-
return f"t = {time:.2e}s{sdf_name}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def calculate_window_velocity_and_edges(
|
|
21
|
-
data: xr.DataArray, x_axis_coord: str
|
|
22
|
-
) -> tuple[float, tuple[float, float], np.ndarray]:
|
|
23
|
-
"""Calculate the moving window's velocity and initial edges.
|
|
24
|
-
|
|
25
|
-
1. Finds a lineout of the target atribute in the x coordinate of the first frame
|
|
26
|
-
2. Removes the NaN values to isolate the simulation window
|
|
27
|
-
3. Produces the index size of the window, indexed at zero
|
|
28
|
-
4. Uses distance moved and final time of the simulation to calculate velocity and initial xlims
|
|
29
|
-
"""
|
|
30
|
-
time_since_start = data["time"].values - data["time"].values[0]
|
|
31
|
-
initial_window_edge = (0, 0)
|
|
32
|
-
target_lineout = data.values[0, :, 0]
|
|
33
|
-
target_lineout_window = target_lineout[~np.isnan(target_lineout)]
|
|
34
|
-
x_grid = data[x_axis_coord].values
|
|
35
|
-
window_size_index = target_lineout_window.size - 1
|
|
36
|
-
|
|
37
|
-
velocity_window = (x_grid[-1] - x_grid[window_size_index]) / time_since_start[-1]
|
|
38
|
-
initial_window_edge = (x_grid[0], x_grid[window_size_index])
|
|
39
|
-
return velocity_window, initial_window_edge, time_since_start
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def compute_global_limits(data: xr.DataArray) -> tuple[float, float]:
|
|
43
|
-
"""Remove all NaN values from the target data to calculate the 1st and 99th percentiles,
|
|
44
|
-
excluding extreme outliers.
|
|
45
|
-
"""
|
|
46
|
-
values_no_nan = data.values[~np.isnan(data.values)]
|
|
47
|
-
global_min = np.percentile(values_no_nan, 1)
|
|
48
|
-
global_max = np.percentile(values_no_nan, 99)
|
|
49
|
-
return global_min, global_max
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def is_1d(data: xr.DataArray) -> bool:
|
|
53
|
-
"""Check if the data is 1D."""
|
|
54
|
-
return len(data.shape) == 2
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def is_2d(data: xr.DataArray) -> bool:
|
|
58
|
-
"""Check if the data is 2D or 3D."""
|
|
59
|
-
return len(data.shape) == 3
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def generate_animation(
|
|
63
|
-
data: xr.DataArray,
|
|
64
|
-
display_sdf_name: bool = False,
|
|
65
|
-
fps: int = 10,
|
|
66
|
-
move_window: bool = False,
|
|
67
|
-
ax: plt.Axes | None = None,
|
|
68
|
-
**kwargs,
|
|
69
|
-
) -> FuncAnimation:
|
|
70
|
-
"""Generate an animation
|
|
71
|
-
|
|
72
|
-
Parameters
|
|
73
|
-
---------
|
|
74
|
-
dataset
|
|
75
|
-
The dataset containing the simulation data
|
|
76
|
-
target_attribute
|
|
77
|
-
The attribute to plot for each timestep
|
|
78
|
-
display_sdf_name
|
|
79
|
-
Display the sdf file name in the animation title
|
|
80
|
-
fps
|
|
81
|
-
Frames per second for the animation (default: 10)
|
|
82
|
-
move_window
|
|
83
|
-
If the simulation has a moving window, the animation will move along
|
|
84
|
-
with it (default: False)
|
|
85
|
-
ax
|
|
86
|
-
Matplotlib axes on which to plot.
|
|
87
|
-
kwargs
|
|
88
|
-
Keyword arguments to be passed to matplotlib.
|
|
89
|
-
|
|
90
|
-
Examples
|
|
91
|
-
--------
|
|
92
|
-
>>> generate_animation(dataset["Derived_Number_Density_Electron"])
|
|
93
|
-
"""
|
|
94
|
-
import matplotlib.pyplot as plt
|
|
95
|
-
from matplotlib.animation import FuncAnimation
|
|
96
|
-
|
|
97
|
-
if ax is None:
|
|
98
|
-
_, ax = plt.subplots()
|
|
99
|
-
|
|
100
|
-
N_frames = data["time"].size
|
|
101
|
-
global_min, global_max = compute_global_limits(data)
|
|
102
|
-
|
|
103
|
-
if is_2d(data):
|
|
104
|
-
kwargs["norm"] = plt.Normalize(vmin=global_min, vmax=global_max)
|
|
105
|
-
kwargs["add_colorbar"] = False
|
|
106
|
-
# Set default x and y coordinates for 2D data if not provided
|
|
107
|
-
kwargs.setdefault("x", "X_Grid_mid")
|
|
108
|
-
kwargs.setdefault("y", "Y_Grid_mid")
|
|
109
|
-
|
|
110
|
-
# Initialize the plot with the first timestep
|
|
111
|
-
plot = data.isel(time=0).plot(ax=ax, **kwargs)
|
|
112
|
-
ax.set_title(get_frame_title(data, 0, display_sdf_name))
|
|
113
|
-
|
|
114
|
-
# Add colorbar
|
|
115
|
-
long_name = data.attrs.get("long_name")
|
|
116
|
-
units = data.attrs.get("units")
|
|
117
|
-
plt.colorbar(plot, ax=ax, label=f"{long_name} [${units}$]")
|
|
118
|
-
|
|
119
|
-
# Initialise plo and set y-limits for 1D data
|
|
120
|
-
if is_1d(data):
|
|
121
|
-
plot = data.isel(time=0).plot(ax=ax, **kwargs)
|
|
122
|
-
ax.set_title(get_frame_title(data, 0, display_sdf_name))
|
|
123
|
-
ax.set_ylim(global_min, global_max)
|
|
124
|
-
|
|
125
|
-
if move_window:
|
|
126
|
-
window_velocity, window_initial_edge, time_since_start = (
|
|
127
|
-
calculate_window_velocity_and_edges(data, kwargs["x"])
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
# User's choice for initial window edge supercides the one calculated
|
|
131
|
-
if "xlim" in kwargs:
|
|
132
|
-
window_initial_edge = kwargs["xlim"]
|
|
133
|
-
|
|
134
|
-
def update(frame):
|
|
135
|
-
# Set the xlim for each frame in the case of a moving window
|
|
136
|
-
if move_window:
|
|
137
|
-
kwargs["xlim"] = (
|
|
138
|
-
window_initial_edge[0] + window_velocity * time_since_start[frame],
|
|
139
|
-
window_initial_edge[1] * 0.99
|
|
140
|
-
+ window_velocity * time_since_start[frame],
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
# Update plot for the new frame
|
|
144
|
-
ax.clear()
|
|
145
|
-
data.isel(time=frame).plot(ax=ax, **kwargs)
|
|
146
|
-
ax.set_title(get_frame_title(data, frame, display_sdf_name))
|
|
147
|
-
|
|
148
|
-
# # Update y-limits for 1D data
|
|
149
|
-
if is_1d(data):
|
|
150
|
-
ax.set_ylim(global_min, global_max)
|
|
151
|
-
|
|
152
|
-
return FuncAnimation(
|
|
153
|
-
ax.get_figure(),
|
|
154
|
-
update,
|
|
155
|
-
frames=range(N_frames),
|
|
156
|
-
interval=1000 / fps,
|
|
157
|
-
repeat=True,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@xr.register_dataarray_accessor("epoch")
|
|
162
|
-
class EpochAccessor:
|
|
163
|
-
def __init__(self, xarray_obj):
|
|
164
|
-
self._obj = xarray_obj
|
|
165
|
-
|
|
166
|
-
def animate(self, *args, **kwargs) -> FuncAnimation:
|
|
167
|
-
"""Generate animations of Epoch data.
|
|
168
|
-
|
|
169
|
-
Parameters
|
|
170
|
-
----------
|
|
171
|
-
args
|
|
172
|
-
Positional arguments passed to :func:`generate_animation`.
|
|
173
|
-
kwargs
|
|
174
|
-
Keyword arguments passed to :func:`generate_animation`.
|
|
175
|
-
|
|
176
|
-
Examples
|
|
177
|
-
--------
|
|
178
|
-
>>> import xarray as xr
|
|
179
|
-
>>> from sdf_xarray import SDFPreprocess
|
|
180
|
-
>>> ds = xr.open_mfdataset("*.sdf", preprocess=SDFPreprocess())
|
|
181
|
-
>>> ani = ds["Electric_Field_Ey"].epoch.animate()
|
|
182
|
-
>>> ani.save("myfile.mp4")
|
|
183
|
-
"""
|
|
184
|
-
return generate_animation(self._obj, *args, **kwargs)
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import pathlib
|
|
2
|
-
import tempfile
|
|
3
|
-
|
|
4
|
-
import matplotlib as mpl
|
|
5
|
-
import pytest
|
|
6
|
-
import xarray as xr
|
|
7
|
-
from matplotlib.animation import PillowWriter
|
|
8
|
-
|
|
9
|
-
from sdf_xarray import SDFPreprocess
|
|
10
|
-
|
|
11
|
-
mpl.use("Agg")
|
|
12
|
-
|
|
13
|
-
EXAMPLE_FILES_DIR = pathlib.Path(__file__).parent / "example_files"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_animation_accessor():
|
|
17
|
-
array = xr.DataArray(
|
|
18
|
-
[1, 2, 3],
|
|
19
|
-
dims=["x"],
|
|
20
|
-
coords={"x": [0, 1, 2]},
|
|
21
|
-
attrs={"long_name": "Test Array", "units": "m"},
|
|
22
|
-
)
|
|
23
|
-
assert hasattr(array, "epoch")
|
|
24
|
-
assert hasattr(array.epoch, "animate")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_animate_headless():
|
|
28
|
-
with xr.open_mfdataset(
|
|
29
|
-
EXAMPLE_FILES_DIR.glob("*.sdf"), preprocess=SDFPreprocess()
|
|
30
|
-
) as ds:
|
|
31
|
-
anim = ds["Derived_Number_Density_electron"].epoch.animate()
|
|
32
|
-
|
|
33
|
-
# Specify a custom writable temporary directory
|
|
34
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
35
|
-
temp_file_path = f"{temp_dir}/output.gif"
|
|
36
|
-
try:
|
|
37
|
-
anim.save(temp_file_path, writer=PillowWriter(fps=2))
|
|
38
|
-
except Exception as e:
|
|
39
|
-
pytest.fail(f"animate().save() failed in headless mode: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|