Source code for kuibit.sensitivity_curves

#!/usr/bin/env python3

# Copyright (C) 2020-2023 Gabriele Bozzola
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see <https://www.gnu.org/licenses/>.

"""The :py:mod:`~.sensitivity_curves` module contains the power spectral
distribution of the noise of known detectors. The available ones are:

- :py:func:`~.Sn_LISA`
- :py:func:`~.Sn_ET_B`
- :py:func:`~.Sn_CE1`
- :py:func:`~.Sn_CE2`
- :py:func:`~.Sn_aLIGO`
- :py:func:`~.Sn_voyager`
- :py:func:`~.Sn_KAGRA_D`
- :py:func:`~.Sn_aLIGO_plus`

"""

import pkgutil
from io import StringIO

import numpy as np

from kuibit.frequencyseries import FrequencySeries, load_FrequencySeries
from kuibit.unitconv import C_SI


[docs]def Sn_LISA(freqs, arms_length=2.5e9): """Return the average power spectral density noise for LISA in 1/Hz. The expression implemented is Equation (13) in https://arxiv.org/abs/1803.01944. :param freqs: Frequencies in Hz over which to evaluate the sensitivity curve. :type freqs: 1d NumPy array :param arms_length: Length of the detector's arms in meters. :type arms_length: float :returns: LISA sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) def P_acc(f): """Equation (11) of 1803.01944""" return ( (3.0e-15) ** 2 * (1.0 + (0.4e-3 / f) ** 2) * (1.0 + (f / 8e-3) ** 4) ) # 1/Hz def P_OMS(f): """Equation (10) of 1803.01944""" return (1.5e-11) ** 2 * (1.0 + (2e-3 / f) ** 4) # 1/Hz # Transfer frequency f_star = C_SI / (2 * np.pi * arms_length) # This is Equation (13) Sn = ( (10.0 / (3 * arms_length**2)) * ( P_OMS(freqs) + 2 * (1.0 + np.cos(freqs / f_star) ** 2) * P_acc(freqs) / ((2 * np.pi * freqs) ** 4) ) * (1.0 + (6.0 / 10.0) * (freqs / f_star) ** 2) ) return FrequencySeries(freqs, Sn)
[docs]def Sn_ET_B(freqs): """Return the average power spectral density noise for Einstein Telescope (variant B) in 1/Hz. .. note:: The data was downloaded from https://apps.et-gw.eu/tds/?content=3&r=14323 and has range ``fmin=1``, ``fmax=10000`` (Hz). :param freqs: Frequencies in Hz over which to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: ET-B sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) # Why is it so difficult to read files in Python packages? :( data = pkgutil.get_data("kuibit", "data/ETB.dat").decode("utf8") # We convert this data in a StringIO that NumPy can read, we can pass this # to load_FrequencySeries, since its backend is np.loadtxt # # ET distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. ETB is well-behaved, so it is fine # to use splines. psd.resample(freqs) return psd
[docs]def Sn_ET_D(freqs): """Return the average power spectral density noise for Einstein Telescope (variant D) in 1/Hz. .. note:: The data was downloaded from https://apps.et-gw.eu/tds/?content=3&r=14065 and has range ``fmin=1``, ``fmax=10000`` (Hz). :param freqs: Frequencies in Hz over which to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: ET-D sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) # Why is it so difficult to read files in Python packages? :( data = pkgutil.get_data("kuibit", "data/ETD.dat").decode("utf8") # We convert this data in a StringIO that NumPy can read, we can pass this # to load_FrequencySeries, since its backend is np.loadtxt # # ET-D has four columns: freq, ET-D-LF, ET-D-HF, ET-D-sum. We only care # about the last one f, _, _, fft = np.loadtxt(StringIO(data), unpack=True) asd = FrequencySeries(f, fft) psd = asd**2 # Resample on the requested frequencies. ETD has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd
[docs]def Sn_CE1(freqs): """Return the average power spectral density noise for Einstein Telescope in 1/Hz. .. note:: The data was downloaded from https://cosmicexplorer.org/data/CE1_strain.txt and has range ``fmin=3``, ``fmax=10000`` (Hz). The resampling to the requested frequencies is done considering the values of the nearest neighbors. :param freqs: Frequencies in Hz over which to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: CE1 sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) data = pkgutil.get_data("kuibit", "data/CE1.dat").decode("utf8") # CE distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. CE1 has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd
[docs]def Sn_CE2(freqs): """Return the average power spectral density noise for Einstein Telescope in 1/Hz. .. note:: The data was downloaded from https://cosmicexplorer.org/data/CE2_strain.txt and has range ``fmin=3``, ``fmax=10000`` (Hz). The resampling to the requested frequencies is done considering the values of the nearest neighbors. :param freqs: Frequencies in Hz over which to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: CE2 sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) data = pkgutil.get_data("kuibit", "data/CE1.dat").decode("utf8") # CE distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. CE1 has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd
[docs]def Sn_aLIGO(freqs): """Return the average power spectral density noise for advanced LIGO in 1/Hz. This is the Zero-Detuned-High-Power noise curve. .. note:: The data was downloaded from https://dcc.ligo.org/LIGO-T1500293-v11/public and has range ``fmin=9``, ``fmax=8192`` (Hz). The resampling to the requested frequencies is done considering the values of the nearest neighbors. :param freqs: Frequencies in Hz over to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: Advanced LIGO Zero-Detuned High-Power sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) data = pkgutil.get_data("kuibit", "data/aLIGO_ZDHP.dat").decode("utf8") # aLIGO distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. CE1 has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd
[docs]def Sn_voyager(freqs): """Return the average power spectral density noise for voyager in 1/Hz. .. note:: The data was downloaded from https://dcc.ligo.org/LIGO-T1500293-v11/public and has range ``fmin=5``, ``fmax=10000`` (Hz). The resampling to the requested frequencies is done considering the values of the nearest neighbors. :param freqs: Frequencies in Hz over to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: Voyager sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) data = pkgutil.get_data("kuibit", "data/voyager.dat").decode("utf8") # Voyager distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. CE1 has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd
[docs]def Sn_KAGRA_D(freqs): """Return the average power spectral density noise for KAGRA in 1/Hz. .. note:: The data was downloaded from https://granite.phys.s.u-tokyo.ac.jp/svn/LCGT/trunk/sensitivity/spectrum/BW2009_VRSED.dat and has range ``fmin=1.00231``, ``fmax=10000`` (Hz). The resampling to the requested frequencies is done considering the values of the nearest neighbors. :param freqs: Frequencies in Hz over to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: KAGRA sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) data = pkgutil.get_data("kuibit", "data/KAGRA_VRSED.dat").decode("utf8") # KAGRA distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. CE1 has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd
[docs]def Sn_aLIGO_plus(freqs): """Return the average power spectral density noise for Advanced LIGO + in 1/Hz. .. note:: The data was downloaded from https://dcc.ligo.org/public/0149/T1800042/005/AplusDesign.txt and has range ``fmin=5``, ``fmax=5000`` (Hz). The resampling to the requested frequencies is done considering the values of the nearest neighbors. :param freqs: Frequencies in Hz over to evaluate the sensitivity curve. :type freqs: 1d NumPy array :returns: aLIGO+ sensitivity curve in 1/Hz. :rtype: :py:class:`~.FrequencySeries` """ freqs = np.asarray(freqs) data = pkgutil.get_data("kuibit", "data/aLIGO_PLUS.dat").decode("utf8") # aLIGO distributes Amplitude Spectral Densities asd = load_FrequencySeries(StringIO(data)) psd = asd**2 # Resample on the requested frequencies. CE1 has some spikes, so it is # better to not use splines. psd.resample(freqs, piecewise_constant=True) return psd