Coverage for pesummary/cli/summarycompare.py: 82.8%

163 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-05-02 08:42 +0000

1#! /usr/bin/env python 

2 

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

4 

5import pesummary 

6from pesummary.core.cli.parser import ArgumentParser as _ArgumentParser 

7from pesummary.core.plots.main import _PlotGeneration 

8from pesummary.core.webpage.main import _WebpageGeneration 

9from pesummary.utils.utils import logger 

10from pesummary.utils.samples_dict import MultiAnalysisSamplesDict 

11from pesummary import conf 

12from pesummary.io import read 

13import numpy as np 

14from getpass import getuser 

15 

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

17__doc__ = """This executable is used to compare multiple files""" 

18COMPARISON_PROPERTIES = [ 

19 "posterior_samples", "config", "priors", "psds" 

20] 

21SAME_STRING = "The result files match for entry: '{}'" 

22 

23 

24class ArgumentParser(_ArgumentParser): 

25 def _pesummary_options(self): 

26 options = super(ArgumentParser, self)._pesummary_options() 

27 options.update( 

28 { 

29 "--properties_to_compare": { 

30 "dest": "compare", 

31 "nargs": "+", 

32 "default": ["posterior_samples"], 

33 "choices": COMPARISON_PROPERTIES, 

34 "help": ( 

35 "list of properties you wish to compare between the " 

36 "files. Default posterior_samples" 

37 ) 

38 }, 

39 "--generate_comparison_page": { 

40 "action": "store_true", 

41 "default": False, 

42 "help": "Generate a comparison page to compare contents" 

43 } 

44 } 

45 ) 

46 return options 

47 

48 

49def _comparison_string(path, values=None, _type=None): 

50 """Print the comparison string 

51 

52 Parameters 

53 ---------- 

54 path: list 

55 list containing the path to the dataset being compared 

56 values: list, optional 

57 list containing the entries which are being compared 

58 _type: optional 

59 the type of the values being compared 

60 """ 

61 _path = "/".join(path) 

62 if values is None: 

63 string = "'{}' is not in both result files. Unable to compare".format( 

64 _path 

65 ) 

66 logger.info(string) 

67 else: 

68 string = ( 

69 "The result files differ for the following entry: '{}'. ".format( 

70 _path 

71 ) 

72 ) 

73 if _type == list: 

74 try: 

75 _diff = np.max(np.array(values[0]) - np.array(values[1])) 

76 string += "The maximum difference is: {}".format(_diff) 

77 except ValueError as e: 

78 if "could not be broadcast together" in str(e): 

79 string += ( 

80 "Datasets contain different number of samples: " 

81 "{}".format( 

82 ", ".join([str(len(values[0])), str(len(values[1]))]) 

83 ) 

84 ) 

85 else: 

86 string += "The entries are: {}".format(", ".join(values)) 

87 

88 logger.info(string) 

89 return string 

90 

91 

92def _compare(data, path=None): 

93 """Compare multiple posterior samples 

94 

95 Parameters 

96 ---------- 

97 data: list, dict 

98 data structure which is to be compared 

99 path: list 

100 path to the data structure being compared 

101 """ 

102 if path is None: 

103 path = [] 

104 

105 string = "" 

106 if isinstance(data[0], dict): 

107 for key, value in data[0].items(): 

108 _path = path + [key] 

109 if not all(key in _dict.keys() for _dict in data): 

110 string += "{}\n".format(_comparison_string(_path)) 

111 continue 

112 if isinstance(value, dict): 

113 _data = [_dict[key] for _dict in data] 

114 string += "{}\n".format(_compare(_data, path=_path)) 

115 else: 

116 string += "{}\n".format( 

117 _compare_datasets([_data[key] for _data in data], path=_path) 

118 ) 

119 else: 

120 string += "{}\n".format(_compare_datasets(data, path=path)) 

121 return string 

122 

123 

124def _compare_datasets(data, path=[]): 

