import bilby
import numpy
import logging
import os
from bilby.gw.detector.psd import PowerSpectralDensity as psd
from bilby.gw.detector.networks import TriangularInterferometer
[docs]
def load_xg_interferometer(filename, power_spectral_density):
"""Load a GWForge XG ``.ifo`` file, attaching ``power_spectral_density``.
This mirrors ``bilby.gw.detector.load_interferometer``'s tiny key = value
parser but injects a valid PSD before the interferometer is constructed.
Two things make this necessary rather than a plain ``load_interferometer``:
- bilby's ``TriangularInterferometer`` indexes ``power_spectral_density[ii]``
for each of the three arms, so a ``.ifo`` file carrying
``power_spectral_density = None`` (as ET does) raises a ``TypeError`` at
load time.
- even for a single detector the PSD has to reach the interferometer(s), not
just the enclosing list, so we set it explicitly on every arm.
Parameters
----------
filename : str
Path to the ``.ifo`` file.
power_spectral_density : bilby.gw.detector.psd.PowerSpectralDensity
The PSD to attach to the interferometer (all three arms for a triangle).
Returns
-------
bilby.gw.detector.Interferometer or bilby.gw.detector.networks.TriangularInterferometer
"""
parameters = {}
with open(filename, "r") as parameter_file:
for line in parameter_file:
if not line.strip() or line.lstrip().startswith("#"):
continue
key, _, value = line.partition("=")
parameters[key.strip()] = eval(value)
parameters["power_spectral_density"] = power_spectral_density
shape = str(parameters.pop("shape", "L"))
if shape.lower() in ("triangular", "triangle"):
ifo = TriangularInterferometer(**parameters)
for arm in ifo:
arm.power_spectral_density = power_spectral_density
else:
ifo = bilby.gw.detector.Interferometer(**parameters)
ifo.power_spectral_density = power_spectral_density
return ifo
[docs]
class IFO:
"""
Examples
--------
>>> ifo = IFO(name='H1', asharp=True)
>>> ifo.initialise_ifo(seed=42)
>>> ifo = IFO(name='ET')
>>> ifo.initialize_ifo()
"""
def __init__(self, name, psd_file=None, asharp=None):
"""
Parameters
----------
name : str
Name of interferometer
psd_file : str (optional)
Path of PSD/ASD File [Default: None]
asharp : bool (optional)
A flag indicating whether to use A# PSD for H1, L1 and A1 ifo
"""
[docs]
self.name = name.upper()
[docs]
self.psd_file = psd_file
if self.name == "CEA":
self.name = "CE40"
elif self.name == "CEB":
self.name = "CE20"
[docs]
def initialise_ifo(self, seed=None):
"""
Initialise the interferometer with optional random seed.
Parameters
----------
seed : int (optional)
The random seed for initialisation. [Default: None]
Returns
-------
ifo: bilby.gw.detector.Intereferometer
Raises
------
ValueError: If the interferometer is not implemented.
"""
if seed is not None:
numpy.random.seed(seed)
try:
# Execute if ifo is ET or CEs
filename = os.path.join(
os.path.dirname(__file__), "ifos", "{}.ifo".format(self.name)
)
if not os.path.isfile(filename):
raise FileNotFoundError(filename)
if self.name == "ET":
file_type = "psd"
load_psd = psd.from_power_spectral_density_file
else:
file_type = "asd"
load_psd = psd.from_amplitude_spectral_density_file
noise_file = os.path.join(
os.path.dirname(__file__),
"noise_curves",
"{}-{}.txt".format(self.name, file_type),
)
if not os.path.isfile(noise_file):
raise ValueError(
"{} has an .ifo file but no noise curve at {}".format(self.name, noise_file)
)
temp = load_psd(noise_file)
# Attach the PSD as the interferometer is built: bilby's
# TriangularInterferometer rejects a None PSD (as ET's .ifo carries),
# and the PSD must reach each ET arm, not just the enclosing list.
ifo = load_xg_interferometer(filename, temp)
except FileNotFoundError:
# Execute if detector is LIGO, Virgo, NEMO or LISA or any other bilby ifos
logging.info(
"{} is not XG. Checking whether it is implemented in Bilby".format(
self.name
)
)
ifo = bilby.gw.detector.get_empty_interferometer(self.name)
if self.name in ["H1", "L1", "A1"] and self.asharp:
logging.info("Assigning {} ifo A# PSD".format(self.name))
asharp_file = os.path.join(
os.path.dirname(__file__), "noise_curves", "Asharp-asd.txt"
)
ifo.power_spectral_density = psd.from_amplitude_spectral_density_file(
asharp_file
)
except Exception as e:
raise ValueError(
"Interferometer {} is not implemented. Error: {}".format(self.name, e)
)
if self.psd_file:
temp = numpy.loadtxt(self.psd_file)
frequency_array, psd_array = temp[:, 0], temp[:, -1]
if numpy.min(psd_array) > 1e-30:
load_psd = psd.from_amplitude_spectral_density_array
else:
load_psd = psd.from_power_spectral_density_array
ifo.power_spectral_density = load_psd(frequency_array, psd_array)
return ifo
# Define an alias for the function
[docs]
initialize_ifo = initialise_ifo
[docs]
class Network(IFO):
"""
Examples
--------
>>> ifos = Network(ifos = ['ET', 'CE20', 'CE40'])
>>> ifos.initialise_network()
"""
def __init__(self, ifos, psd_files=None):
"""
Parameters
----------
ifos: str list
A list of interferometers to include in the network
psd_files: dict or list or str, optional
PSD files corresponding to interferometers [Default: None]
"""
ifo_list = []
k = 0
for ifo in ifos: # Fix the typo here
if isinstance(ifo, str) and not psd_files:
ifo_list.append(IFO(name=ifo).initialise_ifo())
elif isinstance(ifo, str) and isinstance(psd_files, dict):
ifo_list.append(IFO(name=ifo, psd_file=psd_files[ifo]).initialise_ifo())
elif isinstance(ifo, str) and isinstance(psd_files, list):
ifo_list.append(IFO(name=ifo, psd_file=psd_files[k]).initialise_ifo())
k = k + 1
elif (
isinstance(ifo, str)
and isinstance(psd_files, str)
and psd_files.lower() == "asharp"
):
ifo_list.append(IFO(name=ifo, asharp=True).initialise_ifo())
else:
raise ValueError("Input is not compatible")
[docs]
self.ifos = bilby.gw.detector.InterferometerList(ifo_list)
[docs]
def initialise_network(self):
"""
Returns
-------
ifos: bilby.gw.detector.InterferometerList
"""
return self.ifos
# Define aliases for the function
[docs]
initialize_network = initialise_network
[docs]
initialise_ifos = initialise_network
[docs]
initialize_ifos = initialise_network