Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP/ENH: wavefront class #68

Closed
wants to merge 84 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
cf27ef0
WIP/ENH: wavefront support
ken-lauer Jul 3, 2024
b9dde15
MAINT: tweaks from balticfish
ken-lauer Aug 1, 2024
28b73fa
FIX: wavefront example after rename
ken-lauer Aug 1, 2024
dcb0379
FIX: gridding and padding
ken-lauer Aug 1, 2024
fcc3580
MAINT: clean and add docstrings
ken-lauer Aug 1, 2024
9e023cb
REF: redo classes after Chris's feedback
ken-lauer Aug 9, 2024
6437d88
DOC: update docstrings
ken-lauer Aug 14, 2024
297a239
CLN: remove operation log
ken-lauer Aug 14, 2024
df9e569
MAINT: add imports and some simple tests
ken-lauer Aug 14, 2024
da47e22
DOC: more docstrings
ken-lauer Aug 14, 2024
89c4268
Merge remote-tracking branch 'origin/master' into wip_wavefront
ken-lauer Aug 14, 2024
db98ddb
REF: inplace kwarg instead
ken-lauer Aug 14, 2024
eb42cdb
CI: 3.10+ for now
ken-lauer Aug 14, 2024
eb191a3
MAINT: slicing on Python pre-3.11
ken-lauer Aug 14, 2024
77e6c25
CI: test on multiple Python versions
ken-lauer Aug 14, 2024
335fa11
MAINT: move to Sequence[...] and use cartesian name in places
ken-lauer Aug 14, 2024
8191fcd
MAINT: simplify shifts
ken-lauer Aug 15, 2024
f1cb8b5
WIP: use ranges more
ken-lauer Aug 15, 2024
e8c66ef
REF: propagate_z -> propagate('z')
ken-lauer Aug 15, 2024
93658ca
ENH: single plane plotting framework
ken-lauer Aug 15, 2024
b8f822b
MAINT: propagate -> drift
ken-lauer Aug 20, 2024
a879cbe
WIP: assuredly incorrect genesis4 import
ken-lauer Aug 20, 2024
3a89b43
TST: updated padding values (to discuss with balticfish)
ken-lauer Aug 21, 2024
c05fc3f
WIP: openpmd wavefront output
ken-lauer Aug 22, 2024
9281388
WIP: split up metadata and add notes after discussion with Chris
ken-lauer Aug 27, 2024
745b08e
WIP: ranges -> grid_spacing and more
ken-lauer Aug 28, 2024
a27b924
DEV: add pre-commit config
ken-lauer Aug 28, 2024
49d8d9a
DEV: update requirements
ken-lauer Aug 28, 2024
e69e261
TST: wavefront with pmd validator
ken-lauer Aug 28, 2024
db122b0
WIP: notebook update with grid_spacing
ken-lauer Aug 28, 2024
4b5da0a
DEV: openpmd-validator 2.0 is required
ken-lauer Aug 29, 2024
214b3c1
WIP: max divergence padding factor
ken-lauer Sep 4, 2024
fdb36a1
MAINT: rename field_[rk]space -> [rk]mesh
ken-lauer Sep 5, 2024
9406561
BLD: openpmd-validator 2.0.x is not on pypi
ken-lauer Sep 5, 2024
c546e6f
CI: switch to miniforge
ken-lauer Sep 5, 2024
9a0ef40
WIP: longitudinal drift propagation direction and more
ken-lauer Sep 11, 2024
8c59736
WIP: swap axes for now
ken-lauer Sep 11, 2024
240fd65
WIP: tweak gaussian axis dimension handling
ken-lauer Sep 11, 2024
bf40ceb
TST: fix tests for adjusted ordering/API
ken-lauer Sep 11, 2024
6102467
WIP: moving from time[fs]->z[m] and fixing along the way
ken-lauer Sep 12, 2024
d84a605
ENH: show better kspace labels
ken-lauer Sep 13, 2024
4429015
ENH: 'nice' axis labels + extents
ken-lauer Sep 13, 2024
857fb0a
ENH: preliminary plot_reciprocal
ken-lauer Sep 13, 2024
1a96935
WIP: plot_reciprocal averages instead of slice
ken-lauer Sep 19, 2024
f04bd59
MAINT: plotting tweaks
ken-lauer Sep 19, 2024
d8542b1
WIP: start of wigner plot
ken-lauer Sep 19, 2024
bed1795
WIP: unsaved tweaks (dpi)
ken-lauer Oct 23, 2024
cdbc797
FIX/ENH: plot(show_power_density)
ken-lauer Nov 6, 2024
9d6097d
ENH: isophase contour for phase plot
ken-lauer Nov 6, 2024
dce70ca
FIX: time-averaged power density
ken-lauer Nov 6, 2024
cbbf647
FIX: scaling and extents from balticfish
ken-lauer Nov 6, 2024
d722ea6
Merge remote-tracking branch 'origin/master' into wip_wavefront
ken-lauer Nov 19, 2024
0a1022c
TST: sigma_t -> sigma_z
ken-lauer Nov 19, 2024
eee53d7
CI: remove defaults channel
ken-lauer Nov 19, 2024
5e62cb1
REF: simplify and remove code surrounding padding
ken-lauer Dec 16, 2024
adf03ec
STY: move to Python 3.9+ syntax
ken-lauer Dec 16, 2024
9fbfe76
MAINT: allow padding adjustment when replacing rmesh
ken-lauer Dec 16, 2024
172cc73
WIP: write genesis 4 format
ken-lauer Dec 16, 2024
490db5b
STY: move to Python 3.9+ syntax
ken-lauer Dec 16, 2024
25a648c
TST: start of write/read genesis4 wavefront
ken-lauer Dec 16, 2024
6eacca7
BLD: lume-genesis for wavefront loading/saving
ken-lauer Dec 16, 2024
fb43d19
ENH: initial round-tripping of pmd wavefront format
ken-lauer Dec 16, 2024
db7ab1d
CLN: remove longitudinal_axis
ken-lauer Dec 16, 2024
dbdd6b1
CLN: remove axis_labels which are now implicitly xyz
ken-lauer Dec 16, 2024
aad8abf
REF: redo plotting interface as requested
ken-lauer Dec 16, 2024
dbdb43b
MAINT: test and fix plot variants
ken-lauer Dec 16, 2024
6091df9
MAINT: re/im -> re_mesh/im_mesh
ken-lauer Dec 16, 2024
26b418b
MAINT: undo label but set title
ken-lauer Dec 16, 2024
657b384
FIX: genesis4 scaling factor
ken-lauer Dec 17, 2024
e28cc13
MAINT: upright units
ken-lauer Dec 17, 2024
6109322
MAINT: remove transpose
ken-lauer Dec 17, 2024
9c46abb
ENH: aspect ratio for wavefront.plot()
ken-lauer Dec 17, 2024
d2f005c
MAINT: 'of mesh' is implied here but not above
ken-lauer Dec 17, 2024
1e4f15a
ENH: support legacy openpmd format from genesis4
ken-lauer Dec 17, 2024
faedafc
ENH: enumerate wavefronts in file
ken-lauer Dec 17, 2024
7ce2dff
MAINT: move tools and genesis import/export around
ken-lauer Dec 17, 2024
7b1143c
TST/FIX: test coverage, cleanup, and associated fixes
ken-lauer Dec 17, 2024
1d5a7fc
MAINT: move around code in an effort to clean up Wavefront a bit
ken-lauer Dec 17, 2024
81c49a3
MAINT: flatten _fft/_ifft for clarity
ken-lauer Dec 17, 2024
ae99fe8
CLN: unused properties
ken-lauer Dec 17, 2024
60edc0d
API: remove .drift and .focus from wavefront
ken-lauer Dec 18, 2024
10a215a
MAINT: re-run notebook after removal of .drift/.focus
ken-lauer Dec 18, 2024
30b5441
MAINT: function calls are too confusing
ken-lauer Dec 18, 2024
3b02642
FIX: conversion coefficient in x/y dimensions
ken-lauer Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ENH: support legacy openpmd format from genesis4
  • Loading branch information
