Skip to content

Network

gwmock_signal.network

Named detector network catalog for ground-based GW observatories.

Network dataclass

A detector network: a named, ordered collection of detector specs.

Attributes:

Name Type Description
name str

Human-readable label for the network.

detector_names tuple[str | CustomDetector, ...]

Ordered tuple of LAL detector prefix strings (e.g. "H1") or :class:~gwmock_signal.detector.CustomDetector instances.

Source code in src/gwmock_signal/network.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
@dataclass(frozen=True)
class Network:
    """A detector network: a named, ordered collection of detector specs.

    Attributes:
        name: Human-readable label for the network.
        detector_names: Ordered tuple of LAL detector prefix strings (e.g.
            ``"H1"``) or
            :class:`~gwmock_signal.detector.CustomDetector` instances.
    """

    name: str
    detector_names: tuple[str | CustomDetector, ...]

    # ------------------------------------------------------------------
    # Constructors
    # ------------------------------------------------------------------

    @classmethod
    def from_detectors(
        cls,
        detectors: Sequence[str | CustomDetector],
        *,
        name: str = "",
    ) -> Network:
        """Construct a :class:`Network` from any sequence of detector specs.

        String entries are validated against LAL's runtime detector list
        (``lal.cached_detector_by_prefix``), so any detector code that the
        installed LAL bindings know about works here without hard-coding.
        :class:`~gwmock_signal.detector.CustomDetector` instances are accepted
        as-is for non-standard geometries.

        Args:
            detectors: Sequence of LAL detector code strings (e.g. ``"H1"``)
                or :class:`~gwmock_signal.detector.CustomDetector` instances.
            name: Optional human-readable label.  Defaults to the
                comma-joined detector names.

        Returns:
            A :class:`Network` containing exactly the specified detectors.

        Raises:
            ValueError: If any string entry is not in LAL's available
                detector set, or if *detectors* is empty.
        """
        from gwmock_signal.detector import CustomDetector  # noqa: PLC0415

        if not detectors:
            raise ValueError("detectors must be a non-empty sequence.")

        lal_codes: set[str] = set(list_lal_detectors())
        validated: list[str | CustomDetector] = []
        for det in detectors:
            if isinstance(det, CustomDetector):
                validated.append(det)
            else:
                code = str(det)
                if code not in lal_codes:
                    raise ValueError(
                        f"Unknown LAL detector code {code!r}. "
                        f"Run Network.list_lal_detectors() to see available codes, "
                        "or use a CustomDetector for non-standard geometries."
                    )
                validated.append(code)

        auto_name = name or ",".join(d if isinstance(d, str) else d.name for d in validated)
        return cls(name=auto_name, detector_names=tuple(validated))

    @classmethod
    def from_name(cls, alias: str) -> Network:
        """Construct a :class:`Network` from a named preset.

        Args:
            alias: One of the pre-defined network names returned by
                :meth:`list_names`.

        Returns:
            A :class:`Network` whose ``detector_names`` are the detector codes
            for that preset.

        Raises:
            ValueError: If *alias* is not in the named presets.
        """
        if alias in _NETWORK_PRESETS:
            return cls(name=alias, detector_names=_NETWORK_PRESETS[alias])
        if alias in _FILE_BACKED_PRESET_ALIASES:
            return cls.from_preset(alias)
        raise ValueError(f"Unknown network {alias!r}. Known networks: {cls.list_names()}")

    @classmethod
    def from_preset(cls, alias: str) -> Network:
        """Construct a :class:`Network` from a bundled YAML/JSON detector preset.

        Bundled presets live under ``gwmock_signal.data.detectors`` and are
        resolved with :mod:`importlib.resources`, so they work from source trees
        and installed wheels alike.

        Args:
            alias: One of the bundled preset aliases returned by
                :meth:`list_names`, for example ``"ET-Triangle-Sardinia"``.

        Returns:
            A :class:`Network` loaded from the packaged detector geometry file.

        Raises:
            ValueError: If *alias* does not map to a bundled preset file.
            FileNotFoundError: If the mapped packaged preset file is missing.
        """
        resource = _preset_resource(alias)
        if not resource.is_file():
            raise FileNotFoundError(f"Bundled detector preset for {alias!r} is missing.")
        with as_file(resource) as path:
            net = cls.from_file(path)
        return cls.from_detectors(net.detector_names, name=net.name)

    @classmethod
    def from_file(cls, path: str | Path) -> Network:
        """Load a :class:`Network` from a YAML, JSON, or deprecated ``.interferometer`` file.

        The file must have a top-level ``name`` key (str) and a ``detectors``
        key containing a non-empty list of detector entries.  Each entry must
        have a ``name`` field. If any geometry key is present a
        :class:`~gwmock_signal.detector.CustomDetector` is constructed;
        otherwise the name is treated as a plain LAL detector code string.

        ``.interferometer`` files are treated as Bilby compatibility shims for
        a single custom detector and emit a :class:`DeprecationWarning`. Migrate
        those files to the YAML detector preset/network format instead.

        **Angle conventions** — every angle parameter accepts either a degrees
        variant (``*_deg``) or a radians variant (``*_rad``), but *not both* for
        the same parameter:

        +-----------------------+-------------------+-----------+
        | Parameter             | Degrees key       | Radians key |
        +=======================+===================+=============+
        | Geodetic latitude     | ``latitude_deg``  | ``latitude_rad`` |
        | Geodetic longitude    | ``longitude_deg`` | ``longitude_rad`` |
        | x-arm azimuth         | ``xarm_azimuth_deg`` | ``xarm_azimuth_rad`` |
        | y-arm azimuth         | ``yarm_azimuth_deg`` | ``yarm_azimuth_rad`` |
        | x-arm tilt (optional) | ``xarm_tilt_deg`` | ``xarm_tilt_rad`` |
        | y-arm tilt (optional) | ``yarm_tilt_deg`` | ``yarm_tilt_rad`` |
        +-----------------------+-------------------+-----------+

        ``elevation_m`` is always in metres.  Tilt fields default to ``0.0``
        when absent.

        Args:
            path: Path to a ``.yaml``, ``.yml``, ``.json``, or
                deprecated ``.interferometer`` file.

        Returns:
            A :class:`Network` whose ``detector_names`` contains str entries
            for LAL detector aliases and
            :class:`~gwmock_signal.detector.CustomDetector` entries for
            user-defined geometries.

        Raises:
            ValueError: If the file extension is unsupported, a required
                angle is absent, both ``*_deg`` and ``*_rad`` are given for
                the same angle, or a geometry value is out of range.
        """
        path = Path(path)
        if path.suffix.lower() == ".interferometer":
            from gwmock_signal.io.interferometer_format import interferometer_config_to_custom_detector  # noqa: PLC0415

            detector = interferometer_config_to_custom_detector(path)
            return cls.from_detectors((detector,), name=detector.name)

        data = _load_data(path)

        if not isinstance(data, dict):
            raise ValueError("Network config must be a YAML/JSON mapping at the top level.")
        if "name" not in data:
            raise ValueError("Missing required field: 'name'")
        if "detectors" not in data:
            raise ValueError("Missing required field: 'detectors'")

        detectors_raw = data["detectors"]
        if not isinstance(detectors_raw, list) or len(detectors_raw) == 0:
            raise ValueError("'detectors' must be a non-empty list.")

        detectors: list[str | CustomDetector] = []
        for entry in detectors_raw:
            if not isinstance(entry, dict) or "name" not in entry:
                raise ValueError("Each detector entry must be a mapping with a 'name' field.")
            detectors.append(_detector_from_entry(entry))

        return cls.from_detectors(detectors, name=data["name"])

    # ------------------------------------------------------------------
    # Listing helpers
    # ------------------------------------------------------------------

    @classmethod
    def list_names(cls) -> list[str]:
        """Return a sorted list of all named network and bundled preset aliases."""
        return sorted(set(_NETWORK_PRESETS) | set(_FILE_BACKED_PRESET_ALIASES))

    @classmethod
    def list_lal_detectors(cls) -> list[str]:
        """Return every detector code available in the installed LAL, sorted.

        Returns:
            Sorted list of LAL detector prefix strings
            (e.g. ``['E0', 'E1', 'H1', 'L1', 'V1', ...]``).
        """
        return list_lal_detectors()

