Mission Configuration

The 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:

from conops.config import MissionConfig

config = MissionConfig()

2. Programmatic Configuration

Build a configuration by specifying individual components:

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:

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):

config = MissionConfig.from_yaml_file("spacecraft_config.yaml")

5. Save to JSON File

Save a configuration to JSON for version control or sharing:

config.to_json_file("spacecraft_config.json")

6. Save to YAML File

Save a configuration to YAML with helpful annotations explaining units and purpose:

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.

name: str = "Default Config"

Example:

config = MissionConfig(name="STROBE-X Observatory")

random_seed

An optional integer seed for stochastic planning decisions.

random_seed: int | None = None

When set, COASTSim uses this value to seed the random-number generators that break scheduling ties and select ground-station pass opportunities. Identical seeds produce identical plans across runs with the same inputs, making simulation results reproducible and regression-testable.

When None (the default), system-time entropy is used, so each run may produce different orderings when targets have equal merit or multiple ground stations are visible simultaneously.

Example:

config = MissionConfig(name="Reproducible Run", random_seed=42)

attitude_constraint_scopes

Controls which constraints are checked against the executed attitude timeline during post-run plan validation.

attitude_constraint_scopes: dict[str, list[AttitudeConstraintScope]] = <mode-specific defaults>

Background

At the end of every run(), COASTSim compares the exported plan against the ACS telemetry it actually produced — a step called plan execution validation. One part of that validation scans every attitude sample in the telemetry and checks whether the spacecraft was pointing somewhere it shouldn’t have been.

Not all modes are held to the same standard. During SCIENCE mode a pointing violation is unambiguously a simulation bug. During SLEWING the spacecraft is actively rotating between targets and may briefly sweep through a soft-constraint zone by design — flagging that as an error would produce false positives. The attitude_constraint_scopes map lets you express, per ACS mode, which categories of attitude constraints the planner is expected to enforce.

If any sample fails its configured scope check, a PlanExecutionMismatch is recorded and run() raises PlanExecutionMismatchError before returning. The error message includes the first few violations — mode, obsid, RA/Dec/roll, and the constraint name — to aid debugging.

Planner admission guarantees vs. FM-monitored runtime conditions

Not every constraint violation is a scheduling failure. The scopes per mode encode which violations the planner claims to prevent, and therefore which post-simulation telemetry samples should be reported as planning mismatches. Fault Management still monitors configured telemetry thresholds at runtime.

  • Planner admission guarantee — a constraint the scheduler evaluates via in_constraint() before accepting an attitude into the plan. If telemetry shows a violation in one of these modes, the planner broke its contract and the simulation records a PlanExecutionMismatch. This covers the configured scopes for that mode.

  • FM-monitored runtime condition — a violation outside the selected validation scopes (for example, an imaging-quality constraint while a mode is configured only with hardware_safety). The simulation’s Fault Management system dispatches a FaultEvent in real time when a configured threshold is crossed; the post-simulation validator does not raise a mismatch for violations outside the mode’s scoped contract.

Scope values (AttitudeConstraintScope):

Value

Behaviour

"hardware_safety"

Hardware/instrument hard keepouts: explicit safety constraints, star-tracker hard, radiator hard, and telescope hard.

"imaging_quality"

Science/image-quality constraints: Sun, Moon, Earth limb, orbit, anti-Sun, star-tracker soft, and explicit science constraints.

"power_generation"

Solar-panel / power-positive attitude constraints.

"ground_contact"

Ground-contact attitude constraints, when configured.

An empty list disables post-run attitude constraint validation for that ACS mode.

Default scopes per ACS mode:

ACS Mode

Default Scopes

Rationale

SCIENCE

hardware_safety, imaging_quality, power_generation

Science observations must be safe, image-valid, and power-valid.

CHARGING

hardware_safety, power_generation

Emergency charging must be safe and power-positive, but does not inherit imaging-quality keepouts.

SLEWING

hardware_safety

Slews must avoid hard hardware keepouts.

PASS

hardware_safety, power_generation, ground_contact

Ground-station tracking must be safe, power-valid, and satisfy contact-specific pointing constraints.

SAA

hardware_safety

SAA attitude must avoid hard hardware keepouts.

SAFE

hardware_safety, power_generation

