saclay-format 0.2.1__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.
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: saclay-format
3
+ Version: 0.2.1
4
+ Summary: python parser and usefull script related to the saclay format
5
+ Maintainer-email: Guillaume Scamps <guillaume.scamps@l2it.in2p3.fr>, David Regnier <David.REGNIER@cea.fr>, Wouter Ryssens <Wouter.Ryssens@ulb.be>
6
+ Project-URL: Sources, https://gitlab.com/regnier/Saclay_format
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: h5py
10
+ Requires-Dist: numpy
11
+ Requires-Dist: pyyaml
12
+ Provides-Extra: visu
13
+ Requires-Dist: matplotlib; extra == "visu"
14
+ Requires-Dist: PyQt5; extra == "visu"
15
+ Requires-Dist: scipy; extra == "visu"
16
+ Requires-Dist: scikit-image; extra == "visu"
17
+
18
+ # Saclay_format (v0.2.1)
19
+
20
+ This repository contains a proposal format definition to exchange inputs/outputs
21
+ between some mean-field theory codes in the nuclear physics and astrophysics communities.
22
+ This format relies on a series of custom binary files containing the raw data along with
23
+ a YAML header containing metadata. It adheres to the results of the 05/2025 and 10/2025 ESNT
24
+ workshops entitled *"Simulating dense matter on three-dimensional coordinate space meshes (part I and II)"*.
25
+
26
+ ## Content
27
+
28
+ * `saclay_format` — Python3 package to parse and visualize this format
29
+ * `docs` — documentation of the format (not yet up to date)
30
+ * `examples` — nuclear mean-field outputs in this format
31
+ * `tests` — unit tests of the package (not yet up to date)
32
+
33
+ ## Requirements
34
+
35
+ * `pyyaml` — Python3 package for YAML parsing
36
+ * `h5py` — Python3 package for HDF5 parsing
37
+
38
+ ## Running the parser
39
+
40
+ From a Python terminal, import the `parser` module and use its `read()` and `write()` methods.
41
+ To see the expected structure of the data dictionary, check the
42
+ `create_dummy_data()` function in `tests/test_saclay_format.py`.
43
+
44
+ Example:
45
+ ```bash
46
+ cd tests/
47
+ python3 test_saclay_format.py
48
+ ```
49
+
50
+ ## Examples
51
+
52
+ ### Example structure
53
+
54
+ ```
55
+ examples/20Ne_bcs/
56
+ ├── 20Ne_rho_n.wdat
57
+ ├── 20Ne_state
58
+ ├── 20Ne.h5
59
+ ├── 20Ne.yaml
60
+ └── README.md
61
+
62
+ examples/alpha_alpha_3MeV/
63
+ ├── reaction_alpha_alpha_3MeV_header.yml
64
+ ├── reaction_alpha_alpha_3MeV_rho_n.bin
65
+ ├── reaction_alpha_alpha_3MeV_rho_p.bin
66
+ ├── README_visualisation_convert.md
67
+ └── script_test.convert.sh
68
+
69
+ examples/Ca40_ev8/
70
+ ├── Ca40_HF_SLy4.yaml
71
+ ├── Ca40_HF_SLy4_*.dat
72
+ └── README_test.md
73
+ ```
74
+
75
+ ### Conversion test
76
+
77
+ ```bash
78
+ python3 ../../saclay_format/hdf5_yaml_converter.py -h5 Ca40_HF_SLy4.yaml
79
+ python3 ../../saclay_format/ev8_to_ev1.py Ca40_HF_SLy4.h5 -o Ca40_HF_SLy4_ev1.h5
80
+ python3 ../../saclay_format/ev1_to_cr1.py Ca40_HF_SLy4_ev1.h5 -o Ca40_HF_SLy4_cr1.h5
81
+ ```
82
+
83
+ ### Visualization test
84
+
85
+ Static:
86
+ ```bash
87
+ python3 ../../saclay_format/visualisation_rho.py reaction_alpha_alpha_3MeV_header.yml
88
+ ```
89
+
90
+ Dynamic:
91
+ ```bash
92
+ python3 ../../saclay_format/visualisation_rho_TD.py -H reaction_alpha_alpha_3MeV_header.yml
93
+ ```
94
+
95
+ Conversion and round-trip test:
96
+ ```bash
97
+ python3 ../../saclay_format/hdf5_yaml_converter.py -h5 reaction_alpha_alpha_3MeV_header.yml
98
+ mv reaction_alpha_alpha_3MeV_header.h5 reaction_alpha_alpha_3MeV_converted.h5
99
+ python3 ../../saclay_format/visualisation_rho_TD.py reaction_alpha_alpha_3MeV_converted.h5
100
+ python3 ../../saclay_format/hdf5_yaml_converter.py -yaml reaction_alpha_alpha_3MeV_converted.h5
101
+ python3 ../../saclay_format/visualisation_rho_TD.py reaction_alpha_alpha_3MeV_converted.yaml
102
+ rm reaction_alpha_alpha_3MeV_converted*
103
+ ```
104
+
105
+ ## Todo
106
+
107
+ - Test the format
108
+ - Add documentation
109
+ - Update unit tests
110
+
111
+ ## Project layout
112
+
113
+ This project follows the structure recommended by the Hitchhiker’s Guide to Python:
114
+ [https://docs.python-guide.org/writing/structure](https://docs.python-guide.org/writing/structure)
@@ -0,0 +1,97 @@
1
+ # Saclay_format (v0.2.1)
2
+
3
+ This repository contains a proposal format definition to exchange inputs/outputs
4
+ between some mean-field theory codes in the nuclear physics and astrophysics communities.
5
+ This format relies on a series of custom binary files containing the raw data along with
6
+ a YAML header containing metadata. It adheres to the results of the 05/2025 and 10/2025 ESNT
7
+ workshops entitled *"Simulating dense matter on three-dimensional coordinate space meshes (part I and II)"*.
8
+
9
+ ## Content
10
+
11
+ * `saclay_format` — Python3 package to parse and visualize this format
12
+ * `docs` — documentation of the format (not yet up to date)
13
+ * `examples` — nuclear mean-field outputs in this format
14
+ * `tests` — unit tests of the package (not yet up to date)
15
+
16
+ ## Requirements
17
+
18
+ * `pyyaml` — Python3 package for YAML parsing
19
+ * `h5py` — Python3 package for HDF5 parsing
20
+
21
+ ## Running the parser
22
+
23
+ From a Python terminal, import the `parser` module and use its `read()` and `write()` methods.
24
+ To see the expected structure of the data dictionary, check the
25
+ `create_dummy_data()` function in `tests/test_saclay_format.py`.
26
+
27
+ Example:
28
+ ```bash
29
+ cd tests/
30
+ python3 test_saclay_format.py
31
+ ```
32
+
33
+ ## Examples
34
+
35
+ ### Example structure
36
+
37
+ ```
38
+ examples/20Ne_bcs/
39
+ ├── 20Ne_rho_n.wdat
40
+ ├── 20Ne_state
41
+ ├── 20Ne.h5
42
+ ├── 20Ne.yaml
43
+ └── README.md
44
+
45
+ examples/alpha_alpha_3MeV/
46
+ ├── reaction_alpha_alpha_3MeV_header.yml
47
+ ├── reaction_alpha_alpha_3MeV_rho_n.bin
48
+ ├── reaction_alpha_alpha_3MeV_rho_p.bin
49
+ ├── README_visualisation_convert.md
50
+ └── script_test.convert.sh
51
+
52
+ examples/Ca40_ev8/
53
+ ├── Ca40_HF_SLy4.yaml
54
+ ├── Ca40_HF_SLy4_*.dat
55
+ └── README_test.md
56
+ ```
57
+
58
+ ### Conversion test
59
+
60
+ ```bash
61
+ python3 ../../saclay_format/hdf5_yaml_converter.py -h5 Ca40_HF_SLy4.yaml
62
+ python3 ../../saclay_format/ev8_to_ev1.py Ca40_HF_SLy4.h5 -o Ca40_HF_SLy4_ev1.h5
63
+ python3 ../../saclay_format/ev1_to_cr1.py Ca40_HF_SLy4_ev1.h5 -o Ca40_HF_SLy4_cr1.h5
64
+ ```
65
+
66
+ ### Visualization test
67
+
68
+ Static:
69
+ ```bash
70
+ python3 ../../saclay_format/visualisation_rho.py reaction_alpha_alpha_3MeV_header.yml
71
+ ```
72
+
73
+ Dynamic:
74
+ ```bash
75
+ python3 ../../saclay_format/visualisation_rho_TD.py -H reaction_alpha_alpha_3MeV_header.yml
76
+ ```
77
+
78
+ Conversion and round-trip test:
79
+ ```bash
80
+ python3 ../../saclay_format/hdf5_yaml_converter.py -h5 reaction_alpha_alpha_3MeV_header.yml
81
+ mv reaction_alpha_alpha_3MeV_header.h5 reaction_alpha_alpha_3MeV_converted.h5
82
+ python3 ../../saclay_format/visualisation_rho_TD.py reaction_alpha_alpha_3MeV_converted.h5
83
+ python3 ../../saclay_format/hdf5_yaml_converter.py -yaml reaction_alpha_alpha_3MeV_converted.h5
84
+ python3 ../../saclay_format/visualisation_rho_TD.py reaction_alpha_alpha_3MeV_converted.yaml
85
+ rm reaction_alpha_alpha_3MeV_converted*
86
+ ```
87
+
88
+ ## Todo
89
+
90
+ - Test the format
91
+ - Add documentation
92
+ - Update unit tests
93
+
94
+ ## Project layout
95
+
96
+ This project follows the structure recommended by the Hitchhiker’s Guide to Python:
97
+ [https://docs.python-guide.org/writing/structure](https://docs.python-guide.org/writing/structure)
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "saclay-format"
3
+ version = "0.2.1"
4
+ description = "python parser and usefull script related to the saclay format"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = ["h5py","numpy","pyyaml"]
8
+
9
+ optional-dependencies.visu = ["matplotlib", "PyQt5", "scipy", "scikit-image"]
10
+
11
+ maintainers = [{ name = "Guillaume Scamps", email = "guillaume.scamps@l2it.in2p3.fr"} ,
12
+ {name="David Regnier" , email = "David.REGNIER@cea.fr"},
13
+ {name="Wouter Ryssens" , email = "Wouter.Ryssens@ulb.be"},
14
+ ]
15
+
16
+ urls.Sources="https://gitlab.com/regnier/Saclay_format"
17
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ import sys, os
3
+ import numpy as np
4
+
5
+ from saclay_parser import read, write
6
+
7
+ def kramers_partner(states):
8
+ """
9
+ Construct Kramers partners for 2-spinor wavefunctions:
10
+ psi_T = i sigma_y psi* -> ( psi_down*, -psi_up* )
11
+ Input shape: (nx, ny, nz, 2, nw)
12
+ Output shape: (nx, ny, nz, 2, nw)
13
+ """
14
+ conj = np.conjugate(states)
15
+ up = conj[..., 0, :]
16
+ dn = conj[..., 1, :]
17
+ partner = np.empty_like(states)
18
+ partner[..., 0, :] = dn
19
+ partner[..., 1, :] = -up
20
+ return partner
21
+
22
+ def check_orthonormality(states, tol=1e-10):
23
+ """
24
+ Check orthonormality of a set of wavefunctions.
25
+ states: shape (nx, ny, nz, 2, nw)
26
+ """
27
+ nx, ny, nz, _, nw = states.shape
28
+ flat = states.reshape(-1, nw) # (nx*ny*nz*2, nw)
29
+ overlaps = flat.conj().T @ flat # Gram matrix (nw, nw)
30
+ # Normalize
31
+ norms = np.sqrt(np.real(np.diag(overlaps)))
32
+ overlaps = overlaps / (norms[:, None] * norms[None, :])
33
+ deviation = np.max(np.abs(overlaps - np.eye(nw)))
34
+ print("Max deviation from orthonormality: {:.2e}".format(deviation))
35
+ if deviation > tol:
36
+ print("Warning: orthonormality check FAILED")
37
+ else:
38
+ print("Orthonormality OK")
39
+
40
+ def interleave_with_partners(states, partners):
41
+ """
42
+ Interleave original states and their Kramers partners along the last axis:
43
+ [1,2,...,N] + [1*,2*,...,N*] -> [1,1*,2,2*,...,N,N*]
44
+ """
45
+ shape = list(states.shape)
46
+ shape[-1] *= 2 # double last axis
47
+ interleaved = np.empty(shape, dtype=states.dtype)
48
+ interleaved[..., ::2] = states
49
+ interleaved[..., 1::2] = partners
50
+ return interleaved
51
+
52
+
53
+ def interleave_amplitudes(arr):
54
+ # arr: shape (N, ...)
55
+ conj = np.conjugate(arr)
56
+ shape = list(arr.shape)
57
+ shape[0] *= 2
58
+ interleaved = np.empty(shape, dtype=arr.dtype)
59
+ interleaved[::2] = arr
60
+ interleaved[1::2] = conj
61
+ return interleaved
62
+
63
+ def ev1_to_cr1(data):
64
+ """
65
+ Translate EV1 dataset into CR1 by doubling wavefunctions.
66
+ Only wavefunctions and metadata are modified.
67
+ """
68
+ md = data["metadata"].copy()
69
+ wf_md = md.get("wavefunction", {}).copy()
70
+
71
+ nw_n = wf_md["n_neutron_states"]
72
+ nw_p = wf_md["n_proton_states"]
73
+
74
+ # Build Kramers partners
75
+ statesn = data["statesn"]
76
+ statesp = data["statesp"]
77
+ pn = kramers_partner(statesn)
78
+ pp = kramers_partner(statesp)
79
+
80
+ statesn_cr1 = interleave_with_partners(statesn, pn)
81
+ statesp_cr1 = interleave_with_partners(statesp, pp)
82
+
83
+ # Bogoliubov amplitudes
84
+ un = data["un"]; vn = data["vn"]
85
+ up = data["up"]; vp = data["vp"]
86
+
87
+ un_cr1 = interleave_amplitudes(un)
88
+ vn_cr1 = interleave_amplitudes(vn)
89
+ up_cr1 = interleave_amplitudes(up)
90
+ vp_cr1 = interleave_amplitudes(vp)
91
+
92
+ # Update metadata
93
+ md["symmetry"] = "cr1"
94
+ md["prefix"] = md.get("prefix", "run") + "_cr1"
95
+ wf_md["n_neutron_states"] = 2 * nw_n
96
+ wf_md["n_proton_states"] = 2 * nw_p
97
+ md["wavefunction"] = wf_md
98
+
99
+ cr1_data = data.copy()
100
+ cr1_data['metadata'] = md
101
+ cr1_data['statesn'] = statesn_cr1
102
+ cr1_data['statesp'] = statesp_cr1
103
+ cr1_data['un'] = un_cr1
104
+ cr1_data['vn'] = vn_cr1
105
+ cr1_data['up'] = up_cr1
106
+ cr1_data['vp'] = vp_cr1
107
+
108
+ return cr1_data
109
+
110
+ def sort_by_occupation(data):
111
+ """
112
+ Sort states and Bogoliubov amplitudes by descending occupation number.
113
+ Occupation is |v|^2 summed over all spatial and spin coordinates.
114
+ """
115
+ vn = data["vn"]
116
+ vp = data["vp"]
117
+
118
+ occ_n = np.sum(np.abs(vn)**2, axis=tuple(range(1, vn.ndim))) # shape (N_n,)
119
+ occ_p = np.sum(np.abs(vp)**2, axis=tuple(range(1, vp.ndim))) # shape (N_p,)
120
+
121
+ idx_n = np.argsort(-occ_n) # descending
122
+ idx_p = np.argsort(-occ_p)
123
+
124
+ # Sort all neutron-related arrays
125
+ data["statesn"] = data["statesn"][..., idx_n]
126
+ data["un"] = data["un"][idx_n]
127
+ data["vn"] = data["vn"][idx_n]
128
+
129
+ # Sort all proton-related arrays
130
+ data["statesp"] = data["statesp"][..., idx_p]
131
+ data["up"] = data["up"][idx_p]
132
+ data["vp"] = data["vp"][idx_p]
133
+
134
+ return data
135
+
136
+
137
+ #----------------------------------------------------------
138
+ #----------------------------------------------------------
139
+ if __name__ == "__main__":
140
+ import argparse
141
+ import os
142
+ import sys
143
+
144
+ description = "Translate EV1 dataset into CR1 (no symmetry)"
145
+ parser = argparse.ArgumentParser(description=description)
146
+ parser.add_argument(
147
+ "input",
148
+ help="Input file (.yaml/.yml or .h5/.hdf5)"
149
+ )
150
+ parser.add_argument(
151
+ "-o", "--output",
152
+ type=str,
153
+ help="Output file name (.yaml/.yml or .h5/.hdf5). Default: overwrite input"
154
+ )
155
+ parser.add_argument(
156
+ "-f", "--format",
157
+ type=str,
158
+ choices=["yaml", "yml", "hdf5", "h5"],
159
+ help="Output format (default: same as input)"
160
+ )
161
+
162
+ args = parser.parse_args()
163
+
164
+ input_path = args.input
165
+ output_path = args.output or input_path
166
+ output_format = args.format
167
+
168
+ if not os.path.exists(input_path):
169
+ print(f"Input file '{input_path}' not found.")
170
+ sys.exit(1)
171
+
172
+ # --- Determine input format ---
173
+ ext = os.path.splitext(input_path)[1].lower()
174
+ if ext in (".yaml", ".yml"):
175
+ input_format = "yaml"
176
+ elif ext in (".h5", ".hdf5"):
177
+ input_format = "hdf5"
178
+ else:
179
+ print(f"Unsupported input file extension '{ext}'.")
180
+ sys.exit(1)
181
+
182
+ # --- Default output format same as input ---
183
+ if output_format is None:
184
+ output_format = input_format
185
+
186
+ # --- Prevent invalid conversion YAML -> HDF5 ---
187
+ if input_format == "yaml" and output_format in ("hdf5", "h5"):
188
+ print("ERROR: cannot convert YAML input directly to HDF5. Aborting.")
189
+ sys.exit(1)
190
+
191
+ # --- Read input ---
192
+ data_all = read(input_path)
193
+
194
+ sym = data_all["metadata"].get("symmetry", "").lower()
195
+ if sym != "ev1":
196
+ print(f"Symmetry is '{sym}', not 'ev1'. Aborting.")
197
+ sys.exit(1)
198
+
199
+ print("\n--- Input (EV1) ---")
200
+ wf_meta = data_all["metadata"]["wavefunction"]
201
+ print(f"Prefix: {data_all['metadata'].get('prefix')}")
202
+ print(f"Neutron states: {wf_meta['n_neutron_states']}")
203
+ print(f"Proton states: {wf_meta['n_proton_states']}")
204
+
205
+ # --- Translation EV1 -> CR1 ---
206
+ data_cr1 = ev1_to_cr1(data_all)
207
+ # data_cr1 = sort_by_occupation(data_cr1) # optional
208
+
209
+ # --- Update prefix to match output ---
210
+ output_dir = os.path.dirname(output_path) or "."
211
+ output_base = os.path.basename(output_path)
212
+ new_prefix = os.path.splitext(output_base)[0]
213
+ data_cr1["metadata"]["prefix"] = new_prefix
214
+
215
+ # --- Ensure file extension matches chosen format ---
216
+ out_ext = os.path.splitext(output_path)[1].lower()
217
+ if not out_ext:
218
+ if output_format in ("yaml", "yml"):
219
+ output_path += ".yaml"
220
+ elif output_format in ("hdf5", "h5"):
221
+ output_path += ".h5"
222
+
223
+ # --- Write output ---
224
+ print(f"\nWriting to '{output_path}' ({output_format} format)")
225
+ write(data_cr1, output_path)
226
+
227
+ print("\n--- Output (CR1) ---")
228
+ wf_meta = data_cr1["metadata"]["wavefunction"]
229
+ print(f"Prefix: {data_cr1['metadata'].get('prefix')}")
230
+ print(f"Neutron states: {wf_meta['n_neutron_states']}")
231
+ print(f"Proton states: {wf_meta['n_proton_states']}")
232
+
233
+ # --- Check orthonormality ---
234
+ print("\nChecking neutron states:")
235
+ check_orthonormality(data_cr1["statesn"])
236
+ print("Checking proton states:")
237
+ check_orthonormality(data_cr1["statesp"])