from_detectors(detectors, *, name='') classmethod

Construct a :class:Network from any sequence of detector specs.

String entries are validated against LAL's runtime detector list (lal.cached_detector_by_prefix), so any detector code that the installed LAL bindings know about works here without hard-coding. :class:~gwmock_signal.detector.CustomDetector instances are accepted as-is for non-standard geometries.

Parameters:

Name Type Description Default
detectors Sequence[str | CustomDetector]

Sequence of LAL detector code strings (e.g. "H1") or :class:~gwmock_signal.detector.CustomDetector instances.

required
name str

Optional human-readable label. Defaults to the comma-joined detector names.

''

Returns:

Name Type Description
A Network

class:Network containing exactly the specified detectors.

Raises:

Type Description
ValueError

If any string entry is not in LAL's available detector set, or if detectors is empty.

Source code in src/gwmock_signal/network.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@classmethod
def from_detectors(
    cls,
    detectors: Sequence[str | CustomDetector],
    *,
    name: str = "",
) -> Network:
    """Construct a :class:`Network` from any sequence of detector specs.

    String entries are validated against LAL's runtime detector list
    (``lal.cached_detector_by_prefix``), so any detector code that the
    installed LAL bindings know about works here without hard-coding.
    :class:`~gwmock_signal.detector.CustomDetector` instances are accepted
    as-is for non-standard geometries.

    Args:
        detectors: Sequence of LAL detector code strings (e.g. ``"H1"``)
            or :class:`~gwmock_signal.detector.CustomDetector` instances.
        name: Optional human-readable label.  Defaults to the
            comma-joined detector names.

    Returns:
        A :class:`Network` containing exactly the specified detectors.

    Raises:
        ValueError: If any string entry is not in LAL's available
            detector set, or if *detectors* is empty.
    """
    from gwmock_signal.detector import CustomDetector  # noqa: PLC0415

    if not detectors:
        raise ValueError("detectors must be a non-empty sequence.")

    lal_codes: set[str] = set(list_lal_detectors())
    validated: list[str | CustomDetector] = []
    for det in detectors:
        if isinstance(det, CustomDetector):
            validated.append(det)
        else:
            code = str(det)
            if code not in lal_codes:
                raise ValueError(
                    f"Unknown LAL detector code {code!r}. "
                    f"Run Network.list_lal_detectors() to see available codes, "
                    "or use a CustomDetector for non-standard geometries."
                )
            validated.append(code)

    auto_name = name or ",".join(d if isinstance(d, str) else d.name for d in validated)
    return cls(name=auto_name, detector_names=tuple(validated))