Safe-mode attitude must be hardware-safe and power-valid.

IDLE

hardware_safety

Idle hold attitude must avoid hard hardware keepouts.

Overriding scopes for individual modes:

Pass a partial dict — only the modes you specify are overridden; all others keep their defaults. Keys can be ACSMode enum members, their integer values, or their string names.

from conops.config import AttitudeConstraintScope, MissionConfig
from conops.common import ACSMode

config = MissionConfig(
    attitude_constraint_scopes={
        # Disable post-run attitude validation for SLEWING if a separate
        # slew constraint is already enforced by the ACS slew algorithm.
        ACSMode.SLEWING: [],
        # Add imaging-quality validation to PASS for a strict mission context.
        "PASS": [
            AttitudeConstraintScope.HARDWARE_SAFETY,
            AttitudeConstraintScope.IMAGING_QUALITY,
            AttitudeConstraintScope.POWER_GENERATION,
            AttitudeConstraintScope.GROUND_CONTACT,
        ],
    }
)

YAML configuration:

When loading a config from YAML, use the mode name string as the key and a list of scope strings as the value. Omitted modes retain their defaults.

attitude_constraint_scopes:
  SLEWING: []
  PASS:
    - hardware_safety
    - power_generation
    - ground_contact

spacecraft_bus

The SpacecraftBus defines the spacecraft bus subsystems.

Attributes:

  • name (str): Bus identifier

  • inertia_tensor_body_kg_m2 (3x3 matrix | None): Spacecraft body-frame inertia tensor in kg m²

  • power_draw (PowerDraw): Power consumption characteristics

  • attitude_control (AttitudeControlSystem): ACS configuration

  • communications (CommunicationsSystem): Optional comms system

  • heater (Heater): Optional thermal heater

  • data_generation (DataGeneration): Bus-level data generation

  • star_trackers (StarTrackerConfiguration): Optional star tracker configuration

  • radiators (RadiatorConfiguration): Optional body-mounted radiator configuration

AttitudeControlSystem Configuration:

