Skip to main content
This tutorial walks through scenarioSmallBodyNav.py, which demonstrates proximity operations navigation around a small body. The scenario uses smallBodyNavEKF to estimate the spacecraft’s position, velocity, and the small body’s attitude and rate relative to the inertial frame.
This module targets POMDP-solver use cases that require an autonomous navigation solution. Realistic sensor hardware models (e.g. optical navigation) are not included — simpleNav provides truth measurements with no noise by default.

What this scenario demonstrates

  • Defining a custom small body gravity model (createCustomGravObject) and its ephemeris
  • Using planetEphemeris to propagate the asteroid orbit
  • Setting up a spacecraft with RWs and an SRP radiation pressure model
  • Running smallBodyNavEKF for joint state estimation of spacecraft and small body
  • Wiring smallBodyWaypointFeedback for proximity operations guidance
  • Multi-task simulation: separate dynamics, measurement, and FSW update rates

Key modules

ModulePackageRole
spacecraft.SpacecraftBasilisk.simulation6-DOF dynamics
planetEphemeris.PlanetEphemerisBasilisk.simulationPropagates Bennu orbit and orientation
radiationPressure.RadiationPressureBasilisk.simulationSolar radiation pressure cannonball model
simpleNav.SimpleNavBasilisk.simulationTruth navigation measurements
planetNav.PlanetNavBasilisk.simulationPlanet state navigation measurements
smallBodyNavEKF.SmallBodyNavEKFBasilisk.fswAlgorithmsExtended Kalman filter for state estimation
smallBodyWaypointFeedbackBasilisk.fswAlgorithmsProximity operations waypoint controller
ephemerisConverter.EphemerisConverterBasilisk.simulationConverts planet ephemeris to navigation messages

Running the scenario

python3 scenarioSmallBodyNav.py

Step-by-step walkthrough

1

Create the simulation with three tasks

The scenario separates dynamics, measurement, and FSW into three distinct tasks, all running at 1-second intervals.
from Basilisk.utilities import SimulationBaseClass, macros

simTaskName = "simTask"
measTaskName = "measTask"
fswTaskName = "fswTask"
simProcessName = "simProcess"

scSim = SimulationBaseClass.SimBaseClass()
dynProcess = scSim.CreateNewProcess(simProcessName)

simulationTimeStep = macros.sec2nano(1.0)  # [ns] - 1-second step
dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
dynProcess.addTask(scSim.CreateNewTask(measTaskName, simulationTimeStep))
dynProcess.addTask(scSim.CreateNewTask(fswTaskName, simulationTimeStep))
2

Set up the asteroid ephemeris

PlanetEphemeris propagates Bennu’s heliocentric orbit using classical orbital elements and its known rotation state.
from Basilisk.simulation import planetEphemeris
from Basilisk.architecture import astroConstants
from Basilisk.utilities import orbitalMotion

gravBodyEphem = planetEphemeris.PlanetEphemeris()
gravBodyEphem.ModelTag = 'planetEphemeris'
gravBodyEphem.setPlanetNames(planetEphemeris.StringVector(["bennu"]))

# Bennu orbital elements (JPL Horizons, December 31, 2018)
oeAsteroid = planetEphemeris.ClassicElements()
oeAsteroid.a = 1.1259 * astroConstants.AU * 1000  # [m]
oeAsteroid.e = 0.20373
oeAsteroid.i = 6.0343 * macros.D2R     # [rad]
oeAsteroid.Omega = 2.01820 * macros.D2R  # [rad]
oeAsteroid.omega = 66.304 * macros.D2R   # [rad]
oeAsteroid.f = 346.32 * macros.D2R       # [rad]
r_ON_N, v_ON_N = orbitalMotion.elem2rv(
    orbitalMotion.MU_SUN * (1000.**3), oeAsteroid
)

gravBodyEphem.planetElements = planetEphemeris.classicElementVector([oeAsteroid])

# Bennu rotation state
gravBodyEphem.rightAscension = planetEphemeris.DoubleVector([86.6388 * macros.D2R])
gravBodyEphem.declination    = planetEphemeris.DoubleVector([-65.1086 * macros.D2R])
gravBodyEphem.lst0           = planetEphemeris.DoubleVector([0.0 * macros.D2R])
gravBodyEphem.rotRate        = planetEphemeris.DoubleVector(
    [360 * macros.D2R / (4.297461 * 3600.)]  # [rad/s] - ~4.3-hour rotation period
)
3

Create the Bennu gravity object

Basilisk lets you define custom gravity bodies by specifying the gravitational parameter directly.
from Basilisk.utilities import simIncludeGravBody

gravFactory = simIncludeGravBody.gravBodyFactory()
gravFactory.createSun()

mu = 4.892  # [m^3/s^2] - Bennu gravitational parameter
asteroid = gravFactory.createCustomGravObject(
    "bennu", mu,
    modelDictionaryKey="Bennu",
    radEquator=565. / 2.0  # [m] - mean equatorial radius
)
asteroid.planetBodyInMsg.subscribeTo(gravBodyEphem.planetOutMsgs[0])
Set modelDictionaryKey="Bennu" to match the Vizard asteroid asset bundle. Without this key the 3D visualization will not render the correct shape model.
4

Configure the spacecraft

Set up the spacecraft with initial conditions relative to Bennu’s position, an inertia tensor, and reaction wheels.
from Basilisk.simulation import spacecraft
from Basilisk.utilities import unitTestSupport, simIncludeRW
import numpy as np

scObject = spacecraft.Spacecraft()
scObject.ModelTag = "bskSat"
gravFactory.addBodiesTo(scObject)

