Mission Configuration ===================== The :class:`~conops.config.MissionConfig` class is the central configuration object in COASTSim. It aggregates all spacecraft subsystem configurations into a single, serializable Pydantic model that can be passed to DITL simulations. Overview -------- ``MissionConfig`` serves as the complete definition of your spacecraft, including: * Spacecraft bus properties (power, attitude control, communications) * Solar panel configuration and power generation * Payload instruments and data generation * Battery specifications * Pointing constraints (Sun, Moon, Earth limb avoidance) * Ground station network * Onboard data recorder * Fault management system * Visualization settings Creating a Configuration ------------------------ There are several ways to create a ``MissionConfig``: **1. Default Configuration** Create a configuration with all default values: .. code-block:: python from conops.config import MissionConfig config = MissionConfig() **2. Programmatic Configuration** Build a configuration by specifying individual components: .. code-block:: python from conops.config import ( MissionConfig, SpacecraftBus, SolarPanelSet, SolarPanel, Payload, Instrument, Battery, Constraint, GroundStationRegistry, GroundStation, OnboardRecorder, FaultManagement, ) config = MissionConfig( name="My Space Telescope", spacecraft_bus=SpacecraftBus(...), solar_panel=SolarPanelSet(...), payload=Payload(...), battery=Battery(...), constraint=Constraint(...), ground_stations=GroundStationRegistry(...), recorder=OnboardRecorder(...), fault_management=FaultManagement(...), ) **3. Load from JSON File** Load a pre-defined configuration from a JSON file: .. code-block:: python config = MissionConfig.from_json_file("spacecraft_config.json") **4. Load from YAML File** Load a pre-defined configuration from a YAML file (typically more human-readable than JSON): .. code-block:: python config = MissionConfig.from_yaml_file("spacecraft_config.yaml") **5. Save to JSON File** Save a configuration to JSON for version control or sharing: .. code-block:: python config.to_json_file("spacecraft_config.json") **6. Save to YAML File** Save a configuration to YAML with helpful annotations explaining units and purpose: .. code-block:: python config.to_yaml_file("spacecraft_config.yaml") The YAML output includes comprehensive comments explaining: - Default units for physical quantities (power in Watts, time in seconds, etc.) - Purpose and meaning of configuration settings - Valid ranges or constraints where applicable This makes YAML configurations particularly useful for: - Human review and editing - Documentation and configuration examples - Onboarding new team members - Version control with readable diffs Configuration Components ------------------------ name ~~~~ A human-readable name for the mission configuration. .. code-block:: python name: str = "Default Config" Example: .. code-block:: python config = MissionConfig(name="STROBE-X Observatory") spacecraft_bus ~~~~~~~~~~~~~~ The :class:`~conops.config.SpacecraftBus` defines the spacecraft bus subsystems. **Attributes:** * ``name`` (str): Bus identifier * ``power_draw`` (:class:`~conops.config.PowerDraw`): Power consumption characteristics * ``attitude_control`` (:class:`~conops.config.AttitudeControlSystem`): ACS configuration * ``communications`` (:class:`~conops.config.CommunicationsSystem`): Optional comms system * ``heater`` (:class:`~conops.config.Heater`): Optional thermal heater * ``data_generation`` (:class:`~conops.config.DataGeneration`): Bus-level data generation * ``star_trackers`` (:class:`~conops.config.StarTrackerConfiguration`): Optional star tracker configuration * ``radiators`` (:class:`~conops.config.RadiatorConfiguration`): Optional body-mounted radiator configuration **AttitudeControlSystem Configuration:** The :class:`~conops.config.AttitudeControlSystem` defines slew performance and path algorithms: * ``slew_acceleration`` (float): Maximum angular acceleration in deg/s² * ``max_slew_rate`` (float): Maximum slew rate in deg/s * ``slew_accuracy`` (float): Pointing accuracy after slew completion in degrees * ``settle_time`` (float): Time to settle after slew completion in seconds * ``slew_algorithm`` (:class:`~conops.common.enums.SlewAlgorithm`): Algorithm for computing slew paths: - ``QUATERNION`` (default): Full 3-DOF SLERP coupling pointing and roll changes - ``CONSTRAINT_AVOIDING``: Routes around any configured constraints (Sun, Earth, Moon, etc.) * ``slew_constraint`` (ConstraintConfig | None): Optional rust-ephem constraint for slew path planning. When set and ``slew_algorithm`` is ``CONSTRAINT_AVOIDING``, this constraint is used instead of the spacecraft's general pointing constraint. This allows different safety margins for slewing vs. science pointing. .. code-block:: python from conops.config import SpacecraftBus, PowerDraw, AttitudeControlSystem, Heater from conops.common.enums import SlewAlgorithm import rust_ephem spacecraft_bus = SpacecraftBus( name="Observatory Bus", power_draw=PowerDraw( nominal_power=50.0, # Watts - normal operations peak_power=300.0, # Watts - maximum draw power_mode={0: 70.0, 1: 100.0}, # Mode-specific power eclipse_power=75.0, # Power draw during eclipse ), attitude_control=AttitudeControlSystem( slew_acceleration=0.01, # deg/s² - angular acceleration max_slew_rate=0.3, # deg/s - maximum slew rate slew_accuracy=0.01, # deg - pointing accuracy settle_time=10.0, # seconds - time to settle after slew slew_algorithm=SlewAlgorithm.CONSTRAINT_AVOIDING, # Avoid all constraints slew_constraint=( # Optional: separate constraint for slewing rust_ephem.SunConstraint(min_angle=30.0) # Relaxed Sun angle during slews | rust_ephem.EarthLimbConstraint(min_angle=20.0) # Relaxed Earth limb during slews ), ), heater=Heater( name="Bus Heaters", power_draw=PowerDraw( nominal_power=25.0, eclipse_power=75.0, # Higher power in eclipse ), ), ) solar_panel ~~~~~~~~~~~ The :class:`~conops.config.SolarPanelSet` defines the solar array configuration. **SolarPanelSet Attributes:** * ``name`` (str): Array identifier * ``panels`` (list[SolarPanel]): List of individual panel configurations * ``conversion_efficiency`` (float): Default array-level efficiency (0-1) **SolarPanel Attributes:** * ``name`` (str): Panel identifier * ``gimbled`` (bool): Whether the panel can track the Sun * ``normal`` (tuple[float, float, float]): Panel normal vector in spacecraft body frame - +x is the spacecraft pointing direction (boresight) - +y is the spacecraft "up" direction - +z completes the right-handed coordinate system - Should be a unit vector for proper illumination calculations * ``max_power`` (float): Maximum power output at full illumination (Watts) * ``conversion_efficiency`` (float | None): Per-panel efficiency override .. code-block:: python from conops.config import SolarPanelSet, SolarPanel solar_panel = SolarPanelSet( name="Main Solar Array", panels=[ SolarPanel( name="Panel +Y", gimbled=False, normal=(0.0, 1.0, 0.0), # Side-mounted max_power=400.0, conversion_efficiency=0.94, ), SolarPanel( name="Panel -Y", gimbled=False, normal=(0.0, -1.0, 0.0), # Opposite side max_power=400.0, conversion_efficiency=0.94, ), ], conversion_efficiency=0.95, ) Solar Panel Vector Helper Function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Defining panel normal vectors manually can be error-prone. The :func:`~conops.config.solar_panel.create_solar_panel_vector` helper function simplifies this by generating unit normal vectors based on mount type and cant angles. **Supported Mount Types:** * ``'sidemount'``: Panel faces +Y (spacecraft "up") * ``'aftmount'``: Panel faces -X (spacecraft "back") * ``'boresight'``: Panel faces +X (spacecraft forward/pointing direction) **Cant Angles:** * ``cant_z``: Rotation around Z-axis (yaw) in degrees * ``cant_perp``: Rotation around perpendicular axis (pitch) in degrees - For 'sidemount': rotates around X-axis - For 'aftmount': rotates around Y-axis - For 'boresight': rotates around Y-axis **Examples:** .. code-block:: python from conops.config import SolarPanelSet, SolarPanel from conops.config.solar_panel import create_solar_panel_vector # Simple side-mounted panel (no cant) normal = create_solar_panel_vector('sidemount') # Result: (0.0, 1.0, 0.0) # Side-mounted panel with 30° yaw normal = create_solar_panel_vector('sidemount', cant_z=30.0) # Aft-mounted panel with 45° pitch backward slant normal = create_solar_panel_vector('aftmount', cant_perp=-45.0) # Boresight panel tilted backward 45° (forward-facing with backward slant) normal = create_solar_panel_vector('boresight', cant_perp=-45.0) # Complex orientation: boresight with 30° yaw left and 45° backward pitch normal = create_solar_panel_vector('boresight', cant_z=30.0, cant_perp=-45.0) # Use in panel definition solar_panel = SolarPanelSet( name="Main Solar Array", panels=[ SolarPanel( name="Panel +Y", gimbled=False, normal=create_solar_panel_vector('sidemount', cant_z=10.0), max_power=400.0, conversion_efficiency=0.94, ), SolarPanel( name="Panel Boresight Aft", gimbled=False, normal=create_solar_panel_vector('boresight', cant_perp=-30.0), max_power=200.0, conversion_efficiency=0.94, ), ], conversion_efficiency=0.95, ) star_tracker ~~~~~~~~~~~~ The :class:`~conops.config.StarTrackerConfiguration` configures the star tracker system. Star trackers provide attitude determination by identifying star fields and are subject to avoidance constraints (e.g., never look within N° of the Sun). **StarTrackerConfiguration Attributes:** * ``star_trackers`` (list[StarTracker]): Individual star tracker configurations * ``min_functional_trackers`` (int): Minimum trackers not in soft violation for pointing to be valid (science-quality check). Hard constraints are always enforced regardless of this value. * ``modes_require_lock`` (list[ACSMode] | None): ACS modes in which star tracker **soft** constraints are enforced as a science-quality check. ``None`` (default) enforces soft constraints in all modes. An empty list disables soft constraint checks entirely. For example, ``[ACSMode.SCIENCE]`` only applies soft constraints during science observations. .. note:: **Hard** constraints are absolute health-and-safety keep-outs (e.g. sensor blinding) and are *always* enforced in every mode, regardless of ``modes_require_lock``. ``modes_require_lock`` only gates the science-quality **soft** constraint checks. **StarTracker Attributes:** * ``name`` (str): Tracker identifier * ``orientation`` (:class:`~conops.config.StarTrackerOrientation`): Boresight direction in spacecraft body frame * ``hard_constraint`` (optional): Constraint defining regions where the tracker *cannot* operate (e.g. Sun avoidance). Always enforced. Violations are recorded in the ``star_tracker_hard_violations`` telemetry field. * ``soft_constraint`` (optional): Constraint defining regions of degraded performance (science-quality check). Enforced only in modes listed in ``StarTrackerConfiguration.modes_require_lock``. Violations are recorded in ``star_tracker_soft_violations``. **StarTrackerOrientation Attributes:** * ``boresight`` (tuple[float, float, float]): Boresight direction as a unit vector in spacecraft body frame - +x is the spacecraft pointing direction (forward/boresight) - +y is the spacecraft "up" direction - +z completes the right-handed coordinate system Star Tracker Vector Helper Function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :func:`~conops.config.star_tracker.create_star_tracker_vector` helper converts roll, pitch, and yaw Euler angles to a boresight vector, mirroring the solar panel helper: .. code-block:: python from conops.config.star_tracker import create_star_tracker_vector # Star tracker pointing along spacecraft boresight (+X) boresight = create_star_tracker_vector(roll_deg=0, pitch_deg=0, yaw_deg=0) # Result: (1.0, 0.0, 0.0) # Star tracker rotated 90° in pitch to point "up" (+Z) boresight = create_star_tracker_vector(roll_deg=0, pitch_deg=90, yaw_deg=0) # Star tracker angled 45° to the side boresight = create_star_tracker_vector(roll_deg=0, pitch_deg=0, yaw_deg=45) Euler angles use the ZYX convention: yaw about Z, then pitch about Y, then roll about X. **Example:** .. code-block:: python from conops.config import ( SpacecraftBus, StarTracker, StarTrackerConfiguration, StarTrackerOrientation, ) from conops.config.star_tracker import create_star_tracker_vector from conops.common import ACSMode from rust_ephem import SunConstraint, EarthLimbConstraint # Build two star trackers at different orientations st1 = StarTracker( name="ST1", orientation=StarTrackerOrientation( boresight=create_star_tracker_vector(pitch_deg=45), # 45° off boresight ), hard_constraint=SunConstraint(min_angle=30.0), # Always enforced: never look within 30° of Sun soft_constraint=SunConstraint(min_angle=45.0), # Science-quality: degraded within 45° of Sun ) st2 = StarTracker( name="ST2", orientation=StarTrackerOrientation( boresight=create_star_tracker_vector(pitch_deg=-45), # Opposite side ), hard_constraint=SunConstraint(min_angle=30.0), ) star_trackers = StarTrackerConfiguration( star_trackers=[st1, st2], min_functional_trackers=1, # At least one must satisfy soft constraint modes_require_lock=[ACSMode.SCIENCE], # Enforce soft constraints in science mode only ) # Attach to spacecraft bus spacecraft_bus = SpacecraftBus( name="Observatory Bus", star_trackers=star_trackers, # ... other bus fields ... ) The ACS monitors star tracker constraints at each timestep and records: * ``star_tracker_hard_violations``: Number of trackers violating their hard constraint (always monitored) * ``star_tracker_soft_violations``: Whether any tracker is in its soft constraint zone * ``star_tracker_functional_count``: Number of functional (hard-constraint-clear) trackers Hard violations are health-and-safety events and always cause a pointing-invalid result. Soft violations only affect pointing validity in modes listed in ``StarTrackerConfiguration.modes_require_lock``. When star trackers are configured, ``MissionConfig.init_fault_management_defaults()`` automatically adds a ``star_tracker_functional_count`` threshold (``direction="below"``, both yellow and red set to ``num_trackers - 1``) so that any hard violation immediately triggers a RED fault alert. radiators ~~~~~~~~~ The :class:`~conops.config.RadiatorConfiguration` models body-mounted radiators used for thermal rejection. Radiators in COASTSim have: * Hard keep-out constraints (optional) for invalid orientations. * Continuous Sun/Earth exposure metrics. * A first-order net heat-flow estimate used for optimization and analysis. Unlike star trackers, radiators do **not** use soft constraints or functional-count logic. A radiator always exists physically; geometry changes its net thermal behavior. **RadiatorConfiguration Attributes:** * ``radiators`` (list[Radiator]): Individual radiator panels **Radiator Attributes:** * ``name`` (str): Radiator identifier * ``width_m`` (float): Radiator width in meters * ``height_m`` (float): Radiator height in meters * ``orientation`` (:class:`~conops.config.RadiatorOrientation`): Outward radiator normal in spacecraft body frame * ``subsystem`` (str): Subsystem served by radiator (for example ``"payload"`` or ``"spacecraft_bus"``) * ``efficiency`` (float): Thermal efficiency factor [0, 1] * ``emissivity`` (float): Surface emissivity [0, 1] * ``absorptivity`` (float): Effective absorptivity [0, 1] * ``radiator_temperature_k`` (float): Representative radiator temperature in Kelvin * ``sink_temperature_k`` (float): Thermal sink/background temperature in Kelvin * ``solar_constant_w_per_m2`` (float): Solar constant term (W/m²), default 1361 * ``earth_ir_flux_w_per_m2`` (float): Earth IR loading term (W/m²), default 237 * ``dissipation_coefficient_w_per_m2`` (float): Emitted flux cap for model calibration/backward compatibility * ``sun_loading_factor`` (float): Weighting factor applied to Sun exposure term * ``earth_loading_factor`` (float): Weighting factor applied to Earth exposure term * ``hard_constraint`` (optional): Keep-out constraint for invalid radiator pointing **RadiatorOrientation Attributes:** * ``normal`` (tuple[float, float, float]): Unit vector in spacecraft body frame - +x is spacecraft forward/boresight - +y is spacecraft "up" - +z completes right-handed frame **Example:** .. code-block:: python import rust_ephem from conops.config import ( MissionConfig, Radiator, RadiatorConfiguration, RadiatorOrientation, ) config = MissionConfig() bus_radiator = Radiator( name="BusRad+Y", width_m=1.2, height_m=0.8, orientation=RadiatorOrientation(normal=(0.0, 1.0, 0.0)), subsystem="spacecraft_bus", efficiency=0.9, emissivity=0.85, absorptivity=0.2, radiator_temperature_k=300.0, sink_temperature_k=3.0, solar_constant_w_per_m2=1361.0, earth_ir_flux_w_per_m2=237.0, dissipation_coefficient_w_per_m2=220.0, sun_loading_factor=0.7, earth_loading_factor=0.3, hard_constraint=rust_ephem.SunConstraint(min_angle=20.0), ) payload_radiator = Radiator( name="PayloadRad-X", width_m=0.6, height_m=0.6, orientation=RadiatorOrientation(normal=(-1.0, 0.0, 0.0)), subsystem="payload", ) config.spacecraft_bus.radiators = RadiatorConfiguration( radiators=[bus_radiator, payload_radiator] ) Scheduler Optimization Weights ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Queue scheduling can penalize radiator exposure using target-selection weights: * ``config.targets.radiator_sun_exposure_weight`` * ``config.targets.radiator_earth_exposure_weight`` Higher values steer scheduling away from pointings that increase thermal loading on radiators. These are merit penalties, so tune relative to your target merit scale. Radiator Net Heat Model ^^^^^^^^^^^^^^^^^^^^^^^ COASTSim uses a first-order net-radiative model for each radiator: .. math:: Q_{net} = A\,\eta\,[\min(\epsilon\sigma(T_r^4 - T_s^4), C_d) - \alpha(S_0 f_{sun}E_{sun} + F_{earth} f_{earth}E_{earth})] Where: * :math:`S_0` is ``solar_constant_w_per_m2`` * :math:`F_{earth}` is ``earth_ir_flux_w_per_m2`` * :math:`E_{sun}, E_{earth}` are geometric exposure factors in [0, 1] * :math:`f_{sun}, f_{earth}` are loading weights * :math:`\alpha` is ``absorptivity`` and :math:`\epsilon` is ``emissivity`` * :math:`C_d` is ``dissipation_coefficient_w_per_m2`` (emission cap) * :math:`\sigma` is the Stefan-Boltzmann constant * :math:`A` is radiator area and :math:`\eta` is ``efficiency`` Positive :math:`Q_{net}` means net heat rejection (dumping heat). Negative :math:`Q_{net}` means net absorbed external radiative load. payload ~~~~~~~ The :class:`~conops.config.Payload` contains the science instruments. **Payload Attributes:** * ``instruments`` (list[Instrument]): List of :class:`~conops.config.Instrument` (or subclass) instances **Instrument Attributes:** * ``name`` (str): Instrument identifier * ``power_draw`` (:class:`~conops.config.PowerDraw`): Power consumption * ``heater`` (:class:`~conops.config.Heater`): Optional thermal heater * ``data_generation`` (:class:`~conops.config.DataGeneration`): Data output characteristics .. code-block:: python from conops.config import Payload, Instrument, PowerDraw, DataGeneration payload = Payload( instruments=[ Instrument( name="X-ray Detector", power_draw=PowerDraw( nominal_power=100.0, peak_power=150.0, power_mode={0: 120.0, 1: 80.0}, ), data_generation=DataGeneration( rate_gbps=0.001, # 1 Mbps continuous ), ), Instrument( name="Fine Guidance Sensor", power_draw=PowerDraw(nominal_power=15.0), data_generation=DataGeneration( per_observation_gb=0.01, # Fixed per observation ), ), ] ) Telescope Instruments ^^^^^^^^^^^^^^^^^^^^^ :class:`~conops.config.Telescope` is a subclass of :class:`~conops.config.Instrument` that adds optical configuration via a nested :class:`~conops.config.TelescopeConfig`. A ``Telescope`` can be placed directly in ``Payload.instruments`` alongside any other ``Instrument``. **Telescope Attributes** (in addition to Instrument fields): * ``boresight`` (tuple[float, float, float]): Unit vector in spacecraft body frame giving the direction the telescope points. Defaults to ``(1, 0, 0)`` (spacecraft forward/primary boresight). Must be a unit vector (magnitude within 1 %). * ``optics`` (:class:`~conops.config.TelescopeConfig`): Optical configuration **TelescopeConfig Attributes:** * ``aperture_m`` (float | None): Clear aperture diameter in metres * ``focal_length_m`` (float | None): Effective focal length in metres * ``f_number`` (float | None): Focal ratio — auto-derived as ``focal_length_m / aperture_m`` when both are provided and ``f_number`` is omitted; validated for consistency when all three are supplied * ``telescope_type`` (:class:`~conops.config.TelescopeType`): Optical design family * ``tube_length_m`` (float | None): Physical tube length in metres (useful for folded or catadioptric designs where tube length differs from focal length) **TelescopeType values:** .. list-table:: :header-rows: 1 :widths: 40 60 * - Enum member - Value * - ``NEWTONIAN`` - ``"Newtonian"`` * - ``CASSEGRAIN`` - ``"Cassegrain"`` * - ``RITCHEY_CHRETIEN`` - ``"Ritchey-Chrétien"`` * - ``SCHMIDT_CASSEGRAIN`` - ``"Schmidt-Cassegrain"`` * - ``MAKSUTOV_CASSEGRAIN`` - ``"Maksutov-Cassegrain"`` * - ``KORSCH`` - ``"Korsch"`` * - ``GREGORIAN`` - ``"Gregorian"`` * - ``DALL_KIRKHAM`` - ``"Dall-Kirkham"`` * - ``TMA`` - ``"Three Mirror Anastigmat"`` * - ``REFRACTOR`` - ``"Refractor"`` * - ``OTHER`` - ``"Other"`` *(default)* ``TelescopeConfig`` also exposes a read-only ``plate_scale_arcsec_per_um`` property (``206265 / focal_length_m_in_um``) for convenience, returning ``None`` when ``focal_length_m`` is not set. .. code-block:: python from conops.config import ( Payload, Instrument, Telescope, TelescopeConfig, TelescopeType, PowerDraw, DataGeneration, ) import math # f_number is derived automatically from aperture and focal length primary = Telescope( name="Primary Telescope", boresight=(1.0, 0.0, 0.0), # aligned with spacecraft boresight (default) power_draw=PowerDraw(nominal_power=80.0, peak_power=120.0), data_generation=DataGeneration(rate_gbps=0.5), optics=TelescopeConfig( aperture_m=0.6, focal_length_m=6.0, # f_number → 10.0 (auto-derived) telescope_type=TelescopeType.RITCHEY_CHRETIEN, tube_length_m=1.2, ), ) # Off-axis telescope: boresight 30° off spacecraft +X, rotated toward +Y off_axis_boresight = (math.cos(math.radians(30)), math.sin(math.radians(30)), 0.0) secondary = Telescope( name="Secondary Telescope", boresight=off_axis_boresight, optics=TelescopeConfig(aperture_m=0.15, focal_length_m=1.5), ) print(primary.optics.f_number) # 10.0 print(primary.optics.plate_scale_arcsec_per_um) # ~3.44e-5 arcsec/µm # Mix Telescope and plain Instrument in the same payload payload = Payload( instruments=[ primary, Instrument(name="Fine Guidance Sensor", power_draw=PowerDraw(nominal_power=15.0)), ] ) JSON Configuration ^^^^^^^^^^^^^^^^^^ ``Telescope`` serialises cleanly to/from JSON. The ``optics`` block maps directly to the ``TelescopeConfig`` fields: .. code-block:: json { "payload": { "instruments": [ { "name": "Primary Telescope", "boresight": [1.0, 0.0, 0.0], "power_draw": { "nominal_power": 80.0, "peak_power": 120.0, "power_mode": {} }, "data_generation": { "rate_gbps": 0.5, "per_observation_gb": 0.0 }, "optics": { "aperture_m": 0.6, "focal_length_m": 6.0, "f_number": 10.0, "telescope_type": "Ritchey-Chrétien", "tube_length_m": 1.2 } } ] } } battery ~~~~~~~ The :class:`~conops.config.Battery` models the spacecraft battery system. **Attributes:** * ``name`` (str): Battery identifier * ``amphour`` (float): Battery capacity in amp-hours * ``voltage`` (float): Battery voltage (Volts) * ``watthour`` (float): Total energy capacity (Watt-hours, auto-calculated) * ``max_depth_of_discharge`` (float): Maximum allowed DoD (0-1) * ``recharge_threshold`` (float): SOC level to end emergency recharge (0-1) * ``charge_level`` (float): Current charge in Watt-hours .. code-block:: python from conops.config import Battery battery = Battery( name="Primary Battery", amphour=20.0, voltage=28.0, watthour=560.0, # Optional, calculated from amphour * voltage max_depth_of_discharge=0.4, # Allow 40% discharge recharge_threshold=0.95, # Recharge until 95% SOC ) constraint ~~~~~~~~~~ The :class:`~conops.config.Constraint` defines pointing constraints for the spacecraft. **Attributes:** * ``sun_constraint``: Minimum angle from the Sun * ``anti_sun_constraint``: Maximum angle from anti-Sun direction * ``moon_constraint``: Minimum angle from the Moon * ``earth_constraint``: Minimum angle from Earth limb * ``panel_constraint``: Solar panel pointing constraint * ``ephem``: Ephemeris object (set at runtime, not serialized) .. code-block:: python import rust_ephem from conops.config import Constraint constraint = Constraint( sun_constraint=rust_ephem.SunConstraint(min_angle=45.0), moon_constraint=rust_ephem.MoonConstraint(min_angle=20.0), earth_constraint=rust_ephem.EarthLimbConstraint(min_angle=15.0), panel_constraint=( rust_ephem.SunConstraint(min_angle=45.0, max_angle=135.0) & ~rust_ephem.EclipseConstraint() ), ) # Set ephemeris at runtime constraint.ephem = ephemeris ground_stations ~~~~~~~~~~~~~~~ The :class:`~conops.config.GroundStationRegistry` contains all ground stations. **GroundStationRegistry Methods:** * ``add(station)``: Add a ground station * ``get(code)``: Get station by code * ``codes()``: List all station codes * ``default()``: Get pre-populated registry **GroundStation Attributes:** * ``code`` (str): Short identifier (e.g., "MAL", "SGS") * ``name`` (str): Human-readable name * ``latitude_deg`` (float): Latitude in degrees * ``longitude_deg`` (float): Longitude in degrees * ``elevation_m`` (float): Elevation in meters * ``min_elevation_deg`` (float): Minimum pass elevation * ``schedule_probability`` (float): Probability of scheduling (0-1) * ``bands`` (list[BandCapability]): Supported frequency bands * ``gain_db`` (float | None): Antenna gain in dB .. code-block:: python from conops.config import GroundStationRegistry, GroundStation, BandCapability ground_stations = GroundStationRegistry( stations=[ GroundStation( code="MAL", name="Malindi", latitude_deg=-3.22, longitude_deg=40.12, elevation_m=0.0, min_elevation_deg=10.0, schedule_probability=1.0, bands=[ BandCapability( band="S", uplink_rate_mbps=2.0, downlink_rate_mbps=10.0, ), ], ), GroundStation( code="SGS", name="Svalbard", latitude_deg=78.229, longitude_deg=15.407, min_elevation_deg=5.0, schedule_probability=0.8, bands=[ BandCapability(band="X", downlink_rate_mbps=150.0), ], ), ] ) recorder ~~~~~~~~ The :class:`~conops.config.OnboardRecorder` simulates onboard data storage. **Attributes:** * ``name`` (str): Recorder identifier * ``capacity_gb`` (float): Maximum capacity in Gigabits * ``current_volume_gb`` (float): Current stored data in Gigabits * ``yellow_threshold`` (float): Warning threshold (fraction, 0-1) * ``red_threshold`` (float): Critical threshold (fraction, 0-1) .. code-block:: python from conops.config import OnboardRecorder recorder = OnboardRecorder( name="Solid State Recorder", capacity_gb=128.0, current_volume_gb=0.0, yellow_threshold=0.7, # 70% full warning red_threshold=0.9, # 90% full critical ) fault_management ~~~~~~~~~~~~~~~~ The :class:`~conops.config.FaultManagement` monitors parameters and triggers safe mode. **Attributes:** * ``thresholds`` (list[FaultThreshold]): Parameter thresholds * ``red_limit_constraints`` (list[FaultConstraint]): Pointing constraints * ``safe_mode_on_red`` (bool): Trigger safe mode on RED conditions **FaultThreshold Attributes:** * ``name`` (str): Parameter name to monitor * ``yellow`` (float): Yellow (warning) threshold * ``red`` (float): Red (critical) threshold * ``direction`` (str): "below" or "above" .. code-block:: python from conops.config import FaultManagement, FaultThreshold import rust_ephem fault_management = FaultManagement( thresholds=[ FaultThreshold( name="battery_level", yellow=0.5, red=0.4, direction="below", # Alert when value drops below threshold ), FaultThreshold( name="recorder_fill_fraction", yellow=0.7, red=0.9, direction="above", # Alert when value exceeds threshold ), ], safe_mode_on_red=True, ) # Add red limit constraint for Sun avoidance fault_management.add_red_limit_constraint( name="Sun Avoidance", constraint=rust_ephem.SunConstraint(min_angle=20.0) & ~rust_ephem.EclipseConstraint(), time_threshold_seconds=120.0, description="Avoid pointing within 20° of Sun for more than 2 minutes", ) observation_categories ~~~~~~~~~~~~~~~~~~~~~~ The :class:`~conops.config.ObservationCategories` defines how observations are categorized based on their target ID (obsid) for visualization purposes. **Attributes:** * ``categories`` (list[ObservationCategory]): Category definitions * ``default_name`` (str): Default category name * ``default_color`` (str): Default visualization color .. code-block:: python from conops.config import ObservationCategories, ObservationCategory categories = ObservationCategories( categories=[ ObservationCategory( name="Science", obsid_min=10000, obsid_max=30000, color="tab:blue", ), ObservationCategory( name="Calibration", obsid_min=90000, obsid_max=91000, color="gray", ), ], default_name="Other", default_color="tab:purple", ) # Or use defaults categories = ObservationCategories.default_categories() visualization ~~~~~~~~~~~~~ The :class:`~conops.config.VisualizationConfig` controls plot appearance. This field is excluded from JSON serialization. **Attributes:** * ``mode_colors`` (dict): Colors for ACS modes * ``font_family`` (str): Font for plots * ``title_font_size`` (int): Title font size * ``label_font_size`` (int): Axis label font size * ``figsize`` (tuple): Default figure size * ``timeline_figsize`` (tuple): DITL timeline figure size * ``data_telemetry_figsize`` (tuple): Data management plot size .. code-block:: python from conops.config import VisualizationConfig visualization = VisualizationConfig( mode_colors={ "SCIENCE": "green", "SLEWING": "orange", "SAA": "purple", "PASS": "cyan", "CHARGING": "yellow", "SAFE": "red", }, font_family="DejaVu Sans", title_font_size=14, figsize=(16, 10), ) Complete Programmatic Example ----------------------------- Here is a complete example of creating a ``MissionConfig`` programmatically: .. code-block:: python from conops.config import ( MissionConfig, SpacecraftBus, SolarPanelSet, SolarPanel, Payload, Instrument, Battery, Constraint, GroundStationRegistry, GroundStation, OnboardRecorder, FaultManagement, PowerDraw, DataGeneration, BandCapability, AttitudeControlSystem, Heater, ) from conops.common import ACSMode import rust_ephem # Create fault management with custom thresholds fault_management = FaultManagement() fault_management.add_threshold("battery_level", yellow=0.5, red=0.4, direction="below") # Create the complete configuration config = MissionConfig( name="Example Observatory", spacecraft_bus=SpacecraftBus( name="Observatory Bus", power_draw=PowerDraw( nominal_power=50.0, peak_power=300.0, power_mode={0: 70.0, 1: 100.0}, eclipse_power=75.0 ), attitude_control=AttitudeControlSystem( slew_acceleration=0.01, max_slew_rate=0.3, slew_accuracy=0.01, settle_time=10.0 ), heater=Heater( name="Bus Heaters", power_draw=PowerDraw( nominal_power=25.0, eclipse_power=75.0 ) ) ), solar_panel=SolarPanelSet( name="Solar Array", panels=[ SolarPanel( name="Panel A", gimbled=False, sidemount=True, max_power=500.0, conversion_efficiency=0.94 ) ], conversion_efficiency=0.95 ), payload=Payload( instruments=[ Instrument( name="Main Instrument", power_draw=PowerDraw(nominal_power=100.0, peak_power=150.0), data_generation=DataGeneration(rate_gbps=0.001) ) ] ), battery=Battery( amphour=20.0, voltage=28.0, max_depth_of_discharge=0.4, recharge_threshold=0.95 ), constraint=Constraint( sun_constraint=rust_ephem.SunConstraint(min_angle=45.0), moon_constraint=rust_ephem.MoonConstraint(min_angle=20.0), earth_constraint=rust_ephem.EarthLimbConstraint(min_angle=15.0) ), ground_stations=GroundStationRegistry( stations=[ GroundStation( code="GND", name="Ground Station", latitude_deg=35.0, longitude_deg=-106.0, min_elevation_deg=10.0, bands=[BandCapability(band="S", downlink_rate_mbps=10.0)] ) ] ), recorder=OnboardRecorder( capacity_gb=64.0, yellow_threshold=0.7, red_threshold=0.9 ), fault_management=fault_management ) Automatic Fault Thresholds -------------------------- When a ``MissionConfig`` is created, it automatically initializes default fault thresholds based on the battery and recorder configuration: 1. **battery_level**: Yellow at ``1.0 - max_depth_of_discharge``, Red 10% below that 2. **recorder_fill_fraction**: Uses the recorder's ``yellow_threshold`` and ``red_threshold`` 3. **star_tracker_functional_count**: When star trackers are configured, both yellow and red are set to ``num_trackers - 1`` with ``direction="below"``. This fires the moment any tracker enters a hard constraint zone (``functional_count`` drops from ``num_trackers`` to ``num_trackers - 1``), making any hard violation immediately critical. No threshold is added if no star trackers are configured. You can override these by adding custom thresholds to the ``FaultManagement`` instance before calling ``init_fault_management_defaults()`` (defaults are skipped if a threshold for that parameter already exists). .. code-block:: python from conops.config import MissionConfig, FaultManagement from conops.common import ACSMode # Create config (automatically adds default thresholds on first use) config = MissionConfig() # Override the auto-configured battery threshold config.fault_management.add_threshold( "battery_level", yellow=0.35, red=0.25, direction="below", ) Using with DITL Simulation -------------------------- Pass the configuration to DITL for simulation: .. code-block:: python from conops.ditl import DITL, QueueDITL from datetime import datetime, timedelta # Create or load configuration config = MissionConfig.from_json_file("config.json") # Set ephemeris on constraint (required at runtime) config.constraint.ephem = ephemeris # Run simulation ditl = DITL( config=config, ephem=ephemeris, begin=datetime(2025, 1, 1), end=datetime(2025, 1, 2), ) ditl.calc() # Or use QueueDITL for target-driven simulation queue_ditl = QueueDITL( config=config, ephem=ephemeris, begin=datetime(2025, 1, 1), end=datetime(2025, 1, 2), ) queue_ditl.run() API Reference ------------- For detailed API documentation, see: * :class:`~conops.config.MissionConfig` * :class:`~conops.config.SpacecraftBus` * :class:`~conops.config.SolarPanelSet` * :class:`~conops.config.SolarPanel` * :class:`~conops.config.Payload` * :class:`~conops.config.Instrument` * :class:`~conops.config.Telescope` * :class:`~conops.config.TelescopeConfig` * :class:`~conops.config.TelescopeType` * :class:`~conops.config.Battery` * :class:`~conops.config.Constraint` * :class:`~conops.config.GroundStationRegistry` * :class:`~conops.config.GroundStation` * :class:`~conops.config.OnboardRecorder` * :class:`~conops.config.FaultManagement` * :class:`~conops.config.AttitudeControlSystem` * :class:`~conops.config.PowerDraw` * :class:`~conops.config.DataGeneration` * :class:`~conops.config.CommunicationsSystem` * :class:`~conops.config.ObservationCategories` * :class:`~conops.config.VisualizationConfig`