B
    }d0a                 @   s  d Z ddlZddlmZ ddlmZ ddlmZ ddlm	Z	 ddl
mZmZ dd	lmZ dd
lmZ ddlmZ dZdZdZdd Zdd Zdd Zed/ddZd0ddZdd Zed1ddZed2d d!Zed3d#d$Zd4d'd(Zdddd)ed*dfd+d,Z dddd)ed*dfd-d.Z!dS )5zzThis module provides methods to calculate the astrophysical sensitive
distance of an instrumental power-spectral density.
    N)wraps)pi)trapz)interp1d)units	constants   )Spectrogram)
TimeSeries)round_to_powerz(Duncan Macleod <duncan.macleod@ligo.org>z%Alex Urban <alexander.urban@ligo.org>Zmedianc             C   s8   t | | dd}tjd tjd t |  dS )a  Determine the innermost stable circular orbit (ISCO) frequency

    Parameters
    ----------
    mass1 : `float`
        the mass (in solar masses) of the first binary component

    mass2 : `float`
        the mass (in solar masses) of the second binary component

    Returns
    -------
    fisco : `~astropy.units.Quantity`
        linear frequency (Hz) of the innermost stable circular orbit
    solMasskg   gƱd-@Hz)r   Quantitytor   cGr   )mass1mass2mtotal r   ]/work/yifan.wang/ringdown/master-ringdown-env/lib/python3.7/site-packages/gwpy/astro/range.py_get_isco_frequency.   s    r   c          	   K   sD   t | ts@y| jf |} W n$ ttfk
r>   d}t|Y nX | S )a  Check that the input is a spectrogram, or compute one if compatible

    Parameters
    ----------
    hoft : `~gwpy.timeseries.TimeSeries` or `~gwpy.spectrogram.Spectrogram`
        record of gravitational-wave strain output from a detector

    **kwargs : `dict`, optional
        additional keyword arguments to
        `~gwpy.timeseries.TimeSeries.spectrogram`

    Returns
    -------
    hoft : `~gwpy.spectrogram.Spectrogram`
        a time-frequency `Spectrogram` of the input
    zCould not produce a spectrogram from the input, please pass an instance of gwpy.timeseries.TimeSeries or gwpy.spectrogram.Spectrogram)
