Making Python Modules

Python modules are a good alternative to C and C++ modules for quick prototyping. They are defined entirely in a Python script, which means that there is no need for a header (.h), definition (.cpp), or SWIG interface file (.i). However, they are much slower than C or C++ modules, which will significantly slow down your simulation.

Python modules are implemented by subclassing SysModel from Basilisk.architecture.sysModel. Then, one can implement the __init__, Reset, and UpdateState methods in the same way that one would implement these methods in C++. Remember to always call __init__ of the parent class SysModel if you are implementing your own __init__.

The ModelTag value of these python BSK modules will be a unique positive number, same as with C/C++ BSK modules.

Note

To include a pure Python BSK module in the generated Basilisk Python package and wheel, place it in a module folder and give the folder and Python file the same lower camel case name. For example, a flight software module named someModule should live in src/fswAlgorithms/<moduleCategory>/someModule/someModule.py. The same pattern is supported under src/simulation. The src/architecture folder contains support code and message infrastructure, not BSK modules. Define the BSK module class inside that file using upper camel case, such as class SomeModule(sysModel.SysModel):. After configuring and building Basilisk, users can import it from the corresponding Basilisk package, such as from Basilisk.fswAlgorithms import someModule or from Basilisk.simulation import someModule, and instantiate it with someModule.SomeModule().

All Python modules have a logger stored in bskLogger (although it will not be available until the module has been added to a simulation). Additionally, you may declare any other variables, methods, messages, etc. within your Python module.

The script below expands on the code shown in Adding Basilisk Modules to include a Python module.

  1
  2from Basilisk.utilities import SimulationBaseClass
  3from Basilisk.utilities import macros
  4from Basilisk.moduleTemplates import cModuleTemplate
  5from Basilisk.moduleTemplates import cppModuleTemplate
  6from Basilisk.architecture import sysModel
  7from Basilisk.architecture import bskLogging
  8from Basilisk.architecture import messaging
  9
 10import numpy as np
 11
 12
 13def run():
 14    """
 15    Illustration of adding Basilisk Python modules to a task
 16    """
 17
 18    #  Create a sim module as an empty container
 19    scSim = SimulationBaseClass.SimBaseClass()
 20
 21    #  create the simulation process
 22    dynProcess = scSim.CreateNewProcess("dynamicsProcess")
 23
 24    # create the dynamics task and specify the integration update time
 25    dynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(5.0)))
 26
 27    # create copies of the Basilisk modules
 28    mod1 = cModuleTemplate.cModuleTemplate()
 29    mod1.ModelTag = "cModule1"
 30    scSim.AddModelToTask("dynamicsTask", mod1, 0)
 31
 32    mod2 = cppModuleTemplate.CppModuleTemplate()
 33    mod2.ModelTag = "cppModule2"
 34    scSim.AddModelToTask("dynamicsTask", mod2, 5)
 35
 36    mod3 = cModuleTemplate.cModuleTemplate()
 37    mod3.ModelTag = "cModule3"
 38    scSim.AddModelToTask("dynamicsTask", mod3, 15)
 39
 40    # The following is a Python module, which has a higher priority
 41    # then some of the C++/C modules. Observe in the script output
 42    # how the Python module is called in the order that respects
 43    # its priority with respect to the rest of the modules.
 44    mod4 = TestPythonModule()
 45    mod4.ModelTag = "pythonModule4"
 46    scSim.AddModelToTask("dynamicsTask", mod4, 10)
 47
 48    mod2.dataInMsg.subscribeTo(mod4.dataOutMsg)
 49    mod4.dataInMsg.subscribeTo(mod3.dataOutMsg)
 50
 51    # Set up recording
 52    mod2MsgRecorder = mod2.dataOutMsg.recorder()
 53    scSim.AddModelToTask("dynamicsTask", mod2MsgRecorder)
 54
 55    #  initialize Simulation:
 56    scSim.InitializeSimulation()
 57    print("InitializeSimulation() completed...")
 58
 59    #   configure a simulation stop time and execute the simulation run
 60    scSim.ConfigureStopTime(macros.sec2nano(5.0))
 61    scSim.ExecuteSimulation()
 62
 63    print("Recorded mod2.dataOutMsg.dataVector: ", mod2MsgRecorder.dataVector)
 64
 65    return
 66
 67
 68class TestPythonModule(sysModel.SysModel):
 69    def __init__(self):
 70        super().__init__()
 71        self.dataInMsg = messaging.CModuleTemplateMsgReader()
 72        self.dataOutMsg = messaging.CModuleTemplateMsg()
 73
 74    def Reset(self, CurrentSimNanos):
 75        # Ensure that self.dataInMsg is linked
 76        if not self.dataInMsg.isLinked():
 77            self.bskLogger.bskLog(
 78                bskLogging.BSK_ERROR, "TestPythonModule.dataInMsg is not linked."
 79            )
 80
 81        # Initialiazing self.dataOutMsg
 82        payload = self.dataOutMsg.zeroMsgPayload
 83        payload.dataVector = np.array([0, 0, 0])
 84        self.dataOutMsg.write(payload, CurrentSimNanos, self.moduleID)
 85
 86        self.bskLogger.bskLog(bskLogging.BSK_INFORMATION, "Reset in TestPythonModule")
 87
 88    def UpdateState(self, CurrentSimNanos):
 89        # Read input message
 90        inPayload = self.dataInMsg()
 91        inputVector = inPayload.dataVector
 92
 93        # Set output message
 94        payload = self.dataOutMsg.zeroMsgPayload
 95        payload.dataVector = (
 96            self.dataOutMsg.read().dataVector + np.array([0, 1, 0]) + inputVector
 97        )
 98        self.dataOutMsg.write(payload, CurrentSimNanos, self.moduleID)
 99
100        self.bskLogger.bskLog(
101            bskLogging.BSK_INFORMATION,
102            f"Python Module ID {self.moduleID} ran Update at {CurrentSimNanos*1e-9}s",
103        )
104
105
106if __name__ == "__main__":
107    run()

Running the above code prints:

(.venv) source/codeSamples % python making-pyModules.py
BSK_INFORMATION: Variable dummy set to 0.000000 in reset.
BSK_INFORMATION: Reset in TestPythonModule
BSK_INFORMATION: Variable dummy set to 0.000000 in reset.
BSK_INFORMATION: Variable dummy set to 0.000000 in reset.
InitializeSimulation() completed...
BSK_INFORMATION: C Module ID 3 ran Update at 0.000000s
BSK_INFORMATION: Python Module ID 4 ran Update at 0.0s
BSK_INFORMATION: C++ Module ID 2 ran Update at 0.000000s
BSK_INFORMATION: C Module ID 1 ran Update at 0.000000s
BSK_INFORMATION: C Module ID 3 ran Update at 5.000000s
BSK_INFORMATION: Python Module ID 4 ran Update at 5.0s
BSK_INFORMATION: C++ Module ID 2 ran Update at 5.000000s
BSK_INFORMATION: C Module ID 1 ran Update at 5.000000s
Recorded mod2.dataOutMsg.dataVector:  [[2. 1. 0.]
[5. 2. 0.]]

Note how the Python module made use of bskLogger, the Reset and UpdateState were called, how the priority of the Python module was respected, and how messaging happened between a C++ and Python module.

The scenario scenarioAttitudePointingPy further shows how to define Python modules.