125 """Compare two datasets 

126 

127 Parameters 

128 ---------- 

129 data: list, str, int, float 

130 dataset which you want to compare 

131 path: list, optional 

132 path to the dataset being compared 

133 """ 

134 array_types = (list, pesummary.utils.samples_dict.Array, np.ndarray) 

135 numeric_types = (float, int, np.number) 

136 string_types = (str, bytes) 

137 

138 string = SAME_STRING.format("/".join(path)) 

139 if isinstance(data[0], array_types): 

140 try: 

141 np.testing.assert_almost_equal(data[0], data[1]) 

142 logger.debug(string) 

143 except AssertionError: 

144 string = _comparison_string(path, values=data, _type=list) 

145 elif isinstance(data[0], numeric_types): 

146 if not all(i == data[0] for i in data): 

147 string = _comparison_string(path, values=data, _type=float) 

148 else: 

149 logger.debug(string) 

150 elif isinstance(data[0], string_types): 

151 if not all(i == data[0] for i in data): 

152 string = _comparison_string(path, values=data, _type=str) 

153 else: 

154 logger.debug(string) 

155 else: 

156 raise ValueError( 

157 "Unknown data format. Unable to compare: {}".format( 

158 ", ".join([str(i) for i in data]) 

159 ) 

160 ) 

161 return string 

162 

163 

164def compare(samples, properties_to_compare=COMPARISON_PROPERTIES): 

165 """Compare multiple posterior samples 

166 

167 Parameters 

168 ---------- 

169 samples: list 

170 list of files you wish to compare 

171 properties_to_compare: list, optional 

172 optional list of properties you wish to compare 

173 """ 

174 data = [read(path, disable_prior_conversion=True) for path in samples] 

175 string = "" 

176 for prop in properties_to_compare: 

177 if prop.lower() == "posterior_samples": 

178 prop = "samples_dict" 

179 _data = [ 

180 getattr(f, prop) if hasattr(f, prop) else False for f in 

181 data 

182 ] 

183 if False in _data: 

184 logger.warning( 

185 "Unable to compare the property '{}' because not all files " 

186 "share this property".format(prop) 

187 ) 

188 continue 

189 string += "{}\n\n".format(_compare(_data, path=[prop])) 

190 return string 

191 

192 

193class ComparisonPlots(_PlotGeneration): 

194 """Class to handle the generation of comparison plots 

195 """ 

196 def __init__(self, webdir, samples, *args, **kwargs): 

197 logger.info("Starting to generate comparison plots") 

198 parameters = [list(samples[key]) for key in samples.keys()] 

199 params = list(set.intersection(*[set(l) for l in parameters])) 

200 linestyles = ["-"] * len(samples.keys()) 

201 colors = list(conf.colorcycle) 

202 super(ComparisonPlots, self).__init__( 

203 *args, webdir=webdir, labels=list(samples.keys()), samples=samples, 

204 same_parameters=params, injection_data={ 

205 label: {param: float("nan") for param in params} for label 

206 in list(samples.keys()) 

207 }, linestyles=linestyles, colors=colors, **kwargs 

208 ) 

209 

210 def generate_plots(self): 

211 """Generate all plots for all result files 

212 """ 

213 self._generate_comparison_plots() 

214 

215 

216class ComparisonWebpage(_WebpageGeneration): 

217 """Class to handle the generation of comparison plots 

218 """ 

219 def __init__(self, webdir, samples, *args, comparison_string="", **kwargs): 

220 logger.info("Starting to generate comparison pages") 

221 parameters = [list(samples[key]) for key in samples.keys()] 

222 params = list(set.intersection(*[set(l) for l in parameters])) 

223 self.comparison_string = comparison_string 

224 super(ComparisonWebpage, self).__init__( 

225 *args, webdir=webdir, labels=list(samples.keys()), samples=samples, 

226 user=getuser(), same_parameters=params, **kwargs 

227 ) 

