22# SPDX-License-Identifier: Apache-2.0
33
44
5+ import re
6+
57import h5py
68
79
8- def _delete_chains (f : h5py .File , chains_idx : list [int ], n_chains : int ) -> None :
10+ def _parse_chain_names (chains : list [str ]) -> list [str ]:
11+ """Parse chain identifiers supplied as names like chain_0."""
12+ chain_names : list [str ] = []
13+ for chain in chains :
14+ value = str (chain ).strip ()
15+ if re .fullmatch (r"chain_\d+" , value ):
16+ chain_names .append (value )
17+ continue
18+
19+ raise ValueError (
20+ f"Invalid chain identifier '{ chain } '. Use the full chain name, for example chain_0."
21+ )
22+ return chain_names
23+
24+
25+ def _delete_chains (
26+ f : h5py .File ,
27+ chains_to_delete : list [str ],
28+ chain_names : list [str ],
29+ ) -> None :
930 import numpy as np
1031
1132 from gwkokab .analysis .utils .literals import SAMPLES_GROUP_NAME
1233
1334 samples = f [SAMPLES_GROUP_NAME ][()]
1435 del f [SAMPLES_GROUP_NAME ]
1536
37+ n_chains = len (chain_names )
1638 cleaned_chains_flatten = np .vstack ([
1739 chain
18- for idx , chain in enumerate (np .array_split (samples , n_chains , axis = 0 ))
19- if idx not in chains_idx
40+ for chain_name , chain in zip (
41+ chain_names , np .array_split (samples , n_chains , axis = 0 ), strict = True
42+ )
43+ if chain_name not in chains_to_delete
2044 ])
2145
2246 f .create_dataset (SAMPLES_GROUP_NAME , data = cleaned_chains_flatten )
2347
2448
25- def _dc_sampler_is_numpyro (f : h5py .File , chains_idx : list [int ]) -> None :
26- n_chains = f ["/sampler_cfg/mcmc" ].attrs ["num_chains" ]
27- f ["/sampler_cfg/mcmc" ].attrs ["num_chains" ] = n_chains - len (chains_idx )
28- chains = [
29- f ["/chains/chain_{}" .format (idx )][()]
30- for idx in range (n_chains )
31- if idx not in chains_idx
49+ def _dc_sampler_is_numpyro (f : h5py .File , chains_to_delete : list [str ]) -> list [str ]:
50+ chain_names = sorted (f ["/chains" ].keys (), key = lambda name : int (name .split ("_" )[- 1 ]))
51+ remaining_chain_names = [
52+ chain_name for chain_name in chain_names if chain_name not in chains_to_delete
3253 ]
3354
34- from gwkokab .analysis .core .utils import write_to_hdf5
55+ for chain_name in chains_to_delete :
56+ del f [f"/chains/{ chain_name } " ]
3557
36- del f ["/chains" ]
58+ f ["/sampler_cfg/mcmc" ]. attrs [ "num_chains" ] = len ( remaining_chain_names )
3759
38- for idx , chain in enumerate (chains ):
39- write_to_hdf5 (f , f"/chains/chain_{ idx } " , chain )
60+ return chain_names
4061
4162
42- def _dc_sampler_is_flowMC (f : h5py .File , chains_idx : list [int ]) -> None :
43- n_chains = f ["sampler_cfg" ].attrs ["n_chains" ]
44- f ["sampler_cfg" ].attrs ["n_chains" ] = n_chains - len (chains_idx )
63+ def _dc_sampler_is_flowMC (f : h5py .File , chains_to_delete : list [str ]) -> list [str ]:
64+ phase_chain_names : list [list [str ]] = []
4565
46- from gwkokab .analysis .core .utils import write_to_hdf5
66+ for phase in ("train" , "prod" ):
67+ phase_group = f [f"/chains/{ phase } " ]
68+ chain_names = sorted (
69+ phase_group .keys (), key = lambda name : int (name .split ("_" )[- 1 ])
70+ )
71+ phase_chain_names .append (chain_names )
72+
73+ if phase_chain_names [0 ] != phase_chain_names [1 ]:
74+ raise ValueError (
75+ f"flowMC train/prod chain names do not match: { phase_chain_names } "
76+ )
77+
78+ chain_names = phase_chain_names [0 ]
79+ remaining_chain_names = [
80+ chain_name for chain_name in chain_names if chain_name not in chains_to_delete
81+ ]
4782
4883 for phase in ("train" , "prod" ):
49- chains = [
50- f [f"/chains/{ phase } /chain_{ idx } " ]
51- for idx in range (n_chains )
52- if idx not in chains_idx
53- ]
84+ for chain_name in chains_to_delete :
85+ del f [f"/chains/{ phase } /{ chain_name } " ]
5486
55- del f [ f"/chains/ { phase } " ]
87+ f [ "sampler_cfg" ]. attrs [ "n_chains" ] = len ( remaining_chain_names )
5688
57- for idx , chain in enumerate (chains ):
58- group_name = f"/chains/{ phase } /chain_{ idx } "
59- f .create_group (group_name )
60- for key in chain .keys ():
61- write_to_hdf5 (f , f"{ group_name } /{ key } " , chain [key ][()])
89+ return chain_names
6290
6391
64- def _delete_chains_factory (f : h5py .File , chains_idx : list [int ]) -> None :
65- n_chains = 0
92+ def _delete_chains_factory (f : h5py .File , chains_to_delete : list [str ]) -> None :
6693 sampler_name = f ["sampler_cfg" ].attrs ["sampler_name" ]
6794
6895 if sampler_name == "numpyro" :
69- n_chains = len (f ["chains" ].keys ())
96+ chain_names = sorted (
97+ f ["/chains" ].keys (), key = lambda name : int (name .split ("_" )[- 1 ])
98+ )
7099 elif sampler_name == "flowMC" :
71- n_chains = int (f ["sampler_cfg" ].attrs ["n_chains" ])
100+ phase_chain_names = [
101+ sorted (
102+ f [f"/chains/{ phase } " ].keys (), key = lambda name : int (name .split ("_" )[- 1 ])
103+ )
104+ for phase in ("train" , "prod" )
105+ ]
106+ if phase_chain_names [0 ] != phase_chain_names [1 ]:
107+ raise ValueError (
108+ f"flowMC train/prod chain names do not match: { phase_chain_names } "
109+ )
110+ chain_names = phase_chain_names [0 ]
72111 else :
73112 raise ValueError (f"Unrecognized Sampler: { sampler_name } " )
74113
75- unique_chains_idx = set (chains_idx )
76- if not unique_chains_idx :
77- raise ValueError ("No chain indices specified for deletion." )
78- if any (idx < 0 or idx >= n_chains for idx in unique_chains_idx ):
114+ unique_chains_to_delete = set (chains_to_delete )
115+ if not unique_chains_to_delete :
116+ raise ValueError ("No chain names specified for deletion." )
117+ missing_chains = sorted (unique_chains_to_delete - set (chain_names ))
118+ if missing_chains :
79119 raise ValueError (
80- f"Chain indices must be between 0 and { n_chains - 1 } . Got { chains_idx } "
120+ f"Requested chains do not exist: { missing_chains } . Available chains: { chain_names } "
81121 )
82- if len (unique_chains_idx ) >= n_chains :
122+ if len (unique_chains_to_delete ) >= len ( chain_names ) :
83123 raise ValueError (
84- f"Cannot delete all chains. Total chains: { n_chains } , requested to delete: { len (unique_chains_idx )} "
124+ f"Cannot delete all chains. Total chains: { len ( chain_names ) } , requested to delete: { len (unique_chains_to_delete )} "
85125 )
86126
87127 if sampler_name == "numpyro" :
88- _dc_sampler_is_numpyro (f , chains_idx )
128+ original_chain_names = _dc_sampler_is_numpyro (f , chains_to_delete )
89129 elif sampler_name == "flowMC" :
90- _dc_sampler_is_flowMC (f , chains_idx )
130+ original_chain_names = _dc_sampler_is_flowMC (f , chains_to_delete )
91131
92- _delete_chains (f , chains_idx , n_chains )
132+ _delete_chains (f , chains_to_delete , original_chain_names )
93133
94134
95135def main ():
@@ -102,10 +142,11 @@ def main():
102142
103143 parser .add_argument (
104144 "-n" ,
145+ "--chains" ,
105146 nargs = "+" ,
106- type = int ,
147+ type = str ,
107148 required = True ,
108- help = "List of chain numbers to delete. Chain numbering starts from 0 ." ,
149+ help = "List of chains to delete. Use full names like chain_0 chain_2 ." ,
109150 )
110151 parser .add_argument (
111152 "-i" ,
@@ -116,7 +157,7 @@ def main():
116157 parser .add_argument (
117158 "-o" ,
118159 type = str ,
119- required = True ,
160+ default = "clean_data.hdf5" ,
120161 help = "Name of the output HDF5 file where the modified chains will be saved." ,
121162 )
122163
@@ -137,9 +178,9 @@ def main():
137178
138179 input_filename = args .i
139180 output_filename = args .o
140- numbers = args .n
181+ chains_to_delete = _parse_chain_names ( args .chains )
141182
142183 shutil .copy (input_filename , output_filename )
143184
144185 with h5py .File (output_filename , "a" ) as f :
145- _delete_chains_factory (f , numbers )
186+ _delete_chains_factory (f , chains_to_delete )
0 commit comments