Mixed Ground and Space Imaging

This minimal example uses three spacecraft: a ground imager, a space-to-space imager, and one passive RSO target. Each observer has its own scenario and reward channel. MixedReward keeps the different data types separate while FreeCommunication shares knowledge between spacecraft.

BSK-RL and its dependencies must be installed. The fixed geometry and permissive imaging tolerances keep this a short configuration demonstration; they are not mission settings.

[1]:
from typing import ClassVar

import numpy as np
from Basilisk.utilities import orbitalMotion

from bsk_rl import ConstellationTasking, act, data, obs, sats, scene
from bsk_rl.comm import FreeCommunication
from bsk_rl.scene.targets import Target
from bsk_rl.sim import dyn, fsw

EPOCH = "2026 OCT 01 00:00:00.000 (UTC)"

Define the spacecraft

The observers use different dynamics, flight software, observations, and imaging actions. The passive spacecraft is a participant in the RSO scenario but produces no reward data.

[2]:
class GroundObserver(sats.ImagingSatellite):
    dyn_type = dyn.GroundStationDynModel
    fsw_type = fsw.ImagingFSWModel
    observation_spec: ClassVar[list[obs.Observation]] = [obs.Time()]
    action_spec: ClassVar[list[act.Action]] = [
        act.Image(1, max_duration=30),
        act.Charge(duration=10),
    ]


class RSOObserver(sats.Satellite):
    dyn_type = (dyn.SpaceToSpaceImagingDynModel, dyn.GroundStationDynModel)
    fsw_type = fsw.SpaceToSpaceImagingFSWModel
    observation_spec: ClassVar[list[obs.Observation]] = [
        obs.Time(),
        obs.RSOTargetProperties(n_ahead_observe=1),
    ]
    action_spec: ClassVar[list[act.Action]] = [
        act.ImageRSO(
            1,
            max_duration=30,
            min_pointing_hold_s=0,
            require_illumination_during_hold=False,
        ),
        act.Charge(duration=10),
    ]

Define the targets

Both ground (FixedGroundTargets) and space (PassiveTarget) targets are defined. FixedGroundTargets places one ground target below the ground observer after the Earth’s orientation is initialized.

[3]:
class FixedGroundTargets(scene.UniformTargets):
    """Put a ground target directly below the initial imaging satellite."""

    def regenerate_targets(self):
        self.targets = [
            Target("city", [orbitalMotion.REQ_EARTH * 1e3, 0, 0], priority=3)
        ]

    def reset_post_sim_init(self):
        satellite = self.satellites[0]
        position_P = satellite.dynamics.world.PN @ satellite.dynamics.r_BN_N
        # Access registration retains this array; update it before windows are
        # generated, accounting for the realized Earth orientation at the epoch.
        self.targets[0].r_LP_P[:] = (
            self.radius * position_P / np.linalg.norm(position_P)
        )


class PassiveTarget(sats.Satellite):
    dyn_type = dyn.RSOTargetDynModel
    fsw_type = fsw.FSWModel
    observation_spec: ClassVar[list[obs.Observation]] = [obs.Time()]
    action_spec: ClassVar[list[act.Action]] = [act.Drift(duration=1e9)]

Bind scenarios and reward channels

MixedScenario.satellite_mapping lists all participants, including the passive RSO. MixedReward.satellite_mapping lists only the satellites producing each reward channel, and scenario_mapping binds each channel to its scenario ID. Channel IDs are explicit names, independent of data classes.

The ground target has priority 3 and the RSO has priority 2. The RSO reward callback awards its priority at acquisition for this example; the default RSO reward instead requires complete downlink.