from_file(path) classmethod

Load a :class:Network from a YAML, JSON, or deprecated .interferometer file.

The file must have a top-level name key (str) and a detectors key containing a non-empty list of detector entries. Each entry must have a name field. If any geometry key is present a :class:~gwmock_signal.detector.CustomDetector is constructed; otherwise the name is treated as a plain LAL detector code string.

.interferometer files are treated as Bilby compatibility shims for a single custom detector and emit a :class:DeprecationWarning. Migrate those files to the YAML detector preset/network format instead.

Angle conventions — every angle parameter accepts either a degrees variant (*_deg) or a radians variant (*_rad), but not both for the same parameter:

+-----------------------+-------------------+-----------+ | Parameter | Degrees key | Radians key | +=======================+===================+=============+ | Geodetic latitude | latitude_deg | latitude_rad | | Geodetic longitude | longitude_deg | longitude_rad | | x-arm azimuth | xarm_azimuth_deg | xarm_azimuth_rad | | y-arm azimuth | yarm_azimuth_deg | yarm_azimuth_rad | | x-arm tilt (optional) | xarm_tilt_deg | xarm_tilt_rad | | y-arm tilt (optional) | yarm_tilt_deg | yarm_tilt_rad | +-----------------------+-------------------+-----------+

elevation_m is always in metres. Tilt fields default to 0.0 when absent.

Parameters:

Name Type Description Default
path str | Path

Path to a .yaml, .yml, .json, or deprecated .interferometer file.

