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-10-21 00:25:25,197 gym INFO Resetting environment with seed=0
2026-10-21 00:25:25,199 scene.targets INFO Generating 1 targets
2026-10-21 00:25:25,268 sats.satellite.ground INFO <0.00> ground: Finding opportunity windows from 0.00 to 600.00 seconds
2026-10-21 00:25:25,275 gym INFO <0.00> Environment reset
2026-10-21 00:25:25,276 gym INFO <0.00> === STARTING STEP ===
2026-10-21 00:25:25,276 sats.satellite.ground INFO <0.00> ground: target index 0 tasked
2026-10-21 00:25:25,277 sats.satellite.ground INFO <0.00> ground: Target(city) tasked for imaging
2026-10-21 00:25:25,278 sats.satellite.ground INFO <0.00> ground: Target(city) window enabled: 0.0 to 85.4
2026-10-21 00:25:25,279 sats.satellite.ground INFO <0.00> ground: setting timed terminal event at 30.0
2026-10-21 00:25:25,281 sats.satellite.observer INFO <0.00> observer: setting timed terminal event at 30.0
2026-10-21 00:25:25,282 sats.satellite.debris INFO <0.00> debris: action_drift tasked for 1000000000.0 seconds
2026-10-21 00:25:25,282 sats.satellite.debris INFO <0.00> debris: setting timed terminal event at 1000000000.0
2026-10-21 00:25:25,285 sim.simulator INFO <1.00> Max step duration reached
2026-10-21 00:25:25,286 data.base INFO <1.00> Total reward: {}
2026-10-21 00:25:25,286 comm.communication INFO <1.00> Communicating data in 6 directions.
2026-10-21 00:25:25,287 comm.communication INFO <1.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,291 gym INFO <1.00> Step reward: {}
2026-10-21 00:25:25,292 gym INFO <1.00> === STARTING STEP ===
2026-10-21 00:25:25,295 sats.satellite.ground INFO <2.00> ground: imaged Target(city)
2026-10-21 00:25:25,296 sim.simulator INFO <2.00> Max step duration reached
2026-10-21 00:25:25,299 data.base INFO <2.00> Total reward: {'ground': 3.0, 'observer': 2.0}
2026-10-21 00:25:25,299 comm.communication INFO <2.00> Communicating data in 6 directions.
2026-10-21 00:25:25,300 comm.communication INFO <2.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,300 sats.satellite.ground INFO <2.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,301 sats.satellite.observer INFO <2.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,305 gym INFO <2.00> Step reward: {'ground': 3.0, 'observer': 2.0}
2026-10-21 00:25:25,306 gym INFO <2.00> === STARTING STEP ===
2026-10-21 00:25:25,307 sats.satellite.ground WARNING <2.00> ground: Requires retasking but received no task.
2026-10-21 00:25:25,308 sats.satellite.observer WARNING <2.00> observer: Requires retasking but received no task.
2026-10-21 00:25:25,310 sim.simulator INFO <3.00> Max step duration reached
2026-10-21 00:25:25,311 data.base INFO <3.00> Total reward: {}
2026-10-21 00:25:25,311 comm.communication INFO <3.00> Communicating data in 6 directions.
2026-10-21 00:25:25,312 comm.communication INFO <3.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,312 sats.satellite.ground INFO <3.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,313 sats.satellite.observer INFO <3.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,317 gym INFO <3.00> Step reward: {}
2026-10-21 00:25:25,318 gym INFO <3.00> === STARTING STEP ===
2026-10-21 00:25:25,319 sats.satellite.ground WARNING <3.00> ground: Requires retasking but received no task.
2026-10-21 00:25:25,320 sats.satellite.observer WARNING <3.00> observer: Requires retasking but received no task.
2026-10-21 00:25:25,321 sim.simulator INFO <4.00> Max step duration reached
2026-10-21 00:25:25,322 data.base INFO <4.00> Total reward: {}
2026-10-21 00:25:25,323 comm.communication INFO <4.00> Communicating data in 6 directions.
2026-10-21 00:25:25,323 comm.communication INFO <4.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,324 sats.satellite.ground INFO <4.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,325 sats.satellite.observer INFO <4.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,328 gym INFO <4.00> Step reward: {}
2026-10-21 00:25:25,329 gym INFO <4.00> === STARTING STEP ===
2026-10-21 00:25:25,330 sats.satellite.ground WARNING <4.00> ground: Requires retasking but received no task.
2026-10-21 00:25:25,331 sats.satellite.observer WARNING <4.00> observer: Requires retasking but received no task.
2026-10-21 00:25:25,332 sim.simulator INFO <5.00> Max step duration reached
2026-10-21 00:25:25,333 data.base INFO <5.00> Total reward: {}
2026-10-21 00:25:25,334 comm.communication INFO <5.00> Communicating data in 6 directions.
2026-10-21 00:25:25,334 comm.communication INFO <5.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,335 sats.satellite.ground INFO <5.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,336 sats.satellite.observer INFO <5.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,339 gym INFO <5.00> Step reward: {}
2026-10-21 00:25:25,340 gym INFO <5.00> === STARTING STEP ===
2026-10-21 00:25:25,341 sats.satellite.ground WARNING <5.00> ground: Requires retasking but received no task.
2026-10-21 00:25:25,342 sats.satellite.observer WARNING <5.00> observer: Requires retasking but received no task.
2026-10-21 00:25:25,344 sim.simulator INFO <6.00> Max step duration reached
2026-10-21 00:25:25,345 data.base INFO <6.00> Total reward: {}
2026-10-21 00:25:25,345 comm.communication INFO <6.00> Communicating data in 6 directions.
2026-10-21 00:25:25,346 comm.communication INFO <6.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,346 sats.satellite.ground INFO <6.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,347 sats.satellite.observer INFO <6.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,351 gym INFO <6.00> Step reward: {}
2026-10-21 00:25:25,351 gym INFO <6.00> === STARTING STEP ===
2026-10-21 00:25:25,352 sats.satellite.ground WARNING <6.00> ground: Requires retasking but received no task.
2026-10-21 00:25:25,353 sats.satellite.observer WARNING <6.00> observer: Requires retasking but received no task.
2026-10-21 00:25:25,355 sim.simulator INFO <7.00> Max step duration reached
2026-10-21 00:25:25,356 data.base INFO <7.00> Total reward: {}
2026-10-21 00:25:25,357 comm.communication INFO <7.00> Communicating data in 6 directions.
2026-10-21 00:25:25,357 comm.communication INFO <7.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,358 sats.satellite.ground INFO <7.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,359 sats.satellite.observer INFO <7.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,362 gym INFO <7.00> Step reward: {}
2026-10-21 00:25:25,362 gym INFO <7.00> === STARTING STEP ===
2026-10-21 00:25:25,364 sats.satellite.ground WARNING <7.00> ground: Requires retasking but received no task.
2026-10-21 00:25:25,365 sats.satellite.observer WARNING <7.00> observer: Requires retasking but received no task.
2026-10-21 00:25:25,366 sim.simulator INFO <8.00> Max step duration reached
2026-10-21 00:25:25,367 data.base INFO <8.00> Total reward: {}
2026-10-21 00:25:25,367 comm.communication INFO <8.00> Communicating data in 6 directions.
2026-10-21 00:25:25,368 comm.communication INFO <8.00> Optimizing data communication between all pairs of satellites
2026-10-21 00:25:25,369 sats.satellite.ground INFO <8.00> ground: Satellite ground requires retasking
2026-10-21 00:25:25,369 sats.satellite.observer INFO <8.00> observer: Satellite observer requires retasking
2026-10-21 00:25:25,373 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']