Coverage for pesummary/core/file/formats/bilby.py: 85.7%

182 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-11-05 13:38 +0000

1# Licensed under an MIT style license -- see LICENSE.md 

2 

3import os 

4import numpy as np 

5from pesummary.core.file.formats.base_read import SingleAnalysisRead 

6from pesummary.core.plots.latex_labels import latex_labels 

7from pesummary import conf 

8from pesummary.utils.utils import logger 

9 

10__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"] 

11 

12 

13def bilby_version(path=None, data=None): 

14 """Extract the version of bilby used to create a result file 

15 

16 Parameters 

17 ---------- 

18 path: str, optional 

19 path to the bilby result file 

20 data: iterable, optional 

21 the contents of the bilby result file, opened with either h5py or 

22 json libraries 

23 """ 

24 if path is None and data is None: 

25 raise ValueError( 

26 "Pass either the path to the result file, or result file object " 

27 "opened with either h5py or json" 

28 ) 

29 elif data is None: 

30 import json 

31 import h5py 

32 try: 

33 data = h5py.File(path, "r") 

34 except Exception: 

35 with open(path, "r") as f: 

36 data = json.load(f) 

37 option1 = lambda data: data["version"][()] 

38 option2 = lambda data: data["version"] 

39 for opt in [option1, option2]: 

40 try: 

41 version = opt(data) 

42 if isinstance(version, (list, np.ndarray)): 

43 return version[0] 

44 return version 

45 except Exception: 

46 pass 

47 raise ValueError("Unable to extract version from bilby result file") 

48 

49 

50def _load_bilby(path): 

51 """Wrapper for `bilby.core.result.read_in_result` 

52 

53 Parameters 

54 ---------- 

55 path: str 

56 path to the bilby result file you wish to load 

57 """ 

58 from bilby.core.result import read_in_result 

59 from bilby.core.utils import get_version_information 

60 result_version = bilby_version(path=path) 

61 if isinstance(result_version, bytes): 

62 result_version = result_version.decode("utf-8") 

63 env_version = 'bilby={}'.format(get_version_information()) 

64 if result_version != env_version: 

65 logger.warning( 

66 "Result file was written with version {} while your environment " 

67 "has version {}. This may cause problems when reading the result " 

68 "file.".format(result_version, env_version) 

69 ) 

70 return read_in_result(filename=path) 

71 

72 

73def _check_bilby_attributes(result, attribute): 

74 """Check the bilby attributes are the same format across different versions. 

75 Currently this checks that an empty list is returned if the attribute is 

76 None or if the length is 0. This can be deprecated after a few bilby releases. 

77 

78 Parameters 

79 ---------- 

80 result: bilby.core.result.Result 

81 bilby result file to check 

82 attribute: str 

83 attribute you wish to check 

84 """ 

85 _attr = getattr(result, attribute) 

86 if (_attr is None) or (not len(_attr)): 

87 _attr = [] 

88 return _attr 

89 

90 

91def read_bilby( 

92 path, disable_prior=False, complex_params=[], latex_dict=latex_labels, 

93 nsamples_for_prior=None, _bilby_class=None, **kwargs 

94): 

95 """Grab the parameters and samples in a bilby file 

96 

97 Parameters 

98 ---------- 

99 path: str 

100 path to the result file you wish to read in 

101 disable_prior: Bool, optional 

102 if True, do not collect prior samples from the `bilby` result file. 

103 Default False 

104 complex_params: list, optional 

105 list of parameters stored in the bilby result file which are complex 

106 and you wish to store the `amplitude` and `angle` as seperate 

107 posterior distributions 

108 latex_dict: dict, optional 

109 list of latex labels for each parameter 

110 nsamples_for_prior: int, optional 

111 number of samples to draw from the analytic priors 

112 """ 

113 if _bilby_class is None: 

114 _bilby_class = Bilby 

115 bilby_object = _load_bilby(path) 

116 posterior = bilby_object.posterior 

117 _original_keys = posterior.keys() 

118 for key in _original_keys: 

119 for param in complex_params: 

120 if param in key and any(np.iscomplex(posterior[key])): 

121 posterior[key + "_abs"] = abs(posterior[key]) 

122 posterior[key + "_angle"] = np.angle(posterior[key]) 

123 posterior[key] = np.real(posterior[key]) 

124 elif param in key: 

125 posterior[key] = np.real(posterior[key]) 

126 # Drop all non numeric bilby data outputs 