[4]:
# Use a nearby RSO and a ground target below the ground observer so that
# this short demonstration can acquire both images.
satellite_args = dict(
    rN=[7e6, 0, 0],
    vN=[0, 7500, 0],
    oe=None,
    sigma_init=[0, 0, 0],
    omega_init=[0, 0, 0],
    wheelSpeeds=[0.0, 0.0, 0.0],
    imageAttErrorRequirement=2.0,  # Permissive pointing tolerance for this demo.
    instrumentBaudRate=1000,
    batteryStorageCapacity=1e9,
    storedCharge_Init=1e9,
    dataStorageCapacity=1e6,
    storageInit=0,
)
satellites = [
    GroundObserver("ground", sat_args=satellite_args),
    RSOObserver("observer", sat_args=satellite_args),
    PassiveTarget("debris"),
]
catalog = scene.RSOTargetCatalog(
    (
        scene.RSOTarget(
            "object", "debris", (7e6 + 1000, 0, 0), (0, 7500, 0), priority=2
        ),
    ),
    EPOCH,
)

mixed_scene = scene.MixedScenario(
    scenarios={
        "ground_scene": FixedGroundTargets(1),
        "rso_scene": scene.RSOTargets(catalog, ["observer"]),
    },
    satellite_mapping={
        "ground_scene": ["ground"],
        "rso_scene": ["observer", "debris"],
    },
)
mixed_reward = data.MixedReward(
    rewarders={
        "ground_images": data.UniqueImageReward(),
        "rso_images": data.RSOImageReward(
            quality_threshold=0,
            acquisition_reward_fn=lambda record, target: target.priority,
        ),
    },
    satellite_mapping={
        "ground_images": ["ground"],
        "rso_images": ["observer"],
    },
    scenario_mapping={
        "ground_images": "ground_scene",
        "rso_images": "rso_scene",
    },
)
env = ConstellationTasking(
    satellites=satellites,
    scenario=mixed_scene,
    rewarder=mixed_reward,
    communicator=FreeCommunication(),
    world_args=dict(utc_init=EPOCH),
    time_limit=60,
    max_step_duration=1,
    sim_rate=1,
    log_level="INFO",
)

Run the environment

Both observers image their first candidate. This fixed geometry ensures that the RSO candidate slot is populated; general policies must avoid selecting empty RSO slots.

Expected cumulative rewards are ground: 3, observer: 2, and debris: 0. Every spacecraft learns both channels through communication, but each observer generates data only for its assigned channel. Access heterogeneous data explicitly through data.data[channel_id].

Mappings can overlap to assign several reward channels and scenarios to one satellite, provided that satellite implements the corresponding models and actions. Combining ground and RSO imaging on the same physical satellite requires additional model integration.

[5]:
try:
    observations, info = env.reset(seed=0)
    # Action 0 images a target on each observer, and drifts on the passive RSO.
    # None continues the current action on subsequent steps.
    for step in range(8):
        actions = {sat.name: 0 if step == 0 else None for sat in env.satellites}
        observations, rewards, terminated, truncated, info = env.step(actions)

    print("Cumulative rewards:", env.rewarder.cum_reward)
    print("Ground images:", len(env.rewarder.data.data["ground_images"].imaged))
    print("RSO captures:", len(env.rewarder.data.data["rso_images"].captures))
    for sat in env.satellites:
        print(
            sat.name,
            "produces",
            list(sat.data_store.data_stores),
            "knows",
            list(sat.data_store.data.data),
        )
finally:
    env.close()
