chemparseplot 1.2.0__tar.gz → 1.4.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 (60) hide show
  1. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/.gitignore +13 -0
  2. chemparseplot-1.4.0/PKG-INFO +199 -0
  3. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/_version.py +2 -2
  4. chemparseplot-1.4.0/chemparseplot/parse/__init__.py +9 -0
  5. chemparseplot-1.4.0/chemparseplot/parse/chemgp_hdf5.py +174 -0
  6. chemparseplot-1.4.0/chemparseplot/parse/chemgp_jsonl.py +305 -0
  7. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/eon/neb.py +1 -1
  8. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/neb_utils.py +2 -2
  9. chemparseplot-1.4.0/chemparseplot/parse/orca/neb/__init__.py +24 -0
  10. chemparseplot-1.4.0/chemparseplot/parse/orca/neb/opi_parser.py +249 -0
  11. chemparseplot-1.4.0/chemparseplot/parse/plumed.py +425 -0
  12. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/trajectory/hdf5.py +2 -2
  13. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/trajectory/neb.py +1 -1
  14. chemparseplot-1.4.0/chemparseplot/plot/__init__.py +36 -0
  15. chemparseplot-1.4.0/chemparseplot/plot/chemgp.py +1314 -0
  16. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/plot/geomscan.py +8 -1
  17. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/plot/neb.py +358 -12
  18. chemparseplot-1.4.0/chemparseplot/plot/plumed.py +137 -0
  19. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/plot/theme.py +24 -0
  20. {chemparseplot-1.2.0/chemparseplot/parse → chemparseplot-1.4.0/chemparseplot/scripts}/__init__.py +0 -2
  21. chemparseplot-1.4.0/chemparseplot/scripts/plot_gp.py +732 -0
  22. chemparseplot-1.4.0/chemparseplot/scripts/plt_neb.py +423 -0
  23. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/pyproject.toml +65 -6
  24. chemparseplot-1.4.0/readme.md +142 -0
  25. chemparseplot-1.4.0/tests/parse/test_chemgp_hdf5.py +117 -0
  26. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/test_neb_utils.py +1 -1
  27. chemparseplot-1.4.0/tests/parse/test_plumed.py +167 -0
  28. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/test_trajectory_hdf5.py +2 -2
  29. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/test_trajectory_neb.py +3 -3
  30. chemparseplot-1.4.0/tests/plot/test_chemgp_utils.py +61 -0
  31. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/plot/test_neb_renderers.py +3 -3
  32. chemparseplot-1.4.0/tests/scripts/__init__.py +3 -0
  33. chemparseplot-1.4.0/tests/scripts/test_plot_gp_cli.py +54 -0
  34. chemparseplot-1.4.0/tests/scripts/test_plt_neb_cli.py +54 -0
  35. chemparseplot-1.4.0/tests/tutorials/test_chemparseplot.py +151 -0
  36. chemparseplot-1.2.0/PKG-INFO +0 -140
  37. chemparseplot-1.2.0/chemparseplot/plot/__init__.py +0 -9
  38. chemparseplot-1.2.0/readme.md +0 -91
  39. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/LICENSE +0 -0
  40. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/__init__.py +0 -0
  41. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/converter.py +0 -0
  42. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/eon/gprd.py +0 -0
  43. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/eon/minimization.py +0 -0
  44. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/eon/saddle_search.py +0 -0
  45. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/file_.py +0 -0
  46. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/orca/__init__.py +0 -0
  47. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/orca/geomscan.py +0 -0
  48. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/orca/neb/interp.py +0 -0
  49. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/patterns.py +0 -0
  50. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/sella/saddle_search.py +0 -0
  51. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/parse/trajectory/__init__.py +0 -0
  52. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/plot/structs.py +0 -0
  53. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/units.py +0 -0
  54. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/chemparseplot/util.py +0 -0
  55. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/conftest.py +0 -0
  56. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/orca/test_geomscan.py +0 -0
  57. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/orca/test_interp.py +0 -0
  58. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/test_converter.py +0 -0
  59. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/parse/test_patterns.py +0 -0
  60. {chemparseplot-1.2.0 → chemparseplot-1.4.0}/tests/plot/__init__.py +0 -0
@@ -1,5 +1,10 @@
1
1
  .pixi
