From 4f7ab4669b9367593846ec05007a784e8da93c43 Mon Sep 17 00:00:00 2001
From: Soichiro Morisaki <soichiro.morisaki@ligo.org>
Date: Wed, 14 Feb 2024 17:53:36 +0000
Subject: [PATCH] BUG: fix in MBGravitationalWaveTransient for low maximum
 frequency

---
 bilby/gw/likelihood/multiband.py |  4 +-
 test/gw/likelihood_test.py       | 90 ++++++++++++++++++++++++++++----
 2 files changed, 83 insertions(+), 11 deletions(-)

diff --git a/bilby/gw/likelihood/multiband.py b/bilby/gw/likelihood/multiband.py
index 4e338e19a..bec333e71 100644
--- a/bilby/gw/likelihood/multiband.py
+++ b/bilby/gw/likelihood/multiband.py
@@ -517,7 +517,7 @@ class MBGravitationalWaveTransient(GravitationalWaveTransient):
         for ifo in self.interferometers:
             logger.info("Pre-computing linear coefficients for {}".format(ifo.name))
             fddata = np.zeros(N // 2 + 1, dtype=complex)
-            fddata[:len(ifo.frequency_domain_strain)][ifo.frequency_mask] += \
+            fddata[:len(ifo.frequency_domain_strain)][ifo.frequency_mask[:len(fddata)]] += \
                 ifo.frequency_domain_strain[ifo.frequency_mask] / ifo.power_spectral_density_array[ifo.frequency_mask]
             for b in range(self.number_of_bands):
                 Ks, Ke = self.Ks_Ke[b]
@@ -606,7 +606,7 @@ class MBGravitationalWaveTransient(GravitationalWaveTransient):
         for ifo in self.interferometers:
             logger.info("Pre-computing quadratic coefficients for {}".format(ifo.name))
             full_inv_psds = np.zeros(N // 2 + 1)
-            full_inv_psds[:len(ifo.power_spectral_density_array)][ifo.frequency_mask] = (
+            full_inv_psds[:len(ifo.power_spectral_density_array)][ifo.frequency_mask[:len(full_inv_psds)]] = (
                 1 / ifo.power_spectral_density_array[ifo.frequency_mask]
             )
             for b in range(self.number_of_bands):
diff --git a/test/gw/likelihood_test.py b/test/gw/likelihood_test.py
index 286eadea2..08ba958af 100644
--- a/test/gw/likelihood_test.py
+++ b/test/gw/likelihood_test.py
@@ -732,6 +732,32 @@ class TestROQLikelihoodHDF5(unittest.TestCase):
     )
     def test_likelihood_accuracy(self, basis_linear, basis_quadratic, mc_range, roq_scale_factor, add_cal_errors):
         "Compare with log likelihood ratios computed by the non-ROQ likelihood"
+        # The maximum error of log likelihood ratio. It is set to be larger for roq_scale_factor=1 as the injected SNR
+        # is higher.
+        if roq_scale_factor == 1:
+            max_llr_error = 5e-1
+        elif roq_scale_factor == 2:
+            max_llr_error = 5e-2
+        else:
+            raise
+
+        self.assertLess_likelihood_errors(
+            basis_linear, basis_quadratic, mc_range, roq_scale_factor, add_cal_errors, max_llr_error
+        )
+
+    @parameterized.expand([(_path_to_basis_mb, 100, 1024), (_path_to_basis_mb, 20, 200), (_path_to_basis_mb, 100, 200)])
+    def test_likelihood_accuracy_narrower_frequency_range(self, basis, minimum_frequency, maximum_frequency):
+        """Compare with log likelihood ratios computed by the non-ROQ likelihood in the case where analyzed frequency
+        range is narrower than the basis frequency range"""
+        self.assertLess_likelihood_errors(
+            basis, basis, (8, 9), 1, False, 1.5e-1,
+            minimum_frequency=minimum_frequency, maximum_frequency=maximum_frequency
+        )
+
+    def assertLess_likelihood_errors(
+        self, basis_linear, basis_quadratic, mc_range, roq_scale_factor, add_cal_errors, max_llr_error,
+        minimum_frequency=None, maximum_frequency=None
+    ):
         self.minimum_frequency *= roq_scale_factor
         self.sampling_frequency *= roq_scale_factor
         self.duration /= roq_scale_factor
@@ -745,7 +771,12 @@ class TestROQLikelihoodHDF5(unittest.TestCase):
 
         interferometers = bilby.gw.detector.InterferometerList(["H1", "L1"])
         for ifo in interferometers:
-            ifo.minimum_frequency = self.minimum_frequency
+            if minimum_frequency is None:
+                ifo.minimum_frequency = self.minimum_frequency
+            else:
+                ifo.minimum_frequency = minimum_frequency
+            if maximum_frequency is not None:
+                ifo.maximum_frequency = maximum_frequency
         interferometers.set_strain_data_from_zero_noise(
             sampling_frequency=self.sampling_frequency,
             duration=self.duration,
@@ -805,14 +836,6 @@ class TestROQLikelihoodHDF5(unittest.TestCase):
             quadratic_matrix=basis_quadratic,
             roq_scale_factor=roq_scale_factor
         )
-        # The maximum error of log likelihood ratio. It is set to be larger for roq_scale_factor=1 as the injected SNR
-        # is higher.
-        if roq_scale_factor == 1:
-            max_llr_error = 5e-1
-        elif roq_scale_factor == 2:
-            max_llr_error = 5e-2
-        else:
-            raise
         for mc in np.linspace(self.priors["chirp_mass"].minimum, self.priors["chirp_mass"].maximum, 11):
             parameters = self.injection_parameters.copy()
             parameters["chirp_mass"] = mc
@@ -1463,6 +1486,55 @@ class TestMBLikelihood(unittest.TestCase):
 
         self.assertAlmostEqual(llr, llr_from_weights)
 
+    @parameterized.expand([
+        ("IMRPhenomD", True, 2, False, 1e-2),
+        ("IMRPhenomD", True, 2, True, 1e-2),
+        ("IMRPhenomHM", False, 4, False, 5e-3),
+    ])
+    def test_matches_original_likelihood_low_maximum_frequency(
+        self, approximant, linear_interpolation, highest_mode, add_cal_errors, tolerance
+    ):
+        """
+        Test for maximum frequency < sampling frequency / 2
+        """
+        for ifo in self.ifos:
+            ifo.maximum_frequency = self.sampling_frequency / 8
+
+        wfg = bilby.gw.WaveformGenerator(
+            duration=self.duration, sampling_frequency=self.sampling_frequency,
+            frequency_domain_source_model=bilby.gw.source.lal_binary_black_hole,
+            waveform_arguments=dict(
+                reference_frequency=self.fmin, approximant=approximant
+            )
+        )
+        self.ifos.inject_signal(parameters=self.test_parameters, waveform_generator=wfg)
+
+        wfg_mb = bilby.gw.WaveformGenerator(
+            duration=self.duration, sampling_frequency=self.sampling_frequency,
+            frequency_domain_source_model=bilby.gw.source.binary_black_hole_frequency_sequence,
+            waveform_arguments=dict(
+                reference_frequency=self.fmin, approximant=approximant
+            )
+        )
+        likelihood = bilby.gw.likelihood.GravitationalWaveTransient(
+            interferometers=self.ifos, waveform_generator=wfg
+        )
+        likelihood_mb = bilby.gw.likelihood.MBGravitationalWaveTransient(
+            interferometers=self.ifos, waveform_generator=wfg_mb,
+            reference_chirp_mass=self.test_parameters['chirp_mass'],
+            priors=self.priors.copy(), linear_interpolation=linear_interpolation,
+            highest_mode=highest_mode
+        )
+        likelihood.parameters.update(self.test_parameters)
+        likelihood_mb.parameters.update(self.test_parameters)
+        if add_cal_errors:
+            likelihood.parameters.update(self.calibration_parameters)
+            likelihood_mb.parameters.update(self.calibration_parameters)
+        self.assertLess(
+            abs(likelihood.log_likelihood_ratio() - likelihood_mb.log_likelihood_ratio()),
+            tolerance
+        )
+
 
 if __name__ == "__main__":
     unittest.main()
-- 
GitLab