228 self.copy_css_and_js_scripts() 

229 

230 def generate_webpages(self): 

231 """Generate all webpages 

232 """ 

233 self.make_home_pages() 

234 self.make_comparison_string_pages() 

235 self.make_comparison_pages() 

236 self.make_version_page() 

237 self.make_about_page() 

238 self.make_logging_page() 

239 try: 

240 self.generate_specific_javascript() 

241 except Exception: 

242 pass 

243 

244 def make_navbar_for_homepage(self): 

245 """Make a navbar for the homepage 

246 """ 

247 return ["home", "Logging", "Version"] 

248 

249 def make_navbar_for_comparison_page(self): 

250 """Make a navbar for the comparison homepage 

251 """ 

252 links = ["1d Histograms", ["Custom", "All"]] 

253 for i in self.categorize_parameters(self.same_parameters): 

254 links.append(i) 

255 final_links = ["home", "Output", links] 

256 return final_links 

257 

258 def make_comparison_string_pages(self): 

259 """ 

260 """ 

261 self.create_blank_html_pages(["Comparison_Output"], stylesheets=["Output"]) 

262 html_file = self.setup_page( 

263 "Comparison_Output", self.navbar["comparison"], 

264 approximant="Comparison", title="Summarycompare output" 

265 ) 

266 html_file.make_div(indent=2, _class='banner', _style=None) 

267 html_file.add_content("Summarycompare output") 

268 html_file.end_div() 

269 html_file.make_div(indent=2, _class='paragraph') 

270 html_file.add_content( 

271 "Below we show the output from summarycompare" 

272 ) 

273 html_file.end_div() 

274 html_file.make_container() 

275 styles = html_file.make_code_block( 

276 language="bash", contents=self.comparison_string 

277 ) 

278 html_file.end_container() 

279 with open("{}/css/Output.css".format(self.webdir), "w") as f: 

280 f.write(styles) 

281 html_file.make_footer(user=self.user, rundir=self.webdir,) 

282 html_file.close() 

283 

284 def _make_home_pages(self, *args, **kwargs): 

285 """Make the home pages 

286 

287 Parameters 

288 ---------- 

289 pages: list 

290 list of pages that you wish to create 

291 """ 

292 html_file = self.setup_page("home", self.navbar["home"]) 

293 html_file.add_content("<script>") 

294 html_file.add_content( 

295 "window.location.href = './html/Comparison.html'" 

296 ) 

297 html_file.add_content("</script>") 

298 html_file.close() 

299 

300 

301def main(args=None): 

302 """Top level interface for `summarycompare` 

303 """ 

304 _parser = ArgumentParser(description=__doc__) 

305 _parser.add_known_options_to_parser( 

306 [ 

307 "--samples", "--webdir", "--verbose", "--properties_to_compare", 

308 "--generate_comparison_page" 

309 ] 

310 ) 

311 opts, unknown = _parser.parse_known_args(args=args) 

312 string = compare(opts.samples, opts.compare) 

313 if opts.generate_comparison_page and opts.webdir is None: 

314 raise ValueError("Please provide a webdir to save comparison plots") 

315 if opts.generate_comparison_page: 

316 open_files = [read(ff).samples_dict for ff in opts.samples] 

317 samples = {} 

318 for num, _samples in enumerate(open_files): 

319 if isinstance(_samples, MultiAnalysisSamplesDict): 

320 samples.update( 

321 { 

322 "{}_file_{}".format(ff, num): val for ff, val in 

323 _samples.items() 

324 } 

325 ) 

326 else: 

327 samples.update({"file_{}".format(num): _samples}) 

328 plots = ComparisonPlots(opts.webdir, samples) 

329 plots.generate_plots() 

330 webpage = ComparisonWebpage( 

331 opts.webdir, samples, comparison_string=string 

332 ) 

333 webpage.generate_webpages() 

334 

335 

336if __name__ == "__main__": 

337 main()