127 posterior = posterior.select_dtypes(include=[float, int]) 

128 parameters = list(posterior.keys()) 

129 samples = posterior.to_numpy().real 

130 injection = bilby_object.injection_parameters 

131 if injection is None: 

132 injection = {i: j for i, j in zip( 

133 parameters, [float("nan")] * len(parameters))} 

134 else: 

135 for i in parameters: 

136 if i not in injection.keys(): 

137 injection[i] = float("nan") 

138 _injection = injection.copy() 

139 for key, item in _injection.items(): 

140 if isinstance(item, (str, np.str_)): 

141 injection.pop(key) 

142 

143 _fixed_keys = _check_bilby_attributes( 

144 bilby_object, "fixed_parameter_keys" 

145 ) 

146 _constraint_keys = _check_bilby_attributes( 

147 bilby_object, "constraint_parameter_keys" 

148 ) 

149 _search_keys = _check_bilby_attributes( 

150 bilby_object, "search_parameter_keys" 

151 ) 

152 if all(i for i in (_constraint_keys, _search_keys, _fixed_keys)): 

153 for key in (_constraint_keys + _search_keys + _fixed_keys): 

154 if key not in latex_dict: 

155 label = bilby_object.get_latex_labels_from_parameter_keys( 

156 [key])[0] 

157 latex_dict[key] = label 

158 try: 

159 extra_kwargs = _bilby_class.grab_extra_kwargs(bilby_object) 

160 except Exception: 

161 extra_kwargs = {"sampler": {}, "meta_data": {}} 

162 extra_kwargs["sampler"]["nsamples"] = len(samples) 

163 extra_kwargs["sampler"]["pe_algorithm"] = "bilby" 

164 try: 

165 version = bilby_object.version 

166 if isinstance(version, (list, np.ndarray)): 

167 version = version[0] 

168 except Exception as e: 

169 version = None 

170 

171 data = { 

172 "parameters": parameters, 

173 "samples": samples.tolist(), 

174 "injection": injection, 

175 "version": version, 

176 "kwargs": extra_kwargs 

177 } 

178 if bilby_object.meta_data is not None: 

179 if "command_line_args" in bilby_object.meta_data.keys(): 

180 data["config"] = { 

181 "config": bilby_object.meta_data["command_line_args"] 

182 } 

183 if not disable_prior: 

184 logger.debug("Drawing prior samples from bilby result file") 

185 if nsamples_for_prior is None: 

186 nsamples_for_prior = len(samples) 

187 prior_samples = Bilby.grab_priors( 

188 bilby_object, nsamples=nsamples_for_prior 

189 ) 

190 data["prior"] = {"samples": prior_samples} 

191 if len(prior_samples): 

192 data["prior"]["analytic"] = prior_samples.analytic 

193 else: 

194 try: 

195 _prior = bilby_object.priors 

196 data["prior"] = { 

197 "samples": {}, 

198 "analytic": {key: str(item) for key, item in _prior.items()} 

199 } 

200 except (AttributeError, KeyError): 

201 pass 

202 return data 

203 

204 

205def to_bilby( 

206 parameters, samples, label=None, analytic_priors=None, cls=None, 

207 meta_data=None, **kwargs 

208): 

209 """Convert a set of samples to a bilby object 

210 

211 Parameters 

212 ---------- 

213 parameters: list 

214 list of parameters 

215 samples: 2d list 

216 list of samples. Columns correspond to a given parameter 

217 label: str, optional 

218 The label of the analysis. This is used in the filename if a filename 

219 if not specified 

220 """ 

221 from bilby.core.result import Result 

222 from bilby.core.prior import Prior, PriorDict 

223 from pandas import DataFrame 

224 

225 if cls is None: 

226 cls = Result 

227 if analytic_priors is not None: 

228 priors = PriorDict._get_from_json_dict(analytic_priors) 

229 search_parameters = priors.keys() 

230 else: 

231 priors = {param: Prior() for param in parameters} 

232 search_parameters = parameters 

233 posterior_data_frame = DataFrame(samples, columns=parameters) 

234 bilby_object = cls( 

235 search_parameter_keys=search_parameters, samples=samples, priors=priors, 

236 posterior=posterior_data_frame, label="pesummary_%s" % label, 

237 ) 

238 return bilby_object 

239 

240 

241def _write_bilby( 

242 parameters, samples, outdir="./", label=None, filename=None, overwrite=False, 

243 extension="json", save=True, analytic_priors=None, cls=None, 

244 meta_data=None, **kwargs 

245): 

