Source code for ray_tracing.backends.interface

"""Ray-tracing request types, backend contracts, and helpers.

See :class:`RayTracingBackend` for the common backend contract.
"""

from dataclasses import dataclass, field
from importlib import import_module
from typing import Protocol

from simtools.constants import RAY_TRACING_BACKEND_CLASSES
from simtools.context import RunContext
from simtools.utils import names


[docs] class RayTracingBackend(Protocol): """Contract implemented by ray-tracing backends. A backend traces star light through the optics of one telescope model. The ray-tracing applications describe what to trace (``StarImageRequest``) and analyse what comes back; how a backend traces, which files it writes and what it keeps in memory stays inside the backend. A backend class is constructed with the telescope model, the site model, a label, the telescope zenith angle [deg] and the single-mirror flag. Backends are selected by name through ``get_backend``. """
[docs] def simulate(self, requests, test=False, force=False, compress_photons=True, max_workers=8): """Trace the requested star images."""
[docs] def psf_image( self, request, focal_length, containment_fraction, use_rx=False, save_photons=False ): """Return the ``PSFImage`` of one traced image and whether the structure was traced. If the telescope structure was traced, ``telescope_transmission`` does not apply. """
[docs] def incidence_angles(self, request, work_directory, name, with_mirrors, keep_files=False): """Return per-photon incidence angles on the focal surface (and mirrors). Also returns the intermediate files written. """
[docs] class LightSourceRayTracingBackend(RayTracingBackend, Protocol): """Ray-tracing backend that also traces calibration light sources. Such backends set ``traces_light_sources``; for other backends sim_telarray's LightEmission programs make the light and sim_telarray traces it. """
[docs] def camera_photons(self, request): """Return, for each light pulse of a ``LightSourceRequest``, an iterator over blocks. Each block holds the photons crossing the camera plane (through the focal-surface vertex, normal to the optical axis; telescope frame, metres): ``position_m``, ``direction``, ``time_ns``, ``weight`` (photons) and ``wavelength_nm``. """
[docs] @dataclass(frozen=True) class StarImageRequest: """ One star image to trace. Parameters ---------- off_axis_x, off_axis_y: float Star position in the camera [deg]. source_distance_km: float Source distance [km]. seeds: tuple of tuple of int (Instrument seed, simulation seed) of each random instrument instance. photons_per_instance: int Photons traced per instance. mirror_number: int or None Mirror in single-mirror mode. parameter_changes: dict Model-parameter values replacing those of the telescope model. """ off_axis_x: float off_axis_y: float source_distance_km: float seeds: tuple photons_per_instance: int mirror_number: int | None = None parameter_changes: dict = field(default_factory=dict, hash=False, compare=False) @property def key(self): """Return the identifier of the image (offsets and mirror).""" return self.off_axis_x, self.off_axis_y, self.mirror_number
[docs] @dataclass(frozen=True) class LightSourceRequest: """ The light of a calibration light source (a point emitter), pulse by pulse. Parameters ---------- position_m: tuple of float Emitter position in the telescope frame [m] (+z along the optical axis). axis: tuple of float Unit vector of the axis of the angular profile (telescope frame). angular_profile: tuple of numpy.ndarray Angles to the axis [deg] and relative intensities per solid angle. pulse: tuple of numpy.ndarray or None Emission times [ns] and relative rates; None: instantaneous. wavelength_nm: float Wavelength [nm]. photons_per_pulse: tuple of float Photons emitted into 4 pi in each pulse. telescope_zenith: float Telescope zenith angle [deg]. seeds: tuple of int (Instrument seed, simulation seed). inside_telescope: bool The light starts inside the telescope on its way to the camera (flasher): it reaches the camera directly, without the mirrors. camera_radius_m: float or None Radius of the camera [m]; the light of a source inside the telescope is made only on its way to the camera. """ position_m: tuple axis: tuple angular_profile: tuple = field(hash=False, compare=False) pulse: tuple | None = field(hash=False, compare=False) wavelength_nm: float photons_per_pulse: tuple telescope_zenith: float seeds: tuple inside_telescope: bool = False camera_radius_m: float | None = None
[docs] def image_file_name(backend, request, file_type, suffix): """ Return the name of a file of one traced image. Parameters ---------- backend: object Backend (telescope model, zenith angle, label and single-mirror flag). request: StarImageRequest Star image. file_type: str File type (e.g. ``ray_tracing_photons``). suffix: str File suffix. Returns ------- str File name. """ return names.generate_file_name( file_type=file_type, suffix=suffix, site=backend.telescope_model.site, telescope_model_name=backend.telescope_model.name, source_distance=None if backend.single_mirror_mode else request.source_distance_km, zenith_angle=backend.zenith_angle, off_axis_x=request.off_axis_x, off_axis_y=request.off_axis_y, mirror_number=request.mirror_number if backend.single_mirror_mode else None, label=backend.label, )
[docs] def get_backend(name=None, *, context=None): """ Return the ray-tracing backend class. Parameters ---------- name: str or None Backend name; defaults to the context's configured backend. context : RunContext, optional Run configuration. The environment context is used when omitted. Returns ------- type Backend class. """ context = context or RunContext.from_environment() name = name or context.software.ray_tracing_backend try: module_name, class_name = RAY_TRACING_BACKEND_CLASSES[name].split(":") except KeyError as exc: valid_names = ", ".join(RAY_TRACING_BACKEND_CLASSES) raise ValueError( f"Unknown ray-tracing backend {name!r}; choose from: {valid_names}" ) from exc return getattr(import_module(module_name), class_name)