isinstancer	   spectrogramAttributeError	TypeError)hoftkwargsmsgr   r   r   _get_spectrogramD   s    
r!   c                s   t   fdd}|S )Nc                s2   | j dtj kr"|  } | d  | f||S )N   z1/Hz)unitr   r   viewoverride_unit)psdargsr   )funcr   r   decorated_funca   s    
z&_preformat_psd.<locals>.decorated_func)r   )r(   r)   r   )r(   r   _preformat_psd`   s    r*      ffffff?Fc       	      C   s   | j dk}t|dd}t|dd}|| }|| d |d  }|rRdndd tjd	  |tj tjd
  d  dtd  |d
   }d| |  | j | d  | dS )a  Approximate the inspiral sensitive distance PSD from a GW strain PSD

    This method returns the power spectral density (in ``Mpc**2 / Hz``) to
    which a compact binary inspiral with the given component masses would
    be detectable given the instrumental PSD. The calculation is defined in:

    https://dcc.ligo.org/LIGO-T030276/public

    Parameters
    ----------
    psd : `~gwpy.frequencyseries.FrequencySeries`
        the instrumental power-spectral-density data

    snr : `float`, optional
        the signal-to-noise ratio for which to calculate range,
        default: `8`

    mass1 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the first binary
        component, default: `1.4`

    mass2 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the second binary
        component, default: `1.4`

    horizon : `bool`, optional
        if `True`, return the maximal 'horizon' sensitive distance, otherwise
        return the angle-averaged range, default: `False`

    Returns
    -------
    rspec : `~gwpy.frequencyseries.FrequencySeries`
        the calculated inspiral sensitivity PSD [Mpc^2 / Hz]
    r   r   r   g333333?g?   gr-	@   gUUUUUU?r   g?`   gUUUUUU?r"   gz
Mpc^2 / Hz)frequenciesr   r   r   r   r   r   r   )	r&   snrr   r   horizonfranger   ZmchirpZ	prefactorr   r   r   sensemon_range_psdl   s    $
0r4   c             C   s   t ||}t|p| jd}t|p&|d}||krLtd|||f  |}| jd}||k||k @ }	t| |	 ||||d}
tjt	|
j
|j
|	 |
jtj dd dS )aO  Approximate the inspiral sensitive distance from a GW strain PSD

    This method returns the distance (in megaparsecs) to which a compact
    binary inspiral with the given component masses would be detectable
    given the instrumental PSD. The calculation is as defined in:

    https://dcc.ligo.org/LIGO-T030276/public

    Parameters
    ----------
    psd : `~gwpy.frequencyseries.FrequencySeries`
        the instrumental power-spectral-density data

    snr : `float`, optional
        the signal-to-noise ratio for which to calculate range,
        default: `8`

    mass1 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the first binary
        component, default: `1.4`

    mass2 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the second binary
        component, default: `1.4`

    fmin : `float`, optional
        the lower frequency cut-off of the integral, default: `psd.df`

    fmax : `float`, optional
        the maximum frequency limit of the integral, defaults to
        innermost stable circular orbit (ISCO) frequency

    horizon : `bool`, optional
        if `True`, return the maximal 'horizon' sensitive distance, otherwise
        return the angle-averaged range, default: `False`

    Returns
    -------
    range : `~astropy.units.Quantity`
        the calculated inspiral range [Mpc]

    Examples
    --------
    Grab some data for LIGO-Livingston around GW150914 and generate a PSD:

    >>> from gwpy.timeseries import TimeSeries
    >>> hoft = TimeSeries.fetch_open_data('H1', 1126259446, 1126259478)
    >>> hoff = hoft.psd(fftlength=4)

    Now we can calculate the :func:`sensemon_range`:

    >>> from gwpy.astro import sensemon_range
    >>> r = sensemon_range(hoff, fmin=30)
    >>> print(r)
    70.4612102889 Mpc
    r   zIUpper frequency bound greater than %s-%s ISCO frequency of %s, using ISCO)r1   r   r   r2   )r#   g      ?Mpc)r   r   r   dfwarningswarnr0   r   r4   r   valuer#   ZHertz)r&   r1   r   r   fminfmaxr2   Zfiscofr3   	integrandr   r   r   sensemon_range   s    :

r>   c           
   C   s^   yddl m} m} W n2 tk
rF } zt|d |_ W d d }~X Y nX ddlm} | ||fS )Nr   )rangefind_root_redshifta  ; gwpy.astro's inspiral_range and inspiral_range_psd functions require the extra package 'inspiral-range' to provide cosmologically-corrected distance calculations, please install that package and try again, or install gwpy with the 'astro' extra via `python -m pip install gwpy[astro]`)CBCWaveform)inspiral_ranger?   r@   ModuleNotFoundErrorstrr    Zinspiral_range.waveformrA   )r?   r@   excrA   r   r   r   _import_inspiral_range   s    rF   c                s  t  \}}}jdj |  dk f||d|| fdd}	|r^j|	n |  dk j dk |	d}
|	\}}t||d|d dfd }td	|
 d
  |d
 j  dk  }|	 |
d   dk d |_|S )ac  Calculate the cosmology-corrected inspiral sensitive distance PSD

    This method returns the power spectral density (in ``Mpc**2 / Hz``) to
    which a compact binary inspiral with the given component masses would
    be detectable given the instrumental PSD. The calculation is defined in
    Belczynski et. al (2014):

    https://dx.doi.org/10.1088/0004-637x/789/2/120

    Parameters
    ----------
    psd : `~gwpy.frequencyseries.FrequencySeries`
        the instrumental power-spectral-density data

    snr : `float`, optional
        the signal-to-noise ratio for which to calculate range,
        default: `8`

    mass1 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the first binary
        component, default: `1.4`

    mass2 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the second binary
        component, default: `1.4`

    horizon : `bool`, optional
        if `True`, return the maximal 'horizon' luminosity distance, otherwise
        return the angle-averaged comoving distance, default: `False`

    **kwargs : `dict`, optional
        additional keyword arguments to `~inspiral_range.waveform.CBCWaveform`

    Returns
    -------
    rspec : `~gwpy.frequencyseries.FrequencySeries`
        the calculated inspiral sensitivity PSD [Mpc^2 / Hz]

    See also
    --------
    sensemon_range_psd
        for the method based on LIGO-T030276, also known as LIGO SenseMonitor
    inspiral-range
        the package which does heavy lifting for waveform simulation and
        cosmology calculations
    r   r   )m1m2c                s    j dk |  S )Nr   )SNRr9   )z)r<   inspiralr&   r1   r   r   <lambda>K      z$inspiral_range_psd.<locals>.<lambda>)z_horHF)Zbounds_errorZ
