diff --git a/bilby/gw/conversion.py b/bilby/gw/conversion.py index 9bd9cab06..6085de021 100644 --- a/bilby/gw/conversion.py +++ b/bilby/gw/conversion.py @@ -232,19 +232,24 @@ def convert_to_lal_binary_black_hole_parameters(parameters): key = 'chi_{}'.format(idx) if key in original_keys: if "chi_{}_in_plane".format(idx) in original_keys: - converted_parameters["a_{}".format(idx)] = ( - converted_parameters[f"chi_{idx}"] ** 2 - + converted_parameters[f"chi_{idx}_in_plane"] ** 2 - ) ** 0.5 - converted_parameters[f"cos_tilt_{idx}"] = ( - converted_parameters[f"chi_{idx}"] - / converted_parameters[f"a_{idx}"] - ) + a = (converted_parameters[f"chi_{idx}"] ** 2 + + converted_parameters[f"chi_{idx}_in_plane"] ** 2) ** 0.5 + converted_parameters[f"a_{idx}"] = a + with np.errstate(invalid="raise"): + try: + converted_parameters[f"cos_tilt_{idx}"] = ( + converted_parameters[f"chi_{idx}"] / a + ) + except (FloatingPointError, ZeroDivisionError): + logger.debug( + "Error in conversion to spherical spin tilt. " + "This is often due to the spin parameters being zero. " + f"Setting cos_tilt_{idx} = 1." + ) + converted_parameters[f"cos_tilt_{idx}"] = 1.0 elif "a_{}".format(idx) not in original_keys: - converted_parameters['a_{}'.format(idx)] = abs( - converted_parameters[key]) - converted_parameters['cos_tilt_{}'.format(idx)] = \ - np.sign(converted_parameters[key]) + converted_parameters['a_{}'.format(idx)] = abs(converted_parameters[key]) + converted_parameters['cos_tilt_{}'.format(idx)] = np.sign(converted_parameters[key]) else: with np.errstate(invalid="raise"): try: diff --git a/test/gw/conversion_test.py b/test/gw/conversion_test.py index fc0f4321a..3808f38b1 100644 --- a/test/gw/conversion_test.py +++ b/test/gw/conversion_test.py @@ -379,6 +379,51 @@ def test_bbh_cos_angle_to_angle_conversion(self): self.bbh_convert() self.assertEqual(self.parameters["tilt_1"], t1) + def test_bbh_chi_in_plane_zero_spin_both_components(self): + """ + When chi_1 = chi_1_in_plane = 0 the magnitude a_1 = 0, + and the bare division chi_1 / a_1 = 0/0 would produce a NaN. + This tests whether the wrapped np.errstate(invalid="raise") + correctly catches the resulting error, and sets cos_tilt_1 = 1.0 instead. + Same for component labeled 2. + """ + self.parameters["chi_1"] = 0.0 + self.parameters["chi_1_in_plane"] = 0.0 + self.parameters["chi_2"] = 0.0 + self.parameters["chi_2_in_plane"] = 0.0 + self.bbh_convert() + for idx in ["1", "2"]: + self.assertFalse( + np.isnan(self.parameters[f"cos_tilt_{idx}"]), + f"cos_tilt_{idx} must not be NaN for zero spins", + ) + self.assertEqual(self.parameters[f"a_{idx}"], 0.0) + self.assertEqual(self.parameters[f"cos_tilt_{idx}"], 1.0) + + def test_bbh_double_conversion_zero_spin_no_nan(self): + """ + Verify that, when calling convert_to_lal_binary_black_hole_parameters + twice on a parameters dict that has chi_1 = chi_2 = 0, the np.errstate + is correctly triggered and avoids NaNs. + """ + self.parameters["chi_1"] = 0.0 + self.parameters["chi_2"] = 0.0 + # First conversion + self.bbh_convert() + first_pass = self.parameters.copy() + self.assertFalse(np.isnan(first_pass.get("cos_tilt_1", 0.0))) + + # Second conversion on the already-converted parameters (the crash scenario) + (second_pass, _) = conversion.convert_to_lal_binary_black_hole_parameters(first_pass) + self.assertFalse( + np.isnan(second_pass["cos_tilt_1"]), + "cos_tilt_1 must not be NaN after a second conversion of zero-spin parameters", + ) + self.assertFalse( + np.isnan(second_pass["cos_tilt_2"]), + "cos_tilt_2 must not be NaN after a second conversion of zero-spin parameters", + ) + def _conversion_to_component_tidal(self, keys): for key in keys: self.parameters[key] = self.tidal_parameters[key]