Coverage for pesummary/cli/summarycompare.py: 82.8%
163 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
1#! /usr/bin/env python
3# Licensed under an MIT style license -- see LICENSE.md
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
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: '{}'"
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
49def _comparison_string(path, values=None, _type=None):
50 """Print the comparison string
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))
88 logger.info(string)
89 return string
92def _compare(data, path=None):
93 """Compare multiple posterior samples
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 = []
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
124def _compare_datasets(data, path=[]):
125 """Compare two datasets
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)
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
164def compare(samples, properties_to_compare=COMPARISON_PROPERTIES):
165 """Compare multiple posterior samples
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
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 )
210 def generate_plots(self):
211 """Generate all plots for all result files
212 """
213 self._generate_comparison_plots()
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()
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
244 def make_navbar_for_homepage(self):
245 """Make a navbar for the homepage
246 """
247 return ["home", "Logging", "Version"]
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
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()
284 def _make_home_pages(self, *args, **kwargs):
285 """Make the home pages
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()
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()
336if __name__ == "__main__":
337 main()