sdf-xarray 0.3.1__tar.gz → 0.3.2__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 (129) hide show
  1. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/CITATION.cff +4 -0
  2. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/PKG-INFO +2 -2
  3. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/pyproject.toml +1 -0
  4. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/src/sdf_xarray/__init__.py +104 -13
  5. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/src/sdf_xarray/_version.py +3 -3
  6. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/test_basic.py +232 -1
  7. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.github/workflows/black.yml +0 -0
  8. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.github/workflows/build_publish.yml +0 -0
  9. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.github/workflows/lint.yml +0 -0
  10. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.github/workflows/tests.yml +0 -0
  11. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.gitignore +0 -0
  12. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.gitmodules +0 -0
  13. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/.readthedocs.yaml +0 -0
  14. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/BEAM.png +0 -0
  15. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/CMakeLists.txt +0 -0
  16. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/CONTRIBUTING.md +0 -0
  17. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/LICENCE +0 -0
  18. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/PlasmaFAIR.svg +0 -0
  19. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/README.md +0 -0
  20. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/.gitignore +0 -0
  21. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/_templates/custom-class-template.rst +0 -0
  22. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/_templates/custom-module-template.rst +0 -0
  23. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/api.rst +0 -0
  24. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/conf.py +0 -0
  25. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/contributing.rst +0 -0
  26. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/getting_started.rst +0 -0
  27. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/index.rst +0 -0
  28. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/key_functionality.rst +0 -0
  29. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/known_issues.rst +0 -0
  30. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/make.bat +0 -0
  31. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0000.sdf +0 -0
  32. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0001.sdf +0 -0
  33. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0002.sdf +0 -0
  34. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0003.sdf +0 -0
  35. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0004.sdf +0 -0
  36. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0005.sdf +0 -0
  37. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0006.sdf +0 -0
  38. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0007.sdf +0 -0
  39. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0008.sdf +0 -0
  40. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0009.sdf +0 -0
  41. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0010.sdf +0 -0
  42. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0011.sdf +0 -0
  43. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0012.sdf +0 -0
  44. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0013.sdf +0 -0
  45. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0014.sdf +0 -0
  46. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0015.sdf +0 -0
  47. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0016.sdf +0 -0
  48. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0017.sdf +0 -0
  49. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0018.sdf +0 -0
  50. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0019.sdf +0 -0
  51. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0020.sdf +0 -0
  52. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0021.sdf +0 -0
  53. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0022.sdf +0 -0
  54. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0023.sdf +0 -0
  55. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0024.sdf +0 -0
  56. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0025.sdf +0 -0
  57. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0026.sdf +0 -0
  58. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0027.sdf +0 -0
  59. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0028.sdf +0 -0
  60. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0029.sdf +0 -0
  61. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0030.sdf +0 -0
  62. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0031.sdf +0 -0
  63. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0032.sdf +0 -0
  64. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0033.sdf +0 -0
  65. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0034.sdf +0 -0
  66. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0035.sdf +0 -0
  67. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0036.sdf +0 -0
  68. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0037.sdf +0 -0
  69. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0038.sdf +0 -0
  70. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0039.sdf +0 -0
  71. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/0040.sdf +0 -0
  72. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/deck.status +0 -0
  73. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/epoch1d.dat +0 -0
  74. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/input.deck +0 -0
  75. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/normal.visit +0 -0
  76. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_1d/restart.visit +0 -0
  77. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/0000.sdf +0 -0
  78. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/0001.sdf +0 -0
  79. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/0002.sdf +0 -0
  80. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/0003.sdf +0 -0
  81. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/0004.sdf +0 -0
  82. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/0005.sdf +0 -0
  83. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/tutorial_dataset_2d/input.deck +0 -0
  84. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/docs/unit_conversion.rst +0 -0
  85. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/src/sdf_xarray/csdf.pxd +0 -0
  86. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/src/sdf_xarray/dataset_accessor.py +0 -0
  87. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/src/sdf_xarray/plotting.py +0 -0
  88. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/src/sdf_xarray/sdf_interface.pyx +0 -0
  89. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_array_no_grids/0000.sdf +0 -0
  90. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_array_no_grids/0001.sdf +0 -0
  91. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_array_no_grids/README.md +0 -0
  92. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_array_no_grids/input.deck +0 -0
  93. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_dist_fn/0000.sdf +0 -0
  94. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_dist_fn/0001.sdf +0 -0
  95. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_dist_fn/0002.sdf +0 -0
  96. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_dist_fn/input.deck +0 -0
  97. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0000.sdf +0 -0
  98. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0001.sdf +0 -0
  99. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0002.sdf +0 -0
  100. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0003.sdf +0 -0
  101. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0004.sdf +0 -0
  102. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0005.sdf +0 -0
  103. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0006.sdf +0 -0
  104. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0007.sdf +0 -0
  105. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0008.sdf +0 -0
  106. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0009.sdf +0 -0
  107. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/0010.sdf +0 -0
  108. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/README.md +0 -0
  109. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_1D/input.deck +0 -0
  110. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_2D_moving_window/0000.sdf +0 -0
  111. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_2D_moving_window/0001.sdf +0 -0
  112. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_2D_moving_window/0002.sdf +0 -0
  113. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_2D_moving_window/0003.sdf +0 -0
  114. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_2D_moving_window/0004.sdf +0 -0
  115. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_2D_moving_window/input.deck +0 -0
  116. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_3D/0000.sdf +0 -0
  117. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_3D/0001.sdf +0 -0
  118. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_files_3D/input.deck +0 -0
  119. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_mismatched_files/0000.sdf +0 -0
  120. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_mismatched_files/0001.sdf +0 -0
  121. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_mismatched_files/0002.sdf +0 -0
  122. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_two_probes_2D/0000.sdf +0 -0
  123. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_two_probes_2D/0001.sdf +0 -0
  124. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_two_probes_2D/0002.sdf +0 -0
  125. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/example_two_probes_2D/input.deck +0 -0
  126. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/test_cython.py +0 -0
  127. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/test_epoch_dataarray_accessor.py +0 -0
  128. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/tests/test_epoch_dataset_accessor.py +0 -0
  129. {sdf_xarray-0.3.1 → sdf_xarray-0.3.2}/uv.lock +0 -0
