Coverage for pesummary/gw/conversions/__init__.py: 58.8%
1215 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-11-05 13:38 +0000
« 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
3import copy
4import numpy as np
5from pathlib import Path
7from pesummary import conf
8from pesummary.utils.decorators import set_docstring
9from pesummary.utils.exceptions import EvolveSpinError
10from pesummary.utils.utils import logger, import_error_msg
12try:
13 import lalsimulation
14except ImportError:
15 logger.warning(import_error_msg.format("lalsimulation"))
16try:
17 import astropy
18except ImportError:
19 logger.warning(import_error_msg.format("astropy"))
21from .angles import *
22from .cosmology import *
23from .cosmology import _source_from_detector
24from .mass import *
25from .remnant import *
26from .remnant import _final_from_initial_BBH
27from .snr import *
28from .snr import _ifo_snr
29from .spins import *
30from .tidal import *
31from .tidal import _check_NSBH_approximant
32from .time import *
34__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"]
35_conversion_doc = """
36 Class to calculate all possible derived quantities
38 Parameters
39 ----------
40 data: dict, list
41 either a dictionary or samples or a list of parameters and a list of
42 samples. See the examples below for details
43 extra_kwargs: dict, optional
44 dictionary of kwargs associated with this set of posterior samples.
45 f_low: float, optional
46 the low frequency cut-off to use when evolving the spins
47 f_start: float, optional
48 the starting frequency of the waveform
49 f_ref: float, optional
50 the reference frequency when spins are defined
51 f_final: float, optional
52 the final frequency to use when integrating over frequencies
53 approximant: str, optional
54 the approximant to use when evolving the spins
55 approximant_flags: dict, optional
56 dictionary of flags to control the variant of waveform model used
57 evolve_spins_forwards: float/str, optional
58 the final velocity to evolve the spins up to.
59 evolve_spins_backwards: str, optional
60 method to use when evolving the spins backwards to an infinite separation
61 return_kwargs: Bool, optional
62 if True, return a modified dictionary of kwargs containing information
63 about the conversion
64 NRSur_fits: float/str, optional
65 the NRSurrogate model to use to calculate the remnant fits. If nothing
66 passed, the average NR fits are used instead
67 multipole_snr: Bool, optional
68 if True, the SNR in the (l, m) = [(2, 1), (3, 3), (4, 4)] multipoles are
69 calculated from the posterior samples.
70 samples.
71 precessing_snr: Bool, optional
72 if True, the precessing SNR is calculated from the posterior samples.
73 psd: dict, optional
74 dictionary containing a psd frequency series for each detector you wish
75 to include in calculations
76 waveform_fits: Bool, optional
77 if True, the approximant is used to calculate the remnant fits. Default
78 is False which means that the average NR fits are used
79 multi_process: int, optional
80 number of cores to use to parallelize the computationally expensive
81 conversions
82 redshift_method: str, optional
83 method you wish to use when calculating the redshift given luminosity
84 distance samples. If redshift samples already exist, this method is not
85 used. Default is 'approx' meaning that interpolation is used to calculate
86 the redshift given N luminosity distance points.
87 cosmology: str, optional
88 cosmology you wish to use when calculating the redshift given luminosity
89 distance samples.
90 force_non_evolved: Bool, optional
91 force non evolved remnant quantities to be calculated when evolved quantities
92 already exist in the input. Default False
93 force_BBH_remnant_computation: Bool, optional
94 force BBH remnant quantities to be calculated for systems that include
95 tidal deformability parameters where BBH fits may not be applicable.
96 Default False.
97 force_BH_spin_evolution: Bool, optional
98 force BH spin evolution methods to be applied for systems that include
99 tidal deformability parameters where these methods may not be applicable.
100 Default False.
101 disable_remnant: Bool, optional
102 disable all remnant quantities from being calculated. Default False.
103 add_zero_spin: Bool, optional
104 if no spins are present in the posterior table, add spins with 0 value.
105 Default False.
106 psd_default: str/pycbc.psd obj, optional
107 Default PSD to use for conversions when no other PSD is provided.
108 regenerate: list, optional
109 list of posterior distributions that you wish to regenerate
110 return_dict: Bool, optional
111 if True, return a pesummary.utils.utils.SamplesDict object
112 resume_file: str, optional
113 path to file to use for checkpointing. If not provided, checkpointing
114 is not used. Default None
116 Examples
117 --------
118 There are two ways of passing arguments to this conversion class, either
119 a dictionary of samples or a list of parameters and a list of samples. See
120 the examples below:
122 >>> samples = {"mass_1": 10, "mass_2": 5}
123 >>> converted_samples = %(function)s(samples)
125 >>> parameters = ["mass_1", "mass_2"]
126 >>> samples = [10, 5]
127 >>> converted_samples = %(function)s(parameters, samples)
129 >>> samples = {"mass_1": [10, 20], "mass_2": [5, 8]}
130 >>> converted_samples = %(function)s(samples)
132 >>> parameters = ["mass_1", "mass_2"]
133 >>> samples = [[10, 5], [20, 8]]
134 """
137@set_docstring(_conversion_doc % {"function": "convert"})
138def convert(*args, restart_from_checkpoint=False, resume_file=None, **kwargs):
139 import os
140 if resume_file is not None:
141 if os.path.isfile(resume_file) and restart_from_checkpoint:
142 return _Conversion.load_current_state(resume_file)
143 logger.info(
144 "Unable to find resume file for conversion. Not restarting from "
145 "checkpoint"
146 )
147 return _Conversion(*args, resume_file=resume_file, **kwargs)
150class _PickledConversion(object):
151 pass
154@set_docstring(_conversion_doc % {"function": "_Conversion"})
155class _Conversion(object):
156 @classmethod
157 def load_current_state(cls, resume_file):
158 """Load current state from a resume file
160 Parameters
161 ----------
162 resume_file: str
163 path to a resume file to restart conversion
164 """
165 from pesummary.io import read
166 logger.info(
167 "Reading checkpoint file: {}".format(resume_file)
168 )
169 state = read(resume_file, checkpoint=True)
170 return cls(
171 state.parameters, state.samples, extra_kwargs=state.extra_kwargs,
172 evolve_spins_forwards=state.evolve_spins_forwards,
173 evolve_spins_backwards=state.evolve_spins_backwards,
174 NRSur_fits=state.NRSurrogate,
175 waveform_fits=state.waveform_fit, multi_process=state.multi_process,
176 redshift_method=state.redshift_method, cosmology=state.cosmology,
177 force_non_evolved=state.force_non_evolved,
178 force_BBH_remnant_computation=state.force_remnant,
179 disable_remnant=state.disable_remnant,
180 add_zero_spin=state.add_zero_spin, regenerate=state.regenerate,
181 return_kwargs=state.return_kwargs, return_dict=state.return_dict,
182 resume_file=state.resume_file
183 )
185 def write_current_state(self):
186 """Write the current state of the conversion class to file
187 """
188 from pesummary.io import write
189 state = _PickledConversion()
190 for key, value in vars(self).items():
191 setattr(state, key, value)
193 _path = Path(self.resume_file)
194 write(
195 state, outdir=_path.parent, file_format="pickle",
196 filename=_path.name, overwrite=True
197 )
198 logger.debug(
199 "Written checkpoint file: {}".format(self.resume_file)
200 )
202 def __new__(cls, *args, **kwargs):
203 from pesummary.utils.samples_dict import SamplesDict
204 from pesummary.utils.parameters import Parameters
206 obj = super(_Conversion, cls).__new__(cls)
207 base_replace = (
208 "'{}': {} already found in the result file. Overwriting with "
209 "the passed {}"
210 )
211 if len(args) > 2:
212 raise ValueError(
213 "The _Conversion module only takes as arguments a dictionary "
214 "of samples or a list of parameters and a list of samples"
215 )
216 elif isinstance(args[0], dict):
217 parameters = Parameters(args[0].keys())
218 samples = np.atleast_2d(
219 np.array([args[0][i] for i in parameters]).T
220 ).tolist()
221 else:
222 if not isinstance(args[0], Parameters):
223 parameters = Parameters(args[0])
224 else:
225 parameters = args[0]
226 samples = args[1]
227 samples = np.atleast_2d(samples).tolist()
228 extra_kwargs = kwargs.get("extra_kwargs", {"sampler": {}, "meta_data": {}})
229 f_low = kwargs.get("f_low", None)
230 f_start = kwargs.get("f_start", None)
231 f_ref = kwargs.get("f_ref", None)
232 f_final = kwargs.get("f_final", None)
233 delta_f = kwargs.get("delta_f", None)
235 for param, value in {"f_final": f_final, "delta_f": delta_f}.items():
236 if value is not None and param in extra_kwargs["meta_data"].keys():
237 logger.warning(
238 base_replace.format(
239 param, extra_kwargs["meta_data"][param], value
240 )
241 )
242 extra_kwargs["meta_data"][param] = value
243 elif value is not None:
244 extra_kwargs["meta_data"][param] = value
245 elif value is None and param in extra_kwargs["meta_data"].keys():
246 logger.debug(
247 "Found {} in input file. Using {}Hz".format(
248 param, extra_kwargs["meta_data"][param]
249 )
250 )
251 else:
252 logger.warning(
253 "Could not find {} in input file and one was not passed "
254 "from the command line. Using {}Hz as default".format(
255 param, getattr(conf, "default_{}".format(param))
256 )
257 )
258 extra_kwargs["meta_data"][param] = getattr(
259 conf, "default_{}".format(param)
260 )
262 approximant = kwargs.get("approximant", None)
263 approximant_flags = kwargs.get("approximant_flags", {})
264 NRSurrogate = kwargs.get("NRSur_fits", False)
265 redshift_method = kwargs.get("redshift_method", "approx")
266 cosmology = kwargs.get("cosmology", "Planck15")
267 force_non_evolved = kwargs.get("force_non_evolved", False)
268 force_remnant = kwargs.get("force_BBH_remnant_computation", False)
269 force_evolve = kwargs.get("force_BH_spin_evolution", False)
270 disable_remnant = kwargs.get("disable_remnant", False)
271 if redshift_method not in ["approx", "exact"]:
272 raise ValueError(
273 "'redshift_method' can either be 'approx' corresponding to "
274 "an approximant method, or 'exact' corresponding to an exact "
275 "method of calculating the redshift"
276 )
277 if isinstance(NRSurrogate, bool) and NRSurrogate:
278 raise ValueError(
279 "'NRSur_fits' must be a string corresponding to the "
280 "NRSurrogate model you wish to use to calculate the remnant "
281 "quantities"
282 )
283 waveform_fits = kwargs.get("waveform_fits", False)
284 evolve_spins_forwards = kwargs.get("evolve_spins_forwards", False)
285 evolve_spins_backwards = kwargs.get("evolve_spins_backwards", False)
286 if disable_remnant and (
287 force_non_evolved or force_remnant
288 or NRSurrogate or waveform_fits or evolve_spins_forwards
289 ):
290 _disable = []
291 if force_non_evolved:
292 _disable.append("force_non_evolved")
293 force_non_evolved = False
294 if force_remnant:
295 _disable.append("force_BBH_remnant_computation")
296 force_remnant = False
297 if NRSurrogate:
298 _disable.append("NRSur_fits")
299 NRSurrogate = False
300 if waveform_fits:
301 _disable.append("waveform_fits")
302 waveform_fits = False
303 if evolve_spins_forwards:
304 _disable.append("evolve_spins_forwards")
305 evolve_spins_forwards = False
306 logger.warning(
307 "Unable to use 'disable_remnant' and {}. Setting "
308 "{} and disabling all remnant quantities from being "
309 "calculated".format(
310 " or ".join(_disable),
311 " and ".join(["{}=False".format(_p) for _p in _disable])
312 )
313 )
314 if NRSurrogate and waveform_fits:
315 raise ValueError(
316 "Unable to use both the NRSurrogate and {} to calculate "
317 "remnant quantities. Please select only one option".format(
318 approximant
319 )
320 )
321 if isinstance(evolve_spins_forwards, bool) and evolve_spins_forwards:
322 raise ValueError(
323 "'evolve_spins_forwards' must be a float, the final velocity to "
324 "evolve the spins up to, or a string, 'ISCO', meaning "
325 "evolve the spins up to the ISCO frequency"
326 )
327 if not evolve_spins_forwards and (NRSurrogate or waveform_fits):
328 if (approximant is not None and "eob" in approximant) or NRSurrogate:
329 logger.warning(
330 "Only evolved spin remnant quantities are returned by the "
331 "{} fits.".format(
332 "NRSurrogate" if NRSurrogate else approximant
333 )
334 )
335 elif evolve_spins_forwards and (NRSurrogate or waveform_fits):
336 if (approximant is not None and "eob" in approximant) or NRSurrogate:
337 logger.warning(
338 "The {} fits already evolve the spins. Therefore "
339 "additional spin evolution will not be performed.".format(
340 "NRSurrogate" if NRSurrogate else approximant
341 )
342 )
343 else:
344 logger.warning(
345 "The {} fits are not applied with spin evolution.".format(
346 approximant
347 )
348 )
349 evolve_spins_forwards = False
351 multipole_snr = kwargs.get("multipole_snr", False)
352 precessing_snr = kwargs.get("precessing_snr", False)
354 for freq, name in zip([f_start, f_low], ["f_start", "f_low"]):
355 if freq is not None and name in extra_kwargs["meta_data"].keys():
356 logger.warning(
357 base_replace.format(
358 name, extra_kwargs["meta_data"][name], freq
359 )
360 )
361 extra_kwargs["meta_data"][name] = freq
362 elif freq is not None:
363 extra_kwargs["meta_data"][name] = freq
364 elif freq is None and name in extra_kwargs["meta_data"].keys():
365 logger.debug(
366 "Found {} in input file. Using {}Hz".format(
367 name, extra_kwargs["meta_data"][name]
368 )
369 )
370 else:
371 logger.warning(
372 "Could not find {} in input file and one was not passed from "
373 "the command line. Using {}Hz as default".format(
374 name, conf.default_flow
375 )
376 )
377 extra_kwargs["meta_data"][name] = conf.default_flow
378 if len(approximant_flags) and "approximant_flags" in extra_kwargs["meta_data"].keys():
379 logger.warning(
380 base_replace.format(
381 "approximant_flags", extra_kwargs["meta_data"]["approximant_flags"],
382 approximant_flags
383 )
384 )
385 extra_kwargs["meta_data"]["approximant_flags"] = approximant_flags
386 elif len(approximant_flags):
387 extra_kwargs["meta_data"]["approximant_flags"] = approximant_flags
388 if approximant is not None and "approximant" in extra_kwargs["meta_data"].keys():
389 logger.warning(
390 base_replace.format(
391 "approximant", extra_kwargs["meta_data"]["approximant"],
392 approximant
393 )
394 )
395 extra_kwargs["meta_data"]["approximant"] = approximant
396 elif approximant is not None:
397 extra_kwargs["meta_data"]["approximant"] = approximant
398 if f_ref is not None and "f_ref" in extra_kwargs["meta_data"].keys():
399 logger.warning(
400 base_replace.format(
401 "f_ref", extra_kwargs["meta_data"]["f_ref"], f_ref
402 )
403 )
404 extra_kwargs["meta_data"]["f_ref"] = f_ref
405 elif f_ref is not None:
406 extra_kwargs["meta_data"]["f_ref"] = f_ref
407 regenerate = kwargs.get("regenerate", None)
408 multi_process = kwargs.get("multi_process", None)
409 if multi_process is not None:
410 multi_process = int(multi_process)
411 psd_default = kwargs.get("psd_default", "aLIGOZeroDetHighPower")
412 psd = kwargs.get("psd", {})
413 if psd is None:
414 psd = {}
415 elif psd is not None and not isinstance(psd, dict):
416 raise ValueError(
417 "'psd' must be a dictionary of frequency series for each detector"
418 )
419 ifos = list(psd.keys())
420 pycbc_psd = copy.deepcopy(psd)
421 if psd != {}:
422 from pesummary.gw.file.psd import PSD
423 if isinstance(psd[ifos[0]], PSD):
424 for ifo in ifos:
425 try:
426 pycbc_psd[ifo] = pycbc_psd[ifo].to_pycbc(
427 extra_kwargs["meta_data"]["f_low"],
428 f_high=extra_kwargs["meta_data"]["f_final"],
429 f_high_override=True
430 )
431 except (ImportError, IndexError, ValueError):
432 pass
433 obj.__init__(
434 parameters, samples, extra_kwargs, evolve_spins_forwards, NRSurrogate,
435 waveform_fits, multi_process, regenerate, redshift_method,
436 cosmology, force_non_evolved, force_remnant,
437 kwargs.get("add_zero_spin", False), disable_remnant,
438 kwargs.get("return_kwargs", False), kwargs.get("return_dict", True),
439 kwargs.get("resume_file", None), multipole_snr, precessing_snr,
440 pycbc_psd, psd_default, evolve_spins_backwards, force_evolve
441 )
442 return_kwargs = kwargs.get("return_kwargs", False)
443 if kwargs.get("return_dict", True) and return_kwargs:
444 return [
445 SamplesDict(obj.parameters, np.array(obj.samples).T),
446 obj.extra_kwargs
447 ]
448 elif kwargs.get("return_dict", True):
449 return SamplesDict(obj.parameters, np.array(obj.samples).T)
450 elif return_kwargs:
451 return obj.parameters, obj.samples, obj.extra_kwargs
452 else:
453 return obj.parameters, obj.samples
455 def __init__(
456 self, parameters, samples, extra_kwargs, evolve_spins_forwards, NRSurrogate,
457 waveform_fits, multi_process, regenerate, redshift_method,
458 cosmology, force_non_evolved, force_remnant, add_zero_spin,
459 disable_remnant, return_kwargs, return_dict, resume_file, multipole_snr,
460 precessing_snr, psd, psd_default, evolve_spins_backwards, force_evolve
461 ):
462 self.parameters = parameters
463 self.samples = samples
464 self.extra_kwargs = extra_kwargs
465 self.evolve_spins_forwards = evolve_spins_forwards
466 self.evolve_spins_backwards = evolve_spins_backwards
467 self.NRSurrogate = NRSurrogate
468 self.waveform_fit = waveform_fits
469 self.multi_process = multi_process
470 self.regenerate = regenerate
471 self.redshift_method = redshift_method
472 self.cosmology = cosmology
473 self.force_non_evolved = force_non_evolved
474 self.force_remnant = force_remnant
475 self.force_evolve = force_evolve
476 self.disable_remnant = disable_remnant
477 self.return_kwargs = return_kwargs
478 self.return_dict = return_dict
479 self.resume_file = resume_file
480 self.multipole_snr = multipole_snr
481 self.precessing_snr = precessing_snr
482 self.psd = psd
483 self.psd_default = psd_default
484 self.non_precessing = False
485 cond1 = any(
486 param in self.parameters for param in
487 conf.precessing_angles + conf.precessing_spins
488 )
489 if not cond1:
490 self.non_precessing = True
491 if "chi_p" in self.parameters:
492 _chi_p = self.specific_parameter_samples(["chi_p"])
493 if not np.any(_chi_p):
494 logger.info(
495 "chi_p = 0 for all samples. Treating this as a "
496 "non-precessing system"
497 )
498 self.non_precessing = True
499 elif all(param in self.parameters for param in conf.precessing_spins):
500 samples = self.specific_parameter_samples(conf.precessing_spins)
501 if not any(np.array(samples).flatten()):
502 logger.info(
503 "in-plane spins are zero for all samples. Treating this as "
504 "a non-precessing system"
505 )
506 cond1 = self.non_precessing and evolve_spins_forwards
507 cond2 = self.non_precessing and evolve_spins_backwards
508 if cond1 or cond2:
509 logger.info(
510 "Spin evolution is trivial for a non-precessing system. No additional "
511 "transformation required."
512 )
513 self.evolve_spins_forwards = False
514 self.evolve_spins_backwards = False
515 if not self.non_precessing and multipole_snr:
516 logger.warning(
517 "The calculation for computing the SNR in subdominant "
518 "multipoles assumes that the system is non-precessing. "
519 "Since precessing samples are provided, this may give incorrect "
520 "results"
521 )
522 if self.non_precessing and precessing_snr:
523 logger.info(
524 "Precessing SNR is 0 for a non-precessing system. No additional "
525 "conversion required."
526 )
527 self.precessing_snr = False
528 self.has_tidal = self._check_for_tidal_parameters()
529 self.NSBH = self._check_for_NSBH_system()
530 self.compute_remnant = not self.disable_remnant
531 if self.has_tidal:
532 if force_evolve and (self.evolve_spins_forwards or self.evolve_spins_backwards):
533 logger.warning(
534 "Posterior samples for tidal deformability found in the "
535 "posterior table. 'force_evolve' provided so using BH spin "
536 "evolution methods for this system. This may not give "
537 "sensible results"
538 )
539 elif self.evolve_spins_forwards or self.evolve_spins_backwards:
540 logger.warning(
541 "Tidal deformability parameters found in the posterior table. "
542 "Skipping spin evolution as current methods are only valid "
543 "for BHs."
544 )
545 self.evolve_spins_forwards = False
546 self.evolve_spins_backwards = False
548 if force_remnant and self.NSBH and self.compute_remnant:
549 logger.warning(
550 "Posterior samples for lambda_2 found in the posterior table "
551 "but unable to find samples for lambda_1. Assuming this "
552 "is an NSBH system. 'force_remnant' provided so using BBH remnant "
553 "fits for this system. This may not give sensible results"
554 )
555 elif self.NSBH and self.compute_remnant:
556 logger.warning(
557 "Posterior samples for lambda_2 found in the posterior table "
558 "but unable to find samples for lambda_1. Applying NSBH "
559 "fits to this system."
560 )
561 self.waveform_fit = True
562 elif force_remnant and self.compute_remnant:
563 logger.warning(
564 "Posterior samples for tidal deformability found in the "
565 "posterior table. Applying BBH remnant fits to this system. "
566 "This may not give sensible results."
567 )
568 elif self.compute_remnant:
569 logger.info(
570 "Skipping remnant calculations as tidal deformability "
571 "parameters found in the posterior table."
572 )
573 self.compute_remnant = False
574 if self.regenerate is not None:
575 for param in self.regenerate:
576 self.remove_posterior(param)
577 self.add_zero_spin = add_zero_spin
578 self.generate_all_posterior_samples()
580 def _check_for_tidal_parameters(self):
581 """Check to see if any tidal parameters are stored in the table
582 """
583 from pesummary.gw.file.standard_names import tidal_params
585 if any(param in self.parameters for param in tidal_params):
586 if not all(_ in self.parameters for _ in ["lambda_1", "lambda_2"]):
587 return True
588 _tidal_posterior = self.specific_parameter_samples(
589 ["lambda_1", "lambda_2"]
590 )
591 if not all(np.any(_) for _ in _tidal_posterior):
592 logger.warning(
593 "Tidal deformability parameters found in the posterior "
594 "table but they are all exactly 0. Assuming this is a BBH "
595 "system."
596 )
597 return False
598 return True
599 return False
601 def _check_for_NSBH_system(self):
602 """Check to see if the posterior samples correspond to an NSBH
603 system
604 """
605 if "lambda_2" in self.parameters and "lambda_1" not in self.parameters:
606 _lambda_2 = self.specific_parameter_samples(["lambda_2"])
607 if not np.any(_lambda_2):
608 logger.warning(
609 "Posterior samples for lambda_2 found in the posterior "
610 "table but they are all exactly 0. Assuming this is a BBH "
611 "system."
612 )
613 return False
614 return True
615 elif "lambda_2" in self.parameters and "lambda_1" in self.parameters:
616 _lambda_1, _lambda_2 = self.specific_parameter_samples(
617 ["lambda_1", "lambda_2"]
618 )
619 if not np.any(_lambda_1) and not np.any(_lambda_2):
620 return False
621 elif not np.any(_lambda_1):
622 logger.warning(
623 "Posterior samples for lambda_1 and lambda_2 found in the "
624 "posterior table but lambda_1 is always exactly 0. "
625 "Assuming this is an NSBH system."
626 )
627 return True
628 return False
630 def remove_posterior(self, parameter):
631 if parameter in self.parameters:
632 logger.info(
633 "Removing the posterior samples for '{}'".format(parameter)
634 )
635 ind = self.parameters.index(parameter)
636 self.parameters.remove(self.parameters[ind])
637 for i in self.samples:
638 del i[ind]
639 else:
640 logger.info(
641 "'{}' is not in the table of posterior samples. Unable to "
642 "remove".format(parameter)
643 )
645 def _specific_parameter_samples(self, param):
646 """Return the samples for a specific parameter
648 Parameters
649 ----------
650 param: str
651 the parameter that you would like to return the samples for
652 """
653 if param == "empty":
654 return np.array(np.zeros(len(self.samples)))
655 ind = self.parameters.index(param)
656 samples = np.array([i[ind] for i in self.samples])
657 return samples
659 def specific_parameter_samples(self, param):
660 """Return the samples for either a list or a single parameter
662 Parameters
663 ----------
664 param: list/str
665 the parameter/parameters that you would like to return the samples
666 for
667 """
668 if type(param) == list:
669 samples = [self._specific_parameter_samples(i) for i in param]
670 else:
671 samples = self._specific_parameter_samples(param)
672 return samples
674 def append_data(self, parameter, samples):
675 """Add a list of samples to the existing samples data object
677 Parameters
678 ----------
679 parameter: str
680 the name of the parameter you would like to append
681 samples: list
682 the list of samples that you would like to append
683 """
684 if parameter not in self.parameters:
685 self.parameters.append(parameter)
686 for num, i in enumerate(self.samples):
687 self.samples[num].append(samples[num])
688 if self.resume_file is not None:
689 self.write_current_state()
691 def _mchirp_from_mchirp_source_z(self):
692 samples = self.specific_parameter_samples(["chirp_mass_source", "redshift"])
693 chirp_mass = mchirp_from_mchirp_source_z(samples[0], samples[1])
694 self.append_data("chirp_mass", chirp_mass)
696 def _q_from_eta(self):
697 samples = self.specific_parameter_samples("symmetric_mass_ratio")
698 mass_ratio = q_from_eta(samples)
699 self.append_data("mass_ratio", mass_ratio)
701 def _q_from_m1_m2(self):
702 samples = self.specific_parameter_samples(["mass_1", "mass_2"])
703 mass_ratio = q_from_m1_m2(samples[0], samples[1])
704 self.append_data("mass_ratio", mass_ratio)
706 def _invert_q(self):
707 ind = self.parameters.index("mass_ratio")
708 for num, i in enumerate(self.samples):
709 self.samples[num][ind] = 1. / self.samples[num][ind]
711 def _invq_from_q(self):
712 samples = self.specific_parameter_samples("mass_ratio")
713 inverted_mass_ratio = invq_from_q(samples)
714 self.append_data("inverted_mass_ratio", inverted_mass_ratio)
716 def _mchirp_from_mtotal_q(self):
717 samples = self.specific_parameter_samples(["total_mass", "mass_ratio"])
718 chirp_mass = mchirp_from_mtotal_q(samples[0], samples[1])
719 self.append_data("chirp_mass", chirp_mass)
721 def _m1_from_mchirp_q(self):
722 samples = self.specific_parameter_samples(["chirp_mass", "mass_ratio"])
723 mass_1 = m1_from_mchirp_q(samples[0], samples[1])
724 self.append_data("mass_1", mass_1)
726 def _m2_from_mchirp_q(self):
727 samples = self.specific_parameter_samples(["chirp_mass", "mass_ratio"])
728 mass_2 = m2_from_mchirp_q(samples[0], samples[1])
729 self.append_data("mass_2", mass_2)
731 def _m1_from_mtotal_q(self):
732 samples = self.specific_parameter_samples(["total_mass", "mass_ratio"])
733 mass_1 = m1_from_mtotal_q(samples[0], samples[1])
734 self.append_data("mass_1", mass_1)
736 def _m2_from_mtotal_q(self):
737 samples = self.specific_parameter_samples(["total_mass", "mass_ratio"])
738 mass_2 = m2_from_mtotal_q(samples[0], samples[1])
739 self.append_data("mass_2", mass_2)
741 def _reference_frequency(self):
742 nsamples = len(self.samples)
743 extra_kwargs = self.extra_kwargs["meta_data"]
744 if extra_kwargs != {} and "f_ref" in list(extra_kwargs.keys()):
745 self.append_data(
746 "reference_frequency", [float(extra_kwargs["f_ref"])] * nsamples
747 )
748 else:
749 logger.warning(
750 "Could not find reference_frequency in input file. Using 20Hz "
751 "as default")
752 self.append_data("reference_frequency", [20.] * nsamples)
754 def _mtotal_from_m1_m2(self):
755 samples = self.specific_parameter_samples(["mass_1", "mass_2"])
756 m_total = m_total_from_m1_m2(samples[0], samples[1])
757 self.append_data("total_mass", m_total)
759 def _mchirp_from_m1_m2(self):
760 samples = self.specific_parameter_samples(["mass_1", "mass_2"])
761 chirp_mass = mchirp_from_m1_m2(samples[0], samples[1])
762 self.append_data("chirp_mass", chirp_mass)
764 def _eta_from_m1_m2(self):
765 samples = self.specific_parameter_samples(["mass_1", "mass_2"])
766 eta = eta_from_m1_m2(samples[0], samples[1])
767 self.append_data("symmetric_mass_ratio", eta)
769 def _phi_12_from_phi1_phi2(self):
770 samples = self.specific_parameter_samples(["phi_1", "phi_2"])
771 phi_12 = phi_12_from_phi1_phi2(samples[0], samples[1])
772 self.append_data("phi_12", phi_12)
774 def _phi1_from_spins(self):
775 samples = self.specific_parameter_samples(["spin_1x", "spin_1y"])
776 phi_1 = phi1_from_spins(samples[0], samples[1])
777 self.append_data("phi_1", phi_1)
779 def _phi2_from_spins(self):
780 samples = self.specific_parameter_samples(["spin_2x", "spin_2y"])
781 phi_2 = phi2_from_spins(samples[0], samples[1])
782 self.append_data("phi_2", phi_2)
784 def _spin_angles(self):
785 _spin_angles = ["theta_jn", "phi_jl", "tilt_1", "tilt_2", "phi_12",
786 "a_1", "a_2"]
787 spin_angles_to_calculate = [
788 i for i in _spin_angles if i not in self.parameters]
789 spin_components = [
790 "mass_1", "mass_2", "iota", "spin_1x", "spin_1y", "spin_1z",
791 "spin_2x", "spin_2y", "spin_2z", "reference_frequency"]
792 samples = self.specific_parameter_samples(spin_components)
793 if "phase" in self.parameters:
794 spin_components.append("phase")
795 samples.append(self.specific_parameter_samples("phase"))
796 else:
797 logger.warning(
798 "Phase it not given, we will be assuming that a "
799 "reference phase of 0 to calculate all the spin angles"
800 )
801 samples.append([0] * len(samples[0]))
802 angles = spin_angles(
803 samples[0], samples[1], samples[2], samples[3], samples[4],
804 samples[5], samples[6], samples[7], samples[8], samples[9],
805 samples[10])
807 for i in spin_angles_to_calculate:
808 ind = _spin_angles.index(i)
809 data = np.array([i[ind] for i in angles])
810 self.append_data(i, data)
812 def _non_precessing_component_spins(self):
813 spins = ["iota", "spin_1x", "spin_1y", "spin_1z", "spin_2x", "spin_2y",
814 "spin_2z"]
815 angles = ["a_1", "a_2", "theta_jn", "tilt_1", "tilt_2"]
816 if all(i in self.parameters for i in angles):
817 samples = self.specific_parameter_samples(angles)
818 cond1 = all(i in [0, np.pi] for i in samples[3])
819 cond2 = all(i in [0, np.pi] for i in samples[4])
820 spins_to_calculate = [
821 i for i in spins if i not in self.parameters]
822 if cond1 and cond1:
823 spin_1x = np.array([0.] * len(samples[0]))
824 spin_1y = np.array([0.] * len(samples[0]))
825 spin_1z = samples[0] * np.cos(samples[3])
826 spin_2x = np.array([0.] * len(samples[0]))
827 spin_2y = np.array([0.] * len(samples[0]))
828 spin_2z = samples[1] * np.cos(samples[4])
829 iota = np.array(samples[2])
830 spin_components = [
831 iota, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z]
833 for i in spins_to_calculate:
834 ind = spins.index(i)
835 data = spin_components[ind]
836 self.append_data(i, data)
838 def _component_spins(self):
839 spins = ["iota", "spin_1x", "spin_1y", "spin_1z", "spin_2x", "spin_2y",
840 "spin_2z"]
841 spins_to_calculate = [
842 i for i in spins if i not in self.parameters]
843 angles = [
844 "theta_jn", "phi_jl", "tilt_1", "tilt_2", "phi_12", "a_1", "a_2",
845 "mass_1", "mass_2", "reference_frequency"]
846 samples = self.specific_parameter_samples(angles)
847 if "phase" in self.parameters:
848 angles.append("phase")
849 samples.append(self.specific_parameter_samples("phase"))
850 else:
851 logger.warning(
852 "Phase it not given, we will be assuming that a "
853 "reference phase of 0 to calculate all the spin angles"
854 )
855 samples.append([0] * len(samples[0]))
856 spin_components = component_spins(
857 samples[0], samples[1], samples[2], samples[3], samples[4],
858 samples[5], samples[6], samples[7], samples[8], samples[9],
859 samples[10])
861 for i in spins_to_calculate:
862 ind = spins.index(i)
863 data = np.array([i[ind] for i in spin_components])
864 self.append_data(i, data)
866 def _component_spins_from_azimuthal_and_polar_angles(self):
867 spins = ["spin_1x", "spin_1y", "spin_1z", "spin_2x", "spin_2y",
868 "spin_2z"]
869 spins_to_calculate = [
870 i for i in spins if i not in self.parameters]
871 angles = [
872 "a_1", "a_2", "a_1_azimuthal", "a_1_polar", "a_2_azimuthal",
873 "a_2_polar"]
874 samples = self.specific_parameter_samples(angles)
875 spin_components = spin_angles_from_azimuthal_and_polar_angles(
876 samples[0], samples[1], samples[2], samples[3], samples[4],
877 samples[5])
878 for i in spins_to_calculate:
879 ind = spins.index(i)
880 data = np.array([i[ind] for i in spin_components])
881 self.append_data(i, data)
883 def _chi_p(self):
884 parameters = [
885 "mass_1", "mass_2", "spin_1x", "spin_1y", "spin_2x", "spin_2y"]
886 samples = self.specific_parameter_samples(parameters)
887 chi_p_samples = chi_p(
888 samples[0], samples[1], samples[2], samples[3], samples[4],
889 samples[5])
890 self.append_data("chi_p", chi_p_samples)
892 def _chi_p_from_tilts(self, suffix=""):
893 parameters = [
894 "mass_1", "mass_2", "a_1", "tilt_1{}".format(suffix), "a_2",
895 "tilt_2{}".format(suffix)
896 ]
897 samples = self.specific_parameter_samples(parameters)
898 chi_p_samples = chi_p_from_tilts(
899 samples[0], samples[1], samples[2], samples[3], samples[4],
900 samples[5]
901 )
902 self.append_data("chi_p{}".format(suffix), chi_p_samples)
904 def _chi_p_2spin(self):
905 parameters = [
906 "mass_1", "mass_2", "spin_1x", "spin_1y", "spin_2x", "spin_2y"]
907 samples = self.specific_parameter_samples(parameters)
908 chi_p_2spin_samples = chi_p_2spin(
909 samples[0], samples[1], samples[2], samples[3], samples[4],
910 samples[5])
911 self.append_data("chi_p_2spin", chi_p_2spin_samples)
913 def _chi_eff(self, suffix=""):
914 parameters = [
915 "mass_1", "mass_2", "spin_1z{}".format(suffix),
916 "spin_2z{}".format(suffix)
917 ]
918 samples = self.specific_parameter_samples(parameters)
919 chi_eff_samples = chi_eff(
920 samples[0], samples[1], samples[2], samples[3])
921 self.append_data("chi_eff{}".format(suffix), chi_eff_samples)
923 def _aligned_spin_from_magnitude_tilts(
924 self, primary=False, secondary=False, suffix=""
925 ):
926 if primary:
927 parameters = ["a_1", "tilt_1{}".format(suffix)]
928 param_to_add = "spin_1z{}".format(suffix)
929 elif secondary:
930 parameters = ["a_2", "tilt_2{}".format(suffix)]
931 param_to_add = "spin_2z{}".format(suffix)
932 samples = self.specific_parameter_samples(parameters)
933 spin_samples = samples[0] * np.cos(samples[1])
934 self.append_data(param_to_add, spin_samples)
936 def _cos_tilt_1_from_tilt_1(self):
937 samples = self.specific_parameter_samples("tilt_1")
938 cos_tilt_1 = np.cos(samples)
939 self.append_data("cos_tilt_1", cos_tilt_1)
941 def _cos_tilt_2_from_tilt_2(self):
942 samples = self.specific_parameter_samples("tilt_2")
943 cos_tilt_2 = np.cos(samples)
944 self.append_data("cos_tilt_2", cos_tilt_2)
946 def _viewing_angle(self):
947 samples = self.specific_parameter_samples("theta_jn")
948 viewing_angle = viewing_angle_from_inclination(samples)
949 self.append_data("viewing_angle", viewing_angle)
951 def _dL_from_z(self):
952 samples = self.specific_parameter_samples("redshift")
953 distance = dL_from_z(samples, cosmology=self.cosmology)
954 self.extra_kwargs["meta_data"]["cosmology"] = self.cosmology
955 self.append_data("luminosity_distance", distance)
957 def _z_from_dL(self):
958 samples = self.specific_parameter_samples("luminosity_distance")
959 func = getattr(Redshift.Distance, self.redshift_method)
960 redshift = func(
961 samples, cosmology=self.cosmology, multi_process=self.multi_process
962 )
963 self.extra_kwargs["meta_data"]["cosmology"] = self.cosmology
964 self.append_data("redshift", redshift)
966 def _z_from_comoving_volume(self):
967 samples = self.specific_parameter_samples("comoving_volume")
968 func = getattr(Redshift.ComovingVolume, self.redshift_method)
969 redshift = func(
970 samples, cosmology=self.cosmology, multi_process=self.multi_process
971 )
972 self.extra_kwargs["meta_data"]["cosmology"] = self.cosmology
973 self.append_data("redshift", redshift)
975 def _comoving_distance_from_z(self):
976 samples = self.specific_parameter_samples("redshift")
977 distance = comoving_distance_from_z(samples, cosmology=self.cosmology)
978 self.extra_kwargs["meta_data"]["cosmology"] = self.cosmology
979 self.append_data("comoving_distance", distance)
981 def _comoving_volume_from_z(self):
982 samples = self.specific_parameter_samples("redshift")
983 volume = comoving_volume_from_z(samples, cosmology=self.cosmology)
984 self.extra_kwargs["meta_data"]["cosmology"] = self.cosmology
985 self.append_data("comoving_volume", volume)
987 def _m1_source_from_m1_z(self):
988 samples = self.specific_parameter_samples(["mass_1", "redshift"])
989 mass_1_source = m1_source_from_m1_z(samples[0], samples[1])
990 self.append_data("mass_1_source", mass_1_source)
992 def _m1_from_m1_source_z(self):
993 samples = self.specific_parameter_samples(["mass_1_source", "redshift"])
994 mass_1 = m1_from_m1_source_z(samples[0], samples[1])
995 self.append_data("mass_1", mass_1)
997 def _m2_source_from_m2_z(self):
998 samples = self.specific_parameter_samples(["mass_2", "redshift"])
999 mass_2_source = m2_source_from_m2_z(samples[0], samples[1])
1000 self.append_data("mass_2_source", mass_2_source)
1002 def _m2_from_m2_source_z(self):
1003 samples = self.specific_parameter_samples(["mass_2_source", "redshift"])
1004 mass_2 = m2_from_m2_source_z(samples[0], samples[1])
1005 self.append_data("mass_2", mass_2)
1007 def _mtotal_source_from_mtotal_z(self):
1008 samples = self.specific_parameter_samples(["total_mass", "redshift"])
1009 total_mass_source = m_total_source_from_mtotal_z(samples[0], samples[1])
1010 self.append_data("total_mass_source", total_mass_source)
1012 def _mtotal_from_mtotal_source_z(self):
1013 samples = self.specific_parameter_samples(["total_mass_source", "redshift"])
1014 total_mass = mtotal_from_mtotal_source_z(samples[0], samples[1])
1015 self.append_data("total_mass", total_mass)
1017 def _mchirp_source_from_mchirp_z(self):
1018 samples = self.specific_parameter_samples(["chirp_mass", "redshift"])
1019 chirp_mass_source = mchirp_source_from_mchirp_z(samples[0], samples[1])
1020 self.append_data("chirp_mass_source", chirp_mass_source)
1022 def _beta(self):
1023 samples = self.specific_parameter_samples([
1024 "mass_1", "mass_2", "phi_jl", "tilt_1", "tilt_2", "phi_12",
1025 "a_1", "a_2", "reference_frequency", "phase"
1026 ])
1027 beta = opening_angle(
1028 samples[0], samples[1], samples[2], samples[3], samples[4],
1029 samples[5], samples[6], samples[7], samples[8], samples[9]
1030 )
1031 self.append_data("beta", beta)
1033 def _psi_J(self):
1034 samples = self.specific_parameter_samples([
1035 "psi", "theta_jn", "phi_jl", "beta"
1036 ])
1037 psi = psi_J(samples[0], samples[1], samples[2], samples[3])
1038 self.append_data("psi_J", psi)
1040 def _retrieve_detectors(self):
1041 detectors = []
1042 if "IFOs" in list(self.extra_kwargs["meta_data"].keys()):
1043 detectors = self.extra_kwargs["meta_data"]["IFOs"].split(" ")
1044 else:
1045 for i in self.parameters:
1046 if "optimal_snr" in i and i != "network_optimal_snr":
1047 det = i.split("_optimal_snr")[0]
1048 detectors.append(det)
1049 return detectors
1051 def _time_in_each_ifo(self):
1052 detectors = self._retrieve_detectors()
1053 samples = self.specific_parameter_samples(["ra", "dec", "geocent_time"])
1054 for i in detectors:
1055 time = time_in_each_ifo(i, samples[0], samples[1], samples[2])
1056 self.append_data("%s_time" % (i), time)
1058 def _time_delay_between_ifos(self):
1059 from itertools import combinations
1060 detectors = sorted(self._retrieve_detectors())
1061 for ifos in combinations(detectors, 2):
1062 samples = self.specific_parameter_samples(
1063 ["{}_time".format(_ifo) for _ifo in ifos]
1064 )
1065 self.append_data(
1066 "%s_%s_time_delay" % (ifos[0], ifos[1]),
1067 samples[1] - samples[0]
1068 )
1070 def _lambda1_from_lambda_tilde(self):
1071 samples = self.specific_parameter_samples([
1072 "lambda_tilde", "mass_1", "mass_2"])
1073 lambda_1 = lambda1_from_lambda_tilde(samples[0], samples[1], samples[2])
1074 self.append_data("lambda_1", lambda_1)
1076 def _lambda2_from_lambda1(self):
1077 samples = self.specific_parameter_samples([
1078 "lambda_1", "mass_1", "mass_2"])
1079 lambda_2 = lambda2_from_lambda1(samples[0], samples[1], samples[2])
1080 self.append_data("lambda_2", lambda_2)
1082 def _lambda_tilde_from_lambda1_lambda2(self):
1083 samples = self.specific_parameter_samples([
1084 "lambda_1", "lambda_2", "mass_1", "mass_2"])
1085 lambda_tilde = lambda_tilde_from_lambda1_lambda2(
1086 samples[0], samples[1], samples[2], samples[3])
1087 self.append_data("lambda_tilde", lambda_tilde)
1089 def _delta_lambda_from_lambda1_lambda2(self):
1090 samples = self.specific_parameter_samples([
1091 "lambda_1", "lambda_2", "mass_1", "mass_2"])
1092 delta_lambda = delta_lambda_from_lambda1_lambda2(
1093 samples[0], samples[1], samples[2], samples[3])
1094 self.append_data("delta_lambda", delta_lambda)
1096 def _NS_compactness_from_lambda(self, parameter="lambda_1"):
1097 if parameter not in ["lambda_1", "lambda_2"]:
1098 logger.warning(
1099 "Can only use Love-compactness relation for 'lambda_1' and/or "
1100 "'lambda_2'. Skipping conversion"
1101 )
1102 return
1103 ind = parameter.split("lambda_")[1]
1104 samples = self.specific_parameter_samples([parameter])
1105 compactness = NS_compactness_from_lambda(samples[0])
1106 self.append_data("compactness_{}".format(ind), compactness)
1107 self.extra_kwargs["meta_data"]["compactness_fits"] = (
1108 "YagiYunes2017_with_BBHlimit"
1109 )
1111 def _NS_baryonic_mass(self, primary=True):
1112 if primary:
1113 params = ["compactness_1", "mass_1"]
1114 else:
1115 params = ["compactness_2", "mass_2"]
1116 samples = self.specific_parameter_samples(params)
1117 mass = NS_baryonic_mass(samples[0], samples[1])
1118 if primary:
1119 self.append_data("baryonic_mass_1", mass)
1120 else:
1121 self.append_data("baryonic_mass_2", mass)
1122 self.extra_kwargs["meta_data"]["baryonic_mass_fits"] = "Breu2016"
1124 def _lambda1_lambda2_from_polytrope_EOS(self):
1125 samples = self.specific_parameter_samples([
1126 "log_pressure", "gamma_1", "gamma_2", "gamma_3", "mass_1", "mass_2"
1127 ])
1128 lambda_1, lambda_2 = \
1129 lambda1_lambda2_from_4_parameter_piecewise_polytrope_equation_of_state(
1130 *samples, multi_process=self.multi_process
1131 )
1132 if "lambda_1" not in self.parameters:
1133 self.append_data("lambda_1", lambda_1)
1134 if "lambda_2" not in self.parameters:
1135 self.append_data("lambda_2", lambda_2)
1137 def _lambda1_lambda2_from_spectral_decomposition_EOS(self):
1138 samples = self.specific_parameter_samples([
1139 "spectral_decomposition_gamma_0", "spectral_decomposition_gamma_1",
1140 "spectral_decomposition_gamma_2", "spectral_decomposition_gamma_3",
1141 "mass_1", "mass_2"
1142 ])
1143 lambda_1, lambda_2 = lambda1_lambda2_from_spectral_decomposition(
1144 *samples, multi_process=self.multi_process
1145 )
1146 if "lambda_1" not in self.parameters:
1147 self.append_data("lambda_1", lambda_1)
1148 if "lambda_2" not in self.parameters:
1149 self.append_data("lambda_2", lambda_2)
1151 def _ifo_snr(self):
1152 abs_snrs = [
1153 i for i in self.parameters if "_matched_filter_abs_snr" in i
1154 ]
1155 angle_snrs = [
1156 i for i in self.parameters if "_matched_filter_snr_angle" in i
1157 ]
1158 for ifo in [snr.split("_matched_filter_abs_snr")[0] for snr in abs_snrs]:
1159 if "{}_matched_filter_snr".format(ifo) not in self.parameters:
1160 samples = self.specific_parameter_samples(
1161 [
1162 "{}_matched_filter_abs_snr".format(ifo),
1163 "{}_matched_filter_snr_angle".format(ifo)
1164 ]
1165 )
1166 snr = _ifo_snr(samples[0], samples[1])
1167 self.append_data("{}_matched_filter_snr".format(ifo), snr)
1169 def _optimal_network_snr(self):
1170 snrs = [i for i in self.parameters if "_optimal_snr" in i]
1171 samples = self.specific_parameter_samples(snrs)
1172 snr = network_snr(samples)
1173 self.append_data("network_optimal_snr", snr)
1175 def _matched_filter_network_snr(self):
1176 mf_snrs = sorted([
1177 i for i in self.parameters if "_matched_filter_snr" in i
1178 and "_angle" not in i and "_abs" not in i
1179 ])
1180 opt_snrs = sorted([
1181 i for i in self.parameters if "_optimal_snr" in i and "network" not
1182 in i
1183 ])
1184 _mf_detectors = [
1185 param.split("_matched_filter_snr")[0] for param in mf_snrs
1186 ]
1187 _opt_detectors = [
1188 param.split("_optimal_snr")[0] for param in opt_snrs
1189 ]
1190 if _mf_detectors == _opt_detectors:
1191 mf_samples = self.specific_parameter_samples(mf_snrs)
1192 opt_samples = self.specific_parameter_samples(opt_snrs)
1193 snr = network_matched_filter_snr(mf_samples, opt_samples)
1194 self.append_data("network_matched_filter_snr", snr)
1195 else:
1196 logger.warning(
1197 "Unable to generate 'network_matched_filter_snr' as "
1198 "there is an inconsistency in the detector network based on "
1199 "the 'optimal_snrs' and the 'matched_filter_snrs'. We find "
1200 "that from the 'optimal_snrs', the detector network is: {} "
1201 "while we find from the 'matched_filter_snrs', the detector "
1202 "network is: {}".format(_opt_detectors, _mf_detectors)
1203 )
1205 def _rho_hm(self, multipoles):
1206 import copy
1207 required = [
1208 "mass_1", "mass_2", "spin_1z", "spin_2z", "psi", "iota", "ra",
1209 "dec", "geocent_time", "luminosity_distance", "phase",
1210 "reference_frequency"
1211 ]
1212 samples = self.specific_parameter_samples(required)
1213 _f_low = self._retrieve_stored_frequency("f_low")
1214 if isinstance(_f_low, (np.ndarray)):
1215 f_low = _f_low() * len(samples[0])
1216 else:
1217 f_low = [_f_low] * len(samples[0])
1218 original_list = copy.deepcopy(multipoles)
1219 rho, data_used = multipole_snr(
1220 *samples[:-1], f_low=f_low, psd=self.psd, f_ref=samples[-1],
1221 f_final=self.extra_kwargs["meta_data"]["f_final"],
1222 return_data_used=True, multi_process=self.multi_process,
1223 psd_default=self.psd_default, multipole=multipoles,
1224 df=self.extra_kwargs["meta_data"]["delta_f"]
1225 )
1226 for num, mm in enumerate(original_list):
1227 self.append_data("network_{}_multipole_snr".format(mm), rho[num])
1228 self.extra_kwargs["meta_data"]["multipole_snr"] = data_used
1230 def _rho_p(self):
1231 required = [
1232 "mass_1", "mass_2", "beta", "psi_J", "a_1", "a_2", "tilt_1",
1233 "tilt_2", "phi_12", "theta_jn", "ra", "dec", "geocent_time",
1234 "phi_jl", "reference_frequency", "luminosity_distance", "phase"
1235 ]
1236 samples = self.specific_parameter_samples(required)
1237 try:
1238 spins = self.specific_parameter_samples(["spin_1z", "spin_2z"])
1239 except ValueError:
1240 spins = [None, None]
1241 _f_low = self._retrieve_stored_frequency("f_low")
1242 if isinstance(_f_low, (np.ndarray)):
1243 f_low = _f_low() * len(samples[0])
1244 else:
1245 f_low = [_f_low] * len(samples[0])
1246 [rho_p, b_bar, overlap, snrs], data_used = precessing_snr(
1247 samples[0], samples[1], samples[2], samples[3], samples[4],
1248 samples[5], samples[6], samples[7], samples[8], samples[9], samples[10],
1249 samples[11], samples[12], samples[13], samples[15], samples[16], f_low=f_low,
1250 spin_1z=spins[0], spin_2z=spins[1], psd=self.psd, return_data_used=True,
1251 f_final=self.extra_kwargs["meta_data"]["f_final"], f_ref=samples[14],
1252 multi_process=self.multi_process, psd_default=self.psd_default,
1253 df=self.extra_kwargs["meta_data"]["delta_f"], debug=True
1254 )
1255 self.append_data("network_precessing_snr", rho_p)
1256 self.append_data("_b_bar", b_bar)
1257 self.append_data("_precessing_harmonics_overlap", overlap)
1258 nbreakdown = len(np.argwhere(b_bar > 0.3))
1259 if nbreakdown > 0:
1260 logger.warning(
1261 "{}/{} ({}%) samples have b_bar greater than 0.3. For these "
1262 "samples, the two-harmonic approximation used to calculate "
1263 "the precession SNR may not be valid".format(
1264 nbreakdown, len(b_bar),
1265 np.round((nbreakdown / len(b_bar)) * 100, 2)
1266 )
1267 )
1268 try:
1269 _samples = self.specific_parameter_samples("network_optimal_snr")
1270 if np.logical_or(
1271 np.median(snrs) > 1.1 * np.median(_samples),
1272 np.median(snrs) < 0.9 * np.median(_samples)
1273 ):
1274 logger.warning(
1275 "The two-harmonic SNR is different from the stored SNR. "
1276 "This indicates that the provided PSD may be different "
1277 "from the one used in the sampling."
1278 )
1279 except Exception:
1280 pass
1281 self.extra_kwargs["meta_data"]["precessing_snr"] = data_used
1283 def _retrieve_stored_frequency(self, name):
1284 extra_kwargs = self.extra_kwargs["meta_data"]
1285 if extra_kwargs != {} and name in list(extra_kwargs.keys()):
1286 freq = extra_kwargs[name]
1287 else:
1288 raise ValueError(
1289 "Could not find f_low in input file. Please either modify the "
1290 "input file or pass it from the command line"
1291 )
1292 return freq
1294 def _retrieve_approximant(self):
1295 extra_kwargs = self.extra_kwargs["meta_data"]
1296 if extra_kwargs != {} and "approximant" in list(extra_kwargs.keys()):
1297 approximant = extra_kwargs["approximant"]
1298 else:
1299 raise ValueError(
1300 "Unable to find the approximant used to generate the posterior "
1301 "samples in the result file."
1302 )
1303 return approximant
1305 def _evolve_spins(self, final_velocity="ISCO", forward=True):
1306 from .evolve import evolve_spins
1307 from ..waveform import _check_approximant_from_string
1309 samples = self.specific_parameter_samples(
1310 ["mass_1", "mass_2", "a_1", "a_2", "tilt_1", "tilt_2",
1311 "phi_12", "reference_frequency"]
1312 )
1313 approximant = self._retrieve_approximant()
1314 if not _check_approximant_from_string(approximant):
1315 _msg = (
1316 'Not evolving spins: approximant {0} unknown to '
1317 'lalsimulation and gwsignal'.format(approximant)
1318 )
1319 logger.warning(_msg)
1320 raise EvolveSpinError(_msg)
1321 f_low = self._retrieve_stored_frequency("f_low")
1322 if not forward:
1323 [tilt_1_evolved, tilt_2_evolved, phi_12_evolved], fits_used = evolve_spins(
1324 samples[0], samples[1], samples[2], samples[3], samples[4],
1325 samples[5], samples[6], f_low, samples[7][0],
1326 approximant, evolve_limit="infinite_separation",
1327 multi_process=self.multi_process, return_fits_used=True,
1328 method=self.evolve_spins_backwards
1329 )
1330 suffix = ""
1331 if self.evolve_spins_backwards.lower() == "precession_averaged":
1332 suffix = "_only_prec_avg"
1333 self.append_data("tilt_1_infinity{}".format(suffix), tilt_1_evolved)
1334 self.append_data("tilt_2_infinity{}".format(suffix), tilt_2_evolved)
1335 self.extra_kwargs["meta_data"]["backward_spin_evolution"] = fits_used
1336 return
1337 else:
1338 tilt_1_evolved, tilt_2_evolved, phi_12_evolved = evolve_spins(
1339 samples[0], samples[1], samples[2], samples[3], samples[4],
1340 samples[5], samples[6], f_low, samples[7][0],
1341 approximant, final_velocity=final_velocity,
1342 multi_process=self.multi_process
1343 )
1344 self.extra_kwargs["meta_data"]["forward_spin_evolution"] = final_velocity
1345 spin_1z_evolved = samples[2] * np.cos(tilt_1_evolved)
1346 spin_2z_evolved = samples[3] * np.cos(tilt_2_evolved)
1347 self.append_data("tilt_1_evolved", tilt_1_evolved)
1348 self.append_data("tilt_2_evolved", tilt_2_evolved)
1349 self.append_data("phi_12_evolved", phi_12_evolved)
1350 self.append_data("spin_1z_evolved", spin_1z_evolved)
1351 self.append_data("spin_2z_evolved", spin_2z_evolved)
1353 @staticmethod
1354 def _evolved_vs_non_evolved_parameter(
1355 parameter, evolved=False, core_param=False, non_precessing=False
1356 ):
1357 if non_precessing:
1358 base_string = ""
1359 elif evolved and core_param:
1360 base_string = "_evolved"
1361 elif evolved:
1362 base_string = ""
1363 elif core_param:
1364 base_string = ""
1365 else:
1366 base_string = "_non_evolved"
1367 return "{}{}".format(parameter, base_string)
1369 def _precessing_vs_non_precessing_parameters(
1370 self, non_precessing=False, evolved=False
1371 ):
1372 if not non_precessing:
1373 tilt_1 = self._evolved_vs_non_evolved_parameter(
1374 "tilt_1", evolved=evolved, core_param=True
1375 )
1376 tilt_2 = self._evolved_vs_non_evolved_parameter(
1377 "tilt_2", evolved=evolved, core_param=True
1378 )
1379 samples = self.specific_parameter_samples([
1380 "mass_1", "mass_2", "a_1", "a_2", tilt_1, tilt_2
1381 ])
1382 if "phi_12" in self.parameters and evolved:
1383 phi_12_samples = self.specific_parameter_samples([
1384 self._evolved_vs_non_evolved_parameter(
1385 "phi_12", evolved=True, core_param=True
1386 )
1387 ])[0]
1388 elif "phi_12" in self.parameters:
1389 phi_12_samples = self.specific_parameter_samples(["phi_12"])[0]
1390 else:
1391 phi_12_samples = np.zeros_like(samples[0])
1392 samples.append(phi_12_samples)
1393 if self.NRSurrogate:
1394 NRSurrogate_samples = self.specific_parameter_samples([
1395 "phi_jl", "theta_jn", "phase"
1396 ])
1397 for ss in NRSurrogate_samples:
1398 samples.append(ss)
1399 else:
1400 spin_1z = self._evolved_vs_non_evolved_parameter(
1401 "spin_1z", evolved=evolved, core_param=True, non_precessing=True
1402 )
1403 spin_2z = self._evolved_vs_non_evolved_parameter(
1404 "spin_2z", evolved=evolved, core_param=True, non_precessing=True
1405 )
1406 samples = self.specific_parameter_samples([
1407 "mass_1", "mass_2", spin_1z, spin_2z
1408 ])
1409 samples = [
1410 samples[0], samples[1], np.abs(samples[2]), np.abs(samples[3]),
1411 0.5 * np.pi * (1 - np.sign(samples[2])),
1412 0.5 * np.pi * (1 - np.sign(samples[3])),
1413 np.zeros_like(samples[0])
1414 ]
1415 return samples
1417 def _peak_luminosity_of_merger(self, evolved=False):
1418 param = self._evolved_vs_non_evolved_parameter(
1419 "peak_luminosity", evolved=evolved, non_precessing=self.non_precessing
1420 )
1421 spin_1z_param = self._evolved_vs_non_evolved_parameter(
1422 "spin_1z", evolved=evolved, core_param=True,
1423 non_precessing=self.non_precessing
1424 )
1425 spin_2z_param = self._evolved_vs_non_evolved_parameter(
1426 "spin_2z", evolved=evolved, core_param=True,
1427 non_precessing=self.non_precessing
1428 )
1430 samples = self.specific_parameter_samples([
1431 "mass_1", "mass_2", spin_1z_param, spin_2z_param
1432 ])
1433 peak_luminosity, fits = peak_luminosity_of_merger(
1434 samples[0], samples[1], samples[2], samples[3],
1435 return_fits_used=True
1436 )
1437 self.append_data(param, peak_luminosity)
1438 self.extra_kwargs["meta_data"]["peak_luminosity_NR_fits"] = fits
1440 def _final_remnant_properties_from_NRSurrogate(
1441 self, non_precessing=False,
1442 parameters=["final_mass", "final_spin", "final_kick"]
1443 ):
1444 f_low = self._retrieve_stored_frequency("f_start")
1445 approximant = self._retrieve_approximant()
1446 samples = self._precessing_vs_non_precessing_parameters(
1447 non_precessing=non_precessing, evolved=False
1448 )
1449 frequency_samples = self.specific_parameter_samples([
1450 "reference_frequency"
1451 ])
1452 data, fits = final_remnant_properties_from_NRSurrogate(
1453 *samples, f_low=f_low, f_ref=frequency_samples[0],
1454 properties=parameters, return_fits_used=True,
1455 approximant=approximant
1456 )
1457 for param in parameters:
1458 self.append_data(param, data[param])
1459 self.extra_kwargs["meta_data"]["{}_NR_fits".format(param)] = fits
1461 def _final_remnant_properties_from_NSBH_waveform(
1462 self, source=False, parameters=[
1463 "baryonic_torus_mass", "final_mass", "final_spin"
1464 ]
1465 ):
1466 approximant = self._retrieve_approximant()
1467 if source:
1468 sample_params = [
1469 "mass_1_source", "mass_2_source", "spin_1z", "lambda_2"
1470 ]
1471 else:
1472 sample_params = ["mass_1", "mass_2", "spin_1z", "lambda_2"]
1473 samples = self.specific_parameter_samples(sample_params)
1474 _data = _check_NSBH_approximant(
1475 approximant, samples[0], samples[1], samples[2], samples[3],
1476 _raise=False
1477 )
1478 if _data is None:
1479 return
1480 _mapping = {
1481 "220_quasinormal_mode_frequency": 0, "tidal_disruption_frequency": 1,
1482 "baryonic_torus_mass": 2, "compactness_2": 3,
1483 "final_mass": 4, "final_spin": 5
1484 }
1485 for param in parameters:
1486 self.append_data(param, _data[_mapping[param]])
1487 if "final_mass" in parameters:
1488 self.extra_kwargs["meta_data"]["final_mass_NR_fits"] = "Zappa2019"
1489 if "final_spin" in parameters:
1490 self.extra_kwargs["meta_data"]["final_spin_NR_fits"] = "Zappa2019"
1491 if "baryonic_torus_mass" in parameters:
1492 self.extra_kwargs["meta_data"]["baryonic_torus_mass_fits"] = (
1493 "Foucart2012"
1494 )
1495 if "220_quasinormal_mode_frequency" in parameters:
1496 self.extra_kwargs["meta_data"]["quasinormal_mode_fits"] = (
1497 "London2019"
1498 )
1499 if "tidal_disruption_frequency" in parameters:
1500 probabilities = NSBH_merger_type(
1501 samples[0], samples[1], samples[2], samples[3],
1502 approximant=approximant,
1503 _ringdown=_data[_mapping["220_quasinormal_mode_frequency"]],
1504 _disruption=_data[_mapping["tidal_disruption_frequency"]],
1505 _torus=_data[_mapping["baryonic_torus_mass"]], percentages=True
1506 )
1507 self.extra_kwargs["meta_data"]["NSBH_merger_type_probabilities"] = (
1508 probabilities
1509 )
1510 self.extra_kwargs["meta_data"]["tidal_disruption_frequency_fits"] = (
1511 "Pannarale2018"
1512 )
1513 ratio = (
1514 _data[_mapping["tidal_disruption_frequency"]]
1515 / _data[_mapping["220_quasinormal_mode_frequency"]]
1516 )
1517 self.append_data(
1518 "tidal_disruption_frequency_ratio", ratio
1519 )
1521 def _final_remnant_properties_from_waveform(
1522 self, non_precessing=False, parameters=["final_mass", "final_spin"],
1523 ):
1524 f_low = self._retrieve_stored_frequency("f_start")
1525 approximant = self._retrieve_approximant()
1526 if "delta_t" in self.extra_kwargs["meta_data"].keys():
1527 delta_t = self.extra_kwargs["meta_data"]["delta_t"]
1528 else:
1529 delta_t = 1. / 4096
1530 if "seob" in approximant.lower():
1531 logger.warning(
1532 "Could not find 'delta_t' in the meta data. Using {} as "
1533 "default.".format(delta_t)
1534 )
1535 if non_precessing:
1536 sample_params = [
1537 "mass_1", "mass_2", "empty", "empty", "spin_1z", "empty",
1538 "empty", "spin_2z", "iota", "luminosity_distance",
1539 "phase"
1540 ]
1541 else:
1542 sample_params = [
1543 "mass_1", "mass_2", "spin_1x", "spin_1y", "spin_1z",
1544 "spin_2x", "spin_2y", "spin_2z", "iota", "luminosity_distance",
1545 "phase", "reference_frequency"
1546 ]
1547 samples = self.specific_parameter_samples(sample_params)
1548 ind = self.parameters.index("spin_1x")
1549 _data, fits = _final_from_initial_BBH(
1550 *samples[:8], iota=samples[8], luminosity_distance=samples[9],
1551 f_low=[f_low] * len(samples[0]), f_ref=samples[-1],
1552 phi_ref=samples[10], delta_t=1. / 4096, approximant=approximant,
1553 xphm_flags=self.extra_kwargs["meta_data"].get("approximant_flags", {}),
1554 return_fits_used=True, multi_process=self.multi_process
1555 )
1556 data = {"final_mass": _data[0], "final_spin": _data[1]}
1557 for param in parameters:
1558 self.append_data(param, data[param])
1559 self.extra_kwargs["meta_data"]["{}_NR_fits".format(param)] = fits
1561 def _final_mass_of_merger(self, evolved=False):
1562 param = self._evolved_vs_non_evolved_parameter(
1563 "final_mass", evolved=evolved, non_precessing=self.non_precessing
1564 )
1565 spin_1z_param = self._evolved_vs_non_evolved_parameter(
1566 "spin_1z", evolved=evolved, core_param=True,
1567 non_precessing=self.non_precessing
1568 )
1569 spin_2z_param = self._evolved_vs_non_evolved_parameter(
1570 "spin_2z", evolved=evolved, core_param=True,
1571 non_precessing=self.non_precessing
1572 )
1573 samples = self.specific_parameter_samples([
1574 "mass_1", "mass_2", spin_1z_param, spin_2z_param
1575 ])
1576 final_mass, fits = final_mass_of_merger(
1577 *samples, return_fits_used=True,
1578 )
1579 self.append_data(param, final_mass)
1580 self.extra_kwargs["meta_data"]["final_mass_NR_fits"] = fits
1582 def _final_mass_source(self, evolved=False):
1583 param = self._evolved_vs_non_evolved_parameter(
1584 "final_mass", evolved=evolved, non_precessing=self.non_precessing
1585 )
1586 samples = self.specific_parameter_samples([param, "redshift"])
1587 final_mass_source = _source_from_detector(
1588 samples[0], samples[1]
1589 )
1590 self.append_data(param.replace("mass", "mass_source"), final_mass_source)
1592 def _final_spin_of_merger(self, non_precessing=False, evolved=False):
1593 param = self._evolved_vs_non_evolved_parameter(
1594 "final_spin", evolved=evolved, non_precessing=self.non_precessing
1595 )
1596 samples = self._precessing_vs_non_precessing_parameters(
1597 non_precessing=non_precessing, evolved=evolved
1598 )
1599 final_spin, fits = final_spin_of_merger(
1600 *samples, return_fits_used=True,
1601 )
1602 self.append_data(param, final_spin)
1603 self.extra_kwargs["meta_data"]["final_spin_NR_fits"] = fits
1605 def _radiated_energy(self, evolved=False):
1606 param = self._evolved_vs_non_evolved_parameter(
1607 "radiated_energy", evolved=evolved, non_precessing=self.non_precessing
1608 )
1609 final_mass_param = self._evolved_vs_non_evolved_parameter(
1610 "final_mass_source", evolved=evolved, non_precessing=self.non_precessing
1611 )
1612 samples = self.specific_parameter_samples([
1613 "total_mass_source", final_mass_param
1614 ])
1615 radiated_energy = samples[0] - samples[1]
1616 self.append_data(param, radiated_energy)
1618 def _exp(self, parameter_to_add):
1619 samples = self.specific_parameter_samples(
1620 ["log_" + parameter_to_add]
1621 )
1622 exp_samples = np.exp(samples[0])
1623 self.append_data(parameter_to_add, exp_samples)
1625 def _cos_angle(self, parameter_to_add, reverse=False):
1626 if reverse:
1627 samples = self.specific_parameter_samples(
1628 ["cos_" + parameter_to_add])
1629 cos_samples = np.arccos(samples[0])
1630 else:
1631 samples = self.specific_parameter_samples(
1632 [parameter_to_add.split("cos_")[1]]
1633 )
1634 cos_samples = np.cos(samples[0])
1635 self.append_data(parameter_to_add, cos_samples)
1637 def source_frame_from_detector_frame(self, detector_frame_parameter):
1638 samples = self.specific_parameter_samples(
1639 [detector_frame_parameter, "redshift"]
1640 )
1641 source_frame = _source_from_detector(samples[0], samples[1])
1642 self.append_data(
1643 "{}_source".format(detector_frame_parameter), source_frame
1644 )
1646 def _check_parameters(self):
1647 params = ["mass_1", "mass_2", "a_1", "a_2", "mass_1_source", "mass_2_source",
1648 "mass_ratio", "total_mass", "chirp_mass"]
1649 for i in params:
1650 if i in self.parameters:
1651 samples = self.specific_parameter_samples([i])
1652 if "mass" in i:
1653 cond = any(np.array(samples[0]) <= 0.)
1654 else:
1655 cond = any(np.array(samples[0]) < 0.)
1656 if cond:
1657 if "mass" in i:
1658 ind = np.argwhere(np.array(samples[0]) <= 0.)
1659 else:
1660 ind = np.argwhere(np.array(samples[0]) < 0.)
1661 logger.warning(
1662 "Removing %s samples because they have unphysical "
1663 "values (%s < 0)" % (len(ind), i)
1664 )
1665 for i in np.arange(len(ind) - 1, -1, -1):
1666 self.samples.remove(list(np.array(self.samples)[ind[i][0]]))
1668 def generate_all_posterior_samples(self):
1669 logger.debug("Starting to generate all derived posteriors")
1670 evolve_condition = (
1671 True if self.evolve_spins_forwards and self.compute_remnant else False
1672 )
1673 if "log_luminosity_distance" in self.parameters and "luminosity_distance" not in self.parameters:
1674 self._exp("luminosity_distance")
1675 if "cos_theta_jn" in self.parameters and "theta_jn" not in self.parameters:
1676 self._cos_angle("theta_jn", reverse=True)
1677 if "cos_iota" in self.parameters and "iota" not in self.parameters:
1678 self._cos_angle("iota", reverse=True)
1679 if "cos_tilt_1" in self.parameters and "tilt_1" not in self.parameters:
1680 self._cos_angle("tilt_1", reverse=True)
1681 if "cos_tilt_2" in self.parameters and "tilt_2" not in self.parameters:
1682 self._cos_angle("tilt_2", reverse=True)
1683 angles = [
1684 "a_1", "a_2", "a_1_azimuthal", "a_1_polar", "a_2_azimuthal",
1685 "a_2_polar"
1686 ]
1687 if all(i in self.parameters for i in angles):
1688 self._component_spins_from_azimuthal_and_polar_angles()
1689 spin_magnitudes = ["a_1", "a_2"]
1690 angles = ["phi_jl", "tilt_1", "tilt_2", "phi_12"]
1691 cartesian = ["spin_1x", "spin_1y", "spin_1z", "spin_2x", "spin_2y", "spin_2z"]
1692 cond1 = all(i in self.parameters for i in spin_magnitudes)
1693 cond2 = all(i in self.parameters for i in angles)
1694 cond3 = all(i in self.parameters for i in cartesian)
1695 for _param in spin_magnitudes:
1696 if _param in self.parameters and not cond2 and not cond3:
1697 _index = _param.split("a_")[1]
1698 _spin = self.specific_parameter_samples(_param)
1699 _tilt = np.arccos(np.sign(_spin))
1700 self.append_data("tilt_{}".format(_index), _tilt)
1701 _spin_ind = self.parameters.index(_param)
1702 for num, i in enumerate(self.samples):
1703 self.samples[num][_spin_ind] = abs(self.samples[num][_spin_ind])
1705 if not cond2 and not cond3 and self.add_zero_spin:
1706 for _param in spin_magnitudes:
1707 if _param not in self.parameters:
1708 _spin = np.zeros(len(self.samples))
1709 self.append_data(_param, _spin)
1710 _index = _param.split("a_")[1]
1711 self.append_data("spin_{}z".format(_index), _spin)
1712 self._check_parameters()
1713 if "cos_theta_jn" in self.parameters and "theta_jn" not in self.parameters:
1714 self._cos_angle("theta_jn", reverse=True)
1715 if "cos_iota" in self.parameters and "iota" not in self.parameters:
1716 self._cos_angle("iota", reverse=True)
1717 if "cos_tilt_1" in self.parameters and "tilt_1" not in self.parameters:
1718 self._cos_angle("tilt_1", reverse=True)
1719 if "cos_tilt_2" in self.parameters and "tilt_2" not in self.parameters:
1720 self._cos_angle("tilt_2", reverse=True)
1721 if "luminosity_distance" not in self.parameters:
1722 if "redshift" in self.parameters:
1723 self._dL_from_z()
1724 if "redshift" not in self.parameters:
1725 if "luminosity_distance" in self.parameters:
1726 self._z_from_dL()
1727 if "comoving_volume" in self.parameters:
1728 self._z_from_comoving_volume()
1729 if "comoving_distance" not in self.parameters:
1730 if "redshift" in self.parameters:
1731 self._comoving_distance_from_z()
1732 if "comoving_volume" not in self.parameters:
1733 if "redshift" in self.parameters:
1734 self._comoving_volume_from_z()
1736 if "mass_ratio" not in self.parameters and "symmetric_mass_ratio" in \
1737 self.parameters:
1738 self._q_from_eta()
1739 if "mass_ratio" not in self.parameters and "mass_1" in self.parameters \
1740 and "mass_2" in self.parameters:
1741 self._q_from_m1_m2()
1742 if "mass_ratio" in self.parameters:
1743 ind = self.parameters.index("mass_ratio")
1744 median = np.median([i[ind] for i in self.samples])
1745 if median > 1.:
1746 self._invert_q()
1747 if "inverted_mass_ratio" not in self.parameters and "mass_ratio" in \
1748 self.parameters:
1749 self._invq_from_q()
1750 if "chirp_mass" not in self.parameters and "total_mass" in self.parameters:
1751 self._mchirp_from_mtotal_q()
1752 if "mass_1" not in self.parameters and "chirp_mass" in self.parameters:
1753 self._m1_from_mchirp_q()
1754 if "mass_2" not in self.parameters and "chirp_mass" in self.parameters:
1755 self._m2_from_mchirp_q()
1756 if "mass_1" not in self.parameters and "total_mass" in self.parameters:
1757 self._m1_from_mtotal_q()
1758 if "mass_2" not in self.parameters and "total_mass" in self.parameters:
1759 self._m2_from_mtotal_q()
1760 if "mass_1" in self.parameters and "mass_2" in self.parameters:
1761 if "total_mass" not in self.parameters:
1762 self._mtotal_from_m1_m2()
1763 if "chirp_mass" not in self.parameters:
1764 self._mchirp_from_m1_m2()
1765 if "symmetric_mass_ratio" not in self.parameters:
1766 self._eta_from_m1_m2()
1767 if "redshift" in self.parameters:
1768 if "mass_1_source" not in self.parameters:
1769 if "mass_1" in self.parameters:
1770 self._m1_source_from_m1_z()
1771 if "mass_1_source" in self.parameters:
1772 if "mass_1" not in self.parameters:
1773 self._m1_from_m1_source_z()
1774 if "mass_2_source" not in self.parameters:
1775 if "mass_2" in self.parameters:
1776 self._m2_source_from_m2_z()
1777 if "mass_2_source" in self.parameters:
1778 if "mass_2" not in self.parameters:
1779 self._m2_from_m2_source_z()
1780 if "total_mass_source" not in self.parameters:
1781 if "total_mass" in self.parameters:
1782 self._mtotal_source_from_mtotal_z()
1783 if "total_mass_source" in self.parameters:
1784 if "total_mass" not in self.parameters:
1785 self._mtotal_from_mtotal_source_z()
1786 if "chirp_mass_source" not in self.parameters:
1787 if "chirp_mass" in self.parameters:
1788 self._mchirp_source_from_mchirp_z()
1789 if "chirp_mass_source" in self.parameters:
1790 if "chirp_mass" not in self.parameters:
1791 self._mchirp_from_mchirp_source_z()
1793 if "reference_frequency" not in self.parameters:
1794 self._reference_frequency()
1795 condition1 = "phi_12" not in self.parameters
1796 condition2 = "phi_1" in self.parameters and "phi_2" in self.parameters
1797 if condition1 and condition2:
1798 self._phi_12_from_phi1_phi2()
1800 check_for_evolved_parameter = lambda suffix, param, params: (
1801 param not in params and param + suffix not in params if
1802 len(suffix) else param not in params
1803 )
1805 if "mass_1" in self.parameters and "mass_2" in self.parameters:
1806 spin_components = [
1807 "spin_1x", "spin_1y", "spin_1z", "spin_2x", "spin_2y", "spin_2z",
1808 "iota"
1809 ]
1810 angles = ["a_1", "a_2", "tilt_1", "tilt_2", "theta_jn"]
1811 if all(i in self.parameters for i in spin_components):
1812 self._spin_angles()
1813 if all(i in self.parameters for i in angles):
1814 samples = self.specific_parameter_samples(["tilt_1", "tilt_2"])
1815 cond1 = all(i in [0, np.pi] for i in samples[0])
1816 cond2 = all(i in [0, np.pi] for i in samples[1])
1817 if cond1 and cond1:
1818 self._non_precessing_component_spins()
1819 else:
1820 angles = [
1821 "phi_jl", "phi_12", "reference_frequency"]
1822 if all(i in self.parameters for i in angles):
1823 self._component_spins()
1824 cond1 = "spin_1x" in self.parameters and "spin_1y" in self.parameters
1825 if "phi_1" not in self.parameters and cond1:
1826 self._phi1_from_spins()
1827 cond1 = "spin_2x" in self.parameters and "spin_2y" in self.parameters
1828 if "phi_2" not in self.parameters and cond1:
1829 self._phi2_from_spins()
1830 evolve_spins_params = ["tilt_1", "tilt_2", "phi_12"]
1831 if self.evolve_spins_backwards:
1832 if all(i in self.parameters for i in evolve_spins_params):
1833 try:
1834 self._evolve_spins(forward=False)
1835 except EvolveSpinError:
1836 # Raised when approximant is unknown to lalsimulation or
1837 # lalsimulation.SimInspiralGetSpinFreqFromApproximant is
1838 # equal to lalsimulation.SIM_INSPIRAL_SPINS_CASEBYCASE
1839 pass
1840 for suffix in ["_infinity", "_infinity_only_prec_avg", ""]:
1841 if "spin_1z{}".format(suffix) not in self.parameters:
1842 _params = ["a_1", "tilt_1{}".format(suffix)]
1843 if all(i in self.parameters for i in _params):
1844 self._aligned_spin_from_magnitude_tilts(
1845 primary=True, suffix=suffix
1846 )
1847 if "spin_2z{}".format(suffix) not in self.parameters:
1848 _params = ["a_2", "tilt_2{}".format(suffix)]
1849 if all(i in self.parameters for i in _params):
1850 self._aligned_spin_from_magnitude_tilts(
1851 secondary=True, suffix=suffix
1852 )
1853 if "chi_eff{}".format(suffix) not in self.parameters:
1854 _params = ["spin_1z{}".format(suffix), "spin_2z{}".format(suffix)]
1855 if all(i in self.parameters for i in _params):
1856 self._chi_eff(suffix=suffix)
1857 if any(
1858 _p.format(suffix) not in self.parameters for _p in
1859 ["chi_p{}", "chi_p_2spin"]
1860 ):
1861 _params = [
1862 "a_1", "tilt_1{}".format(suffix), "a_2",
1863 "tilt_2{}".format(suffix)
1864 ]
1865 _cartesian_params = ["spin_1x", "spin_1y", "spin_2x", "spin_2y"]
1866 if "chi_p{}".format(suffix) not in self.parameters:
1867 if all(i in self.parameters for i in _params):
1868 self._chi_p_from_tilts(suffix=suffix)
1869 elif all(i in self.parameters for i in _cartesian_params):
1870 self._chi_p()
1871 if "chi_p_2spin" not in self.parameters:
1872 if all(i in self.parameters for i in _cartesian_params):
1873 self._chi_p_2spin()
1874 if "beta" not in self.parameters:
1875 beta_components = [
1876 "mass_1", "mass_2", "phi_jl", "tilt_1", "tilt_2", "phi_12",
1877 "a_1", "a_2", "reference_frequency", "phase"
1878 ]
1879 if all(i in self.parameters for i in beta_components):
1880 self._beta()
1881 polytrope_params = ["log_pressure", "gamma_1", "gamma_2", "gamma_3"]
1882 if all(param in self.parameters for param in polytrope_params):
1883 if "lambda_1" not in self.parameters or "lambda_2" not in self.parameters:
1884 self._lambda1_lambda2_from_polytrope_EOS()
1885 spectral_params = [
1886 "spectral_decomposition_gamma_{}".format(num) for num in
1887 np.arange(4)
1888 ]
1889 if all(param in self.parameters for param in spectral_params):
1890 if "lambda_1" not in self.parameters or "lambda_2" not in self.parameters:
1891 self._lambda1_lambda2_from_spectral_decomposition_EOS()
1892 if "lambda_tilde" in self.parameters and "lambda_1" not in self.parameters:
1893 self._lambda1_from_lambda_tilde()
1894 if "lambda_2" not in self.parameters and "lambda_1" in self.parameters:
1895 self._lambda2_from_lambda1()
1896 if "lambda_1" in self.parameters and "lambda_2" in self.parameters:
1897 if "lambda_tilde" not in self.parameters:
1898 self._lambda_tilde_from_lambda1_lambda2()
1899 if "delta_lambda" not in self.parameters:
1900 self._delta_lambda_from_lambda1_lambda2()
1901 if "psi" in self.parameters:
1902 dpsi_parameters = ["theta_jn", "phi_jl", "beta"]
1903 if all(i in self.parameters for i in dpsi_parameters):
1904 if "psi_J" not in self.parameters:
1905 self._psi_J()
1907 evolve_suffix = "_non_evolved"
1908 final_spin_params = ["a_1", "a_2"]
1909 non_precessing_NR_params = ["spin_1z", "spin_2z"]
1910 if evolve_condition:
1911 final_spin_params += [
1912 "tilt_1_evolved", "tilt_2_evolved", "phi_12_evolved"
1913 ]
1914 non_precessing_NR_params = [
1915 "{}_evolved".format(i) for i in non_precessing_NR_params
1916 ]
1917 evolve_suffix = "_evolved"
1918 if all(i in self.parameters for i in evolve_spins_params):
1919 try:
1920 self._evolve_spins(final_velocity=self.evolve_spins_forwards)
1921 except EvolveSpinError:
1922 # Raised when approximant is unknown to lalsimulation or
1923 # lalsimulation.SimInspiralGetSpinFreqFromApproximant is
1924 # equal to lalsimulation.SIM_INSPIRAL_SPINS_CASEBYCASE
1925 evolve_condition = False
1926 else:
1927 evolve_condition = False
1928 else:
1929 final_spin_params += ["tilt_1", "tilt_2", "phi_12"]
1931 condition_peak_luminosity = check_for_evolved_parameter(
1932 evolve_suffix, "peak_luminosity", self.parameters
1933 )
1934 condition_final_spin = check_for_evolved_parameter(
1935 evolve_suffix, "final_spin", self.parameters
1936 )
1937 condition_final_mass = check_for_evolved_parameter(
1938 evolve_suffix, "final_mass", self.parameters
1939 )
1940 if (self.NRSurrogate or self.waveform_fit) and self.compute_remnant:
1941 parameters = []
1942 _default = ["final_mass", "final_spin"]
1943 if self.NRSurrogate:
1944 _default.append("final_kick")
1945 function = self._final_remnant_properties_from_NRSurrogate
1946 else:
1947 final_spin_params = [
1948 "spin_1x", "spin_1y", "spin_1z", "spin_2x",
1949 "spin_2y", "spin_2z"
1950 ]
1951 function = self._final_remnant_properties_from_waveform
1953 for param in _default:
1954 if param not in self.parameters:
1955 parameters.append(param)
1956 # We already know that lambda_2 is in the posterior table if
1957 # self.NSBH = True
1958 if self.NSBH and "spin_1z" in self.parameters:
1959 self._final_remnant_properties_from_NSBH_waveform()
1960 elif all(i in self.parameters for i in final_spin_params):
1961 function(non_precessing=False, parameters=parameters)
1962 elif all(i in self.parameters for i in non_precessing_NR_params):
1963 function(non_precessing=True, parameters=parameters)
1964 if all(i in self.parameters for i in non_precessing_NR_params):
1965 if condition_peak_luminosity or self.force_non_evolved:
1966 if not self.NSBH:
1967 self._peak_luminosity_of_merger(evolved=evolve_condition)
1968 elif self.compute_remnant:
1969 if all(i in self.parameters for i in final_spin_params):
1970 if condition_final_spin or self.force_non_evolved:
1971 self._final_spin_of_merger(evolved=evolve_condition)
1972 elif all(i in self.parameters for i in non_precessing_NR_params):
1973 if condition_final_spin or self.force_non_evolved:
1974 self._final_spin_of_merger(
1975 non_precessing=True, evolved=False
1976 )
1977 if all(i in self.parameters for i in non_precessing_NR_params):
1978 if condition_peak_luminosity or self.force_non_evolved:
1979 self._peak_luminosity_of_merger(evolved=evolve_condition)
1980 if condition_final_mass or self.force_non_evolved:
1981 self._final_mass_of_merger(evolved=evolve_condition)
1983 # if NSBH system and self.compute_remnant = False and/or BBH fits
1984 # fits used, only calculate baryonic_torus_mass
1985 if self.NSBH and "spin_1z" in self.parameters:
1986 if "baryonic_torus_mass" not in self.parameters:
1987 self._final_remnant_properties_from_NSBH_waveform(
1988 parameters=["baryonic_torus_mass"]
1989 )
1990 # calculate compactness from Love-compactness relation
1991 if "lambda_1" in self.parameters and "compactness_1" not in self.parameters:
1992 self._NS_compactness_from_lambda(parameter="lambda_1")
1993 if "mass_1" in self.parameters and "baryonic_mass_1" not in self.parameters:
1994 self._NS_baryonic_mass(primary=True)
1995 if "lambda_2" in self.parameters and "compactness_2" not in self.parameters:
1996 self._NS_compactness_from_lambda(parameter="lambda_2")
1997 if "mass_2" in self.parameters and "baryonic_mass_2" not in self.parameters:
1998 self._NS_baryonic_mass(primary=False)
1999 for suffix in ["_infinity", "_infinity_only_prec_avg", ""]:
2000 for tilt in ["tilt_1", "tilt_2"]:
2001 cond1 = "cos_{}{}".format(tilt, suffix) not in self.parameters
2002 cond2 = "{}{}".format(tilt, suffix) in self.parameters
2003 if cond1 and cond2:
2004 self._cos_angle("cos_{}{}".format(tilt, suffix))
2005 evolve_suffix = "_non_evolved"
2006 if evolve_condition or self.NRSurrogate or self.waveform_fit or self.non_precessing:
2007 evolve_suffix = ""
2008 evolve_condition = True
2009 if "redshift" in self.parameters:
2010 condition_final_mass_source = check_for_evolved_parameter(
2011 evolve_suffix, "final_mass_source", self.parameters
2012 )
2013 if condition_final_mass_source or self.force_non_evolved:
2014 if "final_mass{}".format(evolve_suffix) in self.parameters:
2015 self._final_mass_source(evolved=evolve_condition)
2016 if "baryonic_torus_mass" in self.parameters:
2017 if "baryonic_torus_mass_source" not in self.parameters:
2018 self.source_frame_from_detector_frame(
2019 "baryonic_torus_mass"
2020 )
2021 if "baryonic_mass_1" in self.parameters:
2022 if "baryonic_mass_1_source" not in self.parameters:
2023 self.source_frame_from_detector_frame(
2024 "baryonic_mass_1"
2025 )
2026 if "baryonic_mass_2" in self.parameters:
2027 if "baryonic_mass_2_source" not in self.parameters:
2028 self.source_frame_from_detector_frame(
2029 "baryonic_mass_2"
2030 )
2031 if "total_mass_source" in self.parameters:
2032 if "final_mass_source{}".format(evolve_suffix) in self.parameters:
2033 condition_radiated_energy = check_for_evolved_parameter(
2034 evolve_suffix, "radiated_energy", self.parameters
2035 )
2036 if condition_radiated_energy or self.force_non_evolved:
2037 self._radiated_energy(evolved=evolve_condition)
2038 if self.NSBH and "spin_1z" in self.parameters:
2039 if all(_p in self.parameters for _p in ["mass_1_source", "mass_2_source"]):
2040 _NSBH_parameters = []
2041 if "tidal_disruption_frequency" not in self.parameters:
2042 _NSBH_parameters.append("tidal_disruption_frequency")
2043 if "220_quasinormal_mode_frequency" not in self.parameters:
2044 _NSBH_parameters.append("220_quasinormal_mode_frequency")
2045 if len(_NSBH_parameters):
2046 self._final_remnant_properties_from_NSBH_waveform(
2047 parameters=_NSBH_parameters, source=True
2048 )
2049 location = ["geocent_time", "ra", "dec"]
2050 if all(i in self.parameters for i in location):
2051 try:
2052 self._time_in_each_ifo()
2053 self._time_delay_between_ifos()
2054 except Exception as e:
2055 logger.warning(
2056 "Failed to generate posterior samples for the time in each "
2057 "detector because %s" % (e)
2058 )
2059 if any("_matched_filter_snr_angle" in i for i in self.parameters):
2060 if any("_matched_filter_abs_snr" in i for i in self.parameters):
2061 self._ifo_snr()
2062 if any("_optimal_snr" in i for i in self.parameters):
2063 if "network_optimal_snr" not in self.parameters:
2064 self._optimal_network_snr()
2065 if any("_matched_filter_snr" in i for i in self.parameters):
2066 if "network_matched_filter_snr" not in self.parameters:
2067 self._matched_filter_network_snr()
2068 if self.multipole_snr:
2069 rho_hm_parameters = [
2070 "mass_1", "mass_2", "spin_1z", "spin_2z", "psi", "iota", "ra",
2071 "dec", "geocent_time", "luminosity_distance", "phase"
2072 ]
2073 cond = [
2074 int(mm) for mm in ['21', '33', '44'] if
2075 "network_{}_multipole_snr".format(mm) not in self.parameters
2076 ]
2077 if all(i in self.parameters for i in rho_hm_parameters) and len(cond):
2078 try:
2079 logger.warning(
2080 "Starting to calculate the SNR in the {} multipole{}. "
2081 "This may take some time".format(
2082 " and ".join([str(mm) for mm in cond]),
2083 "s" if len(cond) > 1 else ""
2084 )
2085 )
2086 self._rho_hm(cond)
2087 except ImportError as e:
2088 logger.warning(e)
2089 elif len(cond):
2090 logger.warning(
2091 "Unable to calculate the multipole SNR because it requires "
2092 "samples for {}".format(
2093 ", ".join(
2094 [i for i in rho_hm_parameters if i not in self.parameters]
2095 )
2096 )
2097 )
2098 if "network_precessing_snr" not in self.parameters and self.precessing_snr:
2099 rho_p_parameters = [
2100 "mass_1", "mass_2", "beta", "psi_J", "a_1", "a_2", "tilt_1",
2101 "tilt_2", "phi_12", "theta_jn", "phi_jl", "ra", "dec", "geocent_time",
2102 "phi_jl"
2103 ]
2104 if all(i in self.parameters for i in rho_p_parameters):
2105 try:
2106 logger.warning(
2107 "Starting to calculate the precessing SNR. This may take "
2108 "some time"
2109 )
2110 self._rho_p()
2111 except ImportError as e:
2112 logger.warning(e)
2113 else:
2114 logger.warning(
2115 "Unable to calculate the precessing SNR because requires "
2116 "samples for {}".format(
2117 ", ".join(
2118 [i for i in rho_p_parameters if i not in self.parameters]
2119 )
2120 )
2121 )
2122 if "theta_jn" in self.parameters and "cos_theta_jn" not in self.parameters:
2123 self._cos_angle("cos_theta_jn")
2124 if "theta_jn" in self.parameters and "viewing_angle" not in self.parameters:
2125 self._viewing_angle()
2126 if "iota" in self.parameters and "cos_iota" not in self.parameters:
2127 self._cos_angle("cos_iota")
2128 remove_parameters = [
2129 "tilt_1_evolved", "tilt_2_evolved", "phi_12_evolved",
2130 "spin_1z_evolved", "spin_2z_evolved", "reference_frequency",
2131 "minimum_frequency"
2132 ]
2133 for param in remove_parameters:
2134 if param in self.parameters:
2135 ind = self.parameters.index(param)
2136 self.parameters.remove(self.parameters[ind])
2137 for i in self.samples:
2138 del i[ind]