fill_value   r   z
Mpc^2 / Hz)rF   r0   r   r9   cosmoluminosity_distanceZz_scaler   type__array_finalize__r%   f0)r&   r1   r   r   r2   r   
range_funcr@   rA   rN   distZfzhzoutr   )r<   rK   r&   r1   r   inspiral_range_psd  s    2".

rZ   c                s   t  \}}	}
jdj}|p$jj}|p8t|d dd}||k||k @  |
|  f||d||	 fdd}tj|rj	|n||  j  |dd	d
S )a  Calculate the cosmology-corrected inspiral sensitive distance

    This method returns the distance (in megaparsecs) to which a compact
    binary inspiral with the given component masses would be detectable
    given the instrumental PSD. The calculation is defined in Belczynski
    et. al (2014):

    https://dx.doi.org/10.1088/0004-637x/789/2/120

    Parameters
    ----------
    psd : `~gwpy.frequencyseries.FrequencySeries`
        the instrumental power-spectral-density data

    snr : `float`, optional
        the signal-to-noise ratio for which to calculate range,
        default: `8`

    mass1 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the first binary
        component, default: `1.4`

    mass2 : `float`, `~astropy.units.Quantity`, optional
        the mass (`float` assumed in solar masses) of the second binary
        component, default: `1.4`

    fmin : `float`, optional
        the lower frequency cut-off of the integral, default: `psd.df`

    fmax : `float`, optional
        the maximum frequency limit of the integral, defaults to the rest-frame
        innermost stable circular orbit (ISCO) frequency

    horizon : `bool`, optional
        if `True`, return the maximal 'horizon' luminosity distance, otherwise
        return the angle-averaged comoving distance, default: `False`

    **kwargs : `dict`, optional
        additional keyword arguments to `~inspiral_range.waveform.CBCWaveform`

    Returns
    -------
    range : `~astropy.units.Quantity`
        the calculated inspiral range [Mpc]

    Examples
    --------
    Grab some data for LIGO-Livingston around GW150914 and generate a PSD:

    >>> from gwpy.timeseries import TimeSeries
    >>> hoft = TimeSeries.fetch_open_data('H1', 1126259446, 1126259478)
    >>> hoff = hoft.psd(fftlength=4)

    Now, we can calculate the :func:`inspiral_range`:

    >>> from gwpy.astro import inspiral_range
    >>> r = inspiral_range(hoff, fmin=30)
    >>> print(r)
    70.4612102889 Mpc

    See also
    --------
    sensemon_range
        for the method based on LIGO-T030276, also known as LIGO SenseMonitor
    inspiral-range
        the package which does heavy lifting for waveform simulation and
        cosmology calculations
    r   lower)which)rG   rH   c                s    j  |  S )N)rI   r9   )rJ   )r3   rK   r&   r1   r   r   rL     rM   z inspiral_range.<locals>.<lambda>)rN   rO   r5   )r#   )
