Skip to main content
This page walks through the complete lifecycle of a Basilisk simulation, from the initial SimBaseClass setup through data retrieval after the run.

Setup overview

1

Create a SimBaseClass instance

All simulation components attach to a single SimBaseClass root object.
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros

scSim = SimulationBaseClass.SimBaseClass()
2

Create processes

A process (task group) is a named container for related tasks. Create one process per logical subsystem — for example, one for dynamics and one for flight software.
dynProcess = scSim.CreateNewProcess("dynamicsProcess")
fswProcess = scSim.CreateNewProcess("fswProcess")
Processes execute in the order they are created. To override this, pass an integer priority — higher values execute first:
dynProcess = scSim.CreateNewProcess("dynamicsProcess", priority=10)
The default priority is -1, meaning the process executes after all positively-prioritized processes in creation order.
3

Create tasks and set update rates

A task is a list of modules that all run at the same fixed rate. The rate is set in nanoseconds — use macros.sec2nano() to convert from seconds.
# Dynamics integrates at 5 s intervals
dynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(5.)))

# Sensors update at 10 s intervals
dynProcess.addTask(scSim.CreateNewTask("sensorsTask", macros.sec2nano(10.)))

# FSW runs at 10 s intervals
fswProcess.addTask(scSim.CreateNewTask("fswTask", macros.sec2nano(10.)))
The nanosecond is the smallest time unit in Basilisk. A 64-bit unsigned integer at nanosecond resolution represents up to 584 years of simulation time.
To delay a task’s first execution, pass FirstStart in nanoseconds:
# First execution at t=60 s, then every 10 s thereafter
dynProcess.addTask(
    scSim.CreateNewTask("delayedTask", macros.sec2nano(10.), FirstStart=macros.sec2nano(60.))
)
Tasks also support priorities, defaulting to -1:
dynProcess.addTask(scSim.CreateNewTask("highPriorityTask", macros.sec2nano(1.), priority=10))
4

Create and configure modules

Import modules from Basilisk.simulation (C++ simulation modules) or Basilisk.fswAlgorithm (C FSW modules). Set ModelTag to a unique string name.
from Basilisk.simulation import spacecraft
from Basilisk.fswAlgorithms import mrpFeedback

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

mrpControl = mrpFeedback.mrpFeedback()
mrpControl.ModelTag = "mrpFeedback"
mrpControl.K = 3.5
mrpControl.P = 30.0
Module parameters are set directly as Python attributes before InitializeSimulation() is called.
5

Add modules to tasks

Register each module with the task it should run in. The optional priority argument controls execution order within the task — higher values run first.
scSim.AddModelToTask("dynamicsTask", scObject)
scSim.AddModelToTask("fswTask", mrpControl, priority=5)
Without an explicit priority, modules execute in the order they were added to the task.
If priority is not specified, the default is -1. All un-prioritized modules execute after any prioritized modules, in the order they were added.
6

Connect messages

Wire output messages to input messages using .subscribeTo(). Both messages must be of the same payload type.
from Basilisk.architecture import messaging

# Connect spacecraft state output to control module input
mrpControl.guidInMsg.subscribeTo(attGuidance.attGuidOutMsg)
mrpControl.attNavInMsg.subscribeTo(scObject.scStateOutMsg)
For configuration data that doesn’t come from a running module, create a stand-alone message:
vehicleConfigData = messaging.VehicleConfigMsgPayload()
vehicleConfigData.massSC = 750.0  # [kg]
self.vehicleConfigMsg = messaging.VehicleConfigMsg().write(vehicleConfigData)
mrpControl.vehConfigInMsg.subscribeTo(self.vehicleConfigMsg)
7

Set up recorders

Create recorder modules to capture message time histories. Add each recorder to a task so it runs every update cycle.
# Record spacecraft state at the dynamics task rate
scRec = scObject.scStateOutMsg.recorder()
scSim.AddModelToTask("dynamicsTask", scRec)

# Record control torque at most once every 10 seconds
torqueRec = mrpControl.cmdTorqueOutMsg.recorder(macros.sec2nano(10.))
scSim.AddModelToTask("fswTask", torqueRec)
8

Initialize and run

Call InitializeSimulation() to trigger the Reset() method on every module and prepare the time loop. Then set the stop time and execute.
scSim.InitializeSimulation()

# Run for 200 seconds
scSim.ConfigureStopTime(macros.sec2nano(200.0))
scSim.ExecuteSimulation()
ConfigureStopTime() takes an absolute time. To continue a simulation for another 100 seconds after already running 200 seconds, set the stop time to 300 seconds:
scSim.ConfigureStopTime(macros.sec2nano(300.0))
scSim.ExecuteSimulation()
9

Retrieve recorded data

After execution, access recorded message fields and time arrays from the recorder objects.
import matplotlib.pyplot as plt

timeAxis = scRec.times() * macros.NANO2SEC     # convert ns to seconds
sigma_BN = scRec.sigma_BN                      # MRP attitude array [N x 3]

plt.figure()
plt.plot(timeAxis, sigma_BN[:, 0], label=r'$\sigma_1$')
plt.plot(timeAxis, sigma_BN[:, 1], label=r'$\sigma_2$')
plt.plot(timeAxis, sigma_BN[:, 2], label=r'$\sigma_3$')
plt.xlabel('Time [s]')
plt.ylabel('MRP')
plt.legend()
plt.show()
msgRec.times() returns recording timestamps in nanoseconds. msgRec.timesWritten() returns the timestamps at which each payload was written — useful when a message is written less frequently than it is read.To clear accumulated recorder data before a second run:
scRec.clear()

Logging module variables

Beyond message recorders, you can log internal module variables directly from Python. This is slower than message recording (it runs in the Python layer) and is intended for debugging and unit tests.
# Log the dummy variable from a C module at the task update rate
modLogger = mod1.logger("dummy")
scSim.AddModelToTask("dynamicsTask", modLogger)

# Run the simulation
scSim.InitializeSimulation()
scSim.ConfigureStopTime(macros.sec2nano(10.0))
scSim.ExecuteSimulation()

# Access the logged data
print(modLogger.dummy)    # array of recorded values
print(modLogger.times())  # nanoseconds — convert with macros.NANO2SEC
To log multiple variables at a reduced rate:
modLogger = mod1.logger(["dummy", "dumVector"], macros.sec2nano(5.))

Complete minimal example

from Basilisk.moduleTemplates import cModuleTemplate
from Basilisk.utilities import SimulationBaseClass, macros

def run():
    scSim = SimulationBaseClass.SimBaseClass()

    dynProcess = scSim.CreateNewProcess("dynamicsProcess")
    dynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(1.)))

    mod1 = cModuleTemplate.cModuleTemplate()
    mod1.ModelTag = "cModule1"
    scSim.AddModelToTask("dynamicsTask", mod1)

    # Feedback: connect output back to input
    mod1.dataInMsg.subscribeTo(mod1.dataOutMsg)

    # Record the output
    msgRec = mod1.dataOutMsg.recorder()
    scSim.AddModelToTask("dynamicsTask", msgRec)

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

    print(msgRec.times() * macros.NANO2SEC)
    print(msgRec.dataVector)

if __name__ == "__main__":
    run()

Build docs developers (and LLMs) love