@@ -16,5 +16,9 @@ authors:
16
16
  given-names: Shaun
17
17
  orcid: 'https://orcid.org/0009-0005-0693-030X'
18
18
  affiliation: University of York
19
+ - family-names: Herdman
20
+ given-names: Chris
21
+ orcid: 'https://orcid.org/0000-0002-5159-0130'
22
+ affiliation: University of York
19
23
  doi: 10.5281/zenodo.15351323
20
24
  date-released: '2024-07-25'
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdf-xarray
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Provides a backend for xarray to read SDF files as created by the EPOCH plasma PIC code.
5
- Author-Email: Peter Hill <peter.hill@york.ac.uk>, Joel Adams <joel.adams@york.ac.uk>, Shaun Doherty <shaun.doherty@york.ac.uk>
5
+ Author-Email: Peter Hill <peter.hill@york.ac.uk>, Joel Adams <joel.adams@york.ac.uk>, Shaun Doherty <shaun.doherty@york.ac.uk>, Chris Herdman <chris.herdman@york.ac.uk>
6
6
  License-Expression: BSD-3-Clause
7
7
  Classifier: Development Status :: 5 - Production/Stable
8
8
  Classifier: Intended Audience :: Science/Research
@@ -16,6 +16,7 @@ authors = [
16
16
  { name = "Peter Hill", email = "peter.hill@york.ac.uk" },
17
17
  { name = "Joel Adams", email = "joel.adams@york.ac.uk" },
18
18
  { name = "Shaun Doherty", email = "shaun.doherty@york.ac.uk" },
19
+ { name = "Chris Herdman", email = "chris.herdman@york.ac.uk" },
19
20
  ]
20
21
  requires-python = ">=3.10,<3.14"
21
22
  dependencies = ["numpy>=2.0.0", "xarray>=2024.1.0", "dask>=2024.7.1"]
@@ -84,8 +84,45 @@ def _resolve_glob(path_glob: PathLike | Iterable[PathLike]):
84
84
  return paths
85
85
 
86
86
 
