Source code for across.tools.visibility.constraints.base

from __future__ import annotations

from abc import ABC, abstractmethod

import numpy as np
import numpy.typing as npt
from astropy.coordinates import SkyCoord  # type: ignore[import-untyped]
from astropy.time import Time  # type: ignore[import-untyped]
from pydantic import Field

from ...core.enums import ConstraintType
from ...core.schemas.base import BaseSchema
from ...core.schemas.visibility import VisibilityComputedValues
from ...ephemeris import Ephemeris


[docs] def get_slice(time: Time, ephemeris: Ephemeris) -> slice: """ Return a slice for what the part of the ephemeris that we're using. Arguments --------- time : Time The time to calculate the slice for ephemeris : Ephemeris The spacecraft ephemeris Returns ------- The slice for the ephemeris """ # Ensure that time is an array-like object if time.isscalar: raise NotImplementedError("Scalar time not supported") # Find the indices for the start and end of the time range and return a # slice for that range return slice(ephemeris.index(time[0]), ephemeris.index(time[-1]) + 1)
[docs] class ConstraintABC(BaseSchema, ABC): """ Base class for constraints. Constraints represent conditions that make a target NOT visible (blocked from observation). A constraint returns True when the target is constrained (NOT visible) and False when the target is visible. When combining multiple constraints, the typical approach in visibility calculations is to use OR logic: the target is not visible if ANY constraint blocks it. AND and NOT logic are provided but are rarely used in practice. Constraints can be combined using logical operators: - | (OR): Combined constraint violated if ANY sub-constraint is violated (typical usage) - & (AND): Combined constraint violated only if ALL sub-constraints are violated (rarely used) - ~ (NOT): Inverts the constraint's logic (rarely used) - ^ (XOR): Combined constraint violated when an odd number of sub-constraints are violated (rarely used) Methods ------- __call__(time, ephemeris, coord) Evaluates if a given coordinate is constrained at the given time. __or__(other) Combines two constraints with OR logic using the | operator (typical usage). __and__(other) Combines two constraints with AND logic using the & operator. __invert__() Negates a constraint using the ~ operator. __xor__(other) Combines two constraints with XOR logic using the ^ operator. Examples -------- Typical usage: combine constraints with OR (if any constraint blocks, target not visible): >>> from across.tools.visibility.constraints import ( ... SunAngleConstraint, MoonAngleConstraint, EarthLimbConstraint ... ) >>> sun = SunAngleConstraint(min_angle=45) # Not visible if <45° from Sun >>> moon = MoonAngleConstraint(min_angle=10) # Not visible if <10° from Moon >>> earth = EarthLimbConstraint(min_angle=33) # Not visible if <33° above limb >>> # Target not visible if ANY constraint blocks it >>> all_constraints = sun | moon | earth """
[docs] short_name: str
[docs] name: ConstraintType
[docs] computed_values: VisibilityComputedValues = Field(default_factory=VisibilityComputedValues, exclude=True)
@abstractmethod
[docs] def __call__(self, time: Time, ephemeris: Ephemeris, coordinate: SkyCoord) -> npt.NDArray[np.bool_]: """ Check for a given time, ephemeris and coordinate if positions given are inside the constraint. Parameters ---------- time : Time The time to check. ephemeris : Ephemeris The ephemeris object. coordinate : SkyCoord The coordinate to check. Returns ------- npt.NDArray[np.bool_] Boolean array where True indicates the coordinate violates the constraint (is not visible). """ raise NotImplementedError("Subclasses must implement this method.") # pragma: no cover
[docs] def __and__(self, other: ConstraintABC) -> ConstraintABC: """ Combine two constraints with AND logic using the & operator. This creates a composite constraint that is violated only when BOTH component constraints are violated. This is rarely used in practice since the typical way to combine visibility constraints is with OR logic (use | instead). Parameters ---------- other : ConstraintABC The constraint to combine with this one Returns ------- AndConstraint A new constraint that is violated only if both constraints are violated Examples -------- >>> from across.tools.visibility.constraints import ( ... SunAngleConstraint, MoonAngleConstraint ... ) >>> sun = SunAngleConstraint(min_angle=45) >>> moon = MoonAngleConstraint(min_angle=10) >>> # Rarely used: target not visible only if BOTH block it >>> combined = sun & moon """ # Import here to avoid circular imports from .logical import AndConstraint return AndConstraint(constraints=[self, other])
[docs] def __or__(self, other: ConstraintABC) -> ConstraintABC: """ Combine two constraints with OR logic using the | operator. This creates a composite constraint that is violated when ANY component constraint is violated. This is the typical and recommended way to combine visibility constraints: the target is not visible if ANY constraint blocks it. Parameters ---------- other : ConstraintABC The constraint to combine with this one Returns ------- OrConstraint A new constraint that is violated if either constraint is violated Examples -------- >>> from across.tools.visibility.constraints import ( ... SunAngleConstraint, MoonAngleConstraint ... ) >>> sun = SunAngleConstraint(min_angle=45) >>> moon = MoonAngleConstraint(min_angle=10) >>> # Typical usage: target not visible if Sun OR Moon blocks it >>> combined = sun | moon """ # Import here to avoid circular imports from .logical import OrConstraint return OrConstraint(constraints=[self, other])
[docs] def __invert__(self) -> ConstraintABC: """ Negate a constraint using the ~ operator. This creates a composite constraint with inverted logic: where the original constraint is violated (target not visible), the negated constraint is not violated (target visible), and vice versa. This is rarely used in practice. Returns ------- NotConstraint A new constraint that is the logical negation of this constraint Examples -------- >>> from across.tools.visibility.constraints import EarthLimbConstraint >>> earth = EarthLimbConstraint(min_angle=33) >>> # Violated when earth constraint is NOT violated >>> not_earth = ~earth """ # Import here to avoid circular imports from .logical import NotConstraint return NotConstraint(constraint=self)
[docs] def __xor__(self, other: ConstraintABC) -> ConstraintABC: """ Combine two constraints with XOR logic using the ^ operator. This creates a composite constraint that is violated when exactly one (but not both) of the component constraints is violated. For multiple constraints, it follows standard XOR behavior: violated when an odd number of constraints are violated. This is rarely used in practice. Parameters ---------- other : ConstraintABC The constraint to combine with this one Returns ------- XorConstraint A new constraint that is violated when an odd number of constraints are violated Examples -------- >>> from across.tools.visibility.constraints import ( ... SunAngleConstraint, MoonAngleConstraint ... ) >>> sun = SunAngleConstraint(min_angle=45) >>> moon = MoonAngleConstraint(min_angle=10) >>> # Rarely used: target not visible if exactly one (but not both) blocks it >>> combined = sun ^ moon """ # Import here to avoid circular imports from .logical import XorConstraint return XorConstraint(constraints=[self, other])