sdf-xarray 0.7.1__tar.gz → 0.7.3__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 (67) hide show
  1. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/.gitignore +1 -0
  2. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/PKG-INFO +1 -1
  3. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/animation.md +12 -7
  4. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/conf.py +26 -1
  5. sdf_xarray-0.7.3/docs/epoch_workshop_2026/animating/animate_1D_density.py +19 -0
  6. sdf_xarray-0.7.3/docs/epoch_workshop_2026/animating/animate_1D_poynting_flux.py +33 -0
  7. sdf_xarray-0.7.3/docs/epoch_workshop_2026/animating/animate_2D_dist_fn_multispecies.py +25 -0
  8. sdf_xarray-0.7.3/docs/epoch_workshop_2026/animating/animate_2D_dist_fn_multispecies_alternative.py +56 -0
  9. sdf_xarray-0.7.3/docs/epoch_workshop_2026/animating/animate_2D_poynting_flux.py +33 -0
  10. sdf_xarray-0.7.3/docs/epoch_workshop_2026/animations.md +48 -0
  11. sdf_xarray-0.7.3/docs/epoch_workshop_2026/histograms/plot_hist_ke.py +38 -0
  12. sdf_xarray-0.7.3/docs/epoch_workshop_2026/histograms/plot_hist_ke_probe.py +47 -0
  13. sdf_xarray-0.7.3/docs/epoch_workshop_2026/histograms/plot_hist_px.py +26 -0
  14. sdf_xarray-0.7.3/docs/epoch_workshop_2026/histograms.md +34 -0
  15. sdf_xarray-0.7.3/docs/epoch_workshop_2026/live_demo.ipynb +38628 -0
  16. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plots.md +70 -0
  17. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_1D_density.py +18 -0
  18. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_1D_poynting_flux.py +32 -0
  19. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_2D_density.py +18 -0
  20. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_2D_dist_fn_multispecies.py +26 -0
  21. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_2D_poynting_flux.py +33 -0
  22. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_time_temperature.py +29 -0
  23. sdf_xarray-0.7.3/docs/epoch_workshop_2026/plotting/plot_x_px_scatter.py +24 -0
  24. sdf_xarray-0.7.3/docs/epoch_workshop_2026/workshop_details.md +7 -0
  25. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/index.md +9 -0
  26. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/loading_data.md +4 -0
  27. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/_version.py +3 -3
  28. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/download.py +13 -18
  29. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/plotting.py +2 -6
  30. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/.github/workflows/build_publish.yml +0 -0
  31. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/.github/workflows/lint.yml +0 -0
  32. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/.github/workflows/tests.yml +0 -0
  33. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/.gitmodules +0 -0
  34. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/.readthedocs.yaml +0 -0
  35. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/CITATION.cff +0 -0
  36. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/CMakeLists.txt +0 -0
  37. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/CONTRIBUTING.md +0 -0
  38. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/LICENCE +0 -0
  39. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/README.md +0 -0
  40. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/.gitignore +0 -0
  41. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_static/BEAM.png +0 -0
  42. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_static/PlasmaFAIR.svg +0 -0
  43. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_static/force_render_dark_xarray_objects.css +0 -0
  44. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_static/sdf-xarray-logo-dark.svg +0 -0
  45. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_static/sdf-xarray-logo-light.svg +0 -0
  46. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_templates/custom-class-template.rst +0 -0
  47. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/_templates/custom-module-template.rst +0 -0
  48. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/api.md +0 -0
  49. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/contributing.md +0 -0
  50. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/installation.md +0 -0
  51. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/known_issues.md +0 -0
  52. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/make.bat +0 -0
  53. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/understanding_datasets.md +0 -0
  54. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/unit_conversion.md +0 -0
  55. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/docs/why_sdf_xarray.md +0 -0
  56. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/pyproject.toml +0 -0
  57. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/__init__.py +0 -0
  58. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/csdf.pxd +0 -0
  59. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/dataarray_accessor.py +0 -0
  60. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/dataset_accessor.py +0 -0
  61. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/src/sdf_xarray/sdf_interface.pyx +0 -0
  62. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/tests/test_cython.py +0 -0
  63. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/tests/test_dataset.py +0 -0
  64. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/tests/test_datatree.py +0 -0
  65. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/tests/test_epoch_dataarray_accessor.py +0 -0
  66. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/tests/test_epoch_dataset_accessor.py +0 -0
  67. {sdf_xarray-0.7.1 → sdf_xarray-0.7.3}/uv.lock +0 -0