ken-lauer committed Dec 17, 2024
commit 1e4f15aa17b1dbe19b5d2559c20e204f177bd19f
121 changes: 93 additions & 28 deletions pmd_beamphysics/wavefront.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
import pathlib
import typing
from typing import Any, Literal, NamedTuple, Union, TYPE_CHECKING
from typing import cast, Any, Literal, NamedTuple, Union, TYPE_CHECKING
from collections.abc import Sequence

import h5py
Expand Down Expand Up @@ -89,6 +89,17 @@ def set_num_fft_workers(workers: int):
logger.info(f"Set number of FFT workers to: {workers}")


def _require_h5_group(parent: h5py.Group, name: str) -> h5py.Group:
try:
group = parent[name]
except KeyError:
raise KeyError(f"Expected HDF group {name} not found in {parent.name}")

if not isinstance(group, h5py.Group):
raise ValueError(f"Key {group} expected to be a group, but is a {type(group)}")
return group


# Wwigner = Wavefront.gaussian_pulse(
# dims=(101, 101, 513),
# wavelength=1.35e-8,
Expand Down Expand Up @@ -1826,13 +1837,13 @@ def from_kmesh(
return self

@classmethod
def from_genesis4(
def from_genesis4_fieldfile(
cls,
h5: h5py.File | pathlib.Path | str,
field: Genesis4FieldFile,
pad: int | tuple[int, int, int] = 0,
) -> Wavefront:
"""
Load a Genesis4-format field file as a `Wavefront`.
Load a Genesis4 `FieldFile` as a `Wavefront`.

Parameters
----------
Expand All @@ -1845,10 +1856,6 @@ def from_genesis4(
-------
Wavefront
"""
from genesis.version4 import FieldFile

field = FieldFile.from_file(h5)

# NOTE: refer to here for more information:
# https://github.com/slaclab/lume-genesis/blob/master/docs/notes/genesis_fields.pdf
genesis_to_v_over_m = np.sqrt(2.0 * Z0) / field.param.gridsize
Expand All @@ -1868,6 +1875,31 @@ def from_genesis4(
wf.metadata.mesh.grid_global_offset = (0.0, 0.0, field.param.refposition)
return wf

@classmethod
def from_genesis4(
cls,
h5: h5py.File | pathlib.Path | str,
pad: int | tuple[int, int, int] = 0,
) -> Wavefront:
"""
Load a Genesis4-format field file as a `Wavefront`.

Parameters
----------
h5 : h5py.File, pathlib.Path, or str
The opened h5py File or a path to it on disk.
pad : int or (int, int, int), default=100
Specify either (padx, pady, padz) or all-around padding.

Returns
-------
Wavefront
"""
from genesis.version4 import FieldFile

field_file = FieldFile.from_file(h5)
return cls.from_genesis4_fieldfile(field_file, pad=pad)

def to_genesis4_fieldfile(self) -> Genesis4FieldFile:
from genesis.version4 import FieldFile
from genesis.version4.field import FieldFileParams
Expand Down Expand Up @@ -1918,11 +1950,56 @@ def write_genesis4(self, h5: h5py.File | pathlib.Path | str) -> None:
field_file.write_genesis4(h5)

@classmethod
def _from_legacy_h5_file(cls, h5: h5py.File, identifier: int) -> Wavefront:
# Legacy file format - written by lume-genesis
raise NotImplementedError(
"Legacy lume-genesis openpmd-beamphysics format is not yet supported"
def _from_legacy_genesis_pmd(cls, h5: h5py.File, identifier: int) -> Wavefront:
from genesis.version4 import FieldFile
from genesis.version4.field import FieldFileParams

# Legacy file format - written by lume-genesis `write_openpmd_wavefront`
group = _require_h5_group(h5, f"/data/{identifier:06}/")
assert isinstance(group, h5py.Group)
h5_meshes = _require_h5_group(group, "meshes")

# Point to the real and imaginary h5 handles
E_complex = _require_h5_group(h5_meshes, "electricField")

gridsize, gridsize, slicespacing = cast(
tuple[float, float, float], E_complex.attrs["gridSpacing"]
)
photon_energy = cast(float, E_complex.attrs["photonEnergy"])
refposition = E_complex.attrs["timeOffset"]
wavelength = 12398.425 / photon_energy * 1.0e-10

# Get arrays
if "x" in E_complex:
fld = E_complex["x"]
elif "y" in E_complex:
fld = E_complex["y"]
else:
raise NotImplementedError(
"X or Y polarization are only supported in the legacy field file format"
)

assert isinstance(fld, h5py.Dataset)

gridpoints, gridpoints, slicecount = np.array(fld.shape)
factorGenesis = gridsize / np.sqrt(2.0 * Z0) # factor to genesis units
unitSI = fld.attrs["unitSI"] # factor to convert to V/m
dflfactor = unitSI * factorGenesis # V/m -> Genesis
dfl = dflfactor * np.asarray(fld)

field_file = FieldFile(
label=identifier,
dfl=dfl,
param=FieldFileParams(
gridpoints=gridpoints,
slicecount=slicecount,
gridsize=gridsize,
refposition=refposition,
slicespacing=slicespacing,
wavelength=wavelength,
),
)
return cls.from_genesis4_fieldfile(field_file)

@classmethod
def _from_h5_file(cls, h5: h5py.File, identifier: int) -> Wavefront:
Expand All @@ -1936,23 +2013,11 @@ def get_string_attr(parent: h5py.Group, attr: str) -> str:
f"Expected bytes or strings for {h5.name} key {attr}; got {type(value).__name__}"
)

def require_group(parent: h5py.Group, name: str) -> h5py.Group:
try:
group = parent[name]
except KeyError:
raise KeyError(f"Expected HDF group {name} not found in {h5.name}")

if not isinstance(group, h5py.Group):
raise ValueError(
f"Key {group} expected to be a group, but is a {type(group)}"
)
return group

base_path = get_string_attr(h5, "basePath")
# data_type = h5.attrs["dataType"]
openpmd_extension = get_string_attr(h5, "openPMDextension").split(";")
if "wavefront" in openpmd_extension:
return cls._load_legacy_wavefront_file(h5)
return cls._from_legacy_genesis_pmd(h5, identifier=identifier)
if "Wavefront" not in openpmd_extension:
raise ValueError(
f"Wavefront extension not enabled in file. "
Expand All @@ -1963,9 +2028,9 @@ def require_group(parent: h5py.Group, name: str) -> h5py.Group:
wavefront_field_path = get_string_attr(h5, "wavefrontFieldPath")

# {iteration group}/{wavefront group}/{efield_group}
iteration_group = require_group(h5, iteration_path)
wavefront_group = require_group(iteration_group, wavefront_field_path)
efield_group = require_group(wavefront_group, "electricField")
iteration_group = _require_h5_group(h5, iteration_path)
wavefront_group = _require_h5_group(iteration_group, wavefront_field_path)
efield_group = _require_h5_group(wavefront_group, "electricField")

photon_energy = efield_group.attrs["photonEnergy"]
assert isinstance(photon_energy, float)
Expand Down
37 changes: 37 additions & 0 deletions tests/test_wavefront.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,41 @@ def test_write_and_read_genesis4(
assert wavefront == loaded


def test_write_and_read_genesis4_legacy_openpmd(
wavefront: Wavefront,
tmp_path: pathlib.Path,
request: pytest.FixtureRequest,
):
fn = tmp_path / f"{request.node.name}.h5"
field_file = wavefront.to_genesis4_fieldfile()

field_file.write_openpmd_wavefront(dest=fn)

wavefront.metadata.mesh.grid_global_offset = (0.0, 0.0, 0.0)

loaded = wavefront.from_file(fn).with_padding(wavefront.pad)

# check these individually before testing full equality, so we don't get just a final failure
assert wavefront.grid == loaded.grid
assert np.allclose(wavefront.rmesh.real, loaded.rmesh.real)
assert np.allclose(wavefront.rmesh.imag, loaded.rmesh.imag)
assert np.isclose(wavefront.wavelength, loaded.wavelength)
assert wavefront.pad == loaded.pad

# NOTE the date isn't retained in the old format
loaded.metadata.base.date = wavefront.metadata.base.date
assert wavefront.metadata.base == loaded.metadata.base
assert wavefront.metadata.iteration == loaded.metadata.iteration
assert wavefront.metadata.mesh == loaded.metadata.mesh
assert wavefront.metadata == loaded.metadata

# Overwrite the above stuff that's close but not equal:
loaded._rmesh = wavefront._rmesh
loaded._kmesh = wavefront._kmesh
loaded.wavelength = wavefront.wavelength
assert wavefront == loaded


def test_write_and_read_openpmd(
wavefront: Wavefront,
tmp_path: pathlib.Path,
Expand All @@ -216,6 +251,8 @@ def test_write_and_read_openpmd(
assert wavefront.pad == loaded.pad

# TODO: we don't store microseconds
# NOTE: the date should be retained here as when it was originally stored,
# minus microseconds as above
loaded.metadata.base.date = loaded.metadata.base.date.replace(
microsecond=wavefront.metadata.base.date.microsecond
)
Expand Down
Loading