Python modules let you add new behavior to a Basilisk simulation without writing any C or C++ code. Because they are implemented entirely in Python, there is no header file, no .cpp file, and no SWIG interface — you simply subclass SysModel and implement Reset and UpdateState.
Python modules are significantly slower than equivalent C or C++ modules. Use them for rapid prototyping and development, then port to C++ for production simulations that must run at high speed.
When to use Python modules
Choose a Python module when:
- You are prototyping a new algorithm and want to iterate quickly without a compile/link cycle.
- The module runs at a low task rate and its execution time is not a bottleneck.
- You are building a simulation-only monitoring or logging module.
- You want to call Python libraries (NumPy, SciPy, etc.) directly from the module logic.
Choose a C or C++ module when:
- The module must run fast (attitude control, orbit propagation, sensor models).
- The module will eventually be used in flight software.
- You need private variables or strict type enforcement.
Module structure
A Python module subclasses SysModel from Basilisk.architecture.sysModel and implements three optional methods:
| Method | When it is called |
|---|
__init__(self) | When the Python object is created |
Reset(self, CurrentSimNanos) | When InitializeSimulation() or module.Reset() is called |
UpdateState(self, CurrentSimNanos) | Every task update step |
Always call super().__init__() when overriding __init__.
Complete example
The following example shows a Python module that reads a CModuleTemplateMsg input message, adds a vector offset, and writes the result to a CModuleTemplateMsg output message. This is the same pattern used in docs/source/codeSamples/making-pyModules.py.
from Basilisk.architecture import sysModel
from Basilisk.architecture import bskLogging
from Basilisk.architecture import messaging
import numpy as np
class TestPythonModule(sysModel.SysModel):
def __init__(self):
super().__init__()
# Declare message objects as instance attributes
self.dataInMsg = messaging.CModuleTemplateMsgReader()
self.dataOutMsg = messaging.CModuleTemplateMsg()
def Reset(self, CurrentSimNanos):
# Check that a required input message is connected
if not self.dataInMsg.isLinked():
self.bskLogger.bskLog(
bskLogging.BSK_ERROR,
"TestPythonModule.dataInMsg is not linked."
)
# Zero the output message on reset
payload = self.dataOutMsg.zeroMsgPayload
payload.dataVector = np.array([0, 0, 0])
self.dataOutMsg.write(payload, CurrentSimNanos, self.moduleID)
self.bskLogger.bskLog(bskLogging.BSK_INFORMATION, "Reset in TestPythonModule")
def UpdateState(self, CurrentSimNanos):
# Read input message
inPayload = self.dataInMsg()
inputVector = inPayload.dataVector
# Compute output: accumulate offset and add input
payload = self.dataOutMsg.zeroMsgPayload
payload.dataVector = (
self.dataOutMsg.read().dataVector + np.array([0, 1, 0]) + inputVector
)
self.dataOutMsg.write(payload, CurrentSimNanos, self.moduleID)
self.bskLogger.bskLog(
bskLogging.BSK_INFORMATION,
f"Python Module ID {self.moduleID} ran Update at {CurrentSimNanos * 1e-9}s",
)
Declaring messages
Python modules declare message objects as instance attributes in __init__. Use the message type suffixed with Reader for input functors and without Reader for output messages:
# Output message
self.dataOutMsg = messaging.CModuleTemplateMsg()
# Input message (read functor)
self.dataInMsg = messaging.CModuleTemplateMsgReader()
You can declare as many input and output messages as your module requires.
Reading messages
Call the input message as a function to get a copy of the payload:
payload = self.dataInMsg() # returns a SomeMsgPayload copy
value = payload.someField
Check connection state before reading optional messages:
if self.dataInMsg.isLinked():
payload = self.dataInMsg()
Writing messages
Always start from a zeroed payload to avoid writing stale data:
# Get a zero'd payload struct
payload = self.dataOutMsg.zeroMsgPayload
# Set the fields you need
payload.dataVector = np.array([1.0, 2.0, 3.0])
# Write to the output message
self.dataOutMsg.write(payload, CurrentSimNanos, self.moduleID)
Logging with bskLogger
After the module is added to a simulation task, the bskLogger attribute becomes available:
self.bskLogger.bskLog(bskLogging.BSK_INFORMATION, "Module reset complete.")
self.bskLogger.bskLog(bskLogging.BSK_WARNING, "Optional message not connected.")
self.bskLogger.bskLog(bskLogging.BSK_ERROR, "Required message not connected!")
bskLogger is not available until after the module has been added to a simulation with AddModelToTask. Do not use it in __init__.
Adding a Python module to a simulation
You use AddModelToTask exactly the same way as for C/C++ modules. The optional third argument sets the module priority within the task.
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.moduleTemplates import cModuleTemplate
from Basilisk.moduleTemplates import cppModuleTemplate
scSim = SimulationBaseClass.SimBaseClass()
dynProcess = scSim.CreateNewProcess("dynamicsProcess")
dynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(5.0)))
# Mix C, C++, and Python modules in the same task
mod1 = cModuleTemplate.cModuleTemplate()
mod1.ModelTag = "cModule1"
scSim.AddModelToTask("dynamicsTask", mod1, 0) # lowest priority
mod2 = cppModuleTemplate.CppModuleTemplate()
mod2.ModelTag = "cppModule2"
scSim.AddModelToTask("dynamicsTask", mod2, 5)
mod4 = TestPythonModule()
mod4.ModelTag = "pythonModule4"
scSim.AddModelToTask("dynamicsTask", mod4, 10) # runs before mod2
# Connect messages across language boundaries
mod2.dataInMsg.subscribeTo(mod4.dataOutMsg)
mod4.dataInMsg.subscribeTo(mod1.dataOutMsg)
# Record output
recorder = mod2.dataOutMsg.recorder()
scSim.AddModelToTask("dynamicsTask", recorder)
scSim.InitializeSimulation()
scSim.ConfigureStopTime(macros.sec2nano(5.0))
scSim.ExecuteSimulation()
print(recorder.dataVector)
The ModelTag value of Python modules is a unique positive integer, the same as for C/C++ modules.
Priority and execution order
Within a task, modules execute in descending order of their priority value. A Python module with priority 10 runs before a C++ module with priority 5. The framework imposes no restriction on mixing module languages in a single task.