RSO Inspection
This example demonstrates the configuration of a resident space object (RSO) inspection environment, in which a servicer spacecraft circumnavigates a RSO to image the illuminated facets.
[2]:
from functools import partial
from importlib.metadata import version
from typing import ClassVar
import numpy as np
from Basilisk.architecture import bskLogging
from Basilisk.utilities.orbitalMotion import elem2rv
from bsk_rl import ConstellationTasking, act, data, obs, sats, scene
from bsk_rl.obs.relative_observations import rso_imaged_regions
from bsk_rl.sim import dyn, fsw
from bsk_rl.utils.attitude import hill_frame_attitude
from bsk_rl.utils.orbital import (
fibonacci_sphere,
random_orbit,
random_unit_vector,
relative_to_chief,
)
bskLogging.setDefaultLogLevel(bskLogging.BSK_WARNING)
/Users/afgh1891/Repositories/Pull_request/.venv_new/bin/python
RLlib is actively developed and can change significantly from version to version. For this script, the following version is used:
[3]:
version("ray") # Parent package of RLlib
[3]:
'2.35.0'
Defining the Satellites
First, the RSO satellite is configured. A simple model is used that has no actuators modelled and just deterministically points nadir.
[4]:
class RSOSat(sats.Satellite):
observation_spec: ClassVar[list[obs.Observation]] = [
obs.SatProperties(dict(prop="one", fn=lambda _: 1.0)),
]
action_spec: ClassVar[list[act.Action]] = [act.NadirPoint(duration=1e9)]
dyn_type = (dyn.ConjunctionDynModel, dyn.RSODynModel)
fsw_type = fsw.FSWModel
Arguments for the satellite are configured for smooth pointing behavior.
[5]:
rso_sat_args = dict(conjunction_radius=2.0)
The inspector satellite has a more complex configuration. First, an observation function for the sun vector is defined.
[6]:
def sun_hat_chief(self, other):
r_SN_N = (
self.simulator.world.gravFactory.spiceObject.planetStateOutMsgs[
self.simulator.world.sun_index
]
.read()
.PositionVector
)
r_BN_N = self.dynamics.r_BN_N
r_SN_N = np.array(r_SN_N)
r_SB_N = r_SN_N - r_BN_N
r_SB_N_hat = r_SB_N / np.linalg.norm(r_SB_N)
HN = other.dynamics.HN
return HN @ r_SB_N_hat
The inspector satellite is configured with observations relating to the relative state and the mission objectives. The satellite is given an action for impulsively thrusting and drifting. The dynamics and flight software models introduce a maximum range check, collision checking orbital maneuvers, and RSO inspection.
[7]:
class InspectorSat(sats.Satellite):
observation_spec: ClassVar[list[obs.Observation]] = [
obs.SatProperties(
dict(prop="dv_available", norm=10),
dict(prop="inclination", norm=np.pi),
dict(prop="eccentricity", norm=0.1),
dict(prop="semi_major_axis", norm=7000),
dict(prop="ascending_node", norm=2 * np.pi),
dict(prop="argument_of_periapsis", norm=2 * np.pi),
dict(prop="true_anomaly", norm=2 * np.pi),
dict(prop="beta_angle", norm=np.pi),
),
obs.ResourceRewardWeight(),
obs.RelativeProperties(
dict(prop="r_DC_Hc", norm=500),
dict(prop="v_DC_Hc", norm=5),
dict(prop="rso_sigma_BH", norm=1.0),
dict(prop="rso_omega_BH", norm=0.01),
dict(
prop="rso_imaged_regions",
fn=partial(
rso_imaged_regions,
region_centers=fibonacci_sphere(15),
frame="chief_hill",
),
),
dict(prop="sun_hat_Hc", fn=sun_hat_chief),
chief_name="RSO",
),
obs.Eclipse(norm=5700),
obs.Time(),
]
action_spec: ClassVar[list[act.Action]] = [
act.ImpulsiveThrustHill(
chief_name="RSO",
max_dv=1.0,
max_drift_duration=5700.0 * 2,
fsw_action="action_inspect_rso",
)
]
dyn_type = (dyn.MaxRangeDynModel, dyn.ConjunctionDynModel, dyn.RSOInspectorDynModel)
fsw_type = (
fsw.SteeringFSWModel,
fsw.MagicOrbitalManeuverFSWModel,
fsw.RSOInspectorFSWModel,
)
Generous configurations are used for the inspector, allowing for “sloppy” attitude control with a low simulation step rate.
[8]:
inspector_sat_args = dict(
imageAttErrorRequirement=1.0,
imageRateErrorRequirement=None,
instrumentBaudRate=1,
dataStorageCapacity=1e6,
batteryStorageCapacity=1e9,
storedCharge_Init=1e9,
conjunction_radius=2.0,
dv_available_init=10.0,
max_range_radius=1000,
chief_name="RSO",
u_max=1.0,
)
Environment Generation
A satellite argument randomizer is defined to configure the initial state of the satellites. The RSO is put into a random orbit with an apogee and perigee between 500 km and 1100 km. The inspector is placed in the region 250 to 750 meters from the RSO, with up to 1 m/s of relative velocity. Finally, the RSO’s attitude and body rate are set up to be in the nadir-pointing initial configuration.
[9]:
def sat_arg_randomizer(satellites, spin_config=None):
# Generate the RSO orbit
R_E = 6371.0 # km
a = R_E + np.random.uniform(500, 1100)
e = np.random.uniform(0.0, min(1 - (R_E + 500) / a, (R_E + 1100) / a - 1))
chief_orbit = random_orbit(a=a, e=e)
inspectors = [sat for sat in satellites if "Inspector" in sat.name]
rso = next(satellite for satellite in satellites if satellite.name == "RSO")
# Generate the inspector initial states.
args = {}
for inspector in inspectors:
relative_randomizer = relative_to_chief(
chief_name="RSO",
chief_orbit=chief_orbit,
deputy_relative_state={
inspector.name: lambda: np.concatenate(
(
random_unit_vector() * np.random.uniform(250, 750),
random_unit_vector() * np.random.uniform(0, 1.0),
)
),
},
)
args.update(relative_randomizer([rso, inspector]))
# Configure the RSO attitude. Zero relative spin preserves nadir pointing.
mu = rso.sat_args_generator["mu"]
r_N, v_N = elem2rv(mu, args[rso]["oe"])
sigma_init, omega_init = hill_frame_attitude(r_N, v_N, spin_config=spin_config)
args[rso]["sigma_init"] = sigma_init
args[rso]["omega_init"] = omega_init
return args
The scenario is configured to set the RSO geometry as a sphere with 100 points at a radius of 1 meter. Points must be imaged within 30 degrees of their normal, with illumination coming from no more than 60 degrees from normal. The inspector must be within 250 meters to inspect the RSO.
[10]:
scenario = scene.SphericalRSO(
n_points=100,
radius=1.0,
theta_max=np.radians(30),
range_max=250,
theta_solar_max=np.radians(60),
)
This scenario uses two rewarders. For the RSO inspection component of the task, a bonus of 1.0 is yielded once at least 90% of the illuminated points have been inspected. The ResourceReward is used to penalize fuel use, with some basic logic add to only apply the reward to the Inspector.
[11]:
rewarders = (
data.RSOInspectionReward(
completion_bonus=1.0,
completion_threshold=0.90,
),
data.ResourceReward(
resource_fn=lambda sat: (
sat.fsw.dv_available
if isinstance(sat.fsw, fsw.MagicOrbitalManeuverFSWModel)
else 0.0
),
reward_weight=np.random.uniform(0.0, 0.5),
),
)
By default, the RSO is initialized in a nadir-pointing attitude. Alternatively, spin_config can be used to specify a fixed or uniformly sampled body rate about a radial, tangential, normal, velocity-aligned, random, or user-defined Hill-frame axis. All body rates are specified in deg/s. Leaving rso_spin_config as None retains nadir pointing for this example.
[ ]:
# For example, use {"type": "fixed", "rate": 1.0, "axis": "normal"} to add a 1 deg/s spin about the orbit-normal axis,
# or {"type": "uniform", "low": 0.0, "high": 1.0, "axis": "normal"} to set a random spin rate within a specific bound
rso_spin_config = None
configured_sat_arg_randomizer = partial(sat_arg_randomizer, spin_config=rso_spin_config)
With all the components defined, the environment can be instantiated.
[12]:
env = ConstellationTasking(
satellites=[
RSOSat("RSO", sat_args=rso_sat_args),
InspectorSat("Inspector", sat_args=inspector_sat_args, obs_type=dict),
],
sat_arg_randomizer=configured_sat_arg_randomizer,
scenario=scenario,
rewarder=rewarders,
time_limit=60000,
sim_rate=5.0,
log_level="INFO",
)
Environment Interaction
The environment is reset and randomly stepped through.
Future Work: This example will be updated with an actual trained policy in the future.
[13]:
env.reset()
for i in range(4):
env.step(dict(RSO=0, Inspector=env.action_space("Inspector").sample()))
2026-10-07 12:59:42,721 gym INFO Resetting environment with seed=3806700768
2026-10-07 12:59:46,190 utils.orbital WARNING <0.00> Could not find eclipse transitions in next 12000.0 seconds
2026-10-07 12:59:46,195 gym INFO <0.00> Environment reset
/Users/afgh1891/Repositories/Pull_request/.venv_new/lib/python3.12/site-packages/gymnasium/spaces/box.py:231: UserWarning: WARN: Box low's precision lowered by casting to float32, current low.dtype=float64
gym.logger.warn(
/Users/afgh1891/Repositories/Pull_request/.venv_new/lib/python3.12/site-packages/gymnasium/spaces/box.py:297: UserWarning: WARN: Box high's precision lowered by casting to float32, current high.dtype=float64
gym.logger.warn(
2026-10-07 12:59:46,495 gym INFO <0.00> === STARTING STEP ===
2026-10-07 12:59:46,496 sats.satellite.RSO INFO <0.00> RSO: action_nadir_point tasked for 1000000000.0 seconds
2026-10-07 12:59:46,496 sats.satellite.RSO INFO <0.00> RSO: setting timed terminal event at 1000000000.0
2026-10-07 12:59:46,497 sats.satellite.Inspector INFO <0.00> Inspector: Thrust clamped from 1.1321751672541251 m/s to 1.0 m/s.
2026-10-07 12:59:46,498 sats.satellite.Inspector INFO <0.00> Inspector: Thrusting with inertial dV [ 0.33530239 -0.94189951 0.01994027] with 1783.0487060546875 second drift.
2026-10-07 12:59:46,500 sats.satellite.Inspector INFO <0.00> Inspector: setting timed terminal event at 1783.0
2026-10-07 12:59:46,500 sats.satellite.Inspector INFO <0.00> Inspector: FSW action action_inspect_rso activated.
2026-10-07 12:59:46,552 sats.satellite.Inspector INFO <1575.00> Inspector: Exceeded maximum range of 1000 m from RSO
2026-10-07 12:59:46,557 sats.satellite.Inspector INFO <1575.00> Inspector: Inspected 8 points this step
2026-10-07 12:59:46,558 data.rso_inspection INFO <1575.00> Inspector inspected 8 new points.
2026-10-07 12:59:46,558 data.rso_inspection INFO <1575.00> Inspected/Illuminated/Total: 8/35/100
2026-10-07 12:59:46,558 data.composition INFO <1575.00> RSOInspectionReward reward: {'Inspector': 0.08}
2026-10-07 12:59:46,559 data.composition INFO <1575.00> ResourceReward reward: {'Inspector': np.float64(-0.3615391244763904)}
2026-10-07 12:59:46,559 data.base INFO <1575.00> Total reward: {'Inspector': np.float64(-0.2815391244763904)}
2026-10-07 12:59:46,561 sats.satellite.Inspector WARNING <1575.00> Inspector: failed range_valid check
2026-10-07 12:59:46,570 utils.orbital WARNING <1575.00> Could not find eclipse transitions in next 12000.0 seconds
2026-10-07 12:59:46,571 gym INFO <1575.00> Step reward: {'Inspector': np.float64(-1.2815391244763905)}
2026-10-07 12:59:46,571 gym INFO <1575.00> Episode terminated: ['Inspector']
2026-10-07 12:59:46,572 gym INFO <1575.00> === STARTING STEP ===
2026-10-07 12:59:46,573 sats.satellite.RSO INFO <1575.00> RSO: action_nadir_point tasked for 1000000000.0 seconds
2026-10-07 12:59:46,574 sats.satellite.RSO INFO <1575.00> RSO: setting timed terminal event at 1000001575.0
2026-10-07 12:59:46,575 sats.satellite.Inspector INFO <1575.00> Inspector: Thrusting with inertial dV [-0.10399702 -0.16877615 0.55939106] with 10681.8955078125 second drift.
2026-10-07 12:59:46,576 sats.satellite.Inspector INFO <1575.00> Inspector: setting timed terminal event at 12256.9
2026-10-07 12:59:46,576 sats.satellite.Inspector INFO <1575.00> Inspector: FSW action action_inspect_rso activated.
2026-10-07 12:59:46,927 sats.satellite.Inspector INFO <12260.00> Inspector: timed termination at 12256.9
2026-10-07 12:59:46,942 data.rso_inspection INFO <12260.00> Inspected/Illuminated/Total: 8/46/100
2026-10-07 12:59:46,943 data.composition INFO <12260.00> ResourceReward reward: {'Inspector': np.float64(-0.21456642807097814)}
2026-10-07 12:59:46,944 data.base INFO <12260.00> Total reward: {'Inspector': np.float64(-0.21456642807097814)}
2026-10-07 12:59:46,944 sats.satellite.Inspector INFO <12260.00> Inspector: Satellite Inspector requires retasking
2026-10-07 12:59:46,946 gym INFO <12260.00> Step reward: {}
2026-10-07 12:59:46,947 gym INFO <12260.00> === STARTING STEP ===
2026-10-07 12:59:46,948 sats.satellite.RSO INFO <12260.00> RSO: action_nadir_point tasked for 1000000000.0 seconds
2026-10-07 12:59:46,948 sats.satellite.RSO INFO <12260.00> RSO: setting timed terminal event at 1000012260.0
2026-10-07 12:59:46,949 sats.satellite.Inspector INFO <12260.00> Inspector: Thrusting with inertial dV [-0.05931276 -0.43832896 -0.21176288] with 6205.38525390625 second drift.
2026-10-07 12:59:46,950 sats.satellite.Inspector INFO <12260.00> Inspector: setting timed terminal event at 18465.4
2026-10-07 12:59:46,951 sats.satellite.Inspector INFO <12260.00> Inspector: FSW action action_inspect_rso activated.
2026-10-07 12:59:47,244 sats.satellite.Inspector INFO <18470.00> Inspector: timed termination at 18465.4
2026-10-07 12:59:47,258 data.rso_inspection INFO <18470.00> Inspected/Illuminated/Total: 8/46/100
2026-10-07 12:59:47,259 data.composition INFO <18470.00> ResourceReward reward: {'Inspector': np.float64(-0.17729938984206417)}
2026-10-07 12:59:47,260 data.base INFO <18470.00> Total reward: {'Inspector': np.float64(-0.17729938984206417)}
2026-10-07 12:59:47,261 sats.satellite.Inspector INFO <18470.00> Inspector: Satellite Inspector requires retasking
2026-10-07 12:59:47,262 gym INFO <18470.00> Step reward: {}
2026-10-07 12:59:47,263 gym INFO <18470.00> === STARTING STEP ===
2026-10-07 12:59:47,265 sats.satellite.RSO INFO <18470.00> RSO: action_nadir_point tasked for 1000000000.0 seconds
2026-10-07 12:59:47,265 sats.satellite.RSO INFO <18470.00> RSO: setting timed terminal event at 1000018470.0
2026-10-07 12:59:47,267 sats.satellite.Inspector INFO <18470.00> Inspector: Thrusting with inertial dV [-0.84690159 -0.02538404 -0.33268091] with 3320.605224609375 second drift.
2026-10-07 12:59:47,268 sats.satellite.Inspector INFO <18470.00> Inspector: setting timed terminal event at 21790.6
2026-10-07 12:59:47,269 sats.satellite.Inspector INFO <18470.00> Inspector: FSW action action_inspect_rso activated.
2026-10-07 12:59:47,392 sats.satellite.Inspector INFO <21795.00> Inspector: timed termination at 21790.6
2026-10-07 12:59:47,401 data.rso_inspection INFO <21795.00> Inspected/Illuminated/Total: 8/46/100
2026-10-07 12:59:47,401 data.composition INFO <21795.00> ResourceReward reward: {'Inspector': np.float64(-0.32909261306083193)}
2026-10-07 12:59:47,402 data.base INFO <21795.00> Total reward: {'Inspector': np.float64(-0.32909261306083193)}
2026-10-07 12:59:47,402 sats.satellite.Inspector INFO <21795.00> Inspector: Satellite Inspector requires retasking
2026-10-07 12:59:47,403 gym INFO <21795.00> Step reward: {}