2026-11-09 20:09:15,588 gym                            INFO       Resetting environment with seed=0
2026-11-09 20:09:15,589 scene.targets                  INFO       Generating 1 targets
2026-11-09 20:09:15,646 sats.satellite.ground          INFO       <0.00> ground: Finding opportunity windows from 0.00 to 600.00 seconds
2026-11-09 20:09:15,650 gym                            INFO       <0.00> Environment reset
2026-11-09 20:09:15,651 gym                            INFO       <0.00> === STARTING STEP ===
2026-11-09 20:09:15,651 sats.satellite.ground          INFO       <0.00> ground: target index 0 tasked
2026-11-09 20:09:15,652 sats.satellite.ground          INFO       <0.00> ground: Target(city) imaging timing: maximum duration 30.0 seconds
2026-11-09 20:09:15,652 sats.satellite.ground          INFO       <0.00> ground: Target(city) tasked for imaging
2026-11-09 20:09:15,652 sats.satellite.ground          INFO       <0.00> ground: Target(city) window enabled: 0.0 to 85.4
2026-11-09 20:09:15,653 sats.satellite.ground          INFO       <0.00> ground: setting timed terminal event at 30.0
2026-11-09 20:09:15,654 sats.satellite.observer        INFO       <0.00> observer: setting timed terminal event at 30.0
2026-11-09 20:09:15,654 sats.satellite.debris          INFO       <0.00> debris: action_drift tasked for 1000000000.0 seconds
2026-11-09 20:09:15,655 sats.satellite.debris          INFO       <0.00> debris: setting timed terminal event at 1000000000.0
2026-11-09 20:09:15,656 sim.simulator                  INFO       <1.00> Max step duration reached
2026-11-09 20:09:15,656 data.base                      INFO       <1.00> Total reward: {}
2026-11-09 20:09:15,657 comm.communication             INFO       <1.00> Communicating data in 6 directions.
2026-11-09 20:09:15,657 comm.communication             INFO       <1.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,659 gym                            INFO       <1.00> Step reward: {}
2026-11-09 20:09:15,660 gym                            INFO       <1.00> === STARTING STEP ===
2026-11-09 20:09:15,661 sats.satellite.ground          INFO       <2.00> ground: imaged Target(city)
2026-11-09 20:09:15,661 sim.simulator                  INFO       <2.00> Max step duration reached
2026-11-09 20:09:15,662 data.base                      INFO       <2.00> Total reward: {'ground': 3.0, 'observer': 2.0}
2026-11-09 20:09:15,662 comm.communication             INFO       <2.00> Communicating data in 6 directions.
2026-11-09 20:09:15,662 comm.communication             INFO       <2.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,663 sats.satellite.ground          INFO       <2.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,663 sats.satellite.observer        INFO       <2.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,665 gym                            INFO       <2.00> Step reward: {'ground': 3.0, 'observer': 2.0}
2026-11-09 20:09:15,665 gym                            INFO       <2.00> === STARTING STEP ===
2026-11-09 20:09:15,666 sats.satellite.ground          WARNING    <2.00> ground: Requires retasking but received no task.
2026-11-09 20:09:15,666 sats.satellite.observer        WARNING    <2.00> observer: Requires retasking but received no task.
2026-11-09 20:09:15,667 sim.simulator                  INFO       <3.00> Max step duration reached
2026-11-09 20:09:15,668 data.base                      INFO       <3.00> Total reward: {}
2026-11-09 20:09:15,668 comm.communication             INFO       <3.00> Communicating data in 6 directions.
2026-11-09 20:09:15,668 comm.communication             INFO       <3.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,669 sats.satellite.ground          INFO       <3.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,669 sats.satellite.observer        INFO       <3.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,671 gym                            INFO       <3.00> Step reward: {}
2026-11-09 20:09:15,671 gym                            INFO       <3.00> === STARTING STEP ===
2026-11-09 20:09:15,672 sats.satellite.ground          WARNING    <3.00> ground: Requires retasking but received no task.
2026-11-09 20:09:15,672 sats.satellite.observer        WARNING    <3.00> observer: Requires retasking but received no task.
2026-11-09 20:09:15,673 sim.simulator                  INFO       <4.00> Max step duration reached
2026-11-09 20:09:15,673 data.base                      INFO       <4.00> Total reward: {}
2026-11-09 20:09:15,674 comm.communication             INFO       <4.00> Communicating data in 6 directions.
2026-11-09 20:09:15,674 comm.communication             INFO       <4.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,674 sats.satellite.ground          INFO       <4.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,674 sats.satellite.observer        INFO       <4.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,677 gym                            INFO       <4.00> Step reward: {}
2026-11-09 20:09:15,677 gym                            INFO       <4.00> === STARTING STEP ===
2026-11-09 20:09:15,677 sats.satellite.ground          WARNING    <4.00> ground: Requires retasking but received no task.
2026-11-09 20:09:15,678 sats.satellite.observer        WARNING    <4.00> observer: Requires retasking but received no task.
2026-11-09 20:09:15,679 sim.simulator                  INFO       <5.00> Max step duration reached
2026-11-09 20:09:15,680 data.base                      INFO       <5.00> Total reward: {}
2026-11-09 20:09:15,680 comm.communication             INFO       <5.00> Communicating data in 6 directions.
2026-11-09 20:09:15,680 comm.communication             INFO       <5.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,680 sats.satellite.ground          INFO       <5.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,681 sats.satellite.observer        INFO       <5.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,683 gym                            INFO       <5.00> Step reward: {}
2026-11-09 20:09:15,683 gym                            INFO       <5.00> === STARTING STEP ===
2026-11-09 20:09:15,684 sats.satellite.ground          WARNING    <5.00> ground: Requires retasking but received no task.
2026-11-09 20:09:15,685 sats.satellite.observer        WARNING    <5.00> observer: Requires retasking but received no task.
2026-11-09 20:09:15,686 sim.simulator                  INFO       <6.00> Max step duration reached
2026-11-09 20:09:15,686 data.base                      INFO       <6.00> Total reward: {}
2026-11-09 20:09:15,686 comm.communication             INFO       <6.00> Communicating data in 6 directions.
2026-11-09 20:09:15,687 comm.communication             INFO       <6.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,687 sats.satellite.ground          INFO       <6.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,687 sats.satellite.observer        INFO       <6.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,689 gym                            INFO       <6.00> Step reward: {}
2026-11-09 20:09:15,689 gym                            INFO       <6.00> === STARTING STEP ===
2026-11-09 20:09:15,690 sats.satellite.ground          WARNING    <6.00> ground: Requires retasking but received no task.
2026-11-09 20:09:15,691 sats.satellite.observer        WARNING    <6.00> observer: Requires retasking but received no task.
2026-11-09 20:09:15,692 sim.simulator                  INFO       <7.00> Max step duration reached
2026-11-09 20:09:15,692 data.base                      INFO       <7.00> Total reward: {}
2026-11-09 20:09:15,692 comm.communication             INFO       <7.00> Communicating data in 6 directions.
2026-11-09 20:09:15,692 comm.communication             INFO       <7.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,693 sats.satellite.ground          INFO       <7.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,693 sats.satellite.observer        INFO       <7.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,695 gym                            INFO       <7.00> Step reward: {}
2026-11-09 20:09:15,695 gym                            INFO       <7.00> === STARTING STEP ===
2026-11-09 20:09:15,696 sats.satellite.ground          WARNING    <7.00> ground: Requires retasking but received no task.
2026-11-09 20:09:15,696 sats.satellite.observer        WARNING    <7.00> observer: Requires retasking but received no task.
2026-11-09 20:09:15,697 sim.simulator                  INFO       <8.00> Max step duration reached
2026-11-09 20:09:15,698 data.base                      INFO       <8.00> Total reward: {}
2026-11-09 20:09:15,698 comm.communication             INFO       <8.00> Communicating data in 6 directions.
2026-11-09 20:09:15,698 comm.communication             INFO       <8.00> Optimizing data communication between all pairs of satellites
2026-11-09 20:09:15,699 sats.satellite.ground          INFO       <8.00> ground: Satellite ground requires retasking
2026-11-09 20:09:15,699 sats.satellite.observer        INFO       <8.00> observer: Satellite observer requires retasking
2026-11-09 20:09:15,701 gym                            INFO       <8.00> Step reward: {}
Cumulative rewards: {'ground': 3.0, 'observer': 2.0, 'debris': 0.0}
Ground images: 1
RSO captures: 1
ground produces ['ground_images'] knows ['ground_images', 'rso_images']
observer produces ['rso_images'] knows ['ground_images', 'rso_images']
debris produces [] knows ['ground_images', 'rso_images']