sdf-xarray 0.2.6__tar.gz → 0.3.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.
Files changed (118) hide show
  1. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/CONTRIBUTING.md +1 -1
  2. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/PKG-INFO +4 -1
  3. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/README.md +3 -0
  4. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/conf.py +2 -2
  5. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/getting_started.rst +28 -9
  6. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/key_functionality.rst +29 -9
  7. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/unit_conversion.rst +2 -3
  8. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/src/sdf_xarray/__init__.py +38 -12
  9. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/src/sdf_xarray/_version.py +3 -3
  10. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/test_basic.py +217 -19
  11. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/test_epoch_accessor.py +69 -19
  12. sdf_xarray-0.3.0/uv.lock +2605 -0
  13. sdf_xarray-0.2.6/uv.lock +0 -2122
  14. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.github/workflows/black.yml +0 -0
  15. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.github/workflows/build_publish.yml +0 -0
  16. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.github/workflows/lint.yml +0 -0
  17. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.github/workflows/tests.yml +0 -0
  18. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.gitignore +0 -0
  19. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.gitmodules +0 -0
  20. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/.readthedocs.yaml +0 -0
  21. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/BEAM.png +0 -0
  22. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/CITATION.cff +0 -0
  23. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/CMakeLists.txt +0 -0
  24. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/LICENCE +0 -0
  25. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/PlasmaFAIR.svg +0 -0
  26. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/.gitignore +0 -0
  27. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/_templates/custom-class-template.rst +0 -0
  28. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/_templates/custom-module-template.rst +0 -0
  29. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/api.rst +0 -0
  30. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/contributing.rst +0 -0
  31. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/index.rst +0 -0
  32. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/known_issues.rst +0 -0
  33. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/make.bat +0 -0
  34. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0000.sdf +0 -0
  35. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0001.sdf +0 -0
  36. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0002.sdf +0 -0
  37. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0003.sdf +0 -0
  38. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0004.sdf +0 -0
  39. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0005.sdf +0 -0
  40. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0006.sdf +0 -0
  41. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0007.sdf +0 -0
  42. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0008.sdf +0 -0
  43. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0009.sdf +0 -0
  44. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0010.sdf +0 -0
  45. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0011.sdf +0 -0
  46. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0012.sdf +0 -0
  47. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0013.sdf +0 -0
  48. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0014.sdf +0 -0
  49. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0015.sdf +0 -0
  50. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0016.sdf +0 -0
  51. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0017.sdf +0 -0
  52. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0018.sdf +0 -0
  53. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0019.sdf +0 -0
  54. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0020.sdf +0 -0
  55. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0021.sdf +0 -0
  56. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0022.sdf +0 -0
  57. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0023.sdf +0 -0
  58. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0024.sdf +0 -0
  59. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0025.sdf +0 -0
  60. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0026.sdf +0 -0
  61. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0027.sdf +0 -0
  62. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0028.sdf +0 -0
  63. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0029.sdf +0 -0
  64. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0030.sdf +0 -0
  65. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0031.sdf +0 -0
  66. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0032.sdf +0 -0
  67. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0033.sdf +0 -0
  68. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0034.sdf +0 -0
  69. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0035.sdf +0 -0
  70. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0036.sdf +0 -0
  71. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0037.sdf +0 -0
  72. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0038.sdf +0 -0
  73. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0039.sdf +0 -0
  74. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/0040.sdf +0 -0
  75. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/deck.status +0 -0
  76. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/epoch1d.dat +0 -0
  77. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/input.deck +0 -0
  78. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/normal.visit +0 -0
  79. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/docs/tutorial_dataset_1d/restart.visit +0 -0
  80. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/pyproject.toml +0 -0
  81. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/src/sdf_xarray/csdf.pxd +0 -0
  82. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/src/sdf_xarray/plotting.py +0 -0
  83. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/src/sdf_xarray/sdf_interface.pyx +0 -0
  84. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_array_no_grids/0000.sdf +0 -0
  85. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_array_no_grids/0001.sdf +0 -0
  86. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_array_no_grids/README.md +0 -0
  87. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_array_no_grids/input.deck +0 -0
  88. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_dist_fn/0000.sdf +0 -0
  89. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_dist_fn/0001.sdf +0 -0
  90. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_dist_fn/0002.sdf +0 -0
  91. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_dist_fn/input.deck +0 -0
  92. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0000.sdf +0 -0
  93. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0001.sdf +0 -0
  94. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0002.sdf +0 -0
  95. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0003.sdf +0 -0
  96. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0004.sdf +0 -0
  97. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0005.sdf +0 -0
  98. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0006.sdf +0 -0
  99. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0007.sdf +0 -0
  100. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0008.sdf +0 -0
  101. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0009.sdf +0 -0
  102. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/0010.sdf +0 -0
  103. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/README.md +0 -0
  104. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_1D/input.deck +0 -0
  105. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_2D_moving_window/0000.sdf +0 -0
  106. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_2D_moving_window/0001.sdf +0 -0
  107. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_2D_moving_window/0002.sdf +0 -0
  108. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_2D_moving_window/0003.sdf +0 -0
  109. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_2D_moving_window/0004.sdf +0 -0
  110. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_files_2D_moving_window/input.deck +0 -0
  111. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_mismatched_files/0000.sdf +0 -0
  112. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_mismatched_files/0001.sdf +0 -0
  113. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_mismatched_files/0002.sdf +0 -0
  114. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_two_probes_2D/0000.sdf +0 -0
  115. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_two_probes_2D/0001.sdf +0 -0
  116. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_two_probes_2D/0002.sdf +0 -0
  117. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/example_two_probes_2D/input.deck +0 -0
  118. {sdf_xarray-0.2.6 → sdf_xarray-0.3.0}/tests/test_cython.py +0 -0
