Source code for phise.classes.telescope
import astropy.units as u
import numpy as np
[docs]
class Telescope:
"""Telescope representation used by the interferometer.
Args:
a (u.Quantity): Collecting area as an Astropy quantity in a surface
area unit (e.g., ``m**2``).
r (u.Quantity): Relative position on the plane as an Astropy quantity
in a length unit (e.g., ``m``). Must be a 2-vector (shape (2,)).
name (str): Human-readable name for the telescope.
Notes:
Exceptions are raised by the setters if units are incorrect.
"""
__slots__ = ('_parent_interferometer', '_a', '_a_unit', '_r', '_r_unit', '_name')
def __init__(self, a: u.Quantity, r: u.Quantity, name: str = 'Unnamed Telescope'):
self._parent_interferometer = None
self.a = a
self.r = r
self.name = name
def __str__(self) -> str:
res = f'Telescope "{self.name}"\n'
res += f' Area: {self.a:.2g}\n'
res += f" Relative position: [{', '.join([f'{i:.2g}' for i in self.r.value])}] {self.r.unit}"
return res
def __repr__(self) -> str:
return self.__str__()
@property
def a(self) -> u.Quantity:
"""Collecting area (u.Quantity in m**2).
Returns:
u.Quantity: Mirror collecting area in square meters.
"""
return (self._a * u.m**2).to(self._a_unit)
@a.setter
def a(self, a: u.Quantity):
if not isinstance(a, u.Quantity):
raise TypeError('a must be an astropy Quantity')
try:
new_a = a.to(u.m ** 2).value
except u.UnitConversionError:
raise ValueError('a must be in a surface area unit')
self._a_unit = a.unit
self._a = new_a
if self.parent_interferometer is not None:
self.parent_interferometer.parent_ctx._update_pf()
@property
def r(self) -> u.Quantity:
"""Relative telescope position on the plane (u.Quantity in m, shape (2,)).
Returns:
u.Quantity: 2-vector [x, y] position in meters.
"""
return (self._r * u.m).to(self._r_unit)
@r.setter
def r(self, r: u.Quantity):
if not isinstance(r, u.Quantity):
raise TypeError('r must be an astropy Quantity')
try:
new_r = r.to(u.m).value
except u.UnitConversionError:
raise ValueError('r must be in a length unit')
if r.shape != (2,):
raise ValueError('r must have a shape of (2,)')
self._r_unit = r.unit
self._r = new_r
if self.parent_interferometer is not None:
self.parent_interferometer.parent_ctx._update_p()
@property
def name(self) -> str:
"""Telescope name.
Returns:
str: Readable name.
"""
return self._name
@name.setter
def name(self, name: str):
if not isinstance(name, str):
raise TypeError('name must be a string')
self._name = name
@property
def parent_interferometer(self):
"""Reference to the parent interferometer (read-only).
Returns:
Any: Parent interferometer reference.
"""
return self._parent_interferometer
@parent_interferometer.setter
def parent_interferometer(self, parent_interferometer):
raise ValueError('parent_interferometer is read-only')
def get_VLTI_UTs() -> list[Telescope]:
"""Return the relative geometry of VLTI UTs.
Returns:
list[Telescope]: Four `Telescope` objects positioned according to the
standard UT configuration.
"""
r = np.array([
[0, 0],
[24.812, 50.837],
[54.840, 86.518],
[113.231, 64.334]
]) * u.m
a = 4 * np.pi * (4 * u.m) ** 2
return [Telescope(a=a, r=pos, name=f'UT {i + 1}') for (i, pos) in enumerate(r)]
def get_LIFE_telescopes() -> list[Telescope]:
"""Generate a telescope configuration for the LIFE concept.
Returns:
list[Telescope]: Four `Telescope` objects.
"""
r = np.array([[0, 0], [1, 0], [0, 6], [1, 6]]) * 100 * u.m
a = np.pi * (2 * u.m) ** 2
return [Telescope(a=a, r=pos, name=f'LIFE telescope {i + 1}') for (i, pos) in enumerate(r)]