# Spacecraft position relative to Bennu in the inertial frame
r_BO_N = np.array([2000., 1500., 1000.])  # [m]   - relative position
v_BO_N = np.array([1., 1., 1.])            # [m/s] - relative velocity

r_BN_N = np.add(r_BO_N, r_ON_N)  # [m]   - inertial position
v_BN_N = np.add(v_BO_N, v_ON_N)  # [m/s] - inertial velocity

scObject.hub.r_CN_NInit = r_BN_N
scObject.hub.v_CN_NInit = v_BN_N

I = [82.12, 0.0, 0.0, 0.0, 98.40, 0.0, 0.0, 0.0, 121.0]  # [kg*m^2]
mass = 330.  # [kg]
scObject.hub.mHub = mass
scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I)

scObject.hub.sigma_BNInit = np.array([0.1, 0.0, 0.0])
scObject.hub.omega_BN_BInit = np.array([0.1, 0.1, 0.1])  # [rad/s]

# Reaction wheels
rwFactory = simIncludeRW.rwFactory()
RW1 = rwFactory.create('Honeywell_HR16', [1, 0, 0], maxMomentum=50., Omega=100.)  # [RPM]
RW2 = rwFactory.create('Honeywell_HR16', [0, 1, 0], maxMomentum=50., Omega=200.)  # [RPM]
RW3 = rwFactory.create('Honeywell_HR16', [0, 0, 1], maxMomentum=50., Omega=300.)  # [RPM]

from Basilisk.simulation import reactionWheelStateEffector
rwStateEffector = reactionWheelStateEffector.ReactionWheelStateEffector()
rwStateEffector.ModelTag = "RW_cluster"
rwFactory.addToSpacecraft(scObject.ModelTag, rwStateEffector, scObject)
rwConfigMsg = rwFactory.getConfigMessage()
5

Add solar radiation pressure

For small body proximity operations near the Sun, SRP is a significant perturbation force.
from Basilisk.simulation import radiationPressure

srp = radiationPressure.RadiationPressure()  # cannonball model
srp.area = 3.    # [m^2] - effective area
srp.coefficientReflection = 0.9
scObject.addDynamicEffector(srp)
6

Set up the EKF navigation filter

smallBodyNavEKF takes navigation and ephemeris messages as inputs and produces a joint state estimate for the spacecraft and the small body.
from Basilisk.fswAlgorithms import smallBodyNavEKF
from Basilisk.simulation import simpleNav, planetNav, ephemerisConverter

# Truth-pass navigation
sNavObject = simpleNav.SimpleNav()
sNavObject.ModelTag = "SimpleNavigation"
sNavObject.scStateInMsg.subscribeTo(scObject.scStateOutMsg)

# Planet state navigation
planetNavObject = planetNav.PlanetNav()
planetNavObject.planetStateInMsg.subscribeTo(gravBodyEphem.planetOutMsgs[0])

# Ephemeris converter (SPICE → EKF-compatible message)
ephemConverter = ephemerisConverter.EphemerisConverter()
ephemConverter.ModelTag = 'ephemConverter'
ephemConverter.addSpiceInputMsg(gravBodyEphem.planetOutMsgs[0])

# EKF filter
ekf = smallBodyNavEKF.SmallBodyNavEKF()
ekf.ModelTag = "smallBodyNavEKF"
ekf.mu_ast = mu  # [m^3/s^2] - Bennu gravitational parameter
ekf.navTransInMsg.subscribeTo(sNavObject.transOutMsg)
ekf.navAttInMsg.subscribeTo(sNavObject.attOutMsg)
ekf.asteroidEphemerisInMsg.subscribeTo(ephemConverter.ephemOutMsgs[0])
ekf.sunEphemerisInMsg.subscribeTo(sunEphemerisMsg)
The EKF outputs:
  • smallBodyNavOutMsg — a SmallBodyNavMsgPayload with the full joint state estimate
  • navTransOutMsg — a NavTransMsgPayload for downstream FSW modules
  • ephemerisOutMsg — an EphemerisMsgPayload with estimated small body state
7

Connect the waypoint feedback controller

smallBodyWaypointFeedback computes the required thrust vector to follow a proximity waypoint sequence around Bennu.
from Basilisk.fswAlgorithms import smallBodyWaypointFeedback

waypointFeedback = smallBodyWaypointFeedback.SmallBodyWaypointFeedback()
waypointFeedback.ModelTag = "smallBodyWaypointFeedback"
waypointFeedback.mu_ast = mu
waypointFeedback.navTransInMsg.subscribeTo(ekf.navTransOutMsg)
waypointFeedback.asteroidEphemerisInMsg.subscribeTo(ekf.ephemerisOutMsg)
8

Initialize and run

scSim.InitializeSimulation()
scSim.ConfigureStopTime(macros.sec2nano(simulationTime))
scSim.ExecuteSimulation()

Estimated state outputs

The EKF estimates a 12-element state vector:
IndicesQuantityUnits
0–2Relative position r_BO_O in small body framem
3–5Relative velocity v_BO_O in small body framem/s
6–8Small body attitude MRP sigma_AN
9–11Small body angular rate omega_AN_Arad/s
The plot_pos_error and plot_vel_error functions plot each component with 2σ covariance bounds:
plot_pos_error(time, r_err, P)  # position error vs 2-sigma bounds
plot_vel_error(time, v_err, P)  # velocity error vs 2-sigma bounds
where P is the 12×12 covariance matrix logged from the EKF output message.

Vizard visualization

To view Bennu in Vizard you must install the asteroid asset bundle. See the Vizard download page for instructions. Set modelDictionaryKey="Bennu" on the gravity object (as shown above) so Vizard can match the body to its 3D mesh.

Build docs developers (and LLMs) love