@@ -339,3 +339,4 @@ src/sdf_xarray/_version.py
339
339
 
340
340
  # Downloaded doc tutorial datasets
341
341
  docs/tutorial_*
342
+ docs/epoch_workshop_2026/datasets
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdf-xarray
3
- Version: 0.7.1
3
+ Version: 0.7.3
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>, Chris Herdman <chris.herdman@york.ac.uk>, Liam Pattinson <liam.pattinson@york.ac.uk>, Sviatoslav Shekhanov <sviatoslav.shekhanov@york.ac.uk>
6
6
  License-Expression: BSD-3-Clause
@@ -16,6 +16,11 @@ import xarray as xr
16
16
  import matplotlib.pyplot as plt
17
17
  from matplotlib.animation import FuncAnimation
18
18
  from IPython.display import HTML
19
+
20
+ # Matplotlib will attempt to display the first frame of the video
21
+ # and since we don't want that on the documentation we run in
22
+ # notebook mode
23
+ %matplotlib notebook
19
24
  ```
20
25
 
21
26
  ## Basic usage
@@ -107,7 +112,7 @@ same way a 2D simulation can be plotted along a line.
107
112
  ds = sdfxr.open_mfdataset("tutorial_dataset_3d/*.sdf")
108
113
 
109
114
  da = ds["Derived_Number_Density"]
110
- da_lineout = da.sel(Y_Grid_mid = 0, method="nearest")
115
+ da_lineout = da.sel(Y_Grid_mid = 0, method = "nearest")
111
116
  anim = da_lineout.epoch.animate(title = "Y = 0 [m]", fps = 2)
112
117
  anim.show()
113
118
  ```
@@ -144,7 +149,7 @@ ds = xr.open_mfdataset(
144
149
  )
145
150
 
146
151
  da = ds["Derived_Number_Density_Beam_Electrons"]
147
- anim = da.epoch.animate(move_window=True, fps = 5)
152
+ anim = da.epoch.animate(move_window = True, fps = 5)
148
153
  anim.show()
149
154
  ```
150
155
 
@@ -180,7 +185,7 @@ anim = da.epoch.animate(
180
185
  max_percentile = 95,
181
186
  title = "Target A",
182
187
  cmap = "plasma",
183
- )
188
+ )
184
189
  anim.show()
185
190
  ```
186
191
 
@@ -200,9 +205,9 @@ ds = sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf")
200
205
  anim = ds.epoch.animate_multiple(
201
206
  ds["Derived_Number_Density_Electron"],
202
207
  ds["Derived_Number_Density_Ion"],
203
- datasets_kwargs=[{"label": "Electron"}, {"label": "Ion"}],
204
- ylim=(0e27,4e27),
205
- ylabel="Derived Number Density [1/m$^3$]"
208
+ datasets_kwargs = [{"label": "Electron"}, {"label": "Ion"}],
209
+ ylim = (0e27,4e27),
210
+ ylabel = "Derived Number Density [1/m$^3$]"
206
211
  )
207
212
 
208
213
  anim.show()