@@ -33,7 +33,7 @@ To run these tools locally, install the optional dependencies and run:
33
33
 
34
34
  ```bash
35
35
  pip install "sdf-xarray[lint]"
36
- ruff check
36
+ ruff check src tests
37
37
  ```
38
38
 
39
39
  ### Running and Adding Tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdf-xarray
3
- Version: 0.2.6
3
+ Version: 0.3.0
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-Expression: BSD-3-Clause
@@ -61,6 +61,9 @@ sdf-xarray provides a backend for [xarray](https://xarray.dev) to read SDF files
61
61
  [EPOCH](https://epochpic.github.io) using the [SDF-C](https://github.com/epochpic/SDF_C) library.
62
62
  Part of [BEAM](#broad-epoch-analysis-modules-beam) (Broad EPOCH Analysis Modules).
63
63
 
64
+ > [!IMPORTANT]
65
+ > To install this package make sure you are using one of the Python versions listed above.
66
+
64
67
  ## Installation
65
68
 
66
69
  Install from PyPI with:
@@ -13,6 +13,9 @@ sdf-xarray provides a backend for [xarray](https://xarray.dev) to read SDF files
13
13
  [EPOCH](https://epochpic.github.io) using the [SDF-C](https://github.com/epochpic/SDF_C) library.
14
14
  Part of [BEAM](#broad-epoch-analysis-modules-beam) (Broad EPOCH Analysis Modules).
15
15
 
16
+ > [!IMPORTANT]
17
+ > To install this package make sure you are using one of the Python versions listed above.
18
+
16
19
  ## Installation
17
20
 
18
21
  Install from PyPI with:
@@ -8,9 +8,9 @@ from importlib.metadata import version as get_version
8
8
  from pathlib import Path
9
9
 
10
10
  with suppress(ImportError):
11
- import matplotlib
11
+ import matplotlib as mpl
12
12
 
13
- matplotlib.use("Agg")
13
+ mpl.use("Agg")
14
14
  # -- Project information -----------------------------------------------------
15
15
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
16
16
 
@@ -7,6 +7,15 @@
7
7
  Installation
8
8
  ------------
9
9
 
10
+ .. |python_versions_pypi| image:: https://img.shields.io/pypi/pyversions/sdf-xarray.svg
11
+ :alt: Supported Python versions
12
+ :target: https://pypi.org/project/sdf-xarray/
13
+
14
+ .. important::
15
+
16
+ To install this package, ensure that you are using one of the supported Python
17
+ versions: |python_versions_pypi|
18
+
10
19
  Install sdf-xarray from PyPI with:
11
20
 
12
21
  .. code-block:: bash
@@ -28,7 +37,7 @@ Usage
28
37
  `xarray`. There are several ways to load SDF files:
29
38
 
30
39
  - To load a single file, use :func:`xarray.open_dataset`.
31
- - To load multiple files, use :func:`xarray.open_mfdataset` or :func:`sdf_xarray.open_mfdataset`.
40
+ - To load multiple files, use :func:`xarray.open_mfdataset` or :func:`sdf_xarray.open_mfdataset` (Recommended).
32
41
  - To access the raw contents of a single SDF file, use :func:`sdf_xarray.sdf_interface.SDFFile`.
33
42
 
34
43
  .. note::
@@ -42,21 +51,32 @@ Basic usage:
42
51
  .. ipython:: python
43
52
 
44
53
  import xarray as xr
54
+ import sdf_xarray as sdfxr
45
55
  with xr.open_dataset("tutorial_dataset_1d/0010.sdf") as df:
46
56
  print(df["Electric_Field_Ex"])
47
57
 
48
58
  Multi file loading
49
59
  ~~~~~~~~~~~~~~~~~~
50
60
 
51
- To open a whole simulation at once, pass
52
- ``preprocess=sdf_xarray.SDFPreprocess()`` to `xarray.open_mfdataset`:
61
+ To open a whole simulation's files at once use the :func:`sdf_xarray.open_mfdataset` function:
62
+
63
+ .. ipython:: python
64
+
65
+ sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf")
66
+
67
+ You can alternatively open the dataset using the xarray's :func:`xarray.open_mfdataset`
68
+ along with the ``preprocess=sdfxr.SDFPreprocess()``:
53
69
 
54
70
  .. ipython:: python
55
71
 
56
- from sdf_xarray import SDFPreprocess
57
- xr.open_mfdataset("tutorial_dataset_1d/*.sdf", preprocess=SDFPreprocess())
72
+ xr.open_mfdataset(
73
+ "tutorial_dataset_1d/*.sdf",
74
+ join="outer",
75
+ compat="no_conflicts",
76
+ preprocess=sdfxr.SDFPreprocess()
77
+ )
58
78
 
59
- `SDFPreprocess` checks that all the files are from the same simulation, and
79
+ :class:`sdf_xarray.SDFPreprocess` checks that all the files are from the same simulation, and
60
80
  ensures there's a ``time`` dimension so the files are correctly concatenated.
61
81
 
62
82
  If your simulation has multiple ``output`` blocks so that not all variables are
@@ -64,12 +84,11 @@ output at every time step, then those variables will have ``NaN`` values at the
64
84
  corresponding time points.
65
85
 
66
86
  Alternatively, we can create a separate time dimensions for each ``output``
67
- block using `sdf_xarray.open_mfdataset` with ``separate_times=True``:
87
+ block using :func:`sdf_xarray.open_mfdataset` with ``separate_times=True``:
68
88
 
69
89
  .. ipython:: python
70
90
 
71
- from sdf_xarray import open_mfdataset
72
- open_mfdataset("tutorial_dataset_1d/*.sdf", separate_times=True)
91
+ sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf", separate_times=True)
73
92
 
74
93
  This is better for memory consumption, at the cost of perhaps slightly less
75
94
  friendly comparisons between variables on different time coordinates.
@@ -6,17 +6,17 @@ Key Functionality
6
6
 
7
7
  .. ipython:: python
8
8
 
9
+ import xarray as xr
10
+ import sdf_xarray as sdfxr
9
11
  import matplotlib.pyplot as plt
10
12
  from IPython.display import display, HTML
11
- import xarray as xr
12
- from sdf_xarray import SDFFile, SDFPreprocess
13
13
 
14
14
  Loading SDF Files
15
15
  -----------------
16
16
  There are several ways to load SDF files:
17
17
 
18
18
  - To load a single file, use :func:`xarray.open_dataset`.
19
- - To load multiple files, use :func:`xarray.open_mfdataset` or :func:`sdf_xarray.open_mfdataset`.
19
+ - To load multiple files, use :func:`sdf_xarray.open_mfdataset` or :func:`xarray.open_mfdataset`.
20
20
  - To access the raw contents of a single SDF file, use :func:`sdf_xarray.sdf_interface.SDFFile`.
21
21
 
22
22
  .. note::
@@ -34,28 +34,48 @@ Loading a Single Raw SDF File
34
34
 
35
35
  .. ipython:: python
36
36
 
37
- with SDFFile("tutorial_dataset_1d/0010.sdf") as sdf_file:
37
+ with sdfxr.SDFFile("tutorial_dataset_1d/0010.sdf") as sdf_file:
38
38
  print(sdf_file.variables)
39
39
 
40
40
  Loading all SDF Files for a Simulation
41
41
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42
42
 
43
- When loading in all the files we have do some processing of the data
43
+ Multiple files can be loaded using one of two methods. The first of which
44
+ is by using the :func:`sdf_xarray.open_mfdataset`
45
+
46
+ .. ipython:: python
47
+
48
+ sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf")
49
+
50
+ Alternatively files can be loaded using :func:`xarray.open_mfdataset`
51
+ however when loading in all the files we have do some processing of the data
44
52
  so that we can correctly align it along the time dimension; This is
45
53
  done via the ``preprocess`` parameter.
46
54
 
47
55
  .. ipython:: python
48
56
 
49
- xr.open_mfdataset("tutorial_dataset_1d/*.sdf", preprocess=SDFPreprocess())
57
+ xr.open_mfdataset(
58
+ "tutorial_dataset_1d/*.sdf",
59
+ join="outer",
60
+ compat="no_conflicts",
61
+ preprocess=sdfxr.SDFPreprocess())
50
62
 
51
63
  Reading particle data
52
64
  ~~~~~~~~~~~~~~~~~~~~~
53
65
 
54
66
  .. warning::
55
- It is **not recommended** to use :func:`xarray.open_mfdataset` or :func:`open_mfdataset` to load particle data from multiple SDF outputs. The number of particles often varies between outputs, which can lead to inconsistent array shapes that these functions cannot handle. Instead, consider loading each file individually and then concatenating them manually.
67
+ It is **not recommended** to use :func:`xarray.open_mfdataset` or
68
+ :func:`sdf_xarray.open_mfdataset` to load particle data from multiple
69
+ SDF outputs. The number of particles often varies between outputs,
70
+ which can lead to inconsistent array shapes that these functions
71
+ cannot handle. Instead, consider loading each file individually and
72
+ then concatenating them manually.
56
73
 
57
74
  .. note::
58
- When loading multiple probes from a single SDF file, you **must** use the `probe_names` parameter to assign a unique name to each. For example, use `probe_names=["Front_Electron_Probe", "Back_Electron_Probe"]`. Failing to do so will result in dimension name conflicts.
75
+ When loading multiple probes from a single SDF file, you **must** use the
76
+ ``probe_names`` parameter to assign a unique name to each. For example,
77
+ use ``probe_names=["Front_Electron_Probe", "Back_Electron_Probe"]``.
78
+ Failing to do so will result in dimension name conflicts.
59
79
 
60
80
  By default, particle data isn't kept as it takes up a lot of space.
61
81
  Pass ``keep_particles=True`` as a keyword argument to
@@ -85,7 +105,7 @@ looking at when you call ``.values``
85
105
 
86
106
  .. ipython:: python
87
107
 
88
- ds = xr.open_mfdataset("tutorial_dataset_1d/*.sdf", preprocess=SDFPreprocess())
108
+ ds = sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf")
89
109
 
90
110
  ds["Electric_Field_Ex"]
91
111
 
@@ -38,8 +38,7 @@ import, the ``xarray.Dataset.pint`` accessor will not be initialised.
38
38
 
39
39
  .. ipython:: python
40
40
 
41
- import xarray as xr
42
- from sdf_xarray import SDFPreprocess
41
+ from sdf_xarray import open_mfdataset
43
42
  import pint_xarray
44
43
 
45
44
  In the following example we will extract the time-resolved total particle
@@ -72,7 +71,7 @@ be removed.
72
71
 
73
72
  .. ipython:: python
74
73
 
75
- with xr.open_mfdataset("tutorial_dataset_1d/*.sdf", preprocess=SDFPreprocess()) as ds:
74
+ with open_mfdataset("tutorial_dataset_1d/*.sdf") as ds:
76
75
  total_particle_energy = ds["Total_Particle_Energy_Electron"]
77
76
 
78
77
  total_particle_energy
@@ -2,12 +2,15 @@ import os
2
2
  import re
3
3
  from collections import Counter, defaultdict
4
4
  from collections.abc import Callable, Iterable
5
+ from importlib.metadata import version
5
6
  from itertools import product
7
+ from os import PathLike as os_PathLike
6
8
  from pathlib import Path
7
9
  from typing import ClassVar
8
10
 
9
11
  import numpy as np
10
12
  import xarray as xr
13
+ from packaging.version import Version
11
14
  from xarray.backends import AbstractDataStore, BackendArray, BackendEntrypoint
12
15
  from xarray.backends.file_manager import CachingFileManager
13
16
  from xarray.backends.locks import ensure_lock
@@ -21,6 +24,12 @@ import sdf_xarray.plotting # noqa: F401
21
24
 
22
25
  from .sdf_interface import Constant, SDFFile # type: ignore # noqa: PGH003
23
26
 
27
+ # TODO Remove this once the new kwarg options are fully implemented
28
+ if Version(version("xarray")) >= Version("2025.8.0"):
29
+ xr.set_options(use_new_combine_kwarg_defaults=True)
30
+
31
+ PathLike = str | os_PathLike
32
+
24
33
 
25
34
  def _rename_with_underscore(name: str) -> str:
26
35
  """A lot of the variable names have spaces, forward slashes and dashes in them, which
@@ -51,14 +60,32 @@ def _process_latex_name(variable_name: str) -> str:
51
60
  return variable_name
52
61
 
53
62
 
63
+ def _resolve_glob(path_glob: PathLike | Iterable[PathLike]):
64
+ """
65
+ Normalise input path_glob into a sorted list of absolute, resolved Path objects.
66
+ """
67
+
68
+ try:
69
+ p = Path(path_glob)
70
+ paths = list(p.parent.glob(p.name)) if p.name == "*.sdf" else list(p)
71
+ except TypeError:
72
+ paths = list({Path(p) for p in path_glob})
73
+
74
+ paths = sorted(p.resolve() for p in paths)
75
+ if not paths:
76
+ raise FileNotFoundError(f"No files matched pattern or input: {path_glob!r}")
77
+ return paths
78
+
79
+
54
80
  def combine_datasets(path_glob: Iterable | str, **kwargs) -> xr.Dataset:
55
81
  """Combine all datasets using a single time dimension"""
56
82
 
57
83
  return xr.open_mfdataset(
58
84
  path_glob,
59
- data_vars="minimal",
60
- coords="minimal",
61
- compat="override",
85
+ data_vars="all",
86
+ coords="different",
87
+ compat="no_conflicts",
88
+ join="outer",
62
89
  preprocess=SDFPreprocess(),
63
90
  **kwargs,
64
91
  )
@@ -103,19 +130,13 @@ def open_mfdataset(
103
130
  List of EPOCH probe names
104
131
  """
105
132
 
106
- # TODO: This is not very robust, look at how xarray.open_mfdataset does it
107
- if isinstance(path_glob, str):
108
- path_glob = Path().glob(path_glob)
109
-
110
- # Coerce to list because we might need to use the sequence multiple times
111
- path_glob = sorted(list(path_glob)) # noqa: C414
112
-
133
+ path_glob = _resolve_glob(path_glob)
113
134
  if not separate_times:
114
135
  return combine_datasets(
115
136
  path_glob, keep_particles=keep_particles, probe_names=probe_names
116
137
  )
117
138
 
118
- time_dims, var_times_map = make_time_dims(path_glob)
139
+ _, var_times_map = make_time_dims(path_glob)
119
140
  all_dfs = [
120
141
  xr.open_dataset(f, keep_particles=keep_particles, probe_names=probe_names)
121
142
  for f in path_glob
@@ -136,7 +157,12 @@ def open_mfdataset(
136
157
  )
137
158
 
138
159
  return xr.combine_by_coords(
139
- all_dfs, data_vars="minimal", combine_attrs="drop_conflicts"
160
+ all_dfs,
161
+ data_vars="all",
162
+ coords="different",
163
+ combine_attrs="drop_conflicts",
164
+ join="outer",
165
+ compat="no_conflicts",
140
166
  )
141
167
 
142
168
 
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.6'
32
- __version_tuple__ = version_tuple = (0, 2, 6)
31
+ __version__ = version = '0.3.0'
32
+ __version_tuple__ = version_tuple = (0, 3, 0)
33
33
 
34
- __commit_id__ = commit_id = 'g67411803b'
34
+ __commit_id__ = commit_id = 'gcca942b3f'
@@ -1,9 +1,11 @@
1
1
  import pathlib
2
2
 
3
+ import numpy as np
4
+ import numpy.testing as npt
3
5
  import pytest
4
6
  import xarray as xr
5
7
 
6
- from sdf_xarray import SDFPreprocess, _process_latex_name, open_mfdataset
8
+ from sdf_xarray import SDFPreprocess, _process_latex_name, _resolve_glob, open_mfdataset
7
9
 
8
10
  EXAMPLE_FILES_DIR = pathlib.Path(__file__).parent / "example_files_1D"
9
11
  EXAMPLE_MISMATCHED_FILES_DIR = (
@@ -84,6 +86,70 @@ def test_multiple_files_one_time_dim():
84
86
  assert tuple(absorption.coords) == ("time",)
85
87
  assert absorption.shape == (11,)
86
88
 
89
+ time = df["time"]
90
+ ex = df.isel(time=10)["Electric_Field_Ex"]
91
+ ex_values = ex.values
92
+ ex_x_coords = ex.coords["X_Grid_mid"].values
93
+ time_values = np.array(
94
+ [
95
+ 5.466993e-14,
96
+ 2.417504e-10,
97
+ 4.833915e-10,
98
+ 7.251419e-10,
99
+ 9.667830e-10,
100
+ 1.208533e-09,
101
+ 1.450175e-09,
102
+ 1.691925e-09,
103
+ 1.933566e-09,
104
+ 2.175316e-09,
105
+ 2.416958e-09,
106
+ ]
107
+ )
108
+
109
+ expected_ex = np.array(
110
+ [
111
+ -3126528.47057157754898071289062500000000,
112
+ -3249643.37612255383282899856567382812500,
113
+ -6827013.11566223856061697006225585937500,
114
+ -9350267.99022011645138263702392578125000,
115
+ -1643592.58487333403900265693664550781250,
116
+ -2044751.41207189299166202545166015625000,
117
+ -4342811.34666103497147560119628906250000,
118
+ -10420841.38402196019887924194335937500000,
119
+ -7038801.83154528774321079254150390625000,
120
+ 781649.31791684380732476711273193359375,
121
+ 4476555.84853181242942810058593750000000,
122
+ 5873312.79385650344192981719970703125000,
123
+ -95930.60501570138148963451385498046875,
124
+ -8977898.96547995693981647491455078125000,
125
+ -7951712.64987809769809246063232421875000,
126
+ -5655667.11171338520944118499755859375000,
127
+ ]
128
+ )
129
+ expected_ex_coords = np.array(
130
+ [
131
+ 1.72522447e-05,
132
+ 5.17567340e-05,
133
+ 8.62612233e-05,
134
+ 1.20765713e-04,
135
+ 1.55270202e-04,
136
+ 1.89774691e-04,
137
+ 2.24279181e-04,
138
+ 2.58783670e-04,
139
+ 2.93288159e-04,
140
+ 3.27792649e-04,
141
+ 3.62297138e-04,
142
+ 3.96801627e-04,
143
+ 4.31306117e-04,
144
+ 4.65810606e-04,
145
+ 5.00315095e-04,
146
+ 5.34819585e-04,
147
+ ]
148
+ )
149
+ npt.assert_allclose(time_values, time.values, rtol=1e-6)
150
+ npt.assert_allclose(ex_values, expected_ex)
151
+ npt.assert_allclose(ex_x_coords, expected_ex_coords)
152
+
87
153
 
88
154
  def test_multiple_files_multiple_time_dims():
89
155
  with open_mfdataset(
@@ -99,7 +165,59 @@ def test_multiple_files_multiple_time_dims():
99
165
  assert df["Absorption_Total_Laser_Energy_Injected"].shape == (11,)
100
166
 
101
167
 
102
- def test_erroring_on_mismatched_jobid_files():
168
+ def test_resolve_glob_from_string_pattern():
169
+ pattern = str(EXAMPLE_FILES_DIR / "*.sdf")
170
+ result = _resolve_glob(pattern)
171
+ expected = sorted(EXAMPLE_FILES_DIR.glob("*.sdf"))
172
+ assert result == expected
173
+
174
+
175
+ def test_resolve_glob_from_path_glob():
176
+ pattern = EXAMPLE_FILES_DIR.glob("*.sdf")
177
+ result = _resolve_glob(pattern)
178
+ expected = sorted(EXAMPLE_FILES_DIR.glob("*.sdf"))
179
+ assert result == expected
180
+
181
+
182
+ def test_resolve_glob_from_path_missing_glob():
183
+ pattern = EXAMPLE_FILES_DIR
184
+ with pytest.raises(TypeError):
185
+ _resolve_glob(pattern)
186
+
187
+
188
+ def test_resolve_glob_from_path_list():
189
+ pattern = [EXAMPLE_FILES_DIR / "0000.sdf"]
190
+ result = _resolve_glob(pattern)
191
+ expected = [EXAMPLE_FILES_DIR / "0000.sdf"]
192
+ assert result == expected
193
+
194
+
195
+ def test_resolve_glob_from_path_list_multiple():
196
+ pattern = [EXAMPLE_FILES_DIR / "0000.sdf", EXAMPLE_FILES_DIR / "0001.sdf"]
197
+ result = _resolve_glob(pattern)
198
+ expected = [EXAMPLE_FILES_DIR / "0000.sdf", EXAMPLE_FILES_DIR / "0001.sdf"]
199
+ assert result == expected
200
+
201
+
202
+ def test_resolve_glob_from_path_list_multiple_unordered():
203
+ pattern = [EXAMPLE_FILES_DIR / "0001.sdf", EXAMPLE_FILES_DIR / "0000.sdf"]
204
+ result = _resolve_glob(pattern)
205
+ expected = [EXAMPLE_FILES_DIR / "0000.sdf", EXAMPLE_FILES_DIR / "0001.sdf"]
206
+ assert result == expected
207
+
208
+
209
+ def test_resolve_glob_from_path_list_multiple_duplicates():
210
+ pattern = [
211
+ EXAMPLE_FILES_DIR / "0000.sdf",
212
+ EXAMPLE_FILES_DIR / "0000.sdf",
213
+ EXAMPLE_FILES_DIR / "0001.sdf",
214
+ ]
215
+ result = _resolve_glob(pattern)
216
+ expected = [EXAMPLE_FILES_DIR / "0000.sdf", EXAMPLE_FILES_DIR / "0001.sdf"]
217
+ assert result == expected
218
+
219
+
220
+ def test_xr_erroring_on_mismatched_jobid_files():
103
221
  with pytest.raises(ValueError): # noqa: PT011
104
222
  xr.open_mfdataset(
105
223
  EXAMPLE_MISMATCHED_FILES_DIR.glob("*.sdf"),
@@ -107,22 +225,100 @@ def test_erroring_on_mismatched_jobid_files():
107
225
  data_vars="minimal",
108
226
  coords="minimal",
109
227
  compat="override",
228
+ join="outer",
110
229
  preprocess=SDFPreprocess(),
111
230
  )
112
231
 
113
232
 
114
- def test_time_dim_units():
233
+ def test_xr_multiple_files_data():
234
+ with xr.open_mfdataset(
235
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
236
+ compat="no_conflicts",
237
+ join="outer",
238
+ preprocess=SDFPreprocess(),
239
+ ) as df:
240
+ ex = df.isel(time=10)["Electric_Field_Ex"]
241
+ ex_values = ex.values
242
+ ex_x_coords = ex.coords["X_Grid_mid"].values
243
+
244
+ expected_ex = np.array(
245
+ [
246
+ -3126528.47057157754898071289062500000000,
247
+ -3249643.37612255383282899856567382812500,
248
+ -6827013.11566223856061697006225585937500,
249
+ -9350267.99022011645138263702392578125000,
250
+ -1643592.58487333403900265693664550781250,
251
+ -2044751.41207189299166202545166015625000,
252
+ -4342811.34666103497147560119628906250000,
253
+ -10420841.38402196019887924194335937500000,
254
+ -7038801.83154528774321079254150390625000,
255
+ 781649.31791684380732476711273193359375,
256
+ 4476555.84853181242942810058593750000000,
257
+ 5873312.79385650344192981719970703125000,
258
+ -95930.60501570138148963451385498046875,
259
+ -8977898.96547995693981647491455078125000,
260
+ -7951712.64987809769809246063232421875000,
261
+ -5655667.11171338520944118499755859375000,
262
+ ]
263
+ )
264
+ expected_ex_coords = np.array(
265
+ [
266
+ 1.72522447e-05,
267
+ 5.17567340e-05,
268
+ 8.62612233e-05,
269
+ 1.20765713e-04,
270
+ 1.55270202e-04,
271
+ 1.89774691e-04,
272
+ 2.24279181e-04,
273
+ 2.58783670e-04,
274
+ 2.93288159e-04,
275
+ 3.27792649e-04,
276
+ 3.62297138e-04,
277
+ 3.96801627e-04,
278
+ 4.31306117e-04,
279
+ 4.65810606e-04,
280
+ 5.00315095e-04,
281
+ 5.34819585e-04,
282
+ ]
283
+ )
284
+ npt.assert_allclose(ex_values, expected_ex)
285
+ npt.assert_allclose(ex_x_coords, expected_ex_coords)
286
+
287
+
288
+ def test_xr_time_dim():
115
289
  with xr.open_mfdataset(
116
- EXAMPLE_ARRAYS_DIR.glob("*.sdf"), preprocess=SDFPreprocess()
290
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
291
+ join="outer",
292
+ preprocess=SDFPreprocess(),
117
293
  ) as df:
118
- assert df["time"].units == "s"
119
- assert df["time"].long_name == "Time"
120
- assert df["time"].full_name == "time"
294
+ time = df["time"]
295
+ assert time.units == "s"
296
+ assert time.long_name == "Time"
297
+ assert time.full_name == "time"
298
+
299
+ time_values = np.array(
300
+ [
301
+ 5.466993e-14,
302
+ 2.417504e-10,
303
+ 4.833915e-10,
304
+ 7.251419e-10,
305
+ 9.667830e-10,
306
+ 1.208533e-09,
307
+ 1.450175e-09,
308
+ 1.691925e-09,
309
+ 1.933566e-09,
310
+ 2.175316e-09,
311
+ 2.416958e-09,
312
+ ]
313
+ )
314
+
315
+ npt.assert_allclose(time_values, time.values, rtol=1e-6)
121
316
 
122
317
 
123
- def test_latex_rename_variables():
318
+ def test_xr_latex_rename_variables():
124
319
  with xr.open_mfdataset(
125
320
  EXAMPLE_ARRAYS_DIR.glob("*.sdf"),
321
+ join="outer",
126
322
  preprocess=SDFPreprocess(),
127
323
  keep_particles=True,
128
324
  ) as df:
@@ -158,7 +354,7 @@ def test_latex_rename_variables():
158
354
  )
159
355
 
160
356
 
161
- def test_arrays_with_no_grids():
357
+ def test_xr_arrays_with_no_grids():
162
358
  with xr.open_dataset(EXAMPLE_ARRAYS_DIR / "0001.sdf") as df:
163
359
  laser_phase = "laser_x_min_phase"
164
360
  assert laser_phase in df
@@ -169,9 +365,11 @@ def test_arrays_with_no_grids():
169
365
  assert df[random_states].shape == (8,)
170
366
 
171
367
 
172
- def test_arrays_with_no_grids_multifile():
368
+ def test_xr_arrays_with_no_grids_multifile():
173
369
  with xr.open_mfdataset(
174
- EXAMPLE_ARRAYS_DIR.glob("*.sdf"), preprocess=SDFPreprocess()
370
+ EXAMPLE_ARRAYS_DIR.glob("*.sdf"),
371
+ join="outer",
372
+ preprocess=SDFPreprocess(),
175
373
  ) as df:
176
374
  laser_phase = "laser_x_min_phase"
177
375
  assert laser_phase in df
@@ -182,20 +380,20 @@ def test_arrays_with_no_grids_multifile():
182
380
  assert df[random_states].shape == (2, 8)
183
381
 
184
382
 
185
- def test_3d_distribution_function():
383
+ def test_xr_3d_distribution_function():
186
384
  with xr.open_dataset(EXAMPLE_3D_DIST_FN / "0000.sdf") as df:
187
385
  distribution_function = "dist_fn_x_px_py_Electron"
188
386
  assert df[distribution_function].shape == (16, 20, 20)
189
387
 
190
388
 
191
- def test_drop_variables():
389
+ def test_xr_drop_variables():
192
390
  with xr.open_dataset(
193
391
  EXAMPLE_FILES_DIR / "0000.sdf", drop_variables=["Electric_Field_Ex"]
194
392
  ) as df:
195
393
  assert "Electric_Field_Ex" not in df
196
394
 
197
395
 
198
- def test_drop_variables_multiple():
396
+ def test_xr_drop_variables_multiple():
199
397
  with xr.open_dataset(
200
398
  EXAMPLE_FILES_DIR / "0000.sdf",
201
399
  drop_variables=["Electric_Field_Ex", "Electric_Field_Ey"],
@@ -204,7 +402,7 @@ def test_drop_variables_multiple():
204
402
  assert "Electric_Field_Ey" not in df
205
403
 
206
404
 
207
- def test_drop_variables_original():
405
+ def test_xr_drop_variables_original():
208
406
  with xr.open_dataset(
209
407
  EXAMPLE_FILES_DIR / "0000.sdf",
210
408
  drop_variables=["Electric_Field/Ex", "Electric_Field/Ey"],
@@ -213,7 +411,7 @@ def test_drop_variables_original():
213
411
  assert "Electric_Field_Ey" not in df
214
412
 
215
413
 
216
- def test_drop_variables_mixed():
414
+ def test_xr_drop_variables_mixed():
217
415
  with xr.open_dataset(
218
416
  EXAMPLE_FILES_DIR / "0000.sdf",
219
417
  drop_variables=["Electric_Field/Ex", "Electric_Field_Ey"],
@@ -222,14 +420,14 @@ def test_drop_variables_mixed():
222
420
  assert "Electric_Field_Ey" not in df
223
421
 
224
422
 
225
- def test_erroring_drop_variables():
423
+ def test_xr_erroring_drop_variables():
226
424
  with pytest.raises(KeyError):
227
425
  xr.open_dataset(
228
426
  EXAMPLE_FILES_DIR / "0000.sdf", drop_variables=["Electric_Field/E"]
229
427
  )
230
428
 
231
429
 
232
- def test_loading_multiple_probes():
430
+ def test_xr_loading_multiple_probes():
233
431
  with xr.open_dataset(
234
432
  EXAMPLE_2D_PARTICLE_DATA / "0002.sdf",
235
433
  keep_particles=True,
@@ -241,7 +439,7 @@ def test_loading_multiple_probes():
241
439
  assert "ID_Electron_Back_Probe_Px" in df.dims
242
440
 
243
441
 
244
- def test_loading_one_probe_drop_second_probe():
442
+ def test_xr_oading_one_probe_drop_second_probe():
245
443
  with xr.open_dataset(
246
444
  EXAMPLE_2D_PARTICLE_DATA / "0002.sdf",
247
445
  keep_particles=True,