The 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_acceleration_body (tuple[float, float, float] | None): Optional body +X/+Y/+Z acceleration limits in deg/s²

  • max_slew_rate_body (tuple[float, float, float] | None): Optional body +X/+Y/+Z slew-rate limits in deg/s

  • slew_accuracy (float): Pointing accuracy after slew completion in degrees

  • settle_time (float): Time to settle after slew completion in seconds

  • stored_momentum (StoredMomentumConfig): Optional planning-level momentum tracking

  • slew_algorithm (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.

When either body-axis tuple is present, COAST applies it as an ellipsoidal coupled-axis envelope. For a unit maneuver axis \(u\) and axis limits \(L=(L_x,L_y,L_z)\), the effective limit along the path is \(1 / \sqrt{\sum_i (u_i/L_i)^2}\). For example, equal X/Y limits of 0.2 deg/s produce a total rate of 0.2 deg/s about a 45-degree X/Y axis, or approximately 0.141 deg/s on each component. Quaternion slews resolve the maneuver axis in the initial spacecraft body frame. When a body-axis tuple is configured, slew estimates require complete starting and target attitudes (RA, Dec, and roll); kinematic calls without a maneuver axis raise an error. Constraint-avoiding paths are evaluated as rest-to-rest segments using each segment’s own body-frame rotation axis. If both tuples are omitted, the scalar fields retain their existing behavior, including legacy RA/Dec-only estimates.

Gravity-gradient momentum tracking is disabled by default. Enable it by supplying a physical body-frame inertia tensor about the spacecraft center of mass and setting gravity_gradient_enabled. Its principal moments must be positive and satisfy the triangle inequalities; a planar-body equality is allowed, including relative rounding of approximately 1.5e-8 (the square root of float64 machine epsilon). The named INERTIA_RELATIVE_TOLERANCE allows roughly eight significant digits of input precision, not physical mass-properties uncertainty. Symmetry uses the same tolerance relative to the largest tensor entry; accepted rounding asymmetry is averaged before validation and storage. COAST integrates the angular impulse in inertial coordinates, then reports the stored-momentum vector in the current body frame. This prevents attitude changes alone from appearing to generate momentum. Capacity enforcement and desaturation scheduling are not part of this tracking model.

The tracker reloads the inertia, initial momentum, sampling limits, and enable flag at the start of every run. Configuration changes between runs therefore take effect without reconstructing the simulation.

This is a sampled torque integrator, not an attitude-trajectory interpolator. To guard against missing torque between samples, enabled runs require both the executed-attitude and ephemeris intervals to be no greater than the smaller of:

  • stored_momentum.max_sample_interval_s (default 10 seconds, configurable downward);

  • stored_momentum.max_attitude_step_deg (default 5 degrees, configurable downward) divided by the fastest configured body-axis slew rate (or scalar max_slew_rate when body-axis limits are absent).

For example, with the default angular limit, a fastest rate of 2 degrees/second requires intervals of at most 2.5 seconds; lowering max_attitude_step_deg to 2 degrees requires intervals of at most 1 second. Both settings can tighten, but not relax, the default sampling guards. Set DITL.step_size and generate an ephemeris at an appropriate resolution; QueueDITL uses the ephemeris step as its execution step. Coarse runs fail before ACS execution or power updates; a fine execution step cannot compensate for a coarse ephemeris. Disabled tracking leaves existing runs unchanged.

These are conservative sampling guards, not a guaranteed integration-error tolerance. Verify convergence at finer cadence for final studies. The guard does not change scheduling cadence automatically or call the ACS state machine at synthetic intermediate times. Use fixed-plan replay when comparing numerical cadences without replanning the science schedule.

spacecraft_bus:
  inertia_tensor_body_kg_m2:
    - [1000.0, 0.0, 0.0]
    - [0.0, 1200.0, 0.0]
    - [0.0, 0.0, 800.0]
  attitude_control:
    stored_momentum:
      gravity_gradient_enabled: true
      max_sample_interval_s: 10.0
      max_attitude_step_deg: 5.0
      initial_momentum_body_n_m_s: [0.0, 0.0, 0.0]
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
        # Optional ellipsoidal (+X, +Y, +Z) body-axis envelopes:
        slew_acceleration_body=(0.02, 0.02, 0.2),  # deg/s²
        max_slew_rate_body=(0.2, 0.2, 2.0),        # deg/s
        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 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 uses the legacy ideal Sun-tracking model, with no travel or rate limits

  • 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

  • single_axis_drive (SingleAxisSolarArrayDrive | None): Optional finite, rate-limited rotation about one spacecraft-body axis

  • drive_control (SolarArrayDriveControl): Mode-aware operational policy for a finite drive; the default holds the initial/current angle in every mode

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,
)

Finite Single-Axis Array Drives

Use SingleAxisSolarArrayDrive when a panel rotates about one physical axis. The panel’s configured normal is its zero-angle reference normal. Positive drive angles follow the right-hand rule about rotation_axis. The drive begins each simulation at initial_angle_deg and holds there unless an explicit SolarArrayDriveControl lists the current ACS mode for Sun tracking. In a listed mode, it moves toward the best available Sun-facing angle without exceeding its travel or rate limits. Tracking in eclipse is disabled unless track_in_eclipse=True is explicitly configured.

The drive kinematics and control policy are separate so hardware capability does not silently impose an operations concept.

from conops.config import (
    SingleAxisSolarArrayDrive,
    SolarArrayDriveControl,
    SolarPanel,
    SolarPanelSet,
)
from conops import ACSMode

solar_panel = SolarPanelSet(
    panels=[
        SolarPanel(
            name="Articulated wing",
            normal=(1.0, 0.0, 0.0),
            max_power=3000.0,
            single_axis_drive=SingleAxisSolarArrayDrive(
                rotation_axis=(0.0, 0.0, 1.0),
                min_angle_deg=-165.0,
                max_angle_deg=165.0,
                max_rate_deg_per_s=0.25,
                initial_angle_deg=0.0,
            ),
            drive_control=SolarArrayDriveControl(
                sun_tracking_modes=[
                    ACSMode.SCIENCE,
                    ACSMode.CHARGING,
                    ACSMode.SAFE,
                ],
                track_in_eclipse=False,
            ),
        )
    ],
    conversion_efficiency=0.95,
)

Drive configuration is immutable. Each simulation owns a separate SolarArrayDriveState containing the executed angles, last update time, and state revision. Candidate pointing and roll calculations score one state snapshot without modifying it or granting motion under an attitude that has not executed. After attitude selection, each DITL sample advances the array exactly once under the executed attitude and reports endpoint power.

