# src/esc_etc/telescope.py
"""Telescope geometry for the ESC ETC.
Holds aperture/collecting-area geometry. Throughput lives in Sensor (mirroring
wcc-etc). Values are extracted from the vendored Lazuli telescope + ESC
(per-arm aperture), matching the original Observatory.make_STP logic.
"""
from pathlib import Path
import numpy as np
from astropy import units as u
from .meta import _MetaHolder_
from .sensor import DEFAULT_FILTER, filter_central_wavelength
[docs]
class Telescope(_MetaHolder_):
"""Collecting area (Lyot-stop), focal geometry, design wavelength and the
resolution element ``resel``. Geometry is fixed once built."""
def __init__(
self,
diameter_primary,
surface,
f_num,
focal_len,
primary_filter,
resel,
diameter_secondary,
jitter_rms,
zodi_profile_path,
):
self._mutable_parameters = [] # geometry is fixed post-config
self.diameter_primary = diameter_primary
self.surface = surface
self.f_num = f_num
self.focal_len = focal_len
self.primary_filter = primary_filter
self.resel = resel
self.diameter_secondary = diameter_secondary
self.jitter_rms = jitter_rms
self.zodi_profile_path = zodi_profile_path
self._init_meta()
[docs]
@classmethod
def from_config(
cls,
telescope_config,
instrument_config,
telescope_support_path,
arm="arm_a",
filter=None,
):
cp = instrument_config["common_params"][arm]
diameter_primary = u.Quantity(cp["pupil"]["aper_clear_OD"])
lyot = cp["optics"]["lyot_stop"]["lyot_ratio"]
surface = np.pi * (0.5 * lyot * diameter_primary) ** 2
diameter_secondary = u.Quantity(
telescope_config["telescope"]["optics"]["m2"]["aper_clear_OD"]
)
primary_filter = (
filter_central_wavelength(filter)
if filter
else u.Quantity(cp["optics"]["filter"]["lam_central"])
).to("AA")
f_num = cp["general"]["f_number_sci_cam"]
focal_len = f_num * diameter_primary
jitter_rms = u.Quantity(
telescope_config["observatory"]["pointing"]["jitter_rms"]
)
# Photometric extraction aperture: a disk of diameter lambda/D. Shared by
# the background solid angle (Simulation) and detector pixel count (Sensor).
resel = np.pi * (((primary_filter) / 2 / diameter_primary * u.radian) ** 2).to(
u.arcsec**2, equivalencies=u.dimensionless_angles()
)
zodi_profile_path = (
Path(telescope_support_path)
/ Path(telescope_config["astrophysics"]["zodi"]["profile"])
).as_posix()
return cls(
diameter_primary,
surface,
f_num,
focal_len,
primary_filter,
resel,
diameter_secondary,
jitter_rms,
zodi_profile_path,
)
[docs]
@classmethod
def from_default(cls, arm="arm_a", filter=DEFAULT_FILTER):
from . import uasal_config
return cls.from_config(
telescope_config=uasal_config.load_config_values(uasal_config.TELESCOPE),
instrument_config=uasal_config.load_config_values(uasal_config.INSTRUMENT),
telescope_support_path=uasal_config.get_data_path(uasal_config.TELESCOPE),
arm=arm,
filter=filter,
)