246 """Write a set of samples to a bilby file 

247 

248 Parameters 

249 ---------- 

250 parameters: list 

251 list of parameters 

252 samples: 2d list 

253 list of samples. Columns correspond to a given parameter 

254 outdir: str, optional 

255 directory to write the dat file 

256 label: str, optional 

257 The label of the analysis. This is used in the filename if a filename 

258 if not specified 

259 filename: str, optional 

260 The name of the file that you wish to write 

261 overwrite: Bool, optional 

262 If True, an existing file of the same name will be overwritten 

263 extension: str, optional 

264 file extension for the bilby result file. Default json. 

265 save: Bool, optional 

266 if True, save the bilby object to file 

267 """ 

268 bilby_object = to_bilby( 

269 parameters, samples, label=None, analytic_priors=None, cls=None, 

270 meta_data=None, **kwargs 

271 ) 

272 if save: 

273 _filename = os.path.join(outdir, filename) 

274 bilby_object.save_to_file(filename=_filename, extension=extension) 

275 else: 

276 return bilby_object 

277 

278 

279def write_bilby( 

280 parameters, samples, outdir="./", label=None, filename=None, overwrite=False, 

281 extension="json", save=True, analytic_priors=None, cls=None, 

282 meta_data=None, labels=None, **kwargs 

283): 

284 """Write a set of samples to a bilby file 

285 

286 Parameters 

287 ---------- 

288 parameters: list 

289 list of parameters 

290 samples: 2d list 

291 list of samples. Columns correspond to a given parameter 

292 outdir: str, optional 

293 directory to write the dat file 

294 label: str, optional 

295 The label of the analysis. This is used in the filename if a filename 

296 if not specified 

297 filename: str, optional 

298 The name of the file that you wish to write 

299 overwrite: Bool, optional 

300 If True, an existing file of the same name will be overwritten 

301 extension: str, optional 

302 file extension for the bilby result file. Default json. 

303 save: Bool, optional 

304 if True, save the bilby object to file 

305 """ 

306 from pesummary.io.write import _multi_analysis_write 

307 

308 func = _write_bilby 

309 if not save: 

310 func = to_bilby 

311 return _multi_analysis_write( 

312 func, parameters, samples, outdir=outdir, label=label, 

313 filename=filename, overwrite=overwrite, extension=extension, 

314 save=save, analytic_priors=analytic_priors, cls=cls, 

315 meta_data=meta_data, file_format="bilby", labels=labels, 

316 _return=True, **kwargs 

317 ) 

318 

319 

320def config_from_file(path): 

321 """Extract the configuration file stored within a bilby result file 

322 

323 Parameters 

324 ---------- 

325 path: str 

326 path to the bilby result file you wish to load 

327 """ 

328 bilby_object = _load_bilby(path) 

329 return config_from_object(bilby_object) 

330 

331 

332def config_from_object(bilby_object): 

333 """Extract the configuration file stored within a `bilby.core.result.Result` 

334 object (or alike) 

335 

336 Parameters 

337 ---------- 

338 bilby_object: bilby.core.result.Result (or alike) 

339 a bilby.core.result.Result object (or alike) you wish to extract the 

340 configuration file from 

341 """ 

342 config = {} 

343 if bilby_object.meta_data is not None: 

344 if "command_line_args" in bilby_object.meta_data.keys(): 

345 config = { 

346 "config": bilby_object.meta_data["command_line_args"] 

347 } 

348 return config 

349 

350 

351def prior_samples_from_file(path, cls="PriorDict", nsamples=5000, **kwargs): 

352 """Return a dict of prior samples from a `bilby` prior file 

353 

354 Parameters 

355 ---------- 

356 path: str 

357 path to a `bilby` prior file 

358 cls: str, optional 

359 class you wish to read in the prior file 

360 nsamples: int, optional 

361 number of samples to draw from a prior file. Default 5000 

362 """ 

363 from bilby.core import prior 

364 

365 if isinstance(cls, str): 

366 cls = getattr(prior, cls) 

367 _prior = cls(filename=path) 

368 samples = _prior.sample(size=nsamples) 

369 return _bilby_prior_dict_to_pesummary_samples_dict(samples, prior=_prior) 

370 

371 

372def prior_samples_from_bilby_object(bilby_object, nsamples=5000, **kwargs): 