required

Returns:

Name Type Description
A Network

class:Network whose detector_names contains str entries

Network

for LAL detector aliases and

Network

class:~gwmock_signal.detector.CustomDetector entries for

Network

user-defined geometries.

Raises:

Type Description
ValueError

If the file extension is unsupported, a required angle is absent, both *_deg and *_rad are given for the same angle, or a geometry value is out of range.

Source code in src/gwmock_signal/network.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
@classmethod
def from_file(cls, path: str | Path) -> Network:
    """Load a :class:`Network` from a YAML, JSON, or deprecated ``.interferometer`` file.

    The file must have a top-level ``name`` key (str) and a ``detectors``
    key containing a non-empty list of detector entries.  Each entry must
    have a ``name`` field. If any geometry key is present a
    :class:`~gwmock_signal.detector.CustomDetector` is constructed;
    otherwise the name is treated as a plain LAL detector code string.

    ``.interferometer`` files are treated as Bilby compatibility shims for
    a single custom detector and emit a :class:`DeprecationWarning`. Migrate
    those files to the YAML detector preset/network format instead.

    **Angle conventions** — every angle parameter accepts either a degrees
    variant (``*_deg``) or a radians variant (``*_rad``), but *not both* for
    the same parameter:

    +-----------------------+-------------------+-----------+
    | Parameter             | Degrees key       | Radians key |
    +=======================+===================+=============+
    | Geodetic latitude     | ``latitude_deg``  | ``latitude_rad`` |
    | Geodetic longitude    | ``longitude_deg`` | ``longitude_rad`` |
    | x-arm azimuth         | ``xarm_azimuth_deg`` | ``xarm_azimuth_rad`` |
    | y-arm azimuth         | ``yarm_azimuth_deg`` | ``yarm_azimuth_rad`` |
    | x-arm tilt (optional) | ``xarm_tilt_deg`` | ``xarm_tilt_rad`` |
    | y-arm tilt (optional) | ``yarm_tilt_deg`` | ``yarm_tilt_rad`` |
    +-----------------------+-------------------+-----------+

    ``elevation_m`` is always in metres.  Tilt fields default to ``0.0``
    when absent.

    Args:
        path: Path to a ``.yaml``, ``.yml``, ``.json``, or
            deprecated ``.interferometer`` file.

    Returns:
        A :class:`Network` whose ``detector_names`` contains str entries
        for LAL detector aliases and
        :class:`~gwmock_signal.detector.CustomDetector` entries for
        user-defined geometries.

    Raises:
        ValueError: If the file extension is unsupported, a required
            angle is absent, both ``*_deg`` and ``*_rad`` are given for
            the same angle, or a geometry value is out of range.
    """
    path = Path(path)
    if path.suffix.lower() == ".interferometer":
        from gwmock_signal.io.interferometer_format import interferometer_config_to_custom_detector  # noqa: PLC0415

        detector = interferometer_config_to_custom_detector(path)
        return cls.from_detectors((detector,), name=detector.name)

    data = _load_data(path)

    if not isinstance(data, dict):
        raise ValueError("Network config must be a YAML/JSON mapping at the top level.")
    if "name" not in data:
        raise ValueError("Missing required field: 'name'")
    if "detectors" not in data:
        raise ValueError("Missing required field: 'detectors'")

    detectors_raw = data["detectors"]
    if not isinstance(detectors_raw, list) or len(detectors_raw) == 0:
        raise ValueError("'detectors' must be a non-empty list.")

    detectors: list[str | CustomDetector] = []
    for entry in detectors_raw:
        if not isinstance(entry, dict) or "name" not in entry:
            raise ValueError("Each detector entry must be a mapping with a 'name' field.")
        detectors.append(_detector_from_entry(entry))

    return cls.from_detectors(detectors, name=data["name"])

from_name(alias) classmethod

Construct a :class:Network from a named preset.

Parameters:

Name Type Description Default
alias str

One of the pre-defined network names returned by :meth:list_names.

required

Returns:

Name Type Description
A Network

class:Network whose detector_names are the detector codes

Network

for that preset.

Raises:

Type Description
ValueError

If alias is not in the named presets.