solar_array_drive_angles housekeeping telemetry records each resulting angle with its configured panel index and name. The solar_array_drive_angles_deg property remains as an order-based compatibility view.

Housekeeping roll offsets use a read-only roll search after the executed power sample, so they reflect the resulting physical drive angle without advancing its state again. Existing fixed-panel radiator shadowing is preserved. Combining PanelGeometry with single_axis_drive is rejected until articulated shadow transforms are supported, rather than treating the zero-angle rectangle as the executed geometry. DumbScheduler also rejects a finite drive with active tracking modes because it cannot propagate runtime drive state; use the queue simulation for dynamic finite drives.

Solar Panel Vector Helper Function

Defining panel normal vectors manually can be error-prone. The 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:

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 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 (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 create_star_tracker_vector() helper converts roll, pitch, and yaw Euler angles to a boresight vector, mirroring the solar panel helper:

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:

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 inside a HARD_KEEPOUT zone (always monitored)

  • star_tracker_soft_violations: Whether any tracker is in its soft constraint zone

  • star_tracker_functional_count: Number of trackers not in a soft constraint zone (num_trackers - soft_violation_count)

  • telescope_hard_violations: 0 or 1 — whether the telescope hard constraint is currently violated

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.

Note: star_tracker_functional_count reflects soft violations only — it does not decrease when a tracker enters a hard keepout zone. Hard violations are tracked exclusively via star_tracker_hard_violations.

When star trackers are configured, MissionConfig.init_fault_management_defaults() automatically adds:

  • A star_tracker_functional_count threshold (direction="below", yellow and red at num_trackers - 1) that fires when any tracker enters a soft constraint zone.

  • A star_tracker_hard_violations threshold (direction="above", yellow and red at 0.5, triggers_safe_mode=False) that fires immediately RED when any tracker enters a hard keepout zone.

  • A telescope_hard_violations threshold (same parameters) when a telescope hard constraint is configured.

radiator_hard_violations receives the same default FM coverage when radiators are configured — see the radiators section below.

radiators

The 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.

The ACS records radiator_hard_violations (count of radiators inside a hard keepout zone) each timestep. init_fault_management_defaults() automatically adds a monitor-only FM threshold for this field when radiators are configured.

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 (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:

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 combine target merit with configurable optimization weights. All weights default to 0.0. When every weight is zero, the queue keeps the legacy fast path: targets are sorted by merit and the first visible target is selected without the extra scoring pass.

When any weight is non-zero, visible candidates are scored as:

score =
    target.merit
    - slew_distance_weight * slew_distance_degrees
    - slew_time_weight * slew_minutes
    + collection_time_weight * collection_minutes
    - radiator_sun_exposure_weight * sun_exposure
    - radiator_earth_exposure_weight * earth_exposure

The available weights are:

  • config.targets.slew_distance_weight - Merit penalty per degree of slew. This favors nearby pointings when candidates have similar target merit.

  • config.targets.slew_time_weight - Merit penalty per minute of estimated slew time. QueueDITL candidate scoring uses a lightweight quaternion attitude-distance estimate and the configured ACS slew-time curve, without materializing the full slew path for every candidate. Once a target is selected, QueueDITL computes the full attitude-aware Slew before enqueueing the command.

  • config.targets.collection_time_weight - Merit reward per minute of useful science collection. Collection time is capped by the target visibility window, remaining target exposure, and ss_max. In QueueDITL it is also capped by the next science deadline, such as the next ground-station pass handoff or the simulation end. If that downstream deadline leaves less than ss_min of collection time, the candidate is skipped.

  • config.targets.radiator_sun_exposure_weight - Merit penalty per unit of radiator Sun exposure.

  • config.targets.radiator_earth_exposure_weight - Merit penalty per unit of radiator Earth exposure.

The radiator exposure values are area-weighted fractions in [0, 1] when radiators are configured. Higher values steer scheduling away from pointings that increase thermal loading on radiators.

All weights operate in merit units, so tune them relative to the target merit scale. For example, if normal target merits are around 100, then collection_time_weight = 1.0 gives a ten-minute candidate a 10 point reward, while slew_time_weight = 1.0 gives a ten-minute slew a 10 point penalty.

Radiator Net Heat Model

COASTSim uses a first-order net-radiative model for each radiator:

\[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:

  • \(S_0\) is solar_constant_w_per_m2

  • \(F_{earth}\) is earth_ir_flux_w_per_m2

  • \(E_{sun}, E_{earth}\) are geometric exposure factors in [0, 1]

  • \(f_{sun}, f_{earth}\) are loading weights

  • \(\alpha\) is absorptivity and \(\epsilon\) is emissivity

  • \(C_d\) is dissipation_coefficient_w_per_m2 (emission cap)

  • \(\sigma\) is the Stefan-Boltzmann constant

  • \(A\) is radiator area and \(\eta\) is efficiency

Positive \(Q_{net}\) means net heat rejection (dumping heat). Negative \(Q_{net}\) means net absorbed external radiative load.

payload

The Payload contains the science instruments.

Payload Attributes:

  • instruments (list[Instrument]): List of Instrument (or subclass) instances

Instrument Attributes:

  • name (str): Instrument identifier

  • power_draw (PowerDraw): Power consumption

  • heater (Heater): Optional thermal heater

  • data_generation (DataGeneration): Data output characteristics

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
            ),
        ),
    ]
)