87
- def combine_datasets(path_glob: Iterable | str, **kwargs) -> xr.Dataset:
88
- """Combine all datasets using a single time dimension"""
87
+ def purge_unselected_data_vars(ds: xr.Dataset, data_vars: list[str]) -> xr.Dataset:
88
+ """
89
+ If the user has exclusively requested only certain variables be
90
+ loaded in then we purge all other variables and dimensions
91
+ """
92
+ existing_data_vars = set(ds.data_vars.keys())
93
+ vars_to_keep = set(data_vars) & existing_data_vars
94
+ vars_to_drop = existing_data_vars - vars_to_keep
95
+ ds = ds.drop_vars(vars_to_drop)
96
+
97
+ existing_dims = set(ds.sizes)
98
+ dims_to_keep = set()
99
+ for var in vars_to_keep:
100
+ dims_to_keep.update(ds[var].coords._names)
101
+ dims_to_keep.update(ds[var].dims)
102
+
103
+ coords_to_drop = existing_dims - dims_to_keep
104
+ return ds.drop_dims(coords_to_drop)
105
+
106
+
107
+ def combine_datasets(
108
+ path_glob: Iterable | str, data_vars: list[str], **kwargs
109
+ ) -> xr.Dataset:
110
+ """
111
+ Combine all datasets using a single time dimension, optionally extract
112
+ data from only the listed data_vars
113
+ """
114
+
115
+ if data_vars is not None:
116
+ return xr.open_mfdataset(
117
+ path_glob,
118
+ join="outer",
119
+ coords="different",
120
+ compat="no_conflicts",
121
+ combine="nested",
122
+ concat_dim="time",
123
+ preprocess=SDFPreprocess(data_vars=data_vars),
124
+ **kwargs,
125
+ )
89
126
 