373 """Return a dict of prior samples from a `bilby.core.result.Result` 

374 object 

375 

376 Parameters 

377 ---------- 

378 bilby_object: bilby.core.result.Result 

379 a bilby.core.result.Result object you wish to draw prior samples from 

380 nsamples: int, optional 

381 number of samples to draw from a prior file. Default 5000 

382 """ 

383 samples = bilby_object.priors.sample(size=nsamples) 

384 return _bilby_prior_dict_to_pesummary_samples_dict( 

385 samples, prior=bilby_object.priors 

386 ) 

387 

388 

389def _bilby_prior_dict_to_pesummary_samples_dict(samples, prior=None): 

390 """Return a pesummary.utils.samples_dict.SamplesDict object from a bilby 

391 priors dict 

392 """ 

393 from pesummary.utils.samples_dict import SamplesDict 

394 

395 _samples = SamplesDict(samples) 

396 if prior is not None: 

397 analytic = {key: str(item) for key, item in prior.items()} 

398 setattr(_samples, "analytic", analytic) 

399 return _samples 

400 

401 

402class Bilby(SingleAnalysisRead): 

403 """PESummary wrapper of `bilby` (https://git.ligo.org/lscsoft/bilby). The 

404 path_to_results_file argument will be passed directly to 

405 `bilby.core.result.read_in_result`. All functions therefore use `bilby` 

406 methods and requires `bilby` to be installed. 

407 

408 Parameters 

409 ---------- 

410 path_to_results_file: str 

411 path to the results file that you wish to read in with `bilby`. 

412 disable_prior: Bool, optional 

413 if True, do not collect prior samples from the `bilby` result file. 

414 Default False 

415 remove_nan_likelihood_samples: Bool, optional 

416 if True, remove samples which have log_likelihood='nan'. Default True 

417 

418 Attributes 

419 ---------- 

420 parameters: list 

421 list of parameters stored in the result file 

422 samples: 2d list 

423 list of samples stored in the result file 

424 samples_dict: dict 

425 dictionary of samples stored in the result file keyed by parameters 

426 input_version: str 

427 version of the result file passed. 

428 extra_kwargs: dict 

429 dictionary of kwargs that were extracted from the result file 

430 injection_parameters: dict 

431 dictionary of injection parameters extracted from the result file 

432 prior: dict 

433 dictionary of prior samples keyed by parameters. The prior functions 

434 are evaluated for 5000 samples. 

435 pe_algorithm: str 

436 name of the algorithm used to generate the posterior samples 

437 

438 Methods 

439 ------- 

440 to_dat: 

441 save the posterior samples to a .dat file 

442 to_latex_table: 

443 convert the posterior samples to a latex table 

444 generate_latex_macros: 

445 generate a set of latex macros for the stored posterior samples 

446 """ 

447 def __init__(self, path_to_results_file, **kwargs): 

448 super(Bilby, self).__init__(path_to_results_file, **kwargs) 

449 self.load(self._grab_data_from_bilby_file, **kwargs) 

450 

451 @staticmethod 

452 def grab_priors(bilby_object, nsamples=5000): 

453 """Draw samples from the prior functions stored in the bilby file 

454 """ 

455 try: 

456 return prior_samples_from_bilby_object( 

457 bilby_object, nsamples=nsamples 

458 ) 

459 except Exception as e: 

460 logger.info("Failed to draw prior samples because {}".format(e)) 

461 return {} 

462 

463 @staticmethod 

464 def grab_extra_kwargs(bilby_object): 

465 """Grab any additional information stored in the lalinference file 

466 """ 

467 f = bilby_object 

468 kwargs = {"sampler": { 

469 conf.log_evidence: np.round(f.log_evidence, 2), 

470 conf.log_evidence_error: np.round(f.log_evidence_err, 2), 

471 conf.log_bayes_factor: np.round(f.log_bayes_factor, 2), 

472 conf.log_noise_evidence: np.round(f.log_noise_evidence, 2)}, 

473 "meta_data": {}, "other": f.meta_data} 

474 return kwargs 

475 

476 @staticmethod 

477 def _grab_data_from_bilby_file(path, **kwargs): 

478 """Load the results file using the `bilby` library 

479 """ 

480 return read_bilby(path, **kwargs) 

481 

482 def add_marginalized_parameters_from_config_file(self, config_file): 

483 """Search the configuration file and add the marginalized parameters 

484 to the list of parameters and samples 

485 

486 Parameters 

487 ---------- 

488 config_file: str 

489 path to the configuration file 

490 """ 

491 pass