rF   r0   r   r9   r6   r   r   r   rQ   rR   )r&   r1   r   r   r:   r;   r2   r   rV   r@   rA   r<   rN   r   )r3   rK   r&   r1   r   rB   \  s    HrB   {Gz?c             C   sT   | j dk}tj| tj d td tj  d }| | d | || j |   dS )aG  Calculate the frequency-dependent GW burst range from a strain PSD

    Parameters
    ----------
    psd : `~gwpy.frequencyseries.FrequencySeries`
        the instrumental power-spectral-density data

    snr : `float`, optional
        the signal-to-noise ratio for which to calculate range,
        default: `8`

    energy : `float`, optional
        the relative energy output of the GW burst,
        default: `0.01` (GRB-like burst)

    Returns
    -------
    rangespec : `~gwpy.frequencyseries.FrequencySeries`
        the burst range `FrequencySeries` [Mpc (default)]
    r   g?r   g      ?g      r5   )r0   r   r   ZM_sunr   r   r   )r&   r1   energyr3   ar   r   r   burst_range_spectrum  s
    
 ra   d     c       	      C   s   | j dj}|p| jdj}|p,|d j}||k||k @ }t| | ||dd }t|j|| }tj|||  |jdd dS )a  Calculate the integrated GRB-like GW burst range from a strain PSD

    Parameters
    ----------
    psd : `~gwpy.frequencyseries.FrequencySeries`
        the instrumental power-spectral-density data

    snr : `float`, optional
        the signal-to-noise ratio for which to calculate range,
        default: ``8``

    energy : `float`, optional
        the relative energy output of the GW burst, defaults to ``1e-2``
        for a GRB-like burst

    fmin : `float`, optional
        the lower frequency cutoff of the burst range integral,
        default: ``100 Hz``

    fmax : `float`, optional
        the upper frequency cutoff of the burst range integral,
        default: ``500 Hz``

    Returns
    -------
    range : `~astropy.units.Quantity`
        the GRB-like-burst sensitive range [Mpc (default)]

    Examples
    --------
    Grab some data for LIGO-Livingston around GW150914 and generate a PSD

    >>> from gwpy.timeseries import TimeSeries
    >>> hoft = TimeSeries.fetch_open_data('H1', 1126259446, 1126259478)
    >>> hoff = hoft.psd(fftlength=4)

    Now we can calculate the :func:`burst_range`:

    >>> from gwpy.astro import burst_range
    >>> r = burst_range(hoff, fmin=30)
    >>> print(r)
    42.5055584195 Mpc
    r   r[   )r1   r_   r   )r#   gUUUUUU?r5   )	r0   r   r9   r6   ra   r   r   r   r#   )	r&   r1   r_   r:   r;   r<   r3   r=   rY   r   r   r   burst_range  s    ,
rd   Zhannr"   c       
   	      sv   pddd dkr$dkr$t  n dkr0t t| ||||||d} t fdd| D }	|	|  |	d |	S )	a	  Measure timeseries trends of astrophysical detector range (Mpc)
    directly from strain

    Parameters
    ----------
    hoft : `~gwpy.timeseries.TimeSeries` or `~gwpy.spectrogram.Spectrogram`
        record of gravitational-wave strain output from a detector

    stride : `float`, optional
        desired step size (seconds) of range timeseries, required if
        `hoft` is an instance of `TimeSeries`

    fftlength : `float`, optional
        number of seconds in a single FFT

    overlap : `float`, optional
        number of seconds of overlap between FFTs, defaults to the
        recommended overlap for the given window (if given), or 0

    window : `str`, `numpy.ndarray`, optional
        window function to apply to timeseries prior to FFT, see
        :func:`scipy.signal.get_window` for details on acceptable
        formats

    method : `str`, optional
        FFT-averaging method, defaults to median averaging, see
        :meth:`~gwpy.timeseries.TimeSeries.spectrogram` for
        more details

    nproc : `int`, optional
        number of CPUs to use in parallel processing of FFTs, default: 1

    range_func : `callable`, optional
        the function to call to generate the range for each stride,
        defaults to ``inspiral_range`` unless ``energy`` is given
        as a keyword argument to the range function

    **rangekwargs : `dict`, optional
        additional keyword arguments to :func:`burst_range` or
        :func:`inspiral_range` (see "Notes" below), defaults to
        inspiral range with `mass1 = mass2 = 1.4` solar masses

    Returns
    -------
    out : `~gwpy.timeseries.TimeSeries`
        timeseries trends of astrophysical range

    Notes
    -----
    This method is designed to quantify a gravitational-wave detector's
    sensitive range as a function of time. It supports the range to
    compact binary inspirals and to unmodelled GW bursts, each a class
    of transient event.

    See also
    --------
    gwpy.timeseries.TimeSeries.spectrogram
        for the underlying power spectral density estimator
    inspiral_range
        for the function that computes inspiral range
    burst_range
        for the function that computes burst range
    range_spectrogram
        for a `~gwpy.spectrogram.Spectrogram` of the range integrand
    gffffff?)r   r   Nr_   )stride	fftlengthoverlapwindowmethodnprocc                s   g | ]} |fj qS r   )r9   ).0r&   )rV   rangekwargsr   r   
