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.
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, *args):
70 super().__init__(*args)
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.
Deprecated way of creating Python modules¶
Warning
This section discusses the deprecated way of setting up Python modules in Basilisk versions earlier than 2.2. Users should refer to the previous section when setting up new simulation scripts using Python modules.
Apart from the way to shown above, there exist an older way to create Python modules which has now been deprecated. This section briefly discusses this older method, its disadvantages, and how to update to the new system. Note that this deprecated method is pending for removal; users are advised to update to the new system.
Before the new system, Python modules had to be added to separate Python processes, which could not have C/C++ modules. Moreover, this Python processes always had to have a lower priority than C++/C processes, which effectively meant that all Python modules would run after the C++/C modules. This severely limited users’ control of the execution order of their simulation.
Moreover, the syntax for creating these modules was slightly different than for C++ modules:
The class inherited from
PythonModelClass
instead ofSysModel
The
ModelTag
had to be passed to the constructor of the classOne had to overload
reset
andupdateState
, instead ofReset
andUpdateState
In order to update python simulation scripts that use the deprecated system to the new system, one needs to:
Replace
CreateNewPythonProcess
byCreateNewProcess
, as the new Python modules can be added to regular processes.Make the Python module class inherit from
SysModel
, and not fromPythonModelClass
Note that you must importSysModel
fromBasilisk.architecture.sysModel
.Instead of passing the module tag and priority in the constructor, set the tag by setting the
ModuleTag
attribute (similar to C++ modules), and set the priority on theaddTask
method.Rename
selfInit
,reset
, andupdateState
toSelftInit
,Reset
, andUpdateState
.
With this depreciated manner of creating a python Basilisk module the ModelTag
value
is an unique negative number, while the C/C++ modules had unique positive numbers.
This means that updating your simulation script
might change the ID of your modules compared to previous versions.
It is possible that you may not even need a separate process for your Python modules, so consider adding the Python modules directly to other existing processes, always with a lower priority if you want to retain the older behaviour.
The scenario scenarioAttitudePointingPyDEPRECATED shows both the deprecated way of creating a Python module.