Constraints API Reference
This page provides complete API documentation for the constraint system in
rust-ephem. Constraints are used to evaluate observational restrictions
for satellite and astronomical observation planning.
Overview
The constraint system provides two complementary APIs:
Rust-backed Constraint class — Low-level interface with factory methods for creating constraints directly in Rust. Faster for simple use cases.
Pydantic configuration models — Type-safe Python models that serialize to/from JSON and support operator-based composition. Recommended for most users.
Both APIs can be used interchangeably and produce identical results.
Quick Start
import rust_ephem
from rust_ephem.constraints import SunConstraint, MoonConstraint
from datetime import datetime, timezone
# Ensure planetary ephemeris is loaded
rust_ephem.ensure_planetary_ephemeris()
# Create ephemeris
ephem = rust_ephem.TLEEphemeris(
norad_id=25544, # ISS
begin=datetime(2024, 1, 1, tzinfo=timezone.utc),
end=datetime(2024, 1, 2, tzinfo=timezone.utc),
step_size=300
)
# Create combined constraint using operators
constraint = SunConstraint(min_angle=45.0) | MoonConstraint(min_angle=10.0)
# Evaluate for a target (Crab Nebula)
result = constraint.evaluate(ephem, target_ra=83.63, target_dec=22.01)
print(f"All satisfied: {result.all_satisfied}")
print(f"Violations: {len(result.violations)}")
print(f"Visibility windows: {len(result.visibility)}")
Constraint Class (Rust Backend)
The low-level Constraint class provides direct access to Rust constraint evaluation
for maximum performance. However, this class still has limited roll support:
single-target methods use a single target_roll value, while batch methods support
per-target target_rolls. Roll-sweeping is not supported at this layer. Most users should use the
Pydantic configuration models (SunConstraint, MoonConstraint, etc.) instead,
which wrap Constraint with full support for per-target rolls and roll-sweeping via target_rolls
and n_roll_samples parameters.
Factory Methods
- static Constraint.sun_proximity(min_angle, max_angle=None)
Create a Sun proximity constraint.
- Parameters:
min_angle (float) – Minimum allowed angular separation from Sun in degrees (0-180)
max_angle (float) – Maximum allowed angular separation from Sun in degrees (optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range
Example:
# Target must be at least 45° from Sun constraint = Constraint.sun_proximity(45.0) # Target must be between 30° and 120° from Sun constraint = Constraint.sun_proximity(30.0, 120.0)
- static Constraint.moon_proximity(min_angle, max_angle=None)
Create a Moon proximity constraint.
- Parameters:
min_angle (float) – Minimum allowed angular separation from Moon in degrees (0-180)
max_angle (float) – Maximum allowed angular separation from Moon in degrees (optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range
Example:
# Target must be at least 10° from Moon constraint = Constraint.moon_proximity(10.0)
- static Constraint.earth_limb(min_angle, max_angle=None)
Create an Earth limb avoidance constraint.
For spacecraft, this ensures the target is sufficiently above Earth’s limb as seen from the spacecraft position.
- Parameters:
min_angle (float) – Additional margin beyond Earth’s apparent angular radius (degrees)
max_angle (float) – Maximum allowed angular separation from Earth limb (degrees, optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range
Example:
# Target must be at least 28° above Earth's limb constraint = Constraint.earth_limb(28.0)
- static Constraint.body_proximity(body, min_angle, max_angle=None)
Create a generic solar system body avoidance constraint.
- Parameters:
body (str) – Body identifier — NAIF ID or name (e.g., “Jupiter”, “499”, “Mars”)
min_angle (float) – Minimum allowed angular separation in degrees (0-180)
max_angle (float) – Maximum allowed angular separation in degrees (optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range
Supported Bodies:
Planet names: “Mercury”, “Venus”, “Mars”, “Jupiter”, “Saturn”, “Uranus”, “Neptune”
Planet barycenters: “Jupiter barycenter”, “5” (NAIF ID)
Other bodies: “Pluto”, various moons (depending on loaded kernels)
Note
Body availability depends on the ephemeris type and loaded SPICE kernels. The default
de440s.bspincludes Sun, Moon, Earth, and planetary barycenters.Example:
# Target must be at least 15° from Mars constraint = Constraint.body_proximity("Mars", 15.0) # Using NAIF ID (5 = Jupiter barycenter) constraint = Constraint.body_proximity("5", 20.0)
- static Constraint.eclipse(umbra_only=True)
Create an eclipse constraint that detects when the observer is in Earth’s shadow.
- Parameters:
umbra_only (bool) – If True, only umbra counts as eclipse. If False, penumbra also counts.
- Returns:
A new Constraint instance
- Return type:
Constraint
Example:
# Constraint violated only in umbra (full shadow) constraint = Constraint.eclipse(umbra_only=True) # Constraint violated in both umbra and penumbra constraint = Constraint.eclipse(umbra_only=False)
- static Constraint.airmass(max_airmass, min_airmass=None)
Create an airmass constraint that limits observations based on atmospheric path length.
- Parameters:
max_airmass (float) – Maximum allowed airmass (> 1.0)
min_airmass (float) – Minimum allowed airmass (≥ 1.0, optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If airmass values are out of valid range
Airmass represents the optical path length through Earth’s atmosphere:
Airmass = 1.0 at zenith (best observing conditions)
Airmass = 2.0 at ~30° altitude
Airmass = 3.0 at ~19° altitude
Higher airmass values indicate worse observing conditions
Example:
# Target must be at airmass ≤ 2.0 (altitude ≥ ~30°) constraint = Constraint.airmass(2.0) # Target must be between airmass 1.2 and 2.5 constraint = Constraint.airmass(2.5, min_airmass=1.2)
- static Constraint.daytime(twilight='civil')
Create a daytime constraint that prevents observations during daylight hours.
- Parameters:
twilight (str) – Twilight definition (“civil”, “nautical”, “astronomical”, or “none”)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If twilight type is invalid
Twilight definitions:
"civil": Civil twilight (-6° below horizon, default)"nautical": Nautical twilight (-12° below horizon)"astronomical": Astronomical twilight (-18° below horizon)"none": Strict daytime only (Sun above horizon)
Example:
# Prevent observations during civil twilight or daylight constraint = Constraint.daytime() # Use nautical twilight definition constraint = Constraint.daytime(twilight="nautical")
- static Constraint.moon_phase(max_illumination, min_illumination=None, min_distance=None, max_distance=None, enforce_when_below_horizon=False, moon_visibility='full')
Create a Moon phase constraint with optional distance filtering.
- Parameters:
max_illumination (float) – Maximum allowed Moon illumination fraction (0.0-1.0)
min_illumination (float) – Minimum allowed Moon illumination fraction (0.0-1.0, optional)
min_distance (float) – Minimum allowed Moon distance in degrees from target (optional)
max_distance (float) – Maximum allowed Moon distance in degrees from target (optional)
enforce_when_below_horizon (bool) – Whether to enforce constraint when Moon is below horizon
moon_visibility (str) – Moon visibility requirement (“full” or “partial”)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If parameters are out of valid range
Moon illumination ranges from 0.0 (new moon) to 1.0 (full moon).
Example:
# Moon illumination must be ≤ 30% constraint = Constraint.moon_phase(0.3) # Moon illumination between 10% and 50%, keep Moon ≥ 30° away constraint = Constraint.moon_phase(0.5, min_illumination=0.1, min_distance=30.0)
- static Constraint.saa(polygon)
Create a South Atlantic Anomaly constraint.
The South Atlantic Anomaly is a region of reduced magnetic field strength that increases radiation exposure for satellites.
- Parameters:
polygon (list) – List of (longitude, latitude) pairs defining the SAA region boundary
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If polygon has fewer than 3 vertices
The polygon should be defined as a list of (longitude, latitude) coordinate pairs in degrees, defining the boundary of the region. The polygon is assumed to be closed (first and last points are connected).
Example:
# Define SAA region as a polygon saa_polygon = [ (-90.0, -50.0), # Southwest corner (-40.0, -50.0), # Southeast corner (-40.0, 0.0), # Northeast corner (-90.0, 0.0), # Northwest corner ] # Avoid SAA region constraint = Constraint.saa(saa_polygon) # To require being in SAA region, use NOT require_saa = ~Constraint.saa(saa_polygon)
- static Constraint.alt_az(min_altitude, max_altitude=None, min_azimuth=None, max_azimuth=None, polygon=None)
Create an altitude/azimuth constraint.
Constrains observations based on target position in the observer’s local horizon coordinate system. Can use simple altitude/azimuth ranges or define a custom polygon region in altitude/azimuth space.
- Parameters:
min_altitude (float) – Minimum allowed altitude in degrees (0-90)
max_altitude (float) – Maximum allowed altitude in degrees (0-90), optional
min_azimuth (float) – Minimum allowed azimuth in degrees (0-360), optional
max_azimuth (float) – Maximum allowed azimuth in degrees (0-360), optional
polygon (list) – List of (altitude, azimuth) pairs defining allowed region, optional
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range or polygon has fewer than 3 vertices
Coordinate System:
Altitude: Angular distance from horizon (0° = horizon, 90° = zenith)
Azimuth: Angular distance from North, measured eastward (0° = North, 90° = East, 180° = South, 270° = West)
Polygon Mode:
When a polygon is provided, the target must be inside the polygon to satisfy the constraint. The polygon is defined as a list of (altitude, azimuth) coordinate pairs forming a closed region. Uses the winding number algorithm for robust point-in-polygon testing.
Example:
# Simple altitude range constraint constraint = Constraint.alt_az(min_altitude=10.0, max_altitude=85.0) # Azimuth range constraint (e.g., avoid west, only observe east/south) constraint = Constraint.alt_az(min_altitude=5.0, min_azimuth=45.0, max_azimuth=225.0) # Define a custom observing region as a polygon # Observing window at altitude [30-70°] and azimuth [90-180°] (south to east) observing_region = [ (30, 90), # Southwest corner (30, 180), # Southeast corner (70, 180), # Northeast corner (70, 90), # Northwest corner ] constraint = Constraint.alt_az(min_altitude=0.0, polygon=observing_region) # Combine polygon with additional altitude constraint constraint = Constraint.alt_az(min_altitude=35.0, polygon=observing_region)
- static Constraint.orbit_ram(min_angle, max_angle=None)
Create an orbit RAM direction constraint.
Ensures the target maintains minimum angular separation from the spacecraft’s velocity vector (RAM direction).
- Parameters:
min_angle (float) – Minimum allowed angular separation from RAM direction in degrees (0-180)
max_angle (float) – Maximum allowed angular separation from RAM direction in degrees (optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range
Requirements:
The ephemeris must contain velocity data (6 columns: position + velocity).
Example:
# Target must be at least 10° from RAM direction constraint = Constraint.orbit_ram(10.0) # Target must be between 5° and 45° from RAM direction constraint = Constraint.orbit_ram(5.0, 45.0)
- static Constraint.orbit_pole(min_angle, max_angle=None)
Create an orbit pole direction constraint.
Ensures the target maintains minimum angular separation from the orbital pole (direction perpendicular to the orbital plane). Useful for maintaining specific orientations relative to the spacecraft’s orbit.
- Parameters:
min_angle (float) – Minimum allowed angular separation from orbital pole in degrees (0-180)
max_angle (float) – Maximum allowed angular separation from orbital pole in degrees (optional)
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If angles are out of valid range
Requirements:
The ephemeris must contain velocity data (6 columns: position + velocity).
Example:
# Target must be at least 15° from orbital pole constraint = Constraint.orbit_pole(15.0) # Target must be between 10° and 80° from orbital pole constraint = Constraint.orbit_pole(10.0, 80.0)
Logical Combinators
- static Constraint.and_(*constraints)
Combine constraints with logical AND.
- Parameters:
constraints – Variable number of Constraint objects
- Returns:
A new Constraint that is satisfied only if ALL input constraints are satisfied
- Return type:
Constraint
- Raises:
ValueError – If no constraints provided
Example:
sun = Constraint.sun_proximity(45.0) moon = Constraint.moon_proximity(10.0) combined = Constraint.and_(sun, moon)
- static Constraint.or_(*constraints)
Combine constraints with logical OR.
- Parameters:
constraints – Variable number of Constraint objects
- Returns:
A new Constraint that is satisfied if ANY input constraint is satisfied
- Return type:
Constraint
- Raises:
ValueError – If no constraints provided
Example:
eclipse = Constraint.eclipse() earth_limb = Constraint.earth_limb(20.0) either = Constraint.or_(eclipse, earth_limb)
- static Constraint.xor_(*constraints)
Combine constraints with logical XOR.
- Parameters:
constraints – Variable number of Constraint objects (minimum 2)
- Returns:
A new Constraint that is violated when EXACTLY ONE input constraint is violated
- Return type:
Constraint
- Raises:
ValueError – If fewer than two constraints are provided
Violation Semantics:
XOR is violated when exactly one sub-constraint is violated
XOR is satisfied when zero or more than one sub-constraints are violated
Example:
sun = Constraint.sun_proximity(45.0) moon = Constraint.moon_proximity(10.0) exclusive = Constraint.xor_(sun, moon)
- static Constraint.not_(constraint)
Negate a constraint with logical NOT.
- Parameters:
constraint (Constraint) – Constraint to negate
- Returns:
A new Constraint that is satisfied when the input is violated
- Return type:
Constraint
Example:
eclipse = Constraint.eclipse() not_eclipse = Constraint.not_(eclipse) # Satisfied when NOT in eclipse
- static Constraint.from_json(json_str)
Create a constraint from a JSON string configuration.
- Parameters:
json_str (str) – JSON representation of the constraint configuration
- Returns:
A new Constraint instance
- Return type:
Constraint
- Raises:
ValueError – If JSON is invalid or contains unknown constraint type
JSON Format Examples:
Simple constraints:
{"type": "sun", "min_angle": 45.0}
{"type": "moon", "min_angle": 10.0, "max_angle": 90.0}
{"type": "eclipse", "umbra_only": true}
{"type": "earth_limb", "min_angle": 28.0}
{"type": "body", "body": "Mars", "min_angle": 15.0}
{"type": "saa", "polygon": [[-90.0, -50.0], [-40.0, -50.0], [-40.0, 0.0], [-90.0, 0.0]]}
Logical combinators:
{"type": "and", "constraints": [{"type": "sun", "min_angle": 45.0}, {"type": "moon", "min_angle": 10.0}]}
{"type": "not", "constraint": {"type": "eclipse", "umbra_only": true}}
Example:
json_config = '{"type": "sun", "min_angle": 45.0}' constraint = Constraint.from_json(json_config)
Evaluation Methods
- Constraint.evaluate(ephemeris, target_ra, target_dec, times=None, indices=None, target_roll=None)
Evaluate constraint against ephemeris data.
- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ra (float) – Target right ascension in degrees (ICRS/J2000)
target_dec (float) – Target declination in degrees (ICRS/J2000)
times – Optional specific time(s) to evaluate (datetime or list of datetimes)
indices – Optional specific time index/indices to evaluate (int or list of ints)
target_roll (float or None) – Spacecraft roll angle (degrees). When
None(default) and the constraint contains a boresight offset with non-zero pitch/yaw, sweepsDEFAULT_N_ROLL_SAMPLESroll angles uniformly and marks a timestamp as violated only when every roll is blocked (no valid spacecraft orientation exists). Pass an explicit float to evaluate at a fixed roll.
- Returns:
ConstraintResult containing violation windows
- Return type:
- Raises:
ValueError – If both times and indices are provided, or if times/indices not found
TypeError – If ephemeris type is not supported
Example:
result = constraint.evaluate(ephem, target_ra=83.63, target_dec=22.01) # Evaluate at specific times from datetime import datetime, timezone times = [ datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc), datetime(2024, 1, 1, 18, 0, 0, tzinfo=timezone.utc), ] result = constraint.evaluate(ephem, 83.63, 22.01, times=times) # Evaluate at specific indices result = constraint.evaluate(ephem, 83.63, 22.01, indices=[0, 10, 20])
- Constraint.in_constraint_batch(ephemeris, target_ras, target_decs, times=None, indices=None, target_rolls=None)
Check if targets are in-constraint for multiple RA/Dec positions (vectorized).
This low-level method evaluates the constraint against multiple target positions. For roll-sweeping (
n_roll_samples), use the Pydantic constraint models (e.g.,SunConstraint) which wrap this API withRustConstraintMixin.in_constraint_batch()instead.- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ras (list) – List of target right ascensions in degrees (ICRS/J2000)
target_decs (list) – List of target declinations in degrees (ICRS/J2000)
times – Optional specific time(s) to evaluate
indices – Optional specific time index/indices to evaluate
target_rolls (list[float] or None) – Optional per-target spacecraft roll angles in degrees. Must be a list of the same length as
target_ras. PassNoneto evaluate without any fixed spacecraft roll.
- Returns:
2D numpy boolean array of shape (n_targets, n_times)
- Return type:
numpy.ndarray
Return Value:
The returned array has shape
(n_targets, n_times)where:violations[i, j] = Truemeans targetiviolates the constraint at timejviolations[i, j] = Falsemeans targetisatisfies the constraint at timej
Example:
import numpy as np # Check 1000 random targets target_ras = np.random.uniform(0, 360, 1000) target_decs = np.random.uniform(-90, 90, 1000) violations = constraint.in_constraint_batch(ephem, target_ras, target_decs) print(f"Shape: {violations.shape}") # (1000, n_times) # Count violations per target violation_counts = violations.sum(axis=1) # Find targets that never violate always_visible = np.where(violation_counts == 0)[0]
- Constraint.evaluate_batch(ephemeris, target_ras, target_decs, times=None, indices=None, target_rolls=None)
Evaluate a constraint for multiple targets and return one
ConstraintResultper target.This low-level method evaluates the constraint against multiple target positions. The returned list has one result per target. For roll-sweeping (
n_roll_samples), use the Pydantic constraint models (e.g.,SunConstraint) which wrap this API withRustConstraintMixin.evaluate_batch()instead.- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ras (list) – List of target right ascensions in degrees (ICRS/J2000)
target_decs (list) – List of target declinations in degrees (ICRS/J2000)
times – Optional specific time(s) to evaluate
indices – Optional specific time index/indices to evaluate
target_rolls (list[float] or None) – Optional per-target spacecraft roll angles in degrees. Must be a list of the same length as
target_ras. PassNoneto evaluate without any fixed spacecraft roll.
- Returns:
List of
ConstraintResultobjects, one per input target- Return type:
list[ConstraintResult]
- Constraint.in_constraint(time, ephemeris, target_ra, target_dec, target_roll=None, n_roll_samples=DEFAULT_N_ROLL_SAMPLES)
Check if the target satisfies the constraint at given time(s).
This method accepts single times, lists of times, or numpy arrays of times. For multiple times, it efficiently uses batch evaluation internally.
- Parameters:
time (datetime or list[datetime] or numpy.ndarray) – The time(s) to check (must exist in ephemeris timestamps). Can be a single datetime, list of datetimes, or numpy array of datetimes.
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ra (float) – Target right ascension in degrees (ICRS/J2000)
target_dec (float) – Target declination in degrees (ICRS/J2000)
target_roll (float or None) – Spacecraft roll angle (degrees). When
None(default) and the constraint contains a boresight offset with non-zero pitch/yaw, sweepsn_roll_samplesroll angles and returnsTrueonly when every roll is blocked (no valid spacecraft orientation exists). Pass an explicit float to evaluate at a fixed roll.n_roll_samples (int) – Number of roll angles to sweep when
target_rollisNoneand the constraint is roll-dependent. DefaultDEFAULT_N_ROLL_SAMPLES. Ignored whentarget_rollis given or no pitch/yaw offset is present.
- Returns:
True if constraint is satisfied at the given time(s). Returns a single bool for a single time, or a list of bools for multiple times.
- Return type:
bool or list[bool]
- Raises:
ValueError – If time is not found in ephemeris timestamps
Examples:
import numpy as np from datetime import datetime, timezone time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) # Single time is_visible = constraint.in_constraint(time, ephem, 83.63, 22.01) # Returns: bool # Multiple times as list times = [time, time] results = constraint.in_constraint(times, ephem, 83.63, 22.01) # Returns: [bool, bool] # Multiple times as numpy array times_array = np.array([time, time, time]) results = constraint.in_constraint(times_array, ephem, 83.63, 22.01) # Returns: [bool, bool, bool]
- Constraint.roll_range(time, ephemeris, target_ra, target_dec, n_roll_samples=360)
Return contiguous roll-angle intervals where the constraint is satisfied (target visible).
Sweeps
n_roll_samplesuniformly-spaced spacecraft roll angles over [0°, 360°), identifies those where the constraint isFalse(not violated), and collapses adjacent valid samples into(min_deg, max_deg)intervals.- Parameters:
time (datetime) – A single datetime to evaluate (must exist in ephemeris).
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ra (float) – Target right ascension in degrees (ICRS/J2000)
target_dec (float) – Target declination in degrees (ICRS/J2000)
n_roll_samples (int) – Number of uniformly-spaced roll angles to test over [0°, 360°). Default 360 (1° resolution).
- Returns:
List of
(min_deg, max_deg)tuples, one per contiguous valid interval. Empty list if no roll is valid.- Return type:
list[tuple[float, float]]
Example:
ranges = constraint.roll_range( ephem.timestamp[0], ephem, target_ra=83.63, target_dec=22.01, ) # e.g. [(12.0, 47.0), (193.0, 228.0)] for lo, hi in ranges: print(f"Valid rolls: {lo:.1f}° – {hi:.1f}°")
- Constraint.instantaneous_field_of_regard(ephemeris, time=None, index=None, n_points=DEFAULT_N_POINTS, n_roll_samples=DEFAULT_N_ROLL_SAMPLES)
Compute instantaneous field of regard in steradians.
- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
time (datetime or None) – Optional datetime to evaluate (must exist in ephemeris)
index (int or None) – Optional ephemeris index to evaluate
n_points (int) – Number of Fibonacci-sphere sky samples (default
DEFAULT_N_POINTS)n_roll_samples (int) – Spacecraft roll angles to sweep when computing FoR over all roll states for boresight-offset constraints with non-zero pitch/yaw (default
DEFAULT_N_ROLL_SAMPLES). Ignored whentarget_rollis specified or when no pitch/yaw offset is present.
- Returns:
Visible solid angle in steradians, range
[0, 4π]- Return type:
float
- Raises:
ValueError – If exactly one of
timeorindexis not provided
Serialization Methods
- Constraint.to_json()
Get constraint configuration as JSON string.
- Returns:
JSON string representation of the constraint
- Return type:
str
- Constraint.to_dict()
Get constraint configuration as Python dictionary.
- Returns:
Dictionary representation of the constraint
- Return type:
dict
Pydantic Configuration Models
The rust_ephem.constraints module provides Pydantic models for type-safe
constraint configuration. These models support:
JSON serialization/deserialization
Validation of parameter ranges
Python operator overloading for composition
IDE autocompletion and type checking
Import all constraint models:
from rust_ephem.constraints import (
SunConstraint,
MoonConstraint,
EarthLimbConstraint,
BodyConstraint,
EclipseConstraint,
AirmassConstraint,
DaytimeConstraint,
MoonPhaseConstraint,
SAAConstraint,
OrbitRamConstraint,
OrbitPoleConstraint,
AndConstraint,
OrConstraint,
XorConstraint,
NotConstraint,
ConstraintConfig,
)
SunConstraint
Sun proximity constraint ensuring target maintains minimum angular separation from Sun.
- class SunConstraint(min_angle, max_angle=None)
- Parameters:
min_angle (float) – Minimum allowed angular separation in degrees (0-180, required)
max_angle (float) – Maximum allowed angular separation in degrees (0-180, optional)
Attributes:
type— Always"sun"(Literal)min_angle— Minimum angle from Sun in degreesmax_angle— Maximum angle from Sun in degrees (or None)
Example:
from rust_ephem.constraints import SunConstraint # Simple minimum angle sun = SunConstraint(min_angle=45.0) # With maximum angle (target must be between 30° and 120° from Sun) sun = SunConstraint(min_angle=30.0, max_angle=120.0)
MoonConstraint
Moon proximity constraint ensuring target maintains minimum angular separation from Moon.
- class MoonConstraint(min_angle, max_angle=None)
- Parameters:
min_angle (float) – Minimum allowed angular separation in degrees (0-180, required)
max_angle (float) – Maximum allowed angular separation in degrees (0-180, optional)
Attributes:
type— Always"moon"(Literal)min_angle— Minimum angle from Moon in degreesmax_angle— Maximum angle from Moon in degrees (or None)
Example:
from rust_ephem.constraints import MoonConstraint moon = MoonConstraint(min_angle=10.0)
EarthLimbConstraint
Earth limb avoidance constraint ensuring target is above Earth’s horizon/limb.
- class EarthLimbConstraint(min_angle, max_angle=None, include_refraction=False, horizon_dip=False)
- Parameters:
min_angle (float) – Minimum angular separation from Earth’s limb in degrees (0-180, required)
max_angle (float) – Maximum angular separation from Earth’s limb in degrees (0-180, optional)
include_refraction (bool) – Include atmospheric refraction correction (~0.57°) for ground observers (default: False)
horizon_dip (bool) – Include geometric horizon dip correction for ground observers (default: False)
Attributes:
type— Always"earth_limb"(Literal)min_angle— Minimum angle from Earth’s limb in degreesmax_angle— Maximum angle from Earth’s limb in degrees (or None)include_refraction— Whether to include atmospheric refractionhorizon_dip— Whether to include geometric horizon dip
Example:
from rust_ephem.constraints import EarthLimbConstraint # For spacecraft: target must be 28° above Earth's limb earth_limb = EarthLimbConstraint(min_angle=28.0) # For ground observers: include atmospheric effects earth_limb = EarthLimbConstraint( min_angle=10.0, include_refraction=True, horizon_dip=True )
BodyConstraint
Generic solar system body proximity constraint.
- class BodyConstraint(body, min_angle, max_angle=None)
- Parameters:
body (str) – Name of the solar system body (e.g., “Mars”, “Jupiter”)
min_angle (float) – Minimum allowed angular separation in degrees (0-180, required)
max_angle (float) – Maximum allowed angular separation in degrees (0-180, optional)
Attributes:
type— Always"body"(Literal)body— Name of the solar system bodymin_angle— Minimum angle from body in degreesmax_angle— Maximum angle from body in degrees (or None)
Example:
from rust_ephem.constraints import BodyConstraint # Avoid Mars mars = BodyConstraint(body="Mars", min_angle=15.0) # Avoid Jupiter barycenter jupiter = BodyConstraint(body="Jupiter barycenter", min_angle=20.0)
EclipseConstraint
Eclipse constraint detecting when observer is in Earth’s shadow. This constraint assumes an Earth-centered ephemeris (Earth at the origin). Results are undefined for other centers.
- class EclipseConstraint(umbra_only=True)
- Parameters:
umbra_only (bool) – If True, only umbra counts as eclipse. If False, includes penumbra. (default: True)
Attributes:
type— Always"eclipse"(Literal)umbra_only— Whether only umbra counts as eclipse
Example:
from rust_ephem.constraints import EclipseConstraint # Only detect full shadow (umbra) eclipse = EclipseConstraint(umbra_only=True) # Detect both umbra and penumbra eclipse = EclipseConstraint(umbra_only=False)
AirmassConstraint
Airmass constraint limiting observations based on atmospheric path length.
- class AirmassConstraint(max_airmass, min_airmass=None)
- Parameters:
max_airmass (float) – Maximum allowed airmass (> 1.0, required)
min_airmass (float) – Minimum allowed airmass (≥ 1.0, optional)
Attributes:
type— Always"airmass"(Literal)max_airmass— Maximum allowed airmassmin_airmass— Minimum allowed airmass (or None)
Airmass represents the optical path length through Earth’s atmosphere:
Airmass = 1.0 at zenith (best observing conditions)
Airmass = 2.0 at ~30° altitude
Airmass = 3.0 at ~19° altitude
Example:
from rust_ephem.constraints import AirmassConstraint # Target must be at airmass ≤ 2.0 (altitude ≥ ~30°) airmass = AirmassConstraint(max_airmass=2.0) # Target must be between airmass 1.2 and 2.5 airmass = AirmassConstraint(max_airmass=2.5, min_airmass=1.2)
DaytimeConstraint
Daytime constraint preventing observations during daylight hours.
- class DaytimeConstraint(twilight='civil')
- Parameters:
twilight (str) – Twilight definition (“civil”, “nautical”, “astronomical”, or “none”, default: “civil”)
Attributes:
type— Always"daytime"(Literal)twilight— Twilight definition
Twilight definitions:
"civil": Civil twilight (-6° below horizon)"nautical": Nautical twilight (-12° below horizon)"astronomical": Astronomical twilight (-18° below horizon)"none": Strict daytime only (Sun above horizon)
Example:
from rust_ephem.constraints import DaytimeConstraint # Prevent observations during civil twilight or daylight daytime = DaytimeConstraint() # Use nautical twilight definition daytime = DaytimeConstraint(twilight="nautical")
MoonPhaseConstraint
Moon phase constraint with optional distance filtering.
- class MoonPhaseConstraint(max_illumination, min_illumination=None, min_distance=None, max_distance=None, enforce_when_below_horizon=False, moon_visibility='full')
- Parameters:
max_illumination (float) – Maximum allowed Moon illumination fraction (0.0-1.0, required)
min_illumination (float) – Minimum allowed Moon illumination fraction (0.0-1.0, optional)
min_distance (float) – Minimum allowed Moon distance in degrees from target (optional)
max_distance (float) – Maximum allowed Moon distance in degrees from target (optional)
enforce_when_below_horizon (bool) – Whether to enforce constraint when Moon is below horizon (default: False)
moon_visibility (str) – Moon visibility requirement (“full” or “partial”, default: “full”)
Attributes:
type— Always"moon_phase"(Literal)max_illumination— Maximum allowed Moon illuminationmin_illumination— Minimum allowed Moon illumination (or None)min_distance— Minimum Moon distance from target (or None)max_distance— Maximum Moon distance from target (or None)enforce_when_below_horizon— Whether to enforce when Moon is below horizonmoon_visibility— Moon visibility requirement
Moon illumination ranges from 0.0 (new moon) to 1.0 (full moon).
Example:
from rust_ephem.constraints import MoonPhaseConstraint # Moon illumination must be ≤ 30% moon_phase = MoonPhaseConstraint(max_illumination=0.3) # Moon illumination between 10% and 50%, keep Moon ≥ 30° away moon_phase = MoonPhaseConstraint( max_illumination=0.5, min_illumination=0.1, min_distance=30.0 )
SAAConstraint
South Atlantic Anomaly constraint with polygon-defined region.
- class SAAConstraint(polygon)
- Parameters:
polygon (list) – List of (longitude, latitude) pairs defining the region boundary (minimum 3 vertices)
Attributes:
type— Always"saa"(Literal)polygon— List of (longitude, latitude) pairs defining the region boundary
The polygon should be defined as a list of (longitude, latitude) coordinate pairs in degrees, defining the boundary of the region. The polygon is assumed to be closed (first and last points are connected). Uses ray casting algorithm to determine if a point is inside the polygon.
Example:
from rust_ephem.constraints import SAAConstraint # Define SAA region as a polygon saa_polygon = [ (-90.0, -50.0), # Southwest corner (-40.0, -50.0), # Southeast corner (-40.0, 0.0), # Northeast corner (-90.0, 0.0), # Northwest corner ] # Avoid SAA region saa_constraint = SAAConstraint(polygon=saa_polygon) # To require being in SAA region, use NOT require_saa = ~SAAConstraint(polygon=saa_polygon)
AltAzConstraint
Altitude/Azimuth constraint restricting observations based on local horizon coordinates.
- class AltAzConstraint(min_altitude, max_altitude=None, min_azimuth=None, max_azimuth=None, polygon=None)
- Parameters:
min_altitude (float) – Minimum allowed altitude in degrees (0-90)
max_altitude (float) – Maximum allowed altitude in degrees (0-90), optional
min_azimuth (float) – Minimum allowed azimuth in degrees (0-360), optional
max_azimuth (float) – Maximum allowed azimuth in degrees (0-360), optional
polygon (list) – List of (altitude, azimuth) pairs defining allowed region, optional
Attributes:
type— Always"alt_az"(Literal)min_altitude— Minimum allowed altitude in degreesmax_altitude— Maximum allowed altitude in degrees (optional)min_azimuth— Minimum allowed azimuth in degrees (optional)max_azimuth— Maximum allowed azimuth in degrees (optional)polygon— List of (altitude, azimuth) pairs defining allowed region (optional)
Coordinate System:
Altitude: Angular distance from horizon (0° = horizon, 90° = zenith)
Azimuth: Angular distance from North, measured eastward (0° = North, 90° = East, 180° = South, 270° = West)
Polygon Mode:
When a polygon is provided, it defines an allowed region in altitude/azimuth space. The target must be inside this polygon to satisfy the constraint. Uses the winding number algorithm for robust point-in-polygon testing.
Example:
from rust_ephem.constraints import AltAzConstraint # Simple altitude constraint (target above 10° elevation) alt_az = AltAzConstraint(min_altitude=10.0) # Altitude and azimuth range constraint alt_az = AltAzConstraint( min_altitude=10.0, max_altitude=85.0, min_azimuth=45.0, # Only observe east/south max_azimuth=225.0 ) # Define custom observing region with polygon observing_window = [ (30, 90), # Southwest corner (alt=30°, az=90°=East) (30, 180), # Southeast corner (alt=30°, az=180°=South) (70, 180), # Northeast corner (alt=70°, az=180°=South) (70, 90), # Northwest corner (alt=70°, az=90°=East) ] alt_az = AltAzConstraint(min_altitude=0.0, polygon=observing_window) # Combine polygon with additional altitude constraint alt_az = AltAzConstraint(min_altitude=35.0, polygon=observing_window)
OrbitRamConstraint
Orbit RAM direction constraint ensuring target maintains minimum angular separation from spacecraft velocity vector.
- class OrbitRamConstraint(min_angle, max_angle=None)
- Parameters:
min_angle (float) – Minimum allowed angular separation from RAM direction in degrees (0-180, required)
max_angle (float) – Maximum allowed angular separation from RAM direction in degrees (0-180, optional)
Attributes:
type— Always"orbit_ram"(Literal)min_angle— Minimum angle from RAM direction in degreesmax_angle— Maximum angle from RAM direction in degrees (or None)
Requirements:
The ephemeris must contain velocity data (6 columns: position + velocity).
Example:
from rust_ephem.constraints import OrbitRamConstraint # Target must be at least 10° from RAM direction orbit_ram = OrbitRamConstraint(min_angle=10.0) # Target must be between 5° and 45° from RAM direction orbit_ram = OrbitRamConstraint(min_angle=5.0, max_angle=45.0)
OrbitPoleConstraint
Orbit pole direction constraint ensuring target maintains minimum angular separation from orbital pole.
- class OrbitPoleConstraint(min_angle, max_angle=None)
- Parameters:
min_angle (float) – Minimum allowed angular separation from orbital pole in degrees (0-180, required)
max_angle (float) – Maximum allowed angular separation from orbital pole in degrees (0-180, optional)
Attributes:
type— Always"orbit_pole"(Literal)min_angle— Minimum angle from orbital pole in degreesmax_angle— Maximum angle from orbital pole in degrees (or None)
Requirements:
The ephemeris must contain velocity data (6 columns: position + velocity).
Example:
from rust_ephem.constraints import OrbitPoleConstraint # Target must be at least 15° from orbital pole orbit_pole = OrbitPoleConstraint(min_angle=15.0) # Target must be between 10° and 80° from orbital pole orbit_pole = OrbitPoleConstraint(min_angle=10.0, max_angle=80.0)
BrightStarConstraint
Bright star avoidance constraint — violated when any catalog star falls within the telescope field of view. Useful for preventing stray light or detector saturation from bright stars.
Stars are supplied by the caller as (ra_deg, dec_deg) pairs; use
get_bright_stars() to obtain a ready-to-use list from the Hipparcos
catalog.
Two FoV shapes are supported:
Circular (
fov_radius) — roll-independent; a star is inside whenever its angular separation from the boresight is less thanfov_radius.Polygon (
fov_polygon) — the polygon is defined in instrument frame coordinates and rotates with spacecraft roll. At roll = 0° the +v axis points north and the +u axis points east on the sky. Whenroll_degisNone, all roll angles are swept (72 samples, ≈5° resolution) and the constraint is violated only if every roll has a star inside the polygon.
- class BrightStarConstraint(stars, *, fov_radius=None, fov_polygon=None, roll_deg=None)
- Parameters:
stars (list) – Stars to avoid as a list of
(ra_deg, dec_deg)pairs (ICRS/J2000, required).fov_radius (float) – Circular FoV radius in degrees (0–180). Mutually exclusive with
fov_polygon.fov_polygon (list) – Polygon FoV as a list of
(u_deg, v_deg)vertices in instrument frame. At roll = 0° the +u axis points east and the +v axis points north. Mutually exclusive withfov_radius. Minimum 3 vertices.roll_deg (float) – Position angle (degrees east of north) of the instrument +v axis.
None(default) sweeps all roll angles — the constraint is violated only when no clear roll exists. Only meaningful withfov_polygon.
Attributes:
type— Always"bright_star"(Literal)stars— List of(ra_deg, dec_deg)catalog entriesfov_radius— Circular FoV radius in degrees (orNone)fov_polygon— Polygon vertices in instrument frame (orNone)roll_deg— Fixed roll angle (orNonefor roll sweep)
Coordinate frame for polygon vertices:
Vertices are in degrees relative to the boresight in the instrument frame:
u = 0, v = 0— boresightAt roll = 0°: +u points east, +v points north
Roll is the position angle of +v from north, measured east of north
A star at sky tangent-plane offset (Δeast, Δnorth) maps to instrument coordinates
(u, v)at roll θ as:\[ \begin{align}\begin{aligned}u = \Delta_\text{east} \cos\theta - \Delta_\text{north} \sin\theta\\v = \Delta_\text{east} \sin\theta + \Delta_\text{north} \cos\theta\end{aligned}\end{align} \]Example — circular FoV:
from rust_ephem import get_bright_stars, Constraint from rust_ephem.constraints import BrightStarConstraint stars = get_bright_stars(mag_limit=7.0) # Pydantic model (JSON-serialisable) c = BrightStarConstraint(stars=stars, fov_radius=0.5) # Or build the Rust evaluator directly c = Constraint.bright_star(stars=stars, fov_radius=0.5) result = c.evaluate(ephem, target_ra, target_dec)
Example — polygon FoV with roll sweep:
# 0.5° × 0.3° rectangular detector; check all rolls c = BrightStarConstraint( stars=stars, fov_polygon=[(-0.25, -0.15), (0.25, -0.15), (0.25, 0.15), (-0.25, 0.15)], ) # Violated only when no spacecraft roll avoids all stars result = c.evaluate(ephem, target_ra, target_dec) # Evaluate at a specific roll (position angle in degrees, east of north) result = c.evaluate(ephem, target_ra, target_dec, target_roll=45.0)
Catalog Utilities
- get_bright_stars(mag_limit=7.0, cache_mag_limit=None, *, refresh=False)
Return bright stars from the Hipparcos catalog suitable for use with
BrightStarConstraintandConstraint.bright_star().Stars brighter than mag_limit (Johnson V magnitude) are returned as
(ra_deg, dec_deg)pairs in ICRS / J2000 coordinates. The catalog is downloaded from VizieR on the first call and cached to disk; subsequent calls with a compatible magnitude limit are served from cache with no network access.Cache reuse: a cache file covers all stars up to a given magnitude limit. Requesting a tighter limit from an existing broader cache never triggers a re-download. Use
cache_mag_limitto pre-download a wider dataset:from rust_ephem import get_bright_stars # Download stars brighter than V = 8 once, return the V < 6 subset stars = get_bright_stars(mag_limit=6.0, cache_mag_limit=8.0) # Future calls for any limit ≤ 8 reuse the cached file instantly stars_7 = get_bright_stars(mag_limit=7.0) # no network call
- Parameters:
mag_limit (float) – Return stars brighter than this V magnitude (lower value = brighter). Default
7.0yields roughly 4 000 stars.cache_mag_limit (float) – Magnitude limit used when writing the on-disk cache. Defaults to mag_limit. Must be ≥ mag_limit.
refresh (bool) – If
True, ignore any existing cache and re-download from VizieR. DefaultFalse.
- Returns:
List of
(ra_deg, dec_deg)tuples for stars brighter than mag_limit.- Raises:
ImportError – If astroquery is not installed.
ValueError – If cache_mag_limit < mag_limit, or VizieR returns no rows.
Cache location:
{rust_ephem.get_cache_dir()}/hipparcos_vmag_<limit>.npyRequires:
astroquery(pip install astroquery)import rust_ephem as re from rust_ephem import get_bright_stars, Constraint # Typical workflow stars = get_bright_stars(mag_limit=7.0) c = Constraint.bright_star(stars=stars, fov_radius=0.5) result = c.evaluate(ephem, target_ra, target_dec) print(f"All satisfied: {result.all_satisfied}")
AndConstraint
Logical AND combination — satisfied only if ALL sub-constraints are satisfied.
- class AndConstraint(constraints)
- Parameters:
constraints (list) – List of ConstraintConfig objects to combine (minimum 1)
Attributes:
type— Always"and"(Literal)constraints— List of constraints to AND together
Example:
from rust_ephem.constraints import AndConstraint, SunConstraint, MoonConstraint combined = AndConstraint(constraints=[ SunConstraint(min_angle=45.0), MoonConstraint(min_angle=10.0), ])
OrConstraint
Logical OR combination — satisfied if ANY sub-constraint is satisfied.
- class OrConstraint(constraints)
- Parameters:
constraints (list) – List of ConstraintConfig objects to combine (minimum 1)
Attributes:
type— Always"or"(Literal)constraints— List of constraints to OR together
Example:
from rust_ephem.constraints import OrConstraint, EclipseConstraint, EarthLimbConstraint either = OrConstraint(constraints=[ EclipseConstraint(), EarthLimbConstraint(min_angle=20.0), ])
XorConstraint
Logical XOR combination — violated when EXACTLY ONE sub-constraint is violated.
- class XorConstraint(constraints)
- Parameters:
constraints (list) – List of ConstraintConfig objects (minimum 2)
Violation Semantics:
XOR is violated when exactly one sub-constraint is violated
XOR is satisfied when zero or more than one sub-constraints are violated
Attributes:
type— Always"xor"(Literal)constraints— List of constraints (minimum 2) evaluated with XOR semantics
Example:
from rust_ephem.constraints import XorConstraint, SunConstraint, MoonConstraint exclusive = XorConstraint(constraints=[ SunConstraint(min_angle=45.0), MoonConstraint(min_angle=30.0), ])
NotConstraint
Logical NOT — inverts a constraint (satisfied when inner constraint is violated).
- class NotConstraint(constraint)
- Parameters:
constraint – ConstraintConfig object to negate
Attributes:
type— Always"not"(Literal)constraint— Constraint to negate
Example:
from rust_ephem.constraints import NotConstraint, EclipseConstraint # Satisfied when NOT in eclipse not_eclipse = NotConstraint(constraint=EclipseConstraint())
Operator Overloading
All Pydantic constraint models support Python bitwise operators for intuitive composition:
Operator |
Equivalent |
Description |
|---|---|---|
|
|
Logical AND — both must be satisfied |
|
|
Logical OR — at least one must be satisfied |
|
|
Logical XOR — violated when exactly one is violated |
|
|
Logical NOT — inverts the constraint |
Example:
from rust_ephem.constraints import (
SunConstraint, MoonConstraint, EclipseConstraint, EarthLimbConstraint
)
# Build complex constraint with operators
constraint = (
SunConstraint(min_angle=45.0) |
MoonConstraint(min_angle=10.0) |
~EclipseConstraint(umbra_only=True)
)
# Equivalent to:
# AndConstraint(constraints=[
# SunConstraint(min_angle=45.0),
# MoonConstraint(min_angle=10.0),
# NotConstraint(constraint=EclipseConstraint(umbra_only=True))
# ])
# Chain multiple operators
complex_constraint = (
(SunConstraint(min_angle=45.0) | MoonConstraint(min_angle=10.0)) |
EarthLimbConstraint(min_angle=28.0)
)
Common Methods (RustConstraintMixin)
All Pydantic constraint models inherit these methods:
- evaluate(ephemeris, target_ra, target_dec, times=None, indices=None, target_roll=None, n_roll_samples=DEFAULT_N_ROLL_SAMPLES)
Evaluate the constraint using the Rust backend.
This method lazily creates the corresponding Rust constraint object on first use.
- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ra (float) – Target right ascension in degrees (ICRS/J2000)
target_dec (float) – Target declination in degrees (ICRS/J2000)
times – Optional specific time(s) to evaluate
indices – Optional specific time index/indices to evaluate
target_roll (float or None) – Spacecraft roll angle (degrees). When
None(default) and the constraint contains a boresight offset with non-zero pitch/yaw, sweepsn_roll_samplesroll angles and marks a timestamp as violated only when every roll is blocked (no valid spacecraft orientation exists). Pass an explicit float to evaluate at a fixed roll.n_roll_samples (int) – Number of roll angles to sweep when
target_rollisNoneand the constraint is roll-dependent. DefaultDEFAULT_N_ROLL_SAMPLES(360 ≈ 1° resolution). Ignored whentarget_rollis given or no pitch/yaw offset is present.
- Returns:
ConstraintResult containing violation windows
- Return type:
- evaluate_batch(ephemeris, target_ras, target_decs, times=None, indices=None, target_rolls=None, n_roll_samples=DEFAULT_N_ROLL_SAMPLES)
Evaluate the constraint for multiple targets and return one
ConstraintResultper target.This is the recommended high-level API for batch constraint evaluation. Supports per-target roll angles and automatic roll-sweeping for roll-dependent constraints.
- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ras (list) – List of target right ascensions in degrees (ICRS/J2000)
target_decs (list) – List of target declinations in degrees (ICRS/J2000)
times – Optional specific time(s) to evaluate
indices – Optional specific time index/indices to evaluate
target_rolls (list[float | None] or None) – Optional per-target spacecraft roll angles in degrees. List of length equal to
target_ras. Each entry may beNoneto auto-sweep that target’s roll. WhenNonefor a target and the constraint is roll-dependent, sweepsn_roll_samplesroll angles and marks violated only if all rolls are blocked.n_roll_samples (int) – Number of roll angles to sweep when a target has
target_roll=Noneand the constraint is roll-dependent. DefaultDEFAULT_N_ROLL_SAMPLES(360 ≈ 1° resolution).
- Returns:
List of
ConstraintResultobjects, one per input target- Return type:
list[ConstraintResult]
- in_constraint_batch(ephemeris, target_ras, target_decs, times=None, indices=None, target_rolls=None, n_roll_samples=DEFAULT_N_ROLL_SAMPLES)
Check if targets are in-constraint for multiple RA/Dec positions (vectorized).
Supports per-target roll angles and automatic roll-sweeping for roll-dependent constraints.
- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ras (list) – List of target right ascensions in degrees
target_decs (list) – List of target declinations in degrees
times – Optional specific time(s) to evaluate
indices – Optional specific time index/indices to evaluate
target_rolls (list[float | None] or None) – Optional per-target spacecraft roll angles in degrees. List of length equal to
target_ras. Each entry may beNoneto auto-sweep that target’s roll. WhenNonefor a target and the constraint is roll-dependent, sweepsn_roll_samplesroll angles and returnsTrueonly when all rolls are blocked.n_roll_samples (int) – Number of roll angles to sweep when a target has
target_roll=Noneand the constraint is roll-dependent. DefaultDEFAULT_N_ROLL_SAMPLES. Ignored when alltarget_rollsare fixed floats or no pitch/yaw offset is present.
- Returns:
2D numpy array of shape (n_targets, n_times) with boolean violation status
- Return type:
numpy.ndarray
Example:
# Evaluate multiple targets, some with fixed rolls, some with auto-sweep violations = constraint.in_constraint_batch( ephem, target_ras=[0.0, 90.0, 180.0], target_decs=[0.0, 45.0, -30.0], target_rolls=[0.0, None, 90.0] # First: fixed 0°, Second: sweep all, Third: fixed 90° ) print(violations.shape) # (3, n_times)
- in_constraint(time, ephemeris, target_ra, target_dec, target_roll=None, n_roll_samples=DEFAULT_N_ROLL_SAMPLES)
Check if target violates the constraint at given time(s).
- Parameters:
time (datetime or list[datetime] or numpy.ndarray) – The time(s) to check (must exist in ephemeris). Can be a single datetime, list of datetimes, or numpy array of datetimes.
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ra (float) – Target right ascension in degrees
target_dec (float) – Target declination in degrees
target_roll (float or None) – Spacecraft roll angle (degrees). When
None(default) and the constraint contains a boresight offset with non-zero pitch/yaw, sweepsn_roll_samplesroll angles and returnsTrueonly when every roll is blocked (no valid spacecraft orientation exists). Pass an explicit float to evaluate at a fixed roll.n_roll_samples (int) – Number of roll angles to sweep when
target_rollisNoneand the constraint is roll-dependent. DefaultDEFAULT_N_ROLL_SAMPLES. Ignored whentarget_rollis given or no pitch/yaw offset is present.
- Returns:
True if constraint is satisfied at the given time(s). Returns a single bool for a single time, or a list of bools for multiple times.
- Return type:
bool or list[bool]
- roll_range(time, ephemeris, target_ra, target_dec, n_roll_samples=360)
Return contiguous roll-angle intervals where the constraint is satisfied (target visible).
Sweeps
n_roll_samplesuniformly-spaced spacecraft roll angles over [0°, 360°), identifies those where the constraint isFalse(not violated), and collapses adjacent valid samples into(min_deg, max_deg)intervals.- Parameters:
time (datetime) – A single datetime to evaluate (must exist in ephemeris).
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
target_ra (float) – Target right ascension in degrees (ICRS/J2000)
target_dec (float) – Target declination in degrees (ICRS/J2000)
n_roll_samples (int) – Number of uniformly-spaced roll angles to test over [0°, 360°). Default 360 (1° resolution).
- Returns:
List of
(min_deg, max_deg)tuples, one per contiguous valid interval. Empty list if no roll is valid.- Return type:
list[tuple[float, float]]
- instantaneous_field_of_regard(ephemeris, time=None, index=None, n_points=DEFAULT_N_POINTS, n_roll_samples=DEFAULT_N_ROLL_SAMPLES, target_roll=None)
Compute instantaneous field of regard in steradians.
Field of regard is the visible solid angle at a single timestamp, where visibility is defined by the constraint not being violated (
False).When
target_rollis not specified (the default), boresight-offset constraints with non-zero pitch/yaw sweepn_roll_samplesspacecraft roll angles uniformly over [0°, 360°) and count a sky direction as accessible if any roll satisfies the inner constraint. This gives the maximum accessible sky over all possible spacecraft orientations. Passtarget_rollto evaluate at a specific spacecraft roll angle.- Parameters:
ephemeris – One of TLEEphemeris, SPICEEphemeris, GroundEphemeris, or OEMEphemeris
time (datetime or None) – Specific datetime to evaluate (must exist in ephemeris)
index (int or None) – Specific time index to evaluate
n_points (int) – Number of Fibonacci-sphere sky samples. Higher values improve accuracy at the cost of speed. Default
DEFAULT_N_POINTS.n_roll_samples (int) – Number of spacecraft roll angles to sweep when
target_rollis not specified (uniformly spaced over [0°, 360°)). Ignored whentarget_rollis given or no pitch/yaw offset is present. DefaultDEFAULT_N_ROLL_SAMPLES(360 ≈ 1° resolution). Can be reduced (e.g., 72 for 5° resolution) for faster evaluation.target_roll (float) – Spacecraft roll angle about +X (degrees). When
None(default), sweeps all roll angles for boresight-offset FoR.
- Returns:
Visible solid angle in steradians, range
[0, 4π]- Return type:
float
- Raises:
ValueError – If exactly one of
timeorindexis not provided
- boresight_offset(roll_deg=0.0, roll_clockwise=False, roll_reference='north', pitch_deg=0.0, yaw_deg=0.0)
Wrap this constraint with a fixed boresight Euler-angle offset.
roll_degis the fixed mechanical roll of the instrument relative to the spacecraft coordinate frame. It defaults to0.0(instrument aligned with spacecraft). Spacecraft roll at observation time is applied separately viatarget_rollon the evaluation methods.- Parameters:
roll_deg (float) – Fixed instrument roll offset about boresight +X in degrees, relative to the spacecraft’s nominal roll frame. Default
0.0.roll_clockwise (bool) – If True, positive roll is clockwise looking along +X.
roll_reference (str) – Roll-zero reference axis. Default is
"north"for celestial-north-projected +Z zero-roll. Use"sun"for Sun-projected +Z zero-roll.pitch_deg (float) – Fixed boresight pitch offset about +Y in degrees
yaw_deg (float) – Fixed boresight yaw offset about +Z in degrees
- Returns:
BoresightOffsetConstraint wrapping this constraint
- Return type:
BoresightOffsetConstraint
- and_(other)
Combine this constraint with another using logical AND.
- Parameters:
other – Another ConstraintConfig
- Returns:
AndConstraint combining both
- Return type:
- or_(other)
Combine this constraint with another using logical OR.
- Parameters:
other – Another ConstraintConfig
- Returns:
OrConstraint combining both
- Return type:
- xor_(other)
Combine this constraint with another using logical XOR.
- Parameters:
other – Another ConstraintConfig
- Returns:
XorConstraint combining both
- Return type:
- not_()
Negate this constraint using logical NOT.
- Returns:
NotConstraint negating this constraint
- Return type:
Result Classes
ConstraintResult
Result of constraint evaluation containing all violation information.
- class ConstraintResult
Attributes:
violations(list[ConstraintViolation]) — List of violation time windowsall_satisfied(bool) — True if constraint was satisfied for entire time rangeconstraint_name(str) — Name/description of the constrainttimestamps(numpy.ndarray | list[datetime]) — Evaluation times (cached, lazy)constraint_array(list[bool]) — Boolean array where True = violated (cached, lazy)visibility(list[VisibilityWindow]) — Contiguous windows when target is visible
Methods:
- total_violation_duration()
Get the total duration of violations in seconds.
- Returns:
Total violation duration in seconds
- Return type:
float
- in_constraint(time)
Check if the target is in-constraint at a given time.
- Parameters:
time (datetime) – A datetime object (must exist in evaluated timestamps)
- Returns:
True if constraint is violated at the given time
- Return type:
bool
- Raises:
ValueError – If time is not in the evaluated timestamps
Example:
result = constraint.evaluate(ephem, 83.63, 22.01) print(f"Constraint: {result.constraint_name}") print(f"All satisfied: {result.all_satisfied}") print(f"Total violation duration: {result.total_violation_duration()} seconds") # Access visibility windows for window in result.visibility: print(f"Visible: {window.start_time} to {window.end_time}") # Efficient iteration using cached arrays for time, violated in zip(result.timestamps, result.constraint_array): if not violated: print(f"Target visible at {time}")
ConstraintViolation
Information about a specific constraint violation time window.
- class ConstraintViolation
Attributes:
start_time(datetime) — Start time of violation windowend_time(datetime) — End time of violation windowmax_severity(float) — Maximum severity of violation (0.0 = just violated, 1.0+ = severe)description(str) — Human-readable description of the violation
Example:
for violation in result.violations: print(f"Violation: {violation.start_time} to {violation.end_time}") print(f" Severity: {violation.max_severity:.2f}") print(f" Description: {violation.description}")
VisibilityWindow
Time window when the observation target is not constrained (visible).
- class VisibilityWindow
Attributes:
start_time(datetime) — Start time of visibility windowend_time(datetime) — End time of visibility windowduration_seconds(float) — Duration of the window in seconds (computed property)
Example:
for window in result.visibility: print(f"Window: {window.start_time} to {window.end_time}") print(f" Duration: {window.duration_seconds / 3600:.2f} hours")
Moving Target Visibility
Use
Constraint.evaluate_moving_body()when RA/Dec change with time. Two modes:Provide aligned arrays:
target_ras,target_decs(same length as ephemeris timestamps).Provide a
bodyname or NAIF ID; positions come fromephemeris.get_body(default planetary kernelde440s.bsp; override withspice_kernelpath or URL, downloads cached under~/.cache/rust_ephem).
JPL Horizons Support
When the SPICE kernel (e.g.,
de440s.bsp) does not contain a body, you can automatically fall back to JPL Horizons to query its position and velocity. Setuse_horizons=Trueinget_body()orevaluate_moving_body()to enable this fallback:from rust_ephem import TLEEphemeris from rust_ephem.constraints import SunConstraint from datetime import datetime eph = TLEEphemeris(norad_id=28485, begin=datetime(2024, 6, 1), end=datetime(2024, 6, 2)) constraint = SunConstraint(min_angle=45) # Query a minor planet (Ceres, NAIF ID 1) using JPL Horizons result = constraint.evaluate_moving_body( ephemeris=eph, body="1", # Ceres use_horizons=True, # Fall back to JPL Horizons if SPICE doesn't have it ) print(result.all_satisfied) # Overall constraint satisfaction print(len(result.visibility)) # Number of visibility windows
Notes:
Horizons queries use the default time range from the ephemeris.
Positions are queried from NASA’s JPL Horizons system via HTTP (requires internet).
Both
get_body()andevaluate_moving_body()support theuse_horizonsparameter.Without
use_horizons=True, bodies not in SPICE kernels will raise an error.
Example (body lookup)
from rust_ephem import TLEEphemeris from rust_ephem.constraints import SunConstraint from datetime import datetime, timedelta eph = TLEEphemeris(norad_id=28485, begin=datetime(2024, 6, 1), end=datetime(2024, 6, 2)) constraint = SunConstraint(min_angle=45) result = constraint.evaluate_moving_body( ephemeris=eph, body="4", # Mars (names like "Mars" also work) ) print(result.constraint_array[0:5]) # per-sample satisfied flags print(len(result.visibility)) # merged visibility windows print(result.visibility)
Example (explicit RA/Dec arrays)
import numpy as np from rust_ephem import TLEEphemeris from rust_ephem.constraints import EarthLimbConstraint eph = TLEEphemeris(norad_id=28485) times = eph.timestamp[:100] # numpy datetime64 array ras = np.linspace(10.0, 12.0, len(times)) decs = np.linspace(-20.0, -21.0, len(times)) constraint = EarthLimbConstraint(min_angle=30) result = constraint.evaluate_moving_body( ephemeris=eph, target_ras=list(ras), target_decs=list(decs), )
Notes
target_ras/target_decsmust have the same length as ephemeris timestamps.When
bodyis set, timestamps come from the ephemeris.Body positions use the planetary ephemeris kernel (default
de440s.bsp). To override for a specific body lookup, callephemeris.get_body(body, spice_kernel="path_or_url", use_horizons=True)(local file or URL). Downloads are cached under~/.cache/rust_ephem; reuse the cached path to avoid re-fetching.If you already have a planetary kernel on disk, point
spice_kernelat that path; this does not affect telescope/observer geometry — only body positions.Use
use_horizons=Truefor bodies not available in your SPICE kernels; JPL Horizons covers all major and many minor solar system bodies.
Type Aliases
- ConstraintConfig
Union type for all constraint configuration classes:
ConstraintConfig = Union[ SunConstraint, MoonConstraint, EclipseConstraint, EarthLimbConstraint, BodyConstraint, AndConstraint, OrConstraint, XorConstraint, NotConstraint, ]
Use this type for function signatures that accept any constraint type.
- CombinedConstraintConfig
Pydantic TypeAdapter for parsing constraint configurations from JSON:
from rust_ephem.constraints import CombinedConstraintConfig json_str = '{"type": "sun", "min_angle": 45.0}' constraint = CombinedConstraintConfig.validate_json(json_str)
JSON Serialization
All Pydantic constraint models can be serialized to/from JSON:
from rust_ephem.constraints import SunConstraint, MoonConstraint
# Create constraint
constraint = SunConstraint(min_angle=45.0) | MoonConstraint(min_angle=10.0)
# Serialize to JSON
json_str = constraint.model_dump_json()
# '{"type":"and","constraints":[{"type":"sun","min_angle":45.0,"max_angle":null},{"type":"moon","min_angle":10.0,"max_angle":null}]}'
# Parse from JSON
from rust_ephem.constraints import CombinedConstraintConfig
parsed = CombinedConstraintConfig.validate_json(json_str)
# Or use the Rust backend directly
import rust_ephem
rust_constraint = rust_ephem.Constraint.from_json(json_str)
Performance Guide
The constraint system is optimized for high-performance evaluation. Follow these guidelines for best performance:
Batch Evaluation (Fastest)
For evaluating many targets, use in_constraint_batch():
import numpy as np
# Generate 10,000 target positions
target_ras = np.random.uniform(0, 360, 10000)
target_decs = np.random.uniform(-90, 90, 10000)
# Single call evaluates all targets (3-50x faster than loop)
violations = constraint.in_constraint_batch(ephem, target_ras, target_decs)
# violations.shape = (10000, n_times)
Performance by constraint type:
Sun/Moon proximity: ~3-4x speedup over loop
Earth limb: ~5x speedup
Eclipse: ~48x speedup (target-independent)
Logical combinators: ~2-3x speedup
Single Target Evaluation
For a single target over many times:
# FAST: Evaluate once, use cached arrays
result = constraint.evaluate(ephem, ra, dec)
# Access cached arrays (90x faster on repeated access)
times = result.timestamp
satisfied = result.constraint_array
# Find visibility windows directly
visible_indices = np.where(satisfied)[0]
Single Time Check
For checking a single time:
# Use in_constraint() for single-time checks
is_visible = constraint.in_constraint(time, ephem, ra, dec)
For checking multiple times efficiently:
# Use in_constraint() with arrays for multiple times
times_array = ephem.timestamp[10:20] # numpy array
results = constraint.in_constraint(times_array, ephem, ra, dec)
# Returns list of booleans
Anti-Patterns (Avoid)
# SLOW: Don't call in_constraint() in a loop
for time in ephem.timestamp:
if constraint.in_constraint(time, ephem, ra, dec): # Re-evaluates every time!
pass
# SLOW: Don't call evaluate() for each target
for ra, dec in zip(target_ras, target_decs):
result = constraint.evaluate(ephem, ra, dec) # Use in_constraint_batch() instead!
Subset Evaluation
Evaluate only specific times to reduce computation:
# Only evaluate first 10 and last 10 times
indices = list(range(10)) + list(range(-10, 0))
result = constraint.evaluate(ephem, ra, dec, indices=indices)
# Only evaluate specific datetimes
specific_times = [
datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
datetime(2024, 1, 1, 18, 0, 0, tzinfo=timezone.utc),
]
result = constraint.evaluate(ephem, ra, dec, times=specific_times)