2
+ CLAUDE.md
3
+ .claude/
2
4
  apidocs/*
5
+ doc/source/apidocs/
6
+ doc/jupyter_execute/
7
+ doc/build/
3
8
  ### Generated by gibo (https://github.com/simonwhitaker/gibo)
4
9
  ### https://raw.github.com/github/gitignore/4488915eec0b3a45b5c63ead28f286819c0917de/Python.gitignore
5
10
 
@@ -166,3 +171,11 @@ cython_debug/
166
171
  /_version.py
167
172
  /.pdm-python
168
173
  *.ipynb
174
+
175
+ # Dolt database files (added by bd init)
176
+ .dolt/
177
+ *.db
178
+
179
+ # Lychee link checker cache
180
+ .lycheecache
181
+ .beads/
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: chemparseplot
3
+ Version: 1.4.0
4
+ Summary: Parsers and plotting tools for computational chemistry
5
+ Project-URL: Documentation, https://chemparseplot.rgoswami.me
6
+ Project-URL: Issues, https://github.com/HaoZeke/chemparseplot/issues
7
+ Project-URL: Source, https://github.com/HaoZeke/chemparseplot
8
+ Project-URL: Changelog, https://github.com/HaoZeke/chemparseplot/blob/main/CHANGELOG.md
9
+ Author-email: Rohit Goswami <rog32@hi.is>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: compchem,parser,plot
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: numpy>=1.26.2
22
+ Requires-Dist: pint>=0.22
23
+ Requires-Dist: rgpycrumbs>=1.3.0
24
+ Provides-Extra: all
25
+ Requires-Dist: ase>=3.22; extra == 'all'
26
+ Requires-Dist: cmcrameri>=1.7; extra == 'all'
27
+ Requires-Dist: h5py>=3.0; extra == 'all'
28
+ Requires-Dist: matplotlib>=3.8.2; extra == 'all'
29
+ Requires-Dist: polars>=0.20; extra == 'all'
30
+ Requires-Dist: xyzrender>=0.1.2; extra == 'all'
31
+ Provides-Extra: doc
32
+ Requires-Dist: mdit-py-plugins>=0.3.4; extra == 'doc'
33
+ Requires-Dist: myst-nb>=1; extra == 'doc'
34
+ Requires-Dist: myst-parser>=2; extra == 'doc'
35
+ Requires-Dist: sphinx-autodoc2>=0.5; extra == 'doc'
36
+ Requires-Dist: sphinx-copybutton>=0.5.2; extra == 'doc'
37
+ Requires-Dist: sphinx-library>=1.1.2; extra == 'doc'
38
+ Requires-Dist: sphinx-sitemap>=2.5.1; extra == 'doc'
39
+ Requires-Dist: sphinx-togglebutton>=0.3.2; extra == 'doc'
40
+ Requires-Dist: sphinx>=7.2.6; extra == 'doc'
41
+ Requires-Dist: sphinxcontrib-apidoc>=0.4; extra == 'doc'
42
+ Provides-Extra: lint
43
+ Requires-Dist: ruff>=0.1.6; extra == 'lint'
44
+ Provides-Extra: neb
45
+ Requires-Dist: ase>=3.22; extra == 'neb'
46
+ Requires-Dist: h5py>=3.0; extra == 'neb'
47
+ Requires-Dist: polars>=0.20; extra == 'neb'
48
+ Provides-Extra: plot
49
+ Requires-Dist: cmcrameri>=1.7; extra == 'plot'
50
+ Requires-Dist: matplotlib>=3.8.2; extra == 'plot'
51
+ Provides-Extra: test
52
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
53
+ Requires-Dist: pytest>=7.4.3; extra == 'test'
54
+ Provides-Extra: xyzrender
55
+ Requires-Dist: xyzrender>=0.1.2; extra == 'xyzrender'
56
+ Description-Content-Type: text/markdown
57
+
58
+
59
+ # Table of Contents
60
+
61
+ - [About](#org69d0d18)
62
+ - [Installation](#org6425b9a)
63
+ - [Ecosystem Overview](#org7717c22)
64
+ - [Features](#org7ec2e1c)
65
+ - [Supported Engines](#org3c6cfc2)
66
+ - [Documentation](#org7d2bbf4)
67
+ - [Contributing](#orgc77604d)
68
+ - [License](#org8180f68)
69
+ - [Acknowledgments](#org7ebb45c)
70
+
71
+
72
+
73
+ <a id="org69d0d18"></a>
74
+
75
+ # About
76
+
77
+ ![img](branding/logo/chemparseplot_logo.png)
78
+
79
+ [![Tests](https://github.com/HaoZeke/chemparseplot/actions/workflows/build_test.yml/badge.svg)](https://github.com/HaoZeke/chemparseplot/actions/workflows/build_test.yml)
80
+ [![Linting](https://github.com/HaoZeke/chemparseplot/actions/workflows/ci_prek.yml/badge.svg)](https://github.com/HaoZeke/chemparseplot/actions/workflows/ci_prek.yml)
81
+ [![Docs](https://github.com/HaoZeke/chemparseplot/actions/workflows/build_docs.yml/badge.svg)](https://github.com/HaoZeke/chemparseplot/actions/workflows/build_docs.yml)
82
+ [![PyPI](https://img.shields.io/pypi/v/chemparseplot)](https://pypi.org/project/chemparseplot/)
83
+ [![Python](https://img.shields.io/pypi/pyversions/chemparseplot)](https://pypi.org/project/chemparseplot/)
84
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
85
+ [![One Good Tutorial docs checklist v1: adopted](https://onegoodtutorial.org/badge/adopted-v1.svg)](https://onegoodtutorial.org/about/badge/?v=1)
86
+ [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)
87
+ [![DOI](https://zenodo.org/badge/725730118.svg)](https://doi.org/10.5281/zenodo.18529752)
88
+
89
+ A **pure-python**<sup><a id="fnr.butwhy" class="footref" href="#fn.butwhy" role="doc-backlink">1</a></sup> parsing and plotting library for computational
90
+ chemistry outputs. `chemparseplot` extracts structured data from quantum
91
+ chemistry codes (ORCA, eOn, Sella, ChemGP) and produces publication-quality,
92
+ unit-aware visualizations with [scientific color maps](https://www.fabiocrameri.ch/colourmaps/).
93
+
94
+ Computational tasks (surface fitting, structure analysis, interpolation) are
95
+ handled by [`rgpycrumbs`](https://github.com/HaoZeke/rgpycrumbs), which is a required dependency. `chemparseplot` parses
96
+ output files, delegates heavy computation to `rgpycrumbs`, and produces
97
+ publication-quality plots.
98
+
99
+
100
+ <a id="org6425b9a"></a>
101
+
102
+ ## Installation
103
+
104
+ pip install chemparseplot
105
+ # With plotting support
106
+ pip install "chemparseplot[plot]"
107
+ # Everything
108
+ pip install "chemparseplot[all]"
109
+
110
+ For development:
111
+
112
+ git clone https://github.com/HaoZeke/chemparseplot
113
+ cd chemparseplot
114
+ uv sync --all-extras
115
+
116
+ See the [installation guide](https://chemparseplot.rgoswami.me/installation.html) and [quickstart](https://chemparseplot.rgoswami.me/quickstart.html) for details.
117
+
118
+
119
+ <a id="org7717c22"></a>
120
+
121
+ ## Ecosystem Overview
122
+
123
+ `chemparseplot` is part of the `rgpycrumbs` suite of interlinked libraries.
124
+
125
+ ![img](branding/logo/ecosystem.png)
126
+
127
+
128
+ <a id="org7ec2e1c"></a>
129
+
130
+ ## Features
131
+
132
+ - **Parsing** computational chemistry output files into structured data
133
+ - **Plotting** with [scientific color maps](https://www.fabiocrameri.ch/colourmaps/) (camera-ready)
134
+ - **Unit preserving** throughout via `pint`
135
+ - **Computation** delegated to [`rgpycrumbs`](https://github.com/HaoZeke/rgpycrumbs) for surface fitting, interpolation,
136
+ and structure analysis
137
+
138
+
139
+ <a id="org3c6cfc2"></a>
140
+
141
+ ### Supported Engines
142
+
143
+ - ORCA (**5.x**)
144
+ - Geometry scan (`OPT`) energy profiles
145
+ - Nudged elastic band (`NEB`) path visualization
146
+ - eOn
147
+ - Saddle search parsing (Dimer, GPRD, LBFGS methods)
148
+ - NEB path energy profiles with landscape projections
149
+ - Sella
150
+ - Saddle point optimization result parsing
151
+ - Trajectory formats
152
+ - HDF5 trajectories (ChemGP output with pre-computed forces)
153
+ - Generic ASE-readable formats (extxyz, .traj) for NEB analysis
154
+
155
+
156
+ <a id="org7d2bbf4"></a>
157
+
158
+ ## Documentation
159
+
160
+ Full documentation is at <https://chemparseplot.rgoswami.me>. This includes:
161
+
162
+ - A [quickstart guide](https://chemparseplot.rgoswami.me/quickstart.html)
163
+ - [Tutorials](https://chemparseplot.rgoswami.me/tutorials/index.html) for common workflows
164
+ - [API reference](https://chemparseplot.rgoswami.me/apidocs/index.html)
165
+
166
+
167
+ <a id="orgc77604d"></a>
168
+
169
+ ## Contributing
170
+
171
+ Contributions are welcome. See [CONTRIBUTING.md](https://github.com/HaoZeke/chemparseplot/blob/main/CONTRIBUTING.md) for development setup and
172
+ guidelines, and our [Code of Conduct](https://github.com/HaoZeke/chemparseplot/blob/main/CODE_OF_CONDUCT.md).
173
+
174
+ For bug reports or questions, open an issue on [GitHub](https://github.com/HaoZeke/chemparseplot/issues).
175
+
176
+
177
+ <a id="org8180f68"></a>
178
+
179
+ # License
180
+
181
+ MIT. However, this is an academic resource, so **please cite** as much as possible
182
+ via:
183
+
184
+ - The [Zenodo DOI](https://doi.org/10.5281/zenodo.18529752) for general use.
185
+ - The `wailord` paper for ORCA usage
186
+
187
+
188
+ <a id="org7ebb45c"></a>
189
+
190
+ # Acknowledgments
191
+
192
+ This project builds on work supported by the University of Iceland and the
193
+ Icelandic Research Fund. `chemparseplot` relies on [`rgpycrumbs`](https://github.com/HaoZeke/rgpycrumbs) for computational
194
+ modules.
195
+
196
+
197
+ # Footnotes
198
+
199
+ <sup><a id="fn.1" href="#fnr.1">1</a></sup> To distinguish it from my other thin-python wrapper projects
@@ -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 = '1.2.0'
32
- __version_tuple__ = version_tuple = (1, 2, 0)
31
+ __version__ = version = '1.4.0'
32
+ __version_tuple__ = version_tuple = (1, 4, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,9 @@
1
+ # SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from chemparseplot.parse import orca, patterns
6
+
7
+ # Lazy imports for modules with optional heavy deps (h5py, pandas)
8
+ # Import directly: from chemparseplot.parse.chemgp_hdf5 import read_h5_table
9
+ # Or: from chemparseplot.parse import plumed
@@ -0,0 +1,174 @@
1
+ # SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """HDF5 file I/O utilities for ChemGP data.
6
+
7
+ This module provides functions for reading structured data from ChemGP HDF5
8
+ output files. The HDF5 layout mirrors the Julia common_plot.jl helpers.
9
+
10
+ HDF5 Layout
11
+ -----------
12
+ - ``grids/<name>``: 2D arrays with attrs x_range, y_range, x_length, y_length
13
+ - ``table/<name>``: group of same-length 1D arrays
14
+ - ``paths/<name>``: point sequences (x, y or rAB, rBC)
15
+ - ``points/<name>``: point sets (x, y or pc1, pc2)
16
+ - Root attrs: metadata scalars
17
+
18
+ .. versionadded:: 1.7.0
19
+ Extracted from chemgp.plt_gp to standalone module.
20
+ """
21
+
22
+ from typing import Any
23
+
24
+ import numpy as np
25
+
26
+
27
+ def read_h5_table(f: Any, name: str = "table") -> Any:
28
+ """Read a group of same-length vectors as a DataFrame.
29
+
30
+ Parameters
31
+ ----------
32
+ f
33
+ Open HDF5 file object
34
+ name
35
+ Name of the table group (default: "table")
36
+
37
+ Returns
38
+ -------
39
+ DataFrame
40
+ DataFrame with columns from the HDF5 group
41
+ """
42
+ import pandas as pd
43
+
44
+ g = f[name]
45
+ cols = {}
46
+ for k in g.keys():
47
+ arr = g[k][()]
48
+ if arr.dtype.kind in {"S", "O"}:
49
+ cols[k] = arr.astype(str).tolist()
50
+ else:
51
+ cols[k] = arr.tolist()
52
+ return pd.DataFrame(cols)
53
+
54
+
55
+ def read_h5_grid(
56
+ f: Any, name: str
57
+ ) -> tuple[np.ndarray, np.ndarray | None, np.ndarray | None]:
58
+ """Read a 2D grid with optional axis ranges.
59
+
60
+ Parameters
61
+ ----------
62
+ f
63
+ Open HDF5 file object
64
+ name
65
+ Name of the grid dataset
66
+
67
+ Returns
68
+ -------
69
+ tuple
70
+ (data, x_coords, y_coords) where x_coords and y_coords may be None
71
+ if axis range attributes are not present
72
+ """
73
+ ds = f[f"grids/{name}"]
74
+ data = ds[()]
75
+ x_coords = None
76
+ y_coords = None
77
+
78
+ if "x_range" in ds.attrs and "x_length" in ds.attrs:
79
+ lo, hi = ds.attrs["x_range"]
80
+ n = int(ds.attrs["x_length"])
81
+ x_coords = np.linspace(lo, hi, n)
82
+
83
+ if "y_range" in ds.attrs and "y_length" in ds.attrs:
84
+ lo, hi = ds.attrs["y_range"]
85
+ n = int(ds.attrs["y_length"])
86
+ y_coords = np.linspace(lo, hi, n)
87
+
88
+ return data, x_coords, y_coords
89
+
90
+
91
+ def read_h5_path(f: Any, name: str) -> dict[str, np.ndarray]:
92
+ """Read a path (ordered point sequence).
93
+
94
+ Parameters
95
+ ----------
96
+ f
97
+ Open HDF5 file object
98
+ name
99
+ Name of the path dataset
100
+
101
+ Returns
102
+ -------
103
+ dict
104
+ Dictionary mapping coordinate names to arrays
105
+ """
106
+ g = f[f"paths/{name}"]
107
+ return {k: g[k][()] for k in g.keys()}
108
+
109
+
110
+ def read_h5_points(f: Any, name: str) -> dict[str, np.ndarray]:
111
+ """Read a point set.
112
+
113
+ Parameters
114
+ ----------
115
+ f
116
+ Open HDF5 file object
117
+ name
118
+ Name of the points dataset
119
+
120
+ Returns
121
+ -------
122
+ dict
123
+ Dictionary mapping coordinate names to arrays
124
+ """
125
+ g = f[f"points/{name}"]
126
+ return {k: g[k][()] for k in g.keys()}
127
+
128
+
129
+ def read_h5_metadata(f: Any) -> dict[str, Any]:
130
+ """Read root-level metadata attributes.
131
+
132
+ Parameters
133
+ ----------
134
+ f
135
+ Open HDF5 file object
136
+
137
+ Returns
138
+ -------
139
+ dict
140
+ Dictionary of metadata attributes
141
+ """
142
+ return {k: f.attrs[k] for k in f.attrs.keys()}
143
+
144
+
145
+ def validate_hdf5_structure(
146
+ f: Any, required_groups: list[str] | None = None
147
+ ) -> list[str]:
148
+ """Validate HDF5 file has expected structure.
149
+
150
+ Parameters
151
+ ----------
152
+ f
153
+ Open HDF5 file object
154
+ required_groups
155
+ List of required group names (default: ["grids", "table"])
156
+
157
+ Returns
158
+ -------
159
+ list[str]
160
+ List of missing groups (empty if all present)
161
+
162
+ Raises
163
+ ------
164
+ ValueError
165
+ If required groups are missing
166
+ """
167
+ if required_groups is None:
168
+ required_groups = ["grids", "table"]
169
+
170
+ missing = [g for g in required_groups if g not in f]
171
+ if missing:
172
+ msg = f"Invalid HDF5 structure. Missing groups: {missing}"
173
+ raise ValueError(msg)
174
+ return missing