{ "cells": [ { "cell_type": "markdown", "id": "5479d126", "metadata": {}, "source": [ "# RSO Inspection\n", "\n", "This example demonstrates the configuration of a resident space object (RSO) inspection\n", "environment, in which a servicer spacecraft circumnavigates a RSO to image the illuminated\n", "facets." ] }, { "cell_type": "code", "execution_count": null, "id": "4209273f", "metadata": {}, "outputs": [], "source": [ "from importlib.metadata import version\n", "from bsk_rl import sats, obs, act, ConstellationTasking, scene, data\n", "from bsk_rl.obs.relative_observations import rso_imaged_regions\n", "from bsk_rl.utils.orbital import fibonacci_sphere\n", "from bsk_rl.sim import dyn, fsw\n", "import types\n", "import numpy as np\n", "from Basilisk.architecture import bskLogging\n", "from functools import partial\n", "from bsk_rl.utils.orbital import random_orbit, random_unit_vector, relative_to_chief\n", "from Basilisk.utilities.orbitalMotion import elem2rv\n", "from Basilisk.utilities.RigidBodyKinematics import C2MRP\n", "\n", "bskLogging.setDefaultLogLevel(bskLogging.BSK_WARNING)\n" ] }, { "cell_type": "markdown", "id": "22082c62", "metadata": {}, "source": [ "RLlib is actively developed and can change significantly from version to version. For this\n", "script, the following version is used:" ] }, { "cell_type": "code", "execution_count": null, "id": "fde78b08", "metadata": {}, "outputs": [], "source": [ "version(\"ray\") # Parent package of RLlib" ] }, { "cell_type": "markdown", "id": "e5875010", "metadata": {}, "source": [ "## Defining the Satellites\n", "\n", "First, the RSO satellite is configured. It is given support for nadir pointing through\n", "the ``ImagingDynModel`` and ``Downlink`` action." ] }, { "cell_type": "code", "execution_count": null, "id": "48b72ea2", "metadata": {}, "outputs": [], "source": [ "class RSOSat(sats.Satellite):\n", " observation_spec = [\n", " obs.SatProperties(dict(prop=\"one\", fn=lambda _: 1.0)),\n", " ]\n", " action_spec = [act.Downlink(duration=1e9)]\n", " dyn_type = types.new_class(\n", " \"Dyn\", (dyn.ImagingDynModel, dyn.ConjunctionDynModel, dyn.RSODynModel)\n", " )\n", " fsw_type = fsw.ContinuousImagingFSWModel\n" ] }, { "cell_type": "markdown", "id": "ac0f8d33", "metadata": {}, "source": [ "Arguments for the satellite are configured for smooth pointing behavior." ] }, { "cell_type": "code", "execution_count": null, "id": "8459a5d1", "metadata": {}, "outputs": [], "source": [ "rso_sat_args = dict(\n", " conjunction_radius=2.0,\n", " K=7.0 / 20,\n", " P=35.0 / 20,\n", " Ki=1e-6,\n", " dragCoeff=0.0,\n", " batteryStorageCapacity=1e9,\n", " storedCharge_Init=1e9,\n", " wheelSpeeds=[0.0, 0.0, 0.0],\n", " u_max=1.0,\n", ")" ] }, { "cell_type": "markdown", "id": "fcbb42c7", "metadata": {}, "source": [ "The inspector satellite has a more complex configuration. First, an observation function\n", "for the sun vector is defined." ] }, { "cell_type": "code", "execution_count": null, "id": "e01b5686", "metadata": {}, "outputs": [], "source": [ "def sun_hat_chief(self, other):\n", " r_SN_N = (\n", " self.simulator.world.gravFactory.spiceObject.planetStateOutMsgs[\n", " self.simulator.world.sun_index\n", " ]\n", " .read()\n", " .PositionVector\n", " )\n", " r_BN_N = self.dynamics.r_BN_N\n", " r_SN_N = np.array(r_SN_N)\n", " r_SB_N = r_SN_N - r_BN_N\n", " r_SB_N_hat = r_SB_N / np.linalg.norm(r_SB_N)\n", " HN = other.dynamics.HN\n", " return HN @ r_SB_N_hat\n" ] }, { "cell_type": "markdown", "id": "a4ea64ca", "metadata": {}, "source": [ "The inspector satellite is configured with observations relating to the relative state and the mission\n", "objectives. The satellite is given an action for impulsively thrusting and drifting. The \n", "dynamics and flight software models introduce a maximum range check, collision checking\n", "orbital maneuvers, and RSO inspection." ] }, { "cell_type": "code", "execution_count": null, "id": "f3cb39ce", "metadata": {}, "outputs": [], "source": [ "class InspectorSat(sats.Satellite):\n", " observation_spec = [\n", " obs.SatProperties(\n", " dict(prop=\"dv_available\", norm=10),\n", " dict(prop=\"inclination\", norm=np.pi),\n", " dict(prop=\"eccentricity\", norm=0.1),\n", " dict(prop=\"semi_major_axis\", norm=7000),\n", " dict(prop=\"ascending_node\", norm=2 * np.pi),\n", " dict(prop=\"argument_of_periapsis\", norm=2 * np.pi),\n", " dict(prop=\"true_anomaly\", norm=2 * np.pi),\n", " dict(prop=\"beta_angle\", norm=np.pi),\n", " ),\n", " obs.ResourceRewardWeight(),\n", " obs.RelativeProperties(\n", " dict(prop=\"r_DC_Hc\", norm=500),\n", " dict(prop=\"v_DC_Hc\", norm=5),\n", " dict(\n", " prop=\"rso_imaged_regions\",\n", " fn=partial(\n", " rso_imaged_regions,\n", " region_centers=fibonacci_sphere(15),\n", " frame=\"chief_hill\",\n", " ),\n", " ),\n", " dict(prop=\"sun_hat_Hc\", fn=sun_hat_chief),\n", " chief_name=\"RSO\",\n", " ),\n", " obs.Eclipse(norm=5700),\n", " obs.Time(),\n", " ]\n", " action_spec = [\n", " act.ImpulsiveThrustHill(\n", " chief_name=\"RSO\",\n", " max_dv=1.0,\n", " max_drift_duration=5700.0 * 2,\n", " fsw_action=\"action_inspect_rso\",\n", " )\n", " ]\n", " dyn_type = types.new_class(\n", " \"Dyn\",\n", " (\n", " dyn.MaxRangeDynModel,\n", " dyn.ConjunctionDynModel,\n", " dyn.RSOInspectorDynModel,\n", " ),\n", " )\n", " fsw_type = types.new_class(\n", " \"FSW\",\n", " (\n", " fsw.SteeringFSWModel,\n", " fsw.MagicOrbitalManeuverFSWModel,\n", " fsw.RSOInspectorFSWModel,\n", " ),\n", " )\n" ] }, { "cell_type": "markdown", "id": "82feb2bc", "metadata": {}, "source": [ "Generous configurations are used for the inspector, allowing for \"sloppy\" attitude control\n", "with a low simulation step rate." ] }, { "cell_type": "code", "execution_count": null, "id": "4e999a09", "metadata": {}, "outputs": [], "source": [ "inspector_sat_args = dict(\n", " imageAttErrorRequirement=1.0,\n", " imageRateErrorRequirement=None,\n", " instrumentBaudRate=1,\n", " dataStorageCapacity=1e6,\n", " batteryStorageCapacity=1e9,\n", " storedCharge_Init=1e9,\n", " conjunction_radius=2.0,\n", " dv_available_init=10.0,\n", " max_range_radius=1000,\n", " chief_name=\"RSO\",\n", " u_max=1.0,\n", ")" ] }, { "cell_type": "markdown", "id": "832adeb8", "metadata": {}, "source": [ "## Environment Generation\n", "\n", "A satellite argument randomizer is defined to configure the initial state of the satellites.\n", "The RSO is put into a random orbit with an apogee and perigee between 500 km and 1100 km.\n", "The inspector is placed in the region 250 to 750 meters from the RSO, with up to 1 m/s of\n", "relative velocity. Finally, the RSO's attitude and body rate are set up to be in the\n", "nadir-pointing initial configuration." ] }, { "cell_type": "code", "execution_count": null, "id": "f6e96922", "metadata": {}, "outputs": [], "source": [ "def sat_arg_randomizer(satellites):\n", " # Generate the RSO orbit\n", " R_E = 6371.0 # km\n", " a = R_E + np.random.uniform(500, 1100)\n", " e = np.random.uniform(0.0, min(1 - (R_E + 500) / a, (R_E + 1100) / a - 1))\n", " chief_orbit = random_orbit(a=a, e=e)\n", "\n", " inspectors = [sat for sat in satellites if \"Inspector\" in sat.name]\n", " rso = [satellite for satellite in satellites if satellite.name == \"RSO\"][0]\n", "\n", " # Generate the inspector initial states.\n", " args = {}\n", " for inspector in inspectors:\n", " relative_randomizer = relative_to_chief(\n", " chief_name=\"RSO\",\n", " chief_orbit=chief_orbit,\n", " deputy_relative_state={\n", " inspector.name: lambda: np.concatenate(\n", " (\n", " random_unit_vector() * np.random.uniform(250, 750),\n", " random_unit_vector() * np.random.uniform(0, 1.0),\n", " )\n", " ),\n", " },\n", " )\n", " args.update(relative_randomizer([rso, inspector]))\n", "\n", " # Align RSO Hill frame for initial nadir pointing\n", " mu = rso.sat_args_generator[\"mu\"]\n", " r_N, v_N = elem2rv(mu, args[rso][\"oe\"])\n", "\n", " r_hat = r_N / np.linalg.norm(r_N)\n", " v_hat = v_N / np.linalg.norm(v_N)\n", " x = r_hat\n", " z = np.cross(r_hat, v_hat)\n", " z = z / np.linalg.norm(z)\n", " y = np.cross(z, x)\n", " HN = np.array([x, y, z])\n", " BH = np.eye(3)\n", "\n", " a = chief_orbit.a\n", " T = np.sqrt(a**3 / mu) * 2 * np.pi\n", " omega_BN_N = z * 2 * np.pi / T\n", "\n", " args[rso][\"sigma_init\"] = C2MRP(BH @ HN)\n", " args[rso][\"omega_init\"] = BH @ HN @ omega_BN_N\n", "\n", " return args\n" ] }, { "cell_type": "markdown", "id": "57fa33ef", "metadata": {}, "source": [ "The scenario is configured to set the RSO geometry as a sphere with 100 points at a radius\n", "of 1 meter. Points must be imaged within 30 degrees of their normal, with illumination coming\n", "from no more than 60 degrees from normal. The inspector must be within 250 meters to inspect\n", "the RSO." ] }, { "cell_type": "code", "execution_count": null, "id": "aa79834c", "metadata": {}, "outputs": [], "source": [ "scenario = scene.SphericalRSO(\n", " n_points=100,\n", " radius=1.0,\n", " theta_max=np.radians(30),\n", " range_max=250,\n", " theta_solar_max=np.radians(60),\n", ")" ] }, { "cell_type": "markdown", "id": "9ae0983f", "metadata": {}, "source": [ "This scenario uses two rewarders. For the RSO inspection component of the task, a bonus\n", "of 1.0 is yielded once at least 90% of the illuminated points have been inspected. The\n", "ResourceReward is used to penalize fuel use, with some basic logic add to only apply the\n", "reward to the Inspector." ] }, { "cell_type": "code", "execution_count": null, "id": "02c7d5d1", "metadata": {}, "outputs": [], "source": [ "rewarders = (\n", " data.RSOInspectionReward(\n", " completion_bonus=1.0,\n", " completion_threshold=0.90,\n", " ),\n", " data.ResourceReward(\n", " resource_fn=lambda sat: sat.fsw.dv_available\n", " if isinstance(sat.fsw, fsw.MagicOrbitalManeuverFSWModel)\n", " else 0.0,\n", " reward_weight=np.random.uniform(0.0, 0.5),\n", " ),\n", ")" ] }, { "cell_type": "markdown", "id": "025e1466", "metadata": {}, "source": [ "With all the components defined, the environment can be instantiated." ] }, { "cell_type": "code", "execution_count": null, "id": "f19447ce", "metadata": {}, "outputs": [], "source": [ "env = ConstellationTasking(\n", " satellites=[\n", " RSOSat(\"RSO\", sat_args=rso_sat_args),\n", " InspectorSat(\"Inspector\", sat_args=inspector_sat_args, obs_type=dict),\n", " ],\n", " sat_arg_randomizer=sat_arg_randomizer,\n", " scenario=scenario,\n", " rewarder=rewarders,\n", " time_limit=60000,\n", " sim_rate=5.0,\n", " log_level=\"INFO\",\n", ")" ] }, { "cell_type": "markdown", "id": "804a563b", "metadata": {}, "source": [ "## Environment Interaction\n", "\n", "The environment is reset and randomly stepped through.\n", "\n", "
\n", "\n", "**Future Work:** This example will be updated with an actual trained policy in the future.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "0fcfc081", "metadata": {}, "outputs": [], "source": [ "env.reset()\n", "for i in range(4):\n", " env.step(dict(RSO=0, Inspector=env.action_space(\"Inspector\").sample()))" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.11" } }, "nbformat": 4, "nbformat_minor": 5 }