Observation Timing Budgets

payload.observation_timing reserves non-collection time inside each fixed science task. All three values are finite, nonnegative seconds and default to zero. ACS settle_time is already included in slewtime; do not include it again in these payload budgets.

payload:
  observation_timing:
    setup_seconds: 30
    cleanup_seconds: 5
    handoff_seconds: 10

The task boundaries remain begin and end. Useful collection starts at begin + slewtime + setup_seconds and stops at end - cleanup_seconds - handoff_seconds. These are exported as collection_begin and collection_end, including with zero budgets. ss_min, ss_max, requested exposure, and queue collection scoring refer to useful collection, not setup or teardown. Charging and ground contacts do not acquire these payload overheads.

Setup is a generic budget for any pre-collection activity: configuration, calibration, acquisition, or another procedure. COAST does not prescribe how that time is used.

QueueDITL reserves the budgets before visibility, locked-attitude constraint, pass, and simulation deadlines. For a pass with multiple tracking profiles, the deadline is the earliest possible ingress trigger across those profiles, not just the default pass attitude. Science cleanup and handoff finish before that deadline; ingress constraints still determine which profile actually executes. For CONSTRAINT_AVOIDING, a path evaluated at admission may change before ingress as constraints move. Admission therefore reserves a time-independent upper bound: two rest-to-rest rotations of at most 180 degrees, using the slowest configured rate and acceleration, plus one settling period, one second of numerical rounding headroom, and the existing pass-trigger buffer. Execution still evaluates the actual path and constraints at ingress. This can reserve extra idle time, but cannot move ingress ahead of the reserved cleanup deadline under the configured kinematic limits. Quaternion admission continues to use the exact attitude-specific duration. New slew algorithms must supply a duration bound before admission can use them; extending the waypoint planner also requires updating its bound. A pending recharge discovered by a timestep’s power integration also shortens the collection window before data is counted, without delaying the charge command. If a later interruption cannot leave the reserved cleanup/handoff time, generation fails instead of exporting a normal observation with impossible timing. This can occur with unanticipated interrupts or a cleanup/handoff budget longer than the available recharge warning time.

Housekeeping collection_seconds integrates the useful part of each timestep, including partial steps. Data generation and remaining requested exposure use this duration for both zero and nonzero budgets. Detailed phase reporting is separate from this collection-window contract. ACS SCIENCE still describes pointing during setup and teardown; its duration must not be used as useful exposure. Zero-budget plans also use exact collection windows instead of whole-timestep exposure decrements; legacy rounded exposure totals and observation boundaries can therefore change. Collection telemetry includes executed time on attempts later dropped for not meeting the minimum snapshot; it can exceed exposure in the delivered entries. Power remains governed by the existing mode-dependent model. DITL replay preserves serialized collection windows, deriving them from configuration only when absent. Collection cannot begin before the executed slew and setup finish, even if the slew takes longer than planned. A late start reduces collection; it does not move the planned cutoff or rewrite the delivered plan. DumbScheduler does not support these budgets and rejects nonzero values; use QueueDITL to generate budgeted plans.