<listcomp>s  s    z$range_timeseries.<locals>.<listcomp>r5   )rd   rB   r!   r
   rT   r%   )
r   re   rf   rg   rh   ri   rj   rV   rl   rY   r   )rV   rl   r   range_timeseries  s    L

rn   c          	      s   pddddkr$dkr$t ndkr0tt| ||||||d} | jd}	td| jd}
tdt	|	d	 j
d
dd}|	|
k|	|k @  t fdd| D }||  |dkrdnd |
|_|S )a
  Calculate the average range or range power spectrogram (Mpc or
    Mpc^2 / Hz) directly from strain

    Parameters
    ----------
    hoft : `~gwpy.timeseries.TimeSeries`  or `~gwpy.spectrogram.Spectrogram`
        record of gravitational-wave strain output from a detector

    stride : `float`, optional
        number of seconds in a single PSD (i.e., step size of spectrogram),
        required if `hoft` is an instance of `TimeSeries`

    fftlength : `float`, optional
        number of seconds in a single FFT

    overlap : `float`, optional
        number of seconds of overlap between FFTs, defaults to the
        recommended overlap for the given window (if given), or 0

    window : `str`, `numpy.ndarray`, optional
        window function to apply to timeseries prior to FFT, see
        :func:`scipy.signal.get_window` for details on acceptable
        formats

    method : `str`, optional
        FFT-averaging method, defaults to median averaging, see
        :meth:`~gwpy.timeseries.TimeSeries.spectrogram` for
        more details

    nproc : `int`, optional
        number of CPUs to use in parallel processing of FFTs, default: 1

    fmin : `float`, optional
        low frequency cut-off (Hz), defaults to `1/fftlength`

    fmax : `float`, optional
        high frequency cut-off (Hz), defaults to Nyquist frequency of `hoft`

    range_func : `callable`, optional
        the function to call to generate the range for each stride,
        defaults to ``inspiral_range`` unless ``energy`` is given
        as a keyword argument to the range function

    **rangekwargs : `dict`, optional
        additional keyword arguments to :func:`burst_range_spectrum` or
        :func:`inspiral_range_psd` (see "Notes" below), defaults to
        inspiral range with `mass1 = mass2 = 1.4` solar masses

    Returns
    -------
    out : `~gwpy.spectrogram.Spectrogram`
        time-frequency spectrogram of astrophysical range

    Notes
    -----
    This method is designed to show the contribution to a
    gravitational-wave detector's sensitive range across frequency bins
    as a function of time. It supports the range to compact binary
    inspirals and to unmodelled GW bursts, each a class of transient
    event.

    If inspiral range is requested and `fmax` exceeds the frequency of the
    innermost stable circular orbit (ISCO), the output will extend only up
    to the latter.

    See also
    --------
    gwpy.timeseries.TimeSeries.spectrogram
        for the underlying power spectral density estimator
    inspiral_range_psd
        for the function that computes inspiral range integrand
    burst_range_spectrum
        for the function that computes burst range integrand
    range_timeseries
        for `TimeSeries` trends of the astrophysical range
    gffffff?)r   r   Nr_   )re   rf   rg   rh   ri   rj   r   r:   r;   r[   r\   )r]   c                s   g | ]}|  fj qS r   )r9   )rk   r&   )r3   rV   rl   r   r   rm     s    z%range_spectrogram.<locals>.<listcomp>r5   z
Mpc^2 / Hz)ra   rZ   r!   r0   r   r   r   popr6   r   r9   r	   rT   r%   rU   )r   re   rf   rg   rh   ri   rj   rV   rl   r<   r:   r;   rY   r   )r3   rV   rl   r   range_spectrogram{  s2    W
rp   )r+   r,   r,   F)r+   r,   r,   NNF)r+   r,   r,   F)r+   r,   r,   NNF)r+   r^   )r+   r^   rb   rc   )"__doc__r7   	functoolsr   mathr   Zscipy.integrater   Zscipy.interpolater   Zastropyr   r   r   r	   Z
timeseriesr
   utilsr   
__author____credits__ZDEFAULT_FFT_METHODr   r!   r*   r4   r>   rF   rZ   rB   ra   rd   rn   rp   r   r   r   r   <module>   sT   < 
NJ ` 
@X