Skip to main content
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:
MethodWhen 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.

Build docs developers (and LLMs) love