These are planning budgets, not flight-procedure timeouts or a guarantee of on-time return. The procedure must independently bound startup, handle settling failure, and enforce the collection cutoff and cleanup deadline.

Telescope Instruments

Telescope is a subclass of Instrument that adds optical configuration via a nested 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]): Compatibility input and derived property giving instrument +X in spacecraft body coordinates. Defaults to (1, 0, 0). When supplied without mounting, COAST normalizes the vector and derives a deterministic mounting. Assigning it later replaces the complete mounting atomically.

  • mounting (InstrumentMounting): Full rigid instrument-to-body rotation. The scalar-first Hamilton quaternion maps instrument vectors into spacecraft body coordinates. Instrument +X is the science boresight and instrument +Z is the zero-roll reference. This is the authoritative geometry. Mounting objects are immutable; replace the complete object to change the installation.

  • optics (TelescopeConfig): Optical configuration

Science target RA/Dec and roll are defined in the selected telescope frame. COAST converts them to the physical spacecraft attitude before evaluating body-mounted panels, radiators, trackers, slew-axis limits, and executed-attitude constraints. Telescope-local keep-outs must be roll-independent and are evaluated against the science line of sight during target selection and against the transformed telescope boresight during execution. Put roll-dependent constraints for body-mounted hardware on the spacecraft constraint configuration, where spacecraft roll is defined.

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 (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:

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.

from conops.config import (
    Payload,
    Instrument,
    InstrumentMounting,
    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.5,
        focal_length_m=3.5,             # f_number → 7.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),
)

# Full +Y mount with instrument +Z aligned to body +Z. A positive 90°
# rotation about body +Z maps instrument +X onto body +Y.
side_mounted = Telescope(
    name="Side-mounted Telescope",
    mounting=InstrumentMounting(
        body_from_instrument_quaternion_wxyz=(2**-0.5, 0.0, 0.0, 2**-0.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:

{
  "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.5,
          "focal_length_m": 3.5,
          "f_number": 7.0,
          "telescope_type": "Ritchey-Chrétien",
          "tube_length_m": 1.2
        }
      }
    ]
  }
}

battery

The 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 that starts emergency recharge (0-1)

  • recharge_clear_threshold (float): SOC level that clears emergency recharge (0-1, defaults to recharge_threshold + 0.005 capped at 1.0)

  • charge_level (float): Current charge in Watt-hours

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,     # Start emergency recharge below 95% SOC
    recharge_clear_threshold=0.955,  # Continue charging until 95.5% SOC
)

constraint

The 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)

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 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

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 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)

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 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”

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 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

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 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

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:

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,
        recharge_clear_threshold=0.955,
    ),
    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 when any tracker enters a soft constraint zone (degraded pointing quality). star_tracker_functional_count is computed as num_trackers - soft_violation_count and is not affected by hard keepout violations.

  4. star_tracker_hard_violations: When star trackers are configured, yellow and red are both set to 0.5 with direction="above" and triggers_safe_mode=False. Any integer violation (≥ 1) immediately fires RED. Hard violations are tracked separately from star_tracker_functional_count and always represent a health-and-safety event (tracker inside a HARD_KEEPOUT zone). No thresholds are added if no star trackers are configured.

  5. radiator_hard_violations: When radiators are configured, yellow and red are both set to 0.5 with direction="above" and triggers_safe_mode=False. Any radiator entering a hard keepout zone immediately fires RED.

  6. telescope_hard_violations: When a telescope hard constraint exists in the payload configuration, yellow and red are both set to 0.5 with direction="above" and triggers_safe_mode=False. The field is 0 or 1 (single constraint); any violation fires RED immediately.

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).

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:

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:

  • MissionConfig

  • AttitudeConstraintScope

  • SpacecraftBus

  • SolarPanelSet

  • SolarPanel

  • Payload

  • Instrument

  • Telescope

  • TelescopeConfig

  • TelescopeType

  • Battery

  • Constraint

  • GroundStationRegistry

  • GroundStation

  • OnboardRecorder

  • FaultManagement

  • AttitudeControlSystem

  • PowerDraw

  • DataGeneration

  • CommunicationsSystem

  • ObservationCategories

  • VisualizationConfig