@@ -242,7 +247,7 @@ flux_norm = LogNorm(
242
247
  anim = ds.epoch.animate_multiple(
243
248
  ds["Derived_Number_Density_Electron"],
244
249
  flux_masked,
245
- datasets_kwargs=[
250
+ datasets_kwargs = [
246
251
  {"alpha": 1.0},
247
252
  {"cmap": "hot", "norm": flux_norm, "alpha": 0.9},
248
253
  ],
@@ -65,7 +65,12 @@ templates_path = ["_templates"]
65
65
  # List of patterns, relative to source directory, that match files and
66
66
  # directories to ignore when looking for source files.
67
67
  # This pattern also affects html_static_path and html_extra_path.
68
- exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
68
+ exclude_patterns = [
69
+ "_build",
70
+ "Thumbs.db",
71
+ ".DS_Store",
72
+ "epoch_workshop_2026/live_demo.ipynb",
73
+ ]
69
74
 
70
75
  # Numpy-doc config
71
76
  napoleon_google_docstring = False
@@ -153,3 +158,23 @@ for dataset in datasets:
153
158
  continue
154
159
  else:
155
160
  fetch_dataset(dataset, save_path=cwd)
161
+
162
+ epoch_workshop_datasets = [
163
+ "1_1_drifting_bunch",
164
+ "2_1_two_stream_instability",
165
+ "3_3_Gaussian_1d_laser",
166
+ "3_5_Gaussian_beam",
167
+ "4_2_self_heating",
168
+ "4_3_basic_target",
169
+ "4_4_momentum_distribution",
170
+ "5_1_probe",
171
+ "5_2_subsets",
172
+ ]
173
+
174
+ epoch_workshop_dataset_folder = cwd / "epoch_workshop_2026" / "datasets"
175
+ for dataset in epoch_workshop_datasets:
176
+ # If the dataset already exists then don't download it again
177
+ if (epoch_workshop_dataset_folder / dataset).exists():
178
+ continue
179
+ else:
180
+ fetch_dataset(dataset, save_path=epoch_workshop_dataset_folder)
@@ -0,0 +1,19 @@
1
+ from pathlib import Path
2
+
3
+ import sdf_xarray as sdfxr
4
+
5
+ input_dir = Path("datasets/1_1_drifting_bunch")
6
+ ds = sdfxr.open_mfdataset(input_dir)
7
+
8
+ # Convert the time to femtoseconds
9
+ ds = ds.epoch.rescale_coords(1e15, "fs", "time")
10
+ # Convert the x and y coords to microns
11
+ ds = ds.epoch.rescale_coords(1e6, "µm", ["X_Grid_mid"])
12
+
13
+ anim = ds["Derived_Number_Density"].epoch.animate()
14
+
15
+ # Visualise it in a Jupyter notebook
16
+ anim.show()
17
+
18
+ # Or save the animation
19
+ # anim.save(input_dir / "number_density.gif", fps=5)
@@ -0,0 +1,33 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+
5
+ import sdf_xarray as sdfxr
6
+
7
+ input_dir = Path("datasets/3_3_Gaussian_1d_laser")
8
+ ds = sdfxr.open_mfdataset(input_dir)
9
+
10
+ # Convert the time to femtoseconds
11
+ ds = ds.epoch.rescale_coords(1e15, "fs", "time")
12
+ # Convert the x and y coords to microns
13
+ ds = ds.epoch.rescale_coords(1e6, "µm", ["X_Grid_mid"])
14
+
15
+ # Calculate Poynting flux magnitude
16
+ flux_magnitude = np.sqrt(
17
+ ds["Derived_Poynting_Flux_x"] ** 2
18
+ + ds["Derived_Poynting_Flux_y"] ** 2
19
+ + ds["Derived_Poynting_Flux_z"] ** 2
20
+ )
21
+
22
+ # convert to W/cm^2
23
+ I_Wcm2 = flux_magnitude * 1e-4
24
+ I_Wcm2.attrs["long_name"] = "Poynting Flux Magnitude"
25
+ I_Wcm2.attrs["units"] = "W/cm$^2$"
26
+
27
+ anim = I_Wcm2.epoch.animate()
28
+
29
+ # Visualise it in a Jupyter notebook
30
+ anim.show()
31
+
32
+ # Or save the animation
33
+ # anim.save(input_dir / "laser.gif", fps=10)
@@ -0,0 +1,25 @@
1
+ from pathlib import Path
2
+
3
+ import sdf_xarray as sdfxr
4
+
5
+ input_dir = Path("datasets/2_1_two_stream_instability")
6
+ ds = sdfxr.open_mfdataset(
7
+ input_dir, data_vars=["dist_fn_x_px_Left", "dist_fn_x_px_Right"]
8
+ )
9
+
10
+ # Rescale coords to account for kilometers
11
+ ds = ds.epoch.rescale_coords(1e-3, "km", ["X_x_px_Left"])
12
+
13
+ # Sum phase-space of species "Left" and "Right" in "x_px" distribution function
14
+ # NOTE: We only use the values from the right distribution function as if we inherit
15
+ # the coords we from the right we end up with 4 coords instead of 2
16
+ total_phase_space = ds["dist_fn_x_px_Left"] + ds["dist_fn_x_px_Right"].values
17
+ total_phase_space.attrs["long_name"] = "Phase Space Distribution"
18
+ total_phase_space.attrs["units"] = "kg.m/s"
19
+
20
+ anim = total_phase_space.epoch.animate()
21
+ # Visualise it in a Jupyter notebook
22
+ anim.show()
23
+
24
+ # Or save the animation
25
+ # anim.save(input_dir / "phase_space.gif")
@@ -0,0 +1,56 @@
1
+ from pathlib import Path
2
+
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+
6
+ import sdf_xarray as sdfxr
7
+
8
+ input_dir = Path("datasets/2_1_two_stream_instability")
9
+ ds = sdfxr.open_mfdataset(
10
+ input_dir, data_vars=["dist_fn_x_px_Left", "dist_fn_x_px_Right"]
11
+ )
12
+
13
+ # Rescale coords to account for kilometers
14
+ ds = ds.epoch.rescale_coords(1e-3, "km", ["X_x_px_Left"])
15
+
16
+ # Sum phase-space of species "Left" and "Right" in "x_px" distribution function
17
+ # NOTE: We only use the values from the right distribution function as if we inherit
18
+ # the coords we from the right we end up with 4 coords instead of 2
19
+ total_phase_space = ds["dist_fn_x_px_Left"] + ds["dist_fn_x_px_Right"].values
20
+ total_phase_space.attrs["long_name"] = "Phase Space Distribution"
21
+ total_phase_space.attrs["units"] = "kg.m/s"
22
+
23
+ fig, ax = plt.subplots()
24
+
25
+ # construct first frame
26
+ # Data needs to be transposed so that axes match
27
+ total_phase_space = total_phase_space.T
28
+ col_min = total_phase_space.values.min()
29
+ col_max = total_phase_space.values.max()
30
+ gif_frame = ax.pcolormesh(
31
+ total_phase_space["X_x_px_Left"],
32
+ total_phase_space["Px_x_px_Left"],
33
+ total_phase_space.isel(time=0),
34
+ vmin=col_min,
35
+ vmax=col_max,
36
+ )
37
+ ax.set_xlabel(
38
+ f"{total_phase_space['X_x_px_Left'].attrs['long_name']} [{total_phase_space['X_x_px_Left'].attrs['units']}]"
39
+ )
40
+ ax.set_ylabel(
41
+ f"{total_phase_space.attrs['long_name']} [{total_phase_space.attrs['units']}]"
42
+ )
43
+ ax.set_title(f"t = {ds['time'].values[0]:.3f} [{ds['time'].attrs['units']}]")
44
+ cbar = fig.colorbar(gif_frame, ax=ax)
45
+ cbar.set_label("Summed particle weight per bin")
46
+
47
+
48
+ # Make GIF
49
+ def update(i):
50
+ gif_frame.set_array(total_phase_space.isel(time=i))
51
+ ax.set_title(f"t = {ds['time'].values[i]:.3f} [{ds['time'].attrs['units']}]")
52
+ return (gif_frame,)
53
+
54
+
55
+ anim = FuncAnimation(fig, update, frames=ds.sizes["time"])
56
+ anim.save(input_dir / "phase_space.gif")
@@ -0,0 +1,33 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+
5
+ import sdf_xarray as sdfxr
6
+
7
+ input_dir = Path("datasets/3_5_Gaussian_beam")
8
+ ds = sdfxr.open_mfdataset(input_dir)
9
+
10
+ # Convert the time to femtoseconds
11
+ ds = ds.epoch.rescale_coords(1e15, "fs", "time")
12
+ # Convert the x and y coords to microns
13
+ ds = ds.epoch.rescale_coords(1e6, "µm", ["X_Grid_mid", "Y_Grid_mid"])
14
+
15
+ # Calculate Poynting flux magnitude
16
+ flux_magnitude = np.sqrt(
17
+ ds["Derived_Poynting_Flux_x"] ** 2
18
+ + ds["Derived_Poynting_Flux_y"] ** 2
19
+ + ds["Derived_Poynting_Flux_z"] ** 2
20
+ )
21
+
22
+ # convert to W/cm^2
23
+ I_Wcm2 = flux_magnitude * 1e-4
24
+ I_Wcm2.attrs["long_name"] = "Poynting Flux Magnitude"
25
+ I_Wcm2.attrs["units"] = "W/cm$^2$"
26
+
27
+ anim = I_Wcm2.epoch.animate()
28
+
29
+ # Visualise it in a Jupyter notebook
30
+ anim.show()
31
+
32
+ # Or save the animation
33
+ # anim.save(input_dir / "laser.gif", fps=10)
@@ -0,0 +1,48 @@
1
+ ---
2
+ file_format: mystnb
3
+ kernelspec:
4
+ name: python3
5
+ ---
6
+
7
+ # Animations
8
+
9
+ ```{note}
10
+ Unfortunately, these animations are not interactive like the rest of the documentation as building them on readthedocs takes too long so we display the GIFs from the datasets instead.
11
+ ```
12
+
13
+ ## Animate Density 1D
14
+
15
+ - **input deck:** <path:datasets/1_1_drifting_bunch/input.deck>
16
+ - **Python File:** <path:animating/animate_1D_density.py>
17
+
18
+ ```{literalinclude} ./animating/animate_1D_density.py
19
+ ```
20
+ ![datasets/1_1_drifting_bunch/number_density.gif](datasets/1_1_drifting_bunch/number_density.gif)
21
+
22
+ ## Animate Poynting Flux 1D
23
+
24
+ - **input deck:** <path:datasets/3_3_Gaussian_1d_laser/input.deck>
25
+ - **Python File:** <path:animating/animate_1D_poynting_flux.py>
26
+
27
+ ```{literalinclude} ./animating/animate_1D_poynting_flux.py
28
+ ```
29
+ ![datasets/3_3_Gaussian_1d_laser/laser.gif](datasets/3_3_Gaussian_1d_laser/laser.gif)
30
+
31
+ ## Animate Distribution Function Multi-Species 2D
32
+
33
+ - **input deck:** <path:datasets/2_1_two_stream_instability/input.deck>
34
+ - **Python File:** <path:animating/animate_2D_dist_fn_multispecies.py>
35
+ - **Python File (alternative):** <path:animating/animate_2D_dist_fn_multispecies_alternative.py>
36
+
37
+ ```{literalinclude} ./animating/animate_2D_dist_fn_multispecies.py
38
+ ```
39
+ ![datasets/2_1_two_stream_instability/phase_space.gif](datasets/2_1_two_stream_instability/phase_space.gif)
40
+
41
+ ## Animate Poynting Flux 2D
42
+
43
+ - **input deck:** <path:datasets/3_5_Gaussian_beam/input.deck>
44
+ - **Python File:** <path:animating/animate_2D_poynting_flux.py>
45
+
46
+ ```{literalinclude} ./animating/animate_2D_poynting_flux.py
47
+ ```
48
+ ![datasets/3_5_Gaussian_beam/laser.gif](datasets/3_5_Gaussian_beam/laser.gif)
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+ import sdf_xarray as sdfxr
7
+
8
+ # Constants
9
+ mass = 9.1093837e-31
10
+ c = 299792458
11
+ q0 = 1.60217663e-19
12
+
13
+ input_dir = Path("datasets/4_3_basic_target")
14
+ ds = sdfxr.open_dataset(input_dir / "0000.sdf", keep_particles=True)
15
+ particles_magnitude = (
16
+ ds["Particles_Px_Electron"] ** 2
17
+ + ds["Particles_Py_Electron"] ** 2
18
+ + ds["Particles_Pz_Electron"] ** 2
19
+ )
20
+ ke_MeV = (np.sqrt(particles_magnitude * c**2 + mass**2 * c**4) - mass * c**2) / (
21
+ 1.0e6 * q0
22
+ )
23
+
24
+ bin_edges = np.linspace(ke_MeV.min().values, ke_MeV.max().values, 101)
25
+ bin_N, _ = np.histogram(
26
+ ke_MeV.values, bins=bin_edges, weights=ds["Particles_Weight_Electron"]
27
+ )
28
+ dKE = np.diff(bin_edges)
29
+ dN_dKE = bin_N / dKE
30
+ bin_centres = 0.5 * (bin_edges[:-1] + bin_edges[1:])
31
+
32
+ plt.plot(bin_centres, dN_dKE)
33
+ plt.xlabel("Kinetic energy [MeV]")
34
+ plt.ylabel("dN/dKE [1/MeV]")
35
+
36
+ plt.savefig(input_dir / "ke_spectrum.png", dpi=300)
37
+ np.savetxt(input_dir / "ke_spectrum_vals.txt", ke_MeV.values)
38
+ np.savetxt(input_dir / "ke_spectrum_ke.txt", bin_centres)
@@ -0,0 +1,47 @@
1
+ from pathlib import Path
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+ import sdf_xarray as sdfxr
7
+
8
+ # Constants
9
+ mass = 9.1093837e-31
10
+ c = 299792458
11
+ q0 = 1.60217663e-19
12
+
13
+ input_dir = Path("datasets/5_1_probe")
14
+ # Since we're loading particle and probes we need to specify their name
15
+ # from the input.deck
16
+ ds = sdfxr.open_mfdataset(
17
+ input_dir, keep_particles=True, probe_names=["Electron_probe"]
18
+ )
19
+
20
+ px = ds["Electron_probe_Px"].values.flatten()
21
+ py = ds["Electron_probe_Py"].values.flatten()
22
+ pz = ds["Electron_probe_Pz"].values.flatten()
23
+ probe_weights_raw = ds["Electron_probe_weight"].values.flatten()
24
+
25
+ # Create a mask to remove NaNs
26
+ mask = ~np.isnan(probe_weights_raw)
27
+ probe_weights = probe_weights_raw[mask]
28
+ probe_px = px[mask]
29
+ probe_py = py[mask]
30
+ probe_pz = pz[mask]
31
+
32
+ probe_magnitude = probe_px**2 + probe_py**2 + probe_pz**2
33
+ ke_MeV = (np.sqrt(probe_magnitude * c**2 + mass**2 * c**4) - mass * c**2) / (1.0e6 * q0)
34
+
35
+ bin_edges = np.linspace(ke_MeV.min(), ke_MeV.max(), 101)
36
+ bin_N, _ = np.histogram(ke_MeV, bins=bin_edges, weights=probe_weights)
37
+ dKE = np.diff(bin_edges)
38
+ dN_dKE = bin_N / dKE
39
+ bin_centres = 0.5 * (bin_edges[:-1] + bin_edges[1:])
40
+
41
+ plt.plot(bin_centres, dN_dKE)
42
+ plt.xlabel("Kinetic energy [MeV]")
43
+ plt.ylabel("dN/dKE [1/MeV]")
44
+
45
+ plt.savefig(input_dir / "ke_spectrum.png", dpi=300)
46
+ np.savetxt(input_dir / "ke_spectrum_vals.txt", ke_MeV)
47
+ np.savetxt(input_dir / "ke_spectrum_ke.txt", bin_centres)
@@ -0,0 +1,26 @@
1
+ from pathlib import Path
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+ import sdf_xarray as sdfxr
7
+
8
+ input_dir = Path("datasets/4_4_momentum_distribution")
9
+ ds = sdfxr.open_dataset(input_dir / "0000.sdf", keep_particles=True)
10
+ px = ds["Particles_Px_Electron_user"].values
11
+
12
+ bin_edges = np.linspace(px.min(), px.max(), 101)
13
+ bin_N, _ = np.histogram(
14
+ px, bins=bin_edges, weights=ds["Particles_Weight_Electron_user"]
15
+ )
16
+ dpx = np.diff(bin_edges)
17
+ dN_dpx = bin_N / dpx
18
+ bin_centres = 0.5 * (bin_edges[:-1] + bin_edges[1:])
19
+
20
+ plt.plot(bin_centres, dN_dpx)
21
+ plt.xlabel("Px [kg.m/s]")
22
+ plt.ylabel("dN/dp$_x$ [s/(kg.m)]")
23
+
24
+ plt.savefig(input_dir / "px_spectrum.png", dpi=300)
25
+ np.savetxt(input_dir / "px_spectrum_vals.txt", dN_dpx)
26
+ np.savetxt(input_dir / "ke_spectrum_px.txt", bin_centres)
@@ -0,0 +1,34 @@
1
+ ---
2
+ file_format: mystnb
3
+ kernelspec:
4
+ name: python3
5
+ ---
6
+
7
+ # Histograms
8
+
9
+ ## Plot Histogram Kinetic Energy Probes
10
+
11
+ - **input deck:** <path:datasets/5_1_probe/input.deck>
12
+ - **Python File:** <path:histograms/plot_hist_ke_probe.py>
13
+
14
+ ```{code-cell} ipython3
15
+ :load: ./histograms/plot_hist_ke_probe.py
16
+ ```
17
+
18
+ ## Plot Histogram Kinetic Energy
19
+
20
+ - **input deck:** <path:datasets/4_3_basic_target/input.deck>
21
+ - **Python File:** <path:histograms/plot_hist_ke.py>
22
+
23
+ ```{code-cell} ipython3
24
+ :load: ./histograms/plot_hist_ke.py
25
+ ```
26
+
27
+ ## Plot Histogram Electron Momentum
28
+
29
+ - **input deck:** <path:datasets/4_4_momentum_distribution/input.deck>
30
+ - **Python File:** <path:histograms/plot_hist_px.py>
31
+
32
+ ```{code-cell} ipython3
33
+ :load: ./histograms/plot_hist_px.py
34
+ ```