Skip to content

Projection

gwmock_signal.projection

Projection of GW polarizations onto detector networks.

project_polarizations_to_network(polarizations, detector_names, *, right_ascension, declination, polarization_angle, earth_rotation=True)

Project tensor plus/cross strains onto detectors using PyCBC geometry.

Uses antenna patterns F_plus, F_cross and geocenter time delays from PyCBC Detector. Polarizations are interpolated in time with cubic splines (see user guide for caveats at edges).

Parameters:

Name Type Description Default
polarizations Mapping[str, TimeSeries]

Mapping containing plus and cross GWpy time series on a common grid.

required
detector_names Sequence[DetectorSpec]

Sequence of IFO codes (e.g. H1, L1, V1) or :class:~gwmock_signal.detector.CustomDetector instances, or a mix of both.

required
right_ascension float

Source right ascension in radians.

required
declination float

Source declination in radians.

required
polarization_angle float

Polarization angle psi in radians (tensor modes).

required
earth_rotation bool

If True, evaluate antenna patterns at time-dependent GPS times (recommended for longer signals). If False, use a single reference time at the segment midpoint for patterns and delays.

True

Returns:

Type Description
dict[str, TimeSeries]

Mapping from each detector name to the projected strain as a GWpy time

dict[str, TimeSeries]

series (same length and sample rate as the inputs).

Raises:

Type Description
TypeError

If polarizations is not a mapping of GWpy series as required.

ValueError

If keys are missing, time grids disagree, or a detector name is not recognized by PyCBC.

Source code in src/gwmock_signal/projection/network.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def project_polarizations_to_network(  # noqa: PLR0913
    polarizations: Mapping[str, GWpyTimeSeries],
    detector_names: Sequence[DetectorSpec],
    *,
    right_ascension: float,
    declination: float,
    polarization_angle: float,
    earth_rotation: bool = True,
) -> dict[str, GWpyTimeSeries]:
    """Project tensor plus/cross strains onto detectors using PyCBC geometry.

    Uses antenna patterns ``F_plus``, ``F_cross`` and geocenter time delays from
    PyCBC [`Detector`](https://pycbc.org/pycbc/latest/html/detector.html). Polarizations are interpolated in
    time with cubic splines (see user guide for caveats at edges).

    Args:
        polarizations: Mapping containing ``plus`` and ``cross`` GWpy time series
            on a common grid.
        detector_names: Sequence of IFO codes (e.g. ``H1``, ``L1``, ``V1``) or
            :class:`~gwmock_signal.detector.CustomDetector` instances, or a mix
            of both.
        right_ascension: Source right ascension in radians.
        declination: Source declination in radians.
        polarization_angle: Polarization angle psi in radians (tensor modes).
        earth_rotation: If ``True``, evaluate antenna patterns at time-dependent
            GPS times (recommended for longer signals). If ``False``, use a single
            reference time at the segment midpoint for patterns and delays.

    Returns:
        Mapping from each detector name to the projected strain as a GWpy time
        series (same length and sample rate as the inputs).

    Raises:
        TypeError: If ``polarizations`` is not a mapping of GWpy series as required.
        ValueError: If keys are missing, time grids disagree, or a detector name
            is not recognized by PyCBC.
    """
    hp, hc = _validate_polarizations(polarizations)
    normalized_names = [d if isinstance(d, str) else d.name for d in detector_names]
    if len(set(normalized_names)) != len(normalized_names):
        raise ValueError("detector_names must not contain duplicates.")
    detectors = _make_detectors(list(detector_names))

    time_array = cast(np.ndarray, hp.times.to_value())
    reference_time = float(0.5 * (time_array[0] + time_array[-1]))
    time_array_wrt_reference = time_array - reference_time

    minimum_number_of_data_points = 4
    interp_kind = "cubic" if len(time_array_wrt_reference) >= minimum_number_of_data_points else "linear"

    hp_func = interp1d(
        time_array_wrt_reference,
        hp.to_value(),
        kind=interp_kind,
        bounds_error=False,
        fill_value=0.0,
    )
    hc_func = interp1d(
        time_array_wrt_reference,
        hc.to_value(),
        kind=interp_kind,
        bounds_error=False,
        fill_value=0.0,
    )

    strains: dict[str, GWpyTimeSeries] = {}

    for name, det in detectors:
        if earth_rotation:
            time_delays = det.time_delay_from_earth_center(
                right_ascension=right_ascension,
                declination=declination,
                t_gps=time_array,
            )
            shifted_times = time_array_wrt_reference - time_delays
            fp_vals, fc_vals = det.antenna_pattern(
                right_ascension=right_ascension,
                declination=declination,
                polarization=polarization_angle,
                t_gps=time_array + time_delays,
                polarization_type="tensor",
            )
        else:
            time_delays = det.time_delay_from_earth_center(
                right_ascension=right_ascension,
                declination=declination,
                t_gps=reference_time,
            )
            antenna = det.antenna_pattern(
                right_ascension=right_ascension,
                declination=declination,
                polarization=polarization_angle,
                t_gps=reference_time,
                polarization_type="tensor",
            )
            fp_vals, fc_vals = antenna
            shifted_times = time_array_wrt_reference - time_delays

        hp_shifted = hp_func(shifted_times)
        hc_shifted = hc_func(shifted_times)
        response = fp_vals * hp_shifted + fc_vals * hc_shifted

        strains[name] = GWpyTimeSeries(
            response,
            t0=float(time_array[0]),
            sample_rate=hp.sample_rate,
            name=name,
        )

    return strains

For usage examples, see the User guide — Detector projection examples.