"""Validate HDF5 PyTree save/load.
Extended Summary
----------------
The tests exercise ``save_to_h5`` and ``load_from_h5`` for all registered
PyTree types. They cover spin and OAM variants and files with multiple
PyTrees. Round-trip tests compare numerical values within tolerance.
Error tests cover unknown types, missing groups, and invalid options.
The tests also verify applicable compression and data set options.
"""
import json
import tempfile
from dataclasses import Field, fields
from pathlib import Path
import chex
import h5py
import jax.numpy as jnp
import numpy as np
import pytest
from beartype.typing import Any, Callable
from jaxtyping import Array
import diffpes
from diffpes.inout import load_from_h5, save_to_h5
from diffpes.inout.hdf5 import _decode_static, _encode_static
from diffpes.types import (
make_arpes_spectrum,
make_band_structure,
make_crystal_geometry,
make_density_of_states,
make_kpath_info,
make_orbital_projection,
make_polarization_config,
make_simulation_params,
)
[docs]
class TestDensityOfStates(chex.TestCase):
"""Round-trip tests for DensityOfStates HDF5 serialization.
Verifies that energy, total DOS, and Fermi energy arrays
survive a save-then-load cycle with exact numerical
fidelity.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify DensityOfStates survives HDF5 round-trip.
The test establishes the round trip contract for density of states with the
concrete values and array shapes described below.
Notes
-----
1. **Create** a DensityOfStates with 100-point energy
axis and uniform DOS.
2. **Save** to a temporary HDF5 file.
3. **Load** back by group name.
**Expected assertions**
All three array fields match to within 1e-12.
"""
td: str
dos: diffpes.types.DensityOfStates
path: Path
loaded: Any
dos = make_density_of_states(
energy=jnp.linspace(-10.0, 5.0, 100),
total_dos=jnp.ones(100),
fermi_energy=-1.5,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "dos.h5"
save_to_h5(path, dos=dos)
loaded = load_from_h5(path, name="dos")
chex.assert_trees_all_close(loaded.energy, dos.energy, atol=1e-12)
chex.assert_trees_all_close(
loaded.total_dos,
dos.total_dos,
atol=1e-12,
)
chex.assert_trees_all_close(
loaded.fermi_energy,
dos.fermi_energy,
atol=1e-12,
)
[docs]
class TestBandStructure(chex.TestCase):
"""Round-trip tests for BandStructure HDF5 serialization.
The tests verify preservation of the eigenvalues, k-points, weights,
and Fermi energy through save and load operations.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify BandStructure survives HDF5 round-trip.
The test establishes the round trip contract for band structure with the
concrete values and array shapes described below.
Notes
-----
1. **Create** a BandStructure with 10 k-points, 4 bands.
2. **Save** and **load** via HDF5.
**Expected assertions**
All four array fields match to within 1e-12.
"""
td: str
nk: int
nb: int
bands: diffpes.types.BandStructure
path: Path
loaded: Any
nk, nb = 10, 4
bands = make_band_structure(
eigenvalues=jnp.linspace(-2.0, 0.5, nk * nb).reshape(nk, nb),
kpoints=jnp.zeros((nk, 3)),
fermi_energy=0.0,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "bands.h5"
save_to_h5(path, bands=bands)
loaded = load_from_h5(path, name="bands")
chex.assert_trees_all_close(
loaded.eigenvalues,
bands.eigenvalues,
atol=1e-12,
)
chex.assert_trees_all_close(loaded.kpoints, bands.kpoints, atol=1e-12)
chex.assert_trees_all_close(
loaded.kpoint_weights,
bands.kpoint_weights,
atol=1e-12,
)
chex.assert_trees_all_close(
loaded.fermi_energy,
bands.fermi_energy,
atol=1e-12,
)
[docs]
class TestArpesSpectrum(chex.TestCase):
"""Round-trip tests for ArpesSpectrum HDF5 serialization.
Verifies intensity map and energy axis survive the
save/load cycle.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify ArpesSpectrum survives HDF5 round-trip.
The test establishes the round trip contract for arpes spectrum with the
concrete values and array shapes described below.
Notes
-----
1. **Create** an ArpesSpectrum with shape (20, 100).
2. **Save** and **load** via HDF5.
**Expected assertions**
Both array fields match to within 1e-12.
"""
td: str
spectrum: diffpes.types.ArpesSpectrum
path: Path
loaded: Any
spectrum = make_arpes_spectrum(
intensity=jnp.ones((20, 100)),
energy_axis=jnp.linspace(-3.0, 1.0, 100),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "spectrum.h5"
save_to_h5(path, spectrum=spectrum)
loaded = load_from_h5(path, name="spectrum")
chex.assert_trees_all_close(
loaded.intensity,
spectrum.intensity,
atol=1e-12,
)
chex.assert_trees_all_close(
loaded.energy_axis,
spectrum.energy_axis,
atol=1e-12,
)
[docs]
class TestOrbitalProjection(chex.TestCase):
"""Round-trip tests for OrbitalProjection HDF5 serialization.
Verifies projections array and Optional spin/oam fields
(both None and non-None) survive the cycle.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip_none_optionals(self) -> None:
"""Verify OrbitalProjection with None spin/oam.
The test establishes the round trip none optionals contract for orbital
projection with the concrete values and array shapes described below.
Notes
-----
1. **Create** with only projections (spin=None,
oam=None).
2. **Save** and **load** via HDF5.
**Expected assertions**
Projections match; spin and oam are None.
"""
td: str
nk: int
nb: int
na: int
orb: diffpes.types.OrbitalProjection
path: Path
loaded: Any
nk, nb, na = 5, 3, 2
orb = make_orbital_projection(
projections=jnp.ones((nk, nb, na, 9)) * 0.1,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "orb.h5"
save_to_h5(path, orb=orb)
loaded = load_from_h5(path, name="orb")
chex.assert_trees_all_close(
loaded.projections,
orb.projections,
atol=1e-12,
)
assert loaded.spin is None
assert loaded.oam is None
[docs]
def test_round_trip_with_spin(self) -> None:
"""Verify OrbitalProjection with non-None spin.
The test establishes the round trip with spin contract for orbital projection
with the concrete values and array shapes described below.
Notes
-----
1. **Create** with both projections and spin arrays.
2. **Save** and **load** via HDF5.
**Expected assertions**
Projections and spin match; oam is None.
"""
td: str
nk: int
nb: int
na: int
orb: diffpes.types.OrbitalProjection
path: Path
loaded: Any
nk, nb, na = 5, 3, 2
orb = make_orbital_projection(
projections=jnp.ones((nk, nb, na, 9)) * 0.1,
spin=jnp.zeros((nk, nb, na, 6)),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "orb_spin.h5"
save_to_h5(path, orb=orb)
loaded = load_from_h5(path, name="orb")
chex.assert_trees_all_close(
loaded.projections,
orb.projections,
atol=1e-12,
)
chex.assert_trees_all_close(loaded.spin, orb.spin, atol=1e-12)
assert loaded.oam is None
[docs]
def test_round_trip_with_all(self) -> None:
"""Verify OrbitalProjection with spin and oam.
The test establishes the round trip with all contract for orbital projection
with the concrete values and array shapes described below.
Notes
-----
1. **Create** with projections, spin, and oam.
2. **Save** and **load** via HDF5.
**Expected assertions**
All three array fields match.
"""
td: str
nk: int
nb: int
na: int
orb: diffpes.types.OrbitalProjection
path: Path
loaded: Any
nk, nb, na = 5, 3, 2
orb = make_orbital_projection(
projections=jnp.ones((nk, nb, na, 9)) * 0.1,
spin=jnp.zeros((nk, nb, na, 6)),
oam=jnp.ones((nk, nb, na, 3)) * 0.5,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "orb_all.h5"
save_to_h5(path, orb=orb)
loaded = load_from_h5(path, name="orb")
chex.assert_trees_all_close(
loaded.projections,
orb.projections,
atol=1e-12,
)
chex.assert_trees_all_close(loaded.spin, orb.spin, atol=1e-12)
chex.assert_trees_all_close(loaded.oam, orb.oam, atol=1e-12)
[docs]
class TestSimulationParams(chex.TestCase):
"""Round-trip tests for SimulationParams HDF5 serialization.
Verifies that all six float children and the integer
``fidelity`` aux_data survive the cycle.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify SimulationParams survives HDF5 round-trip.
The test establishes the round trip contract for simulation params with the
concrete values and array shapes described below.
Notes
-----
1. **Create** with non-default values for all fields.
2. **Save** and **load** via HDF5.
**Expected assertions**
All float fields match, and the integer fidelity matches.
"""
td: str
params: diffpes.types.SimulationParams
path: Path
loaded: Any
params = make_simulation_params(
energy_min=-5.0,
energy_max=2.0,
fidelity=500,
sigma=0.08,
gamma=0.15,
temperature=30.0,
photon_energy=21.2,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "params.h5"
save_to_h5(path, params=params)
loaded = load_from_h5(path, name="params")
chex.assert_trees_all_close(
loaded.energy_min,
params.energy_min,
atol=1e-12,
)
chex.assert_trees_all_close(loaded.sigma, params.sigma, atol=1e-12)
chex.assert_trees_all_close(
loaded.photon_energy,
params.photon_energy,
atol=1e-12,
)
assert loaded.fidelity == 500
[docs]
class TestPolarizationConfig(chex.TestCase):
"""Round-trip tests for PolarizationConfig serialization.
Verifies that float angles and the string
``polarization_type`` aux_data survive the cycle.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify PolarizationConfig survives HDF5 round-trip.
The test establishes the round trip contract for polarization config with the
concrete values and array shapes described below.
Notes
-----
1. **Create** an LHP config with custom angles.
2. **Save** and **load** via HDF5.
**Expected assertions**
The float angles and the ``polarization_type`` string match.
"""
td: str
pol: diffpes.types.PolarizationConfig
path: Path
loaded: Any
pol = make_polarization_config(
theta=0.5,
phi=1.2,
polarization_angle=0.3,
polarization_type="LHP",
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "pol.h5"
save_to_h5(path, pol=pol)
loaded = load_from_h5(path, name="pol")
chex.assert_trees_all_close(loaded.theta, pol.theta, atol=1e-12)
chex.assert_trees_all_close(loaded.phi, pol.phi, atol=1e-12)
assert loaded.polarization_type == "LHP"
[docs]
class TestKPathInfo(chex.TestCase):
"""Round-trip tests for KPathInfo HDF5 serialization.
Verifies that integer arrays and the ``(mode, labels)``
tuple aux_data survive the cycle.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify KPathInfo survives HDF5 round-trip.
The test establishes the round trip contract for k path info with the concrete
values and array shapes described below.
Notes
-----
1. **Create** with 3 symmetry labels.
2. **Save** and **load** via HDF5.
**Expected assertions**
The integer arrays, mode, and label strings match.
"""
td: str
kpath: diffpes.types.KPathInfo
path: Path
loaded: Any
kpath = make_kpath_info(
num_kpoints=100,
label_indices=[0, 49, 99],
segments=2,
mode="Line-mode",
labels=("G", "M", "K"),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "kpath.h5"
save_to_h5(path, kpath=kpath)
loaded = load_from_h5(path, name="kpath")
chex.assert_trees_all_close(
loaded.num_kpoints,
kpath.num_kpoints,
atol=0,
)
chex.assert_trees_all_close(
loaded.label_indices,
kpath.label_indices,
atol=0,
)
assert loaded.mode == "Line-mode"
assert loaded.labels == ("G", "M", "K")
[docs]
class TestCrystalGeometry(chex.TestCase):
"""Round-trip tests for CrystalGeometry HDF5 serialization.
Verifies that lattice, reciprocal lattice, coords,
atom_counts arrays and the ``symbols`` tuple aux_data
survive the cycle.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_round_trip(self) -> None:
"""Verify CrystalGeometry survives HDF5 round-trip.
The test establishes the round trip contract for crystal geometry with the
concrete values and array shapes described below.
Notes
-----
1. **Create** a cubic Si structure with 2 atoms.
2. **Save** and **load** via HDF5.
**Expected assertions**
All four arrays and the symbols tuple match.
"""
td: str
lattice: Array
positions: Array
geo: diffpes.types.CrystalGeometry
path: Path
loaded: Any
lattice = jnp.eye(3) * 5.43
positions = jnp.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]])
geo = make_crystal_geometry(
lattice=lattice,
positions=positions,
species=("Si", "Si"),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "geo.h5"
save_to_h5(path, geo=geo)
loaded = load_from_h5(path, name="geo")
chex.assert_trees_all_close(loaded.lattice, geo.lattice, atol=1e-12)
chex.assert_trees_all_close(
loaded.reciprocal,
geo.reciprocal,
atol=1e-12,
)
chex.assert_trees_all_close(
loaded.positions,
geo.positions,
atol=1e-12,
)
assert loaded.species == ("Si", "Si")
[docs]
class TestSaveToH5(chex.TestCase):
"""Validate saving/loading multiple PyTrees in one file.
Verifies multi-group HDF5 files and both loading modes
(by name, load all).
:see: :func:`~diffpes.inout.save_to_h5`
"""
[docs]
def test_save_load_multiple(self) -> None:
"""Verify two PyTrees coexist in one HDF5 file.
The test establishes the save load multiple contract for multi py tree with the
concrete values and array shapes described below.
Notes
-----
1. **Create** a BandStructure and an OrbitalProjection.
2. **Save** both to one file under different names.
3. **Load** each by name.
**Expected assertions**
Both round-trip correctly and independently.
"""
td: str
nk: int
nb: int
na: int
bands: diffpes.types.BandStructure
orb: diffpes.types.OrbitalProjection
path: Path
loaded_bands: Any
loaded_orb: Any
nk, nb, na = 8, 3, 2
bands = make_band_structure(
eigenvalues=jnp.linspace(-2.0, 0.5, nk * nb).reshape(nk, nb),
kpoints=jnp.zeros((nk, 3)),
)
orb = make_orbital_projection(
projections=jnp.ones((nk, nb, na, 9)) * 0.1,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "multi.h5"
save_to_h5(path, bands=bands, orb=orb)
loaded_bands = load_from_h5(path, name="bands")
loaded_orb = load_from_h5(path, name="orb")
chex.assert_trees_all_close(
loaded_bands.eigenvalues,
bands.eigenvalues,
atol=1e-12,
)
chex.assert_trees_all_close(
loaded_orb.projections,
orb.projections,
atol=1e-12,
)
[docs]
def test_load_all_returns_dict(self) -> None:
"""Verify load_from_h5 without name returns dict.
The test establishes the load all returns dict contract for multi py tree with
the concrete values and array shapes described below.
Notes
-----
1. **Save** two PyTrees to one file.
2. **Load** without specifying a name.
**Expected assertions**
Result is a dict with both group names as keys.
"""
td: str
nk: int
nb: int
bands: diffpes.types.BandStructure
spectrum: diffpes.types.ArpesSpectrum
path: Path
loaded: Any
nk, nb = 8, 3
bands = make_band_structure(
eigenvalues=jnp.linspace(-2.0, 0.5, nk * nb).reshape(nk, nb),
kpoints=jnp.zeros((nk, 3)),
)
spectrum = make_arpes_spectrum(
intensity=jnp.ones((20, 50)),
energy_axis=jnp.linspace(-3.0, 1.0, 50),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "multi_all.h5"
save_to_h5(path, bands=bands, spectrum=spectrum)
loaded = load_from_h5(path)
assert isinstance(loaded, dict)
assert "bands" in loaded
assert "spectrum" in loaded
[docs]
class TestLoadFromH5(chex.TestCase):
"""Validate :func:`~diffpes.inout.load_from_h5`.
Covers named-group recovery of a concrete Equinox carrier from a file
produced by the public HDF5 writer.
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_load_named_band_structure(self) -> None:
"""Recover a named band structure without changing its arrays.
The loaded carrier must preserve the eigenvalue and k-point arrays at
exact float64 precision for this deterministic fixture.
Notes
-----
Saves one two-k-point ``BandStructure``, loads the ``bands`` group by
name, narrows its runtime type, and compares both array fields exactly.
"""
temporary_directory: str
expected: diffpes.types.BandStructure
path: Path
loaded: Any
expected = make_band_structure(
eigenvalues=jnp.array([[-1.0], [0.5]], dtype=jnp.float64),
kpoints=jnp.zeros((2, 3), dtype=jnp.float64),
)
with tempfile.TemporaryDirectory() as temporary_directory:
path = Path(temporary_directory) / "named.h5"
save_to_h5(path, bands=expected)
loaded = load_from_h5(path, name="bands")
assert isinstance(loaded, diffpes.types.BandStructure)
chex.assert_trees_all_equal(loaded.eigenvalues, expected.eigenvalues)
chex.assert_trees_all_equal(loaded.kpoints, expected.kpoints)
[docs]
class TestErrorHandling(chex.TestCase):
"""Validate error conditions in save/load functions.
The tests verify the applicable exceptions for invalid inputs and
missing data.
:see: :func:`~diffpes.inout.save_to_h5`
:see: :func:`~diffpes.inout.load_from_h5`
"""
[docs]
def test_no_pytrees_raises(self) -> None:
"""Verify save_to_h5 with no kwargs raises ValueError.
The test establishes the no pytrees raises contract for error handling with the
concrete values and array shapes described below.
Notes
-----
1. **Call** save_to_h5 with only a path, no PyTrees.
**Expected assertions**
The function raises ``ValueError``.
"""
td: str
path: Path
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "empty.h5"
with pytest.raises(ValueError, match="At least"):
save_to_h5(path)
[docs]
def test_unsupported_type_raises(self) -> None:
"""Verify unregistered type raises TypeError.
The test establishes the unsupported type raises contract for error handling
with the concrete values and array shapes described below.
Notes
-----
1. **Call** save_to_h5 with a plain tuple (not a
registered PyTree).
**Expected assertions**
The function raises ``TypeError``.
"""
td: str
path: Path
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "bad.h5"
with pytest.raises(TypeError, match="Unsupported"):
save_to_h5(path, bad=(1, 2, 3))
[docs]
def test_missing_group_raises(self) -> None:
"""Verify load with nonexistent name raises KeyError.
The test establishes the missing group raises contract for error handling with
the concrete values and array shapes described below.
Notes
-----
1. **Save** a PyTree under name ``"a"``.
2. **Load** with name ``"b"``.
**Expected assertions**
The function raises ``KeyError``.
"""
td: str
dos: diffpes.types.DensityOfStates
path: Path
dos = make_density_of_states(
energy=jnp.linspace(-5.0, 5.0, 50),
total_dos=jnp.ones(50),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "missing.h5"
save_to_h5(path, a=dos)
with pytest.raises(KeyError, match="not found"):
load_from_h5(path, name="b")
[docs]
def test_load_unknown_pytree_type_raises(self) -> None:
"""Verify loading a group with unknown _pytree_type raises TypeError.
The test establishes the load unknown pytree type raises contract for error
handling with the concrete values and array shapes described below.
Notes
-----
1. **Create** an HDF5 file with a group that has
_pytree_type = "UnknownType" (not in _PYTREE_REGISTRY).
2. **Load** that group with load_from_h5(path, name="bad").
**Expected assertions**
The function raises ``TypeError`` with the unknown type in its message.
"""
td: str
f: h5py.File
path: Path
grp: h5py.Group
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "unknown_type.h5"
with h5py.File(path, "w") as f:
grp = f.create_group("bad")
grp.attrs["_pytree_type"] = "UnknownType"
grp.attrs["_aux_data_json"] = "null"
grp.attrs["_none_fields"] = "[]"
with pytest.raises(TypeError, match="Unknown PyTree type"):
load_from_h5(path, name="bad")
[docs]
class TestDatasetFlags(chex.TestCase):
"""Validate HDF5 dataset storage flags in save_to_h5.
:see: :func:`~diffpes.inout.save_to_h5`
"""
[docs]
def test_compression_flags_applied_to_arrays(self) -> None:
"""Verify application of storage flags to non-scalar data sets.
The test establishes the compression flags applied to arrays contract for
dataset flags with the concrete values and array shapes described below.
Notes
-----
1. **Create** an ArpesSpectrum (array datasets) and
SimulationParams (scalar datasets).
2. **Save** both with compression/chunk/checksum flags.
3. **Inspect** HDF5 dataset properties directly.
4. **Round-trip load** to verify numerical integrity.
**Expected assertions**
- Array dataset has requested filter/chunk settings.
- Scalar dataset remains uncompressed (safe handling).
- Loaded spectrum matches original data.
"""
td: str
f: h5py.File
spectrum: diffpes.types.ArpesSpectrum
params: diffpes.types.SimulationParams
path: Path
ds: h5py.Dataset
scalar_ds: h5py.Dataset
loaded: Any
spectrum = make_arpes_spectrum(
intensity=jnp.ones((12, 30)),
energy_axis=jnp.linspace(-2.0, 1.0, 30),
)
params = make_simulation_params(
fidelity=30,
sigma=0.04,
gamma=0.1,
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "flags.h5"
save_to_h5(
path,
compression="gzip",
compression_opts=4,
shuffle=True,
fletcher32=True,
chunks=True,
spectrum=spectrum,
params=params,
)
with h5py.File(path, "r") as f:
ds = f["spectrum"]["intensity"]
assert ds.compression == "gzip"
assert ds.compression_opts == 4
assert ds.shuffle
assert ds.fletcher32
assert ds.chunks is not None
scalar_ds = f["params"]["energy_min"]
assert scalar_ds.shape == ()
assert scalar_ds.compression is None
loaded = load_from_h5(path, name="spectrum")
chex.assert_trees_all_close(
loaded.intensity,
spectrum.intensity,
atol=1e-12,
)
[docs]
def test_compression_opts_without_compression_raises(self) -> None:
"""Verify invalid compression flag combination raises ValueError.
The test establishes the compression opts without compression raises contract
for dataset flags with the concrete values and array shapes described below.
Notes
-----
1. **Create** a simple DensityOfStates PyTree.
2. **Call** ``save_to_h5`` with ``compression_opts`` only.
**Expected assertions**
The function raises ``ValueError`` with an explanatory message.
"""
td: str
dos: diffpes.types.DensityOfStates
path: Path
dos = make_density_of_states(
energy=jnp.linspace(-5.0, 5.0, 50),
total_dos=jnp.ones(50),
)
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "bad_flags.h5"
with pytest.raises(ValueError, match="compression_opts"):
save_to_h5(
path,
compression_opts=4,
dos=dos,
)