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
| Module | Package | Role |
|---|
spacecraft.Spacecraft | Basilisk.simulation | 6-DOF dynamics |
planetEphemeris.PlanetEphemeris | Basilisk.simulation | Propagates Bennu orbit and orientation |
radiationPressure.RadiationPressure | Basilisk.simulation | Solar radiation pressure cannonball model |
simpleNav.SimpleNav | Basilisk.simulation | Truth navigation measurements |
planetNav.PlanetNav | Basilisk.simulation | Planet state navigation measurements |
smallBodyNavEKF.SmallBodyNavEKF | Basilisk.fswAlgorithms | Extended Kalman filter for state estimation |
smallBodyWaypointFeedback | Basilisk.fswAlgorithms | Proximity operations waypoint controller |
ephemerisConverter.EphemerisConverter | Basilisk.simulation | Converts planet ephemeris to navigation messages |
Running the scenario
python3 scenarioSmallBodyNav.py
Step-by-step walkthrough
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))
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
)
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.
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()
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)
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
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)
Initialize and run
scSim.InitializeSimulation()
scSim.ConfigureStopTime(macros.sec2nano(simulationTime))
scSim.ExecuteSimulation()
Estimated state outputs
The EKF estimates a 12-element state vector:
| Indices | Quantity | Units |
|---|
| 0–2 | Relative position r_BO_O in small body frame | m |
| 3–5 | Relative velocity v_BO_O in small body frame | m/s |
| 6–8 | Small body attitude MRP sigma_AN | — |
| 9–11 | Small body angular rate omega_AN_A | rad/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.