Source code in src/gwmock_signal/network.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@classmethod
def from_name(cls, alias: str) -> Network:
    """Construct a :class:`Network` from a named preset.

    Args:
        alias: One of the pre-defined network names returned by
            :meth:`list_names`.

    Returns:
        A :class:`Network` whose ``detector_names`` are the detector codes
        for that preset.

    Raises:
        ValueError: If *alias* is not in the named presets.
    """
    if alias in _NETWORK_PRESETS:
        return cls(name=alias, detector_names=_NETWORK_PRESETS[alias])
    if alias in _FILE_BACKED_PRESET_ALIASES:
        return cls.from_preset(alias)
    raise ValueError(f"Unknown network {alias!r}. Known networks: {cls.list_names()}")

from_preset(alias) classmethod

Construct a :class:Network from a bundled YAML/JSON detector preset.

Bundled presets live under gwmock_signal.data.detectors and are resolved with :mod:importlib.resources, so they work from source trees and installed wheels alike.

Parameters:

Name Type Description Default
alias str

One of the bundled preset aliases returned by :meth:list_names, for example "ET-Triangle-Sardinia".

required

Returns:

Name Type Description
A Network

class:Network loaded from the packaged detector geometry file.

Raises:

Type Description
ValueError

If alias does not map to a bundled preset file.

FileNotFoundError

If the mapped packaged preset file is missing.

Source code in src/gwmock_signal/network.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
@classmethod
def from_preset(cls, alias: str) -> Network:
    """Construct a :class:`Network` from a bundled YAML/JSON detector preset.

    Bundled presets live under ``gwmock_signal.data.detectors`` and are
    resolved with :mod:`importlib.resources`, so they work from source trees
    and installed wheels alike.

    Args:
        alias: One of the bundled preset aliases returned by
            :meth:`list_names`, for example ``"ET-Triangle-Sardinia"``.

    Returns:
        A :class:`Network` loaded from the packaged detector geometry file.

    Raises:
        ValueError: If *alias* does not map to a bundled preset file.
        FileNotFoundError: If the mapped packaged preset file is missing.
    """
    resource = _preset_resource(alias)
    if not resource.is_file():
        raise FileNotFoundError(f"Bundled detector preset for {alias!r} is missing.")
    with as_file(resource) as path:
        net = cls.from_file(path)
    return cls.from_detectors(net.detector_names, name=net.name)

list_lal_detectors() classmethod

Return every detector code available in the installed LAL, sorted.

Returns:

Type Description
list[str]

Sorted list of LAL detector prefix strings

list[str]

(e.g. ['E0', 'E1', 'H1', 'L1', 'V1', ...]).

Source code in src/gwmock_signal/network.py
378
379
380
381
382
383
384
385
386
@classmethod
def list_lal_detectors(cls) -> list[str]:
    """Return every detector code available in the installed LAL, sorted.

    Returns:
        Sorted list of LAL detector prefix strings
        (e.g. ``['E0', 'E1', 'H1', 'L1', 'V1', ...]``).
    """
    return list_lal_detectors()

list_names() classmethod

Return a sorted list of all named network and bundled preset aliases.

Source code in src/gwmock_signal/network.py
373
374
375
376
@classmethod
def list_names(cls) -> list[str]:
    """Return a sorted list of all named network and bundled preset aliases."""
    return sorted(set(_NETWORK_PRESETS) | set(_FILE_BACKED_PRESET_ALIASES))

list_lal_detectors()

Return every detector code available in the installed LAL bindings.

Source code in src/gwmock_signal/network.py
134
135
136
def list_lal_detectors() -> list[str]:
    """Return every detector code available in the installed LAL bindings."""
    return sorted(str(code) for code in lal.cached_detector_by_prefix)

Used by the command-line interface (--network) and by library workflows that need named IFO sets. For antenna-pattern projection of \(h_{+}\), \(h_{\times}\), see Projection.

Bundled detector-geometry presets currently include:

  • ET-Triangle-Sardinia (compatibility alias: ET-Sardinia)
  • ET-Triangle-EMR (compatibility alias: ET-EMR)
  • ET-2L-Aligned
  • ET-2L-Misaligned