90
127
  return xr.open_mfdataset(
91
128
  path_glob,
@@ -104,6 +141,7 @@ def open_mfdataset(
104
141
  separate_times: bool = False,
105
142
  keep_particles: bool = False,
106
143
  probe_names: list[str] | None = None,
144
+ data_vars: list[str] | None = None,
107
145
  ) -> xr.Dataset:
108
146
  """Open a set of EPOCH SDF files as one `xarray.Dataset`
109
147
 
@@ -135,19 +173,34 @@ def open_mfdataset(
135
173
  If ``True``, also load particle data (this may use a lot of memory!)
136
174
  probe_names :
137
175
  List of EPOCH probe names
176
+ data_vars :
177
+ List of data vars to load in (If not specified loads in all variables)
138
178
  """
139
179
 
140
180
  path_glob = _resolve_glob(path_glob)
181
+
141
182
  if not separate_times:
142
183
  return combine_datasets(
143
- path_glob, keep_particles=keep_particles, probe_names=probe_names
184
+ path_glob,
185
+ data_vars=data_vars,
186
+ keep_particles=keep_particles,
187
+ probe_names=probe_names,
144
188
  )
145
189
 
146
190
  _, var_times_map = make_time_dims(path_glob)
147
- all_dfs = [
148
- xr.open_dataset(f, keep_particles=keep_particles, probe_names=probe_names)
149
- for f in path_glob
150
- ]
191
+
192
+ all_dfs = []
193
+ for f in path_glob:
194
+ ds = xr.open_dataset(f, keep_particles=keep_particles, probe_names=probe_names)
195
+
196
+ # If the data_vars are specified then only load them in and disregard the rest.
197
+ # If there are no remaining data variables then skip adding the dataset to list
198
+ if data_vars is not None:
199
+ ds = purge_unselected_data_vars(ds, data_vars)
200
+ if not ds.data_vars:
201
+ continue
202
+
203
+ all_dfs.append(ds)
151
204
 
152
205
  for df in all_dfs:
153
206
  for da in df:
@@ -165,7 +218,6 @@ def open_mfdataset(
165
218
 
166
219
  return xr.combine_by_coords(
167
220
  all_dfs,
168
- data_vars="all",
169
221
  coords="different",
170
222
  combine_attrs="drop_conflicts",
171
223
  join="outer",
@@ -523,10 +575,43 @@ class SDFEntrypoint(BackendEntrypoint):
523
575
 
524
576
 
525
577
  class SDFPreprocess:
526
- """Preprocess SDF files for xarray ensuring matching job ids and sets time dimension"""
578
+ """Preprocess SDF files for xarray ensuring matching job ids and sets
579
+ time dimension.
580
+
581
+ This class is used as a 'preprocess' function within ``xr.open_mfdataset``. It
582
+ performs three main duties on each individual file's Dataset:
583
+
584
+ 1. Checks for a **matching job ID** across all files to ensure dataset consistency.
585
+ 2. **Filters** the Dataset to keep only the variables specified in `data_vars`
586
+ and their required coordinates.
587
+ 3. **Expands dimensions** to include a single 'time' coordinate, preparing the
588
+ Dataset for concatenation.
589
+
590
+ EPOCH can output variables at different intervals, so some SDF files
591
+ may not contain the requested variable. We combine this data into one
592
+ dataset by concatenating across the time dimension.
593
+
594
+ The combination is performed using ``join="outer"`` (in the calling ``open_mfdataset`` function),
595
+ meaning that the final combined dataset will contain the variable across the
596
+ entire time span, with NaNs filling the time steps where the variable was absent in
597
+ the individual file.
598
+
599
+ With large SDF files, this filtering method will save on memory consumption when
600
+ compared to loading all variables from all files before concatenation.
527
601
 
528
- def __init__(self):
602
+ Parameters
603
+ ----------
604
+ data_vars :
605
+ A list of data variables to load in (If not specified loads
606
+ in all variables)
607
+ """
608
+
609
+ def __init__(
610
+ self,
611
+ data_vars: list[str] | None = None,
612
+ ):
529
613
  self.job_id: int | None = None
614
+ self.data_vars = data_vars
530
615
 
531
616
  def __call__(self, ds: xr.Dataset) -> xr.Dataset:
532
617
  if self.job_id is None:
@@ -537,17 +622,23 @@ class SDFPreprocess:
537
622
  f"Mismatching job ids (got {ds.attrs['jobid1']}, expected {self.job_id})"
538
623
  )
539
624
 
540
- ds = ds.expand_dims(time=[ds.attrs["time"]])
625
+ # If the user has exclusively requested only certain variables be
626
+ # loaded in then we purge all other variables and coordinates
627
+ if self.data_vars:
628
+ ds = purge_unselected_data_vars(ds, self.data_vars)
629
+
630
+ time_val = ds.attrs.get("time", np.nan)
631
+ ds = ds.expand_dims(time=[time_val])
541
632
  ds = ds.assign_coords(
542
633
  time=(
543
634
  "time",
544
- [ds.attrs["time"]],
635
+ [time_val],
545
636
  {"units": "s", "long_name": "Time", "full_name": "time"},
546
637
  )
547
638
  )
548
639
  # Particles' spartial coordinates also evolve in time
549
640
  for coord, value in ds.coords.items():
550
641
  if value.attrs.get("point_data", False):
551
- ds.coords[coord] = value.expand_dims(time=[ds.attrs["time"]])
642
+ ds.coords[coord] = value.expand_dims(time=[time_val])
552
643
 
553
644
  return ds
@@ -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.3.1'
32
- __version_tuple__ = version_tuple = (0, 3, 1)
31
+ __version__ = version = '0.3.2'
32
+ __version_tuple__ = version_tuple = (0, 3, 2)
33
33
 
34
- __commit_id__ = commit_id = 'gce5426d4a'
34
+ __commit_id__ = commit_id = 'g331520e50'
@@ -5,7 +5,12 @@ import numpy.testing as npt
5
5
  import pytest
6
6
  import xarray as xr
7
7
 
8
- from sdf_xarray import SDFPreprocess, _process_latex_name, _resolve_glob, open_mfdataset
8
+ from sdf_xarray import (
9
+ SDFPreprocess,
10
+ _process_latex_name,
11
+ _resolve_glob,
12
+ open_mfdataset,
13
+ )
9
14
 
10
15
  EXAMPLE_FILES_DIR = pathlib.Path(__file__).parent / "example_files_1D"
11
16
  EXAMPLE_MISMATCHED_FILES_DIR = (
@@ -454,3 +459,229 @@ def test_xr_oading_one_probe_drop_second_probe():
454
459
  assert "X_Probe_Electron_Front_Probe" in df.coords
455
460
  assert "ID_Electron_Front_Probe_Px" in df.dims
456
461
  assert "ID_Electron_Back_Probe_Px" not in df.dims
462
+
463
+
464
+ def test_open_mfdataset_data_vars_single():
465
+ with open_mfdataset(
466
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
467
+ data_vars=["Electric_Field_Ex"],
468
+ ) as df:
469
+ ex_field = "Electric_Field_Ex"
470
+ x_coord = "X_Grid_mid"
471
+ assert ex_field in df
472
+ assert x_coord in df[ex_field].coords
473
+ assert "time" in df[ex_field].coords
474
+ assert df[x_coord].attrs["long_name"] == "X"
475
+
476
+ assert "Electric_Field_Ey" not in df
477
+
478
+
479
+ def test_open_mfdataset_data_vars_multiple():
480
+ with open_mfdataset(
481
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
482
+ data_vars=["Electric_Field_Ex", "Electric_Field_Ey"],
483
+ ) as df:
484
+ ex_field = "Electric_Field_Ex"
485
+ x_coord = "X_Grid_mid"
486
+ assert ex_field in df
487
+ assert x_coord in df[ex_field].coords
488
+ assert "time" in df[ex_field].coords
489
+ assert df[x_coord].attrs["long_name"] == "X"
490
+
491
+ ey_field = "Electric_Field_Ey"
492
+ x_coord = "X_Grid_mid"
493
+ assert ey_field in df
494
+ assert x_coord in df[ey_field].coords
495
+ assert "time" in df[ey_field].coords
496
+ assert df[x_coord].attrs["long_name"] == "X"
497
+
498
+
499
+ def test_open_mfdataset_data_vars_sparse_multiple():
500
+ with open_mfdataset(
501
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
502
+ keep_particles=True,
503
+ data_vars=[
504
+ "Particles_Particles_Per_Cell_proton",
505
+ "Electric_Field_Ez",
506
+ "dist_fn_x_px_proton",
507
+ ],
508
+ ) as df:
509
+ ppc_proton = "Particles_Particles_Per_Cell_proton"
510
+ assert ppc_proton in df
511
+ assert "time" in df[ppc_proton].coords
512
+ assert (
513
+ df[ppc_proton].attrs["long_name"] == "Particles Particles Per Cell proton"
514
+ )
515
+
516
+ ez_field = "Electric_Field_Ez"
517
+ assert ez_field in df
518
+ assert len(df[ez_field].coords) == 2
519
+ assert "time" in df[ez_field].coords
520
+ assert "X_Grid_mid" in df[ez_field].coords
521
+ assert df[ez_field].attrs["long_name"] == "Electric Field $E_z$"
522
+
523
+ dist_fn = "dist_fn_x_px_proton"
524
+ assert dist_fn in df
525
+ assert len(df[dist_fn].coords) == 3
526
+ assert "time" in df[dist_fn].coords
527
+ assert "X_x_px_proton" in df[dist_fn].coords
528
+ assert "Px_x_px_proton" in df[dist_fn].coords
529
+
530
+ assert df["time"].size == 11
531
+
532
+
533
+ def test_open_mfdataset_data_vars_invalid_var():
534
+ with open_mfdataset(
535
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
536
+ data_vars=["Electric_Field"],
537
+ ) as df:
538
+ assert len(df.variables.keys()) == 1
539
+ assert df["time"].size == 11
540
+
541
+
542
+ def test_open_mfdataset_data_vars_time():
543
+ with open_mfdataset(
544
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
545
+ data_vars=["Electric_Field_Ex"],
546
+ ) as df:
547
+ time = df["time"]
548
+ assert time.units == "s"
549
+ assert time.long_name == "Time"
550
+ assert time.full_name == "time"
551
+
552
+ time_values = np.array(
553
+ [
554
+ 5.466993e-14,
555
+ 2.417504e-10,
556
+ 4.833915e-10,
557
+ 7.251419e-10,
558
+ 9.667830e-10,
559
+ 1.208533e-09,
560
+ 1.450175e-09,
561
+ 1.691925e-09,
562
+ 1.933566e-09,
563
+ 2.175316e-09,
564
+ 2.416958e-09,
565
+ ]
566
+ )
567
+
568
+ npt.assert_allclose(time_values, time.values, rtol=1e-6)
569
+
570
+
571
+ def test_open_mfdataset_data_vars_sparse_time():
572
+ with open_mfdataset(
573
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
574
+ data_vars=["Particles_Particles_Per_Cell_proton"],
575
+ ) as df:
576
+ time = df["time"]
577
+ assert time.units == "s"
578
+ assert time.long_name == "Time"
579
+ assert time.full_name == "time"
580
+
581
+ time_values = np.array(
582
+ [
583
+ 5.466993e-14,
584
+ 2.417504e-10,
585
+ 4.833915e-10,
586
+ 7.251419e-10,
587
+ 9.667830e-10,
588
+ 1.208533e-09,
589
+ 1.450175e-09,
590
+ 1.691925e-09,
591
+ 1.933566e-09,
592
+ 2.175316e-09,
593
+ 2.416958e-09,
594
+ ]
595
+ )
596
+
597
+ npt.assert_allclose(time_values, time.values, rtol=1e-6)
598
+
599
+
600
+ def test_open_mfdataset_data_vars_separate_times_single():
601
+ with open_mfdataset(
602
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
603
+ data_vars=["Electric_Field_Ex"],
604
+ separate_times=True,
605
+ ) as df:
606
+ coords = df.coords.sizes
607
+ assert len(coords) == 2
608
+ assert coords["time0"] == 11
609
+ assert coords["X_Grid_mid"] == 16
610
+
611
+ elec_x = "Electric_Field_Ex"
612
+ elec_x_coords = df[elec_x].coords.sizes
613
+ assert elec_x in df
614
+ assert len(elec_x_coords) == 2
615
+ assert "time0" in elec_x_coords
616
+ assert "X_Grid_mid" in elec_x_coords
617
+
618
+ assert elec_x_coords["time0"] == 11
619
+ assert elec_x_coords["X_Grid_mid"] == 16
620
+
621
+
622
+ def test_open_mfdataset_data_vars_separate_times_multiple():
623
+ with open_mfdataset(
624
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
625
+ data_vars=["Electric_Field_Ex", "Electric_Field_Ey"],
626
+ separate_times=True,
627
+ ) as df:
628
+ coords = df.coords.sizes
629
+ assert len(coords) == 2
630
+ assert coords["time0"] == 11
631
+ assert coords["X_Grid_mid"] == 16
632
+
633
+ elec_x = "Electric_Field_Ex"
634
+ elec_x_coords = df[elec_x].coords.sizes
635
+ assert elec_x in df
636
+ assert len(elec_x_coords) == 2
637
+ assert "time0" in elec_x_coords
638
+ assert "X_Grid_mid" in elec_x_coords
639
+
640
+ assert elec_x_coords["time0"] == 11
641
+ assert elec_x_coords["X_Grid_mid"] == 16
642
+
643
+ elec_y = "Electric_Field_Ey"
644
+ elec_y_coords = df[elec_y].coords.sizes
645
+ assert elec_y in df
646
+ assert len(elec_y_coords) == 2
647
+ assert "time0" in elec_y_coords
648
+ assert "X_Grid_mid" in elec_y_coords
649
+
650
+ assert elec_y_coords["time0"] == 11
651
+ assert elec_y_coords["X_Grid_mid"] == 16
652
+
653
+
654
+ def test_open_mfdataset_data_vars_separate_times_multiple_times_keep_particles():
655
+ with open_mfdataset(
656
+ EXAMPLE_FILES_DIR.glob("*.sdf"),
657
+ data_vars=["Electric_Field_Ex", "Particles_Px_electron_beam"],
658
+ separate_times=True,
659
+ keep_particles=True,
660
+ ) as df:
661
+ coords = df.coords.sizes
662
+ assert len(coords) == 5
663
+ assert coords["time0"] == 11
664
+ assert coords["time1"] == 1
665
+ assert coords["time2"] == 1
666
+ assert coords["X_Grid_mid"] == 16
667
+ assert coords["ID_electron_beam"] == 1440
668
+
669
+ elec_x = "Electric_Field_Ex"
670
+ elec_x_coords = df[elec_x].coords.sizes
671
+ assert elec_x in df
672
+ assert len(elec_x_coords) == 2
673
+ assert "time0" in elec_x_coords
674
+ assert "X_Grid_mid" in elec_x_coords
675
+
676
+ assert elec_x_coords["time0"] == 11
677
+ assert elec_x_coords["X_Grid_mid"] == 16
678
+
679
+ particle_px = "Particles_Px_electron_beam"
680
+ particle_px_coords = df[particle_px].coords.sizes
681
+ assert particle_px in df
682
+ assert len(particle_px_coords) == 2
683
+ assert "time2" in particle_px_coords
684
+ assert "ID_electron_beam" in particle_px_coords
685
+
686
+ assert particle_px_coords["time2"] == 1
687
+ assert particle_px_coords["ID_electron_beam"] == 1440
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