Source code for phise.classes.companion

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from target import Target
from astropy import units as u

[docs] class Companion: """Point-like astronomical companion. Args: c (float): Contrast relative to the host star (must be >= 0). ρ (u.Quantity): Angular separation (e.g., ``100 * u.mas``). θ (u.Quantity): Parallactic angle (e.g., ``0.1 * u.rad``). name (str, optional): Readable companion name. Raises: TypeError: If provided types are not as expected. ValueError: If invalid physical values are supplied (e.g., negative contrast). """ __slots__ = ('_parent_target', '_c', '_ρ', '_ρ_unit', '_θ', '_θ_unit', '_name') def __init__(self, c: float, ρ: u.Quantity, θ: u.Quantity, name: str = 'Unnamed Companion'): self._parent_target = None self.ρ = ρ self.θ = θ self.c = c self.name = name def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: res = f'Companion "{self.name}"\n' res += f' Contrast: {self.c:.2g}\n' res += f' Angular separation: {self.ρ:.2g}\n' res += f' Parallactic angle: {self.θ:.2g}' return res @property def c(self) -> float: """Companion contrast (dimensionless). Returns: float: Positive contrast value. """ return self._c @c.setter def c(self, c: float): """Set the companion contrast. Args: c (float): Contrast (>= 0). Raises ``TypeError`` if not int/float. """ if not isinstance(c, (int, float)): raise TypeError('c must be a float') if c < 0: raise ValueError('c must be positive') self._c = float(c) @property def ρ(self) -> u.Quantity: """Angular separation (u.Quantity in mas). Returns: u.Quantity: Separation in milliarcseconds (mas). """ return (self. * u.mas).to(self._ρ_unit) .setter def ρ(self, ρ: u.Quantity): """Set the angular separation. Args: ρ (u.Quantity): Angle quantity (e.g., ``100 * u.mas`` or ``0.1 * u.arcsec``). """ if not isinstance(ρ, u.Quantity): raise TypeError('ρ must be an astropy Quantity') try: new_ρ = ρ.to(u.mas).value except u.UnitConversionError: raise ValueError('ρ must be an angle') self._ρ_unit = ρ.unit self. = new_ρ @property def θ(self) -> u.Quantity: """Parallactic angle (u.Quantity in radians). Returns: u.Quantity: Angle in radians. """ return (self. * u.rad).to(self._θ_unit) .setter def θ(self, θ: u.Quantity): """Set the parallactic angle. Args: θ (u.Quantity): Angle quantity (e.g., ``0.1 * u.rad`` or ``10 * u.deg``). """ if not isinstance(θ, u.Quantity): raise TypeError('θ must be an astropy Quantity') try: new_θ = θ.to(u.rad).value except u.UnitConversionError: raise ValueError('θ must be an angle') self._θ_unit = θ.unit self. = new_θ @property def parent_target(self) -> Target: """Read-only reference to the parent `Target` object. Any direct assignment attempts will raise; the relation is set by the parent. """ return self._parent_target @parent_target.setter def parent_target(self, target: Target): """Setter is disabled; ``parent_target`` is read-only. Raises: ValueError: Always raised; property is read-only. """ raise ValueError('parent_target is read-only') @property def name(self) -> str: """Readable companion name. Returns: str: Name of the companion. """ return self._name @name.setter def name(self, name: str): """Set the companion name. Args: name (str): Human-readable name. """ if not isinstance(name, str): raise TypeError('name must be a string') self._name = name