当前位置: 首页 > news >正文

Basilisk库教程(二)

一、查看进程、任务和模块的执行顺序

现在你已经学会了如何通过任务列表添加和设置进程优先级,以及如何将Basilisk模块分配到任务以实现有序执行,接下来可以查看仿真的具体执行顺序。SimulationBaseClass定义了一个方法ShowExecutionOrder,它会在终端窗口打印出进程名称和优先级,以及它们被设置为执行的顺序。对于每个进程,你还可以看到任务的调用顺序,以及将要执行的任务模块的顺序。这对于快速验证仿真设置是否符合预期非常有用。

下面的示例脚本创建了两个进程,分别叫做dynamcsProcess和fswProcess。注意,由于fswProcess的优先级更高,即使它是后添加的,也会被优先执行。同样的两个模块会被添加到不同任务中,并以不同顺序和优先级进行测试。


from Basilisk.moduleTemplates import cModuleTemplate
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macrosdef run():"""Controlling the simulation time"""#  Create a sim module as an empty containerscSim = SimulationBaseClass.SimBaseClass()#  create the simulation processdynProcess = scSim.CreateNewProcess("dynamicsProcess")fswProcess = scSim.CreateNewProcess("fswProcess", 10)# create the dynamics task and specify the integration update timefswProcess.addTask(scSim.CreateNewTask("fswTask1", macros.sec2nano(1.)))fswProcess.addTask(scSim.CreateNewTask("fswTask2", macros.sec2nano(2.)))fswProcess.addTask(scSim.CreateNewTask("fswTask3", macros.sec2nano(3.)), 10)dynProcess.addTask(scSim.CreateNewTask("dynamicsTask1", macros.sec2nano(1.)))dynProcess.addTask(scSim.CreateNewTask("dynamicsTask2", macros.sec2nano(5.)), 10)dynProcess.addTask(scSim.CreateNewTask("dynamicsTask3", macros.sec2nano(10.)))# create modulesmod1 = cModuleTemplate.cModuleTemplate()mod1.ModelTag = "cModule1"mod2 = cModuleTemplate.cModuleTemplate()mod2.ModelTag = "cModule2"# add modules to various task listsscSim.AddModelToTask("dynamicsTask1", mod1, 4)scSim.AddModelToTask("dynamicsTask1", mod2, 5)scSim.AddModelToTask("dynamicsTask2", mod2)scSim.AddModelToTask("dynamicsTask2", mod1)scSim.AddModelToTask("dynamicsTask3", mod1)scSim.AddModelToTask("dynamicsTask3", mod2)scSim.AddModelToTask("fswTask1", mod1)scSim.AddModelToTask("fswTask1", mod2, 2)scSim.AddModelToTask("fswTask2", mod2)scSim.AddModelToTask("fswTask2", mod1)scSim.AddModelToTask("fswTask3", mod1)scSim.AddModelToTask("fswTask3", mod2)# print to the terminal window the execution order of the processes, task lists and modulesscSim.ShowExecutionOrder()# uncomment this code to show the execution order figure and save it off# fig = scSim.ShowExecutionFigure(False)# fig.savefig("qs-bsk-2b-order.svg", transparent=True, bbox_inches = 'tight', pad_inches = 0)returnif __name__ == "__main__":run()

为了执行这行代码,脚本本身并不会运行仿真。相反,它只是完成仿真的设置和配置。然后调用ShowExecutionOrder方法:

scSim.ShowExecutionOrder()

如果你执行了这行代码,你将看到下面的终端输出。

$ python3 bsk-2b.py
Process Name: fswProcess , priority: 10
Task Name: fswTask3, priority: 10, TaskPeriod: 3.0s
ModuleTag: cModule1, priority: -1
ModuleTag: cModule2, priority: -1
Task Name: fswTask1, priority: -1, TaskPeriod: 1.0s
ModuleTag: cModule2, priority: 2
ModuleTag: cModule1, priority: -1
Task Name: fswTask2, priority: -1, TaskPeriod: 2.0s
ModuleTag: cModule2, priority: -1
ModuleTag: cModule1, priority: -1Process Name: dynamicsProcess , priority: -1
Task Name: dynamicsTask2, priority: 10, TaskPeriod: 5.0s
ModuleTag: cModule2, priority: -1
ModuleTag: cModule1, priority: -1
Task Name: dynamicsTask1, priority: -1, TaskPeriod: 1.0s
ModuleTag: cModule2, priority: 5
ModuleTag: cModule1, priority: 4
Task Name: dynamicsTask3, priority: -1, TaskPeriod: 10.0s
ModuleTag: cModule1, priority: -1
ModuleTag: cModule2, priority: -1

方法ShowExecutionFigure(True)会执行于ShowExecutionOrder相同的Basilisk进程、任务和模块顺序提取过程,但会以图形方式显示结果。该方法会返回该图形的一个副本,因此可以用于自动文档功能,或保存以备将来使用。例如,将此命令添加到示例脚本中,将会产生如下图所示的结果。

二、消息连接

到目前为止,我们已经学习了如何将C或C++的Basilisk模块添加到任务中,设置优先级以执行这些模块,并指定更新时间;接下来我们将学习如何将模块消息相互连接。消息是模块在执行后输出的任何内容,比如航天器状态信息、电池电量等。这里我们依然使用Module:cModuleTemplate和Module:cppModuleTemplate作为示例模块,来说明如何设置信息连接。请注意,这些模块的输入和输出消息连接类型是相同的。下方的仿真脚本依然只用一个进程和任务。模块被创建后,其输入和输出信息按照下图所示方式连接。

下方展示了源代码,由于我们现在要使用Basilisk的消息系统,因此需要从Basilisk.architecture导入messaging包。如果没有这一步,Python代码将无法订阅任何信息,也无法创建独立信息。


from Basilisk.moduleTemplates import cModuleTemplate
from Basilisk.moduleTemplates import cppModuleTemplate
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.architecture import messagingdef run():"""Illustration of connecting module messages"""#  Create a sim module as an empty containerscSim = SimulationBaseClass.SimBaseClass()#  create the simulation processdynProcess = scSim.CreateNewProcess("dynamicsProcess")# create the dynamics task and specify the integration update timedynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(5.)))# create modulesmod1 = cModuleTemplate.cModuleTemplate()mod1.ModelTag = "cModule1"mod2 = cppModuleTemplate.CppModuleTemplate()mod2.ModelTag = "cppModule2"# add modules to task listscSim.AddModelToTask("dynamicsTask", mod1)scSim.AddModelToTask("dynamicsTask", mod2)# connect messagesmod2.dataInMsg.subscribeTo(mod1.dataOutMsg)mod1.dataInMsg.subscribeTo(mod2.dataOutMsg)#  initialize Simulation:scSim.InitializeSimulation()#   configure a simulation stop time and execute the simulation runscSim.ConfigureStopTime(macros.sec2nano(5.0))scSim.ExecuteSimulation()returnif __name__ == "__main__":run()

方法.subscribeTo将输出信息(变量名称以OutMsg结尾)连接到输入信息(变量名称以InMsg结尾),如上面的第36-37行所示。虽然C模块包含具有C接口的消息对象,而C模块包含C消息对象,但.subscribeTo()方法的设计让用户无需关心这种区别。实际上,这个方法可以实现C到C、C到C++、C++到C++以及C++到C的消息连接。因此,一个模块的输出消息anotherModule.xxxOutMsg可以通过.subscribeTo()方法连接到另一个模块的输入消息someModule.xxxInMsg,用法如下:

someModule.xxxInMsg.subscribeTo(anotherModule.xxxOutMsg)

输入和输出信息的名称是任意的,但被连接的消息必须是相同类型。在上面的仿真代码中,使用该协议将C模块1的输出信息连接到C++模块2的输入信息。接着,将C++模块2的输出连接到C模块1的输入,形成一个闭环信息传递设置的示例。

你只能将输入信息订阅到已经存在的输出信息!不要再输出信息还没创建之前就尝试订阅。在本次仿真中,所有订阅操作都发生在模块创建之后。如果你运行这段Python代码,终端会输出如下内容:

source/codeSamples % python3 bsk-3.py
BSK_INFORMATION: Variable dummy set to 0.000000 in reset.
BSK_INFORMATION: Variable dummy set to 0.000000 in reset.
BSK_INFORMATION: C Module ID 1 ran Update at 0.000000s
BSK_INFORMATION: C++ Module ID 2 ran Update at 0.000000s
BSK_INFORMATION: C Module ID 1 ran Update at 5.000000s
BSK_INFORMATION: C++ Module ID 2 ran Update at 5.000000s

注意,这里两个模块在没有设置优先级的情况下被添加,因此它们会按照被添加到Basilisk任务的顺序执行。

三、记录信息

太好了,现在我们已经有了一个正常运行的仿真,Basilisk模块已经设置好并且信息也已连接。那么,我们如何获取仿真数据呢?这可以通过创建记录器模块(recorder modules)来实现,这些模块会存储生成信息的时间历史数据。

上图展示了一个Basilisk仿真的示例。单个测试模块cModule1的输出信息被连接到它的输入信息。这会形成一个反馈回路,使输出信息随之变化。具体的数学原理可以参考模块代码。为了在不同的时间步记录消息状态,会创建记录器模块来完成这个任务。

记录消息的方法是一样的,无论是C模块还是C++模块,也无论消息是C接口还是C++接口。

下方包含了仿真代码。由于记录下来的消息数据将在本脚本中绘图,代码开头会引入matplotlib库,以及来自Basilisk.utilities的辅助包unitTestSupport。


import sysimport matplotlib.pyplot as plt
from Basilisk.moduleTemplates import cModuleTemplate
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import unitTestSupportdef run():"""Illustration of recording messages"""#  Create a sim module as an empty containerscSim = SimulationBaseClass.SimBaseClass()#  create the simulation processdynProcess = scSim.CreateNewProcess("dynamicsProcess")# create the dynamics task and specify the integration update timedynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(1.)))# create modulesmod1 = cModuleTemplate.cModuleTemplate()mod1.ModelTag = "cModule1"scSim.AddModelToTask("dynamicsTask", mod1)mod1.dataInMsg.subscribeTo(mod1.dataOutMsg)# setup message recordingmsgRec = mod1.dataOutMsg.recorder()scSim.AddModelToTask("dynamicsTask", msgRec)msgRec2 = mod1.dataOutMsg.recorder(macros.sec2nano(20.))scSim.AddModelToTask("dynamicsTask", msgRec2)#  initialize Simulation:scSim.InitializeSimulation()#   configure a simulation stop time and execute the simulation runscSim.ConfigureStopTime(macros.sec2nano(60.0))scSim.ExecuteSimulation()# plot recorded dataplt.close("all")plt.figure(1)figureList = {}for idx in range(3):plt.plot(msgRec.times() * macros.NANO2SEC, msgRec.dataVector[:, idx],color=unitTestSupport.getLineColor(idx, 3),label='$x_{' + str(idx) + '}$')plt.plot(msgRec2.times() * macros.NANO2SEC, msgRec2.dataVector[:, idx],'--',color=unitTestSupport.getLineColor(idx, 3),label=r'$\hat x_{' + str(idx) + '}$')plt.legend(loc='lower right')plt.xlabel('Time [sec]')plt.ylabel('Module Data [units]')if "pytest" not in sys.modules:plt.show()figureList["bsk-4"] = plt.figure(1)plt.close("all")return figureListif __name__ == "__main__":run()

添加消息记录器

在单个BSK模块实例被创建并添加到任务列表后,可以编写新代码来设置消息记录器。通用语法如下。假设你想记录module.someOutMsg。注意,这个信息可以是输出信息,也可以是输入信息。对应的记录器模块可以这样创建:

someMsgRec = module.someOutMsg.recorder()
scSim.AddModelToTask("taskName", someMsgRec)

代码块的第一行创建了BSK消息记录器对象,用于记录someOutMsg。和任何Basilisk模块一样,接下来需要将其添加到任务中,以便在每个更新周期执行。默认情况下,记录器会以任务列表的更新频率记录消息。如果你想减少记录的数据点数量,可以提供一个可选参数:

someMsgRec = module.someOutMsg.recorder(minUpdateTime)

这里,minUpdateTime是在记录器对象记录消息前必须经过的最小时间间隔。

在上面的完整脚本中,记录器模块msgRec被设置为以dynamicTask的更新频率记录消息。而模块msgRec20被设置为仅在经过20秒后才记录信息。注意,minUpdateTime参数必须以纳秒为单位再次提供。这就是设置信息记录所需的全部内容。接下来,代码会初始化仿真并执行。

提取记录的消息数据

仿真完成后,记录的数据会存储在msgRec和msgRec20记录器中。要访问消息的变量,只需使用msgRec.variable,其中variable是你要访问的消息结构体变量。要访问消息被记录时的时间数组,可以用msgRec.times()。还有一个时间数组,存储信息被写入时的时间,用msgRec.timeWritten()。为什么有两个时间数组?举例来说,假设一个输出消息每3秒才更新一次,但消息每秒都被读取和记录一次。此时.timesWritten()的值会在每次新输出消息创建时重复。Module:cModuleTemplate的输出消息只包含数组dataVector。在本次仿真中,它以1Hz的频率被msgRec记录,每20秒被msgRec20记录一次。仿真会生成如下图所示的曲线。

清除消息记录器数据日志

请注意,消息记录器会不断地将消息数据添加到其内部数据向量中。如果你多次启动和停止仿真、提取数据、恢复仿真等,消息数据的记录是累积的。如果你停止仿真后想情况消息记录器的数据日志,只保留新数据,可以使用.clear()方法。例如,假设有一个消息记录器scRec需要清空,可以这样做:

scRec.clear()

读取消息的当前值

如果你有一个消息msg,想要获取当前的消息数据或内容,可以用如下方法(适用于C和C++封装的消息对象)

msgCopy = msg.read()

四、创建独立消息

独立消息的基础

前面的例子展示了如何连接嵌入在Basilisk模块中的信息。然而,有时你可能需要创建某个消息的独立副本。例如,一些飞行算法模块需要输入独立消息,以提供航天器的质量和惯性属性、推力器或反作用轮的配置信息。例如,模块单元测试理想情况下只允许被测试的模块。该模块需要的任何输入消息都应创建为独立消息,这样可以避免单元测试脚本依赖于其他模块的输出消息,使模块测试能够独立运行。本教程将演示如何创建独立消息,并将其连接到Module:cppModuleTemplate的输入消息。其语法与将独立消息连接到C模块完全相同。此外,本例还说没了如何在多次启动和仿真的过程中,修改消息或模块变量。

要创建独立消息,首先创建消息载荷(即数据)容器。假设消息类型为someMsg,对应的载荷叫做someMsgPayload,因此,载荷容器的创建方式如下:

msgData = messaging.someMsgPayload()

本质上,这就是消息结构定义的一个Python实例,定义可在architecture/msgPaylaodDefC/SomeMsg.h中找到。消息载荷的内容在创建时会被清零。如果结构体中有你想要更改的变量,只需这样赋值即可:

msgData.variable = .....

接下来,创建消息对象并将消息数据写入其中。消息对象的创建方式如下:

msg = messaging.someMsg()

下面的模拟代码将创建一个独立的消息,然后将其连接到模块输入信息。


import sysimport matplotlib.pyplot as plt
from Basilisk.architecture import messaging
from Basilisk.moduleTemplates import cppModuleTemplate
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import unitTestSupportdef run():"""Illustration of creating stand-alone messages"""#  Create a sim module as an empty containerscSim = SimulationBaseClass.SimBaseClass()#  create the simulation processdynProcess = scSim.CreateNewProcess("dynamicsProcess")# create the dynamics task and specify the integration update timedynProcess.addTask(scSim.CreateNewTask("dynamicsTask", macros.sec2nano(1.)))# create modulesmod1 = cppModuleTemplate.CppModuleTemplate()mod1.ModelTag = "cppModule1"scSim.AddModelToTask("dynamicsTask", mod1)# create stand-alone input messagemsgData = messaging.CModuleTemplateMsgPayload()msgData.dataVector = [1., 2., 3.]msg = messaging.CModuleTemplateMsg().write(msgData)# connect to stand-alone msgmod1.dataInMsg.subscribeTo(msg)# setup message recordingmsgRec = mod1.dataOutMsg.recorder()scSim.AddModelToTask("dynamicsTask", msgRec)#  initialize Simulation:scSim.InitializeSimulation()#   configure a simulation stop time and execute the simulation runscSim.ConfigureStopTime(macros.sec2nano(10.0))scSim.ExecuteSimulation()# change input message and continue simulationmsgData.dataVector = [-1., -2., -3.]msg.write(msgData)scSim.ConfigureStopTime(macros.sec2nano(20.0))scSim.ExecuteSimulation()# plot recorded dataplt.close("all")figureList = {}plt.figure(1)for idx in range(3):plt.plot(msgRec.times() * macros.NANO2SEC, msgRec.dataVector[:, idx],color=unitTestSupport.getLineColor(idx, 3),label='$r_{BN,' + str(idx) + '}$')plt.legend(loc='lower right')plt.xlabel('Time [sec]')plt.ylabel('Module Data [units]')figureList["bsk-5"] = plt.figure(1)if "pytest" not in sys.modules:plt.show()plt.close("all")return figureListif __name__ == "__main__":run()

仿真运行10秒后,独立消息的数据被更爱为消息对象。注意,独立消息对象本身不需要重新创建,因为它仍然有效并且已经连接到目标模块。我们著需要更新消息的内容即可。

五、在内存中保留消息对象

在Python中创建独立消息对象时,了解这些对象必须保留在内存中才能正常运行,这一点至关重要。如果消息对象是在函数或方法中本地创建的,并且未存储在持久变量中,则Python的垃圾回收器可能会在函数退出后将其从内存中删除,即使C组件仍需要访问它。在以下情况下,这一点尤其重要:

(1)在子例程或辅助函数中创建消息对象。

(2)根据运行时条件动态生成消息。

(3)设置将在整个过程中使用的消息接口。

示例:使用类级Registry保留对象。Basilisk中确保消息对象保留在对象内。

Basilisk中确保消息对象保留在内存中的常见模式是使用在模拟的声明周期内持续存在的类级注册表或列表。以下是在Basilisk自己的代码中实现它的方法:

class BskSimulation:def __init__(self):# Create class-level registry if it doesn't existif not hasattr(self, '_message_registry'):self._message_registry = []def setup_sensors(self):"""Set up sensor messages and devices."""# Create a messagesensor_msg = messaging.SensorMsg()# Store message in class-level registry to prevent garbage collectionself._message_registry.append(sensor_msg)# Create the sensor module that will use this messageself.sensor_module = sensorModule.SensorModule()self.sensor_module.sensorOutMsg.subscribeTo(sensor_msg)return

此模式用于Basilisk自己的组件实现,例如Coarse Sun Sensors(CSS)

def SetCSSConstellation(self):"""Set the CSS sensors"""self.CSSConstellationObject.ModelTag = "cssConstellation"# Create class-level registry if it doesn't existif not hasattr(self, '_css_registry'):self._css_registry = []def setupCSS(cssDevice):cssDevice = coarseSunSensor.CoarseSunSensor()cssDevice.fov = 80. * mc.D2RcssDevice.scaleFactor = 2.0cssDevice.sunInMsg.subscribeTo(self.gravFactory.spiceObject.planetStateOutMsgs[self.sun])cssDevice.stateInMsg.subscribeTo(self.scObject.scStateOutMsg)# Store CSS in class-level registry to prevent garbage collectionself._css_registry.append(cssDevice)# Create CSS devices and add them to the registry...

替代方法:使用Dictionary Registry

对于稍后需要检索特定消息的更复杂的模拟,可以使用基于字典的注册表:

class BskSimulation:"""A registry to keep message objects alive in Python memory."""def __init__(self):self.messages = {}def make_message(self, name):"""Make a message object with a unique name."""msg_obj = messaging.SensorMsg()self.messages[name] = msg_objreturn msg_objdef get_message(self, name):"""Retrieve a message object by name."""return self.messages.get(name)

让Parent方法在内存中保留Msg对象

如果message设置方法在消息的实例中返回,则父方法可能负责将此消息保留在内存中。

class BskSimulation:"""A registry to keep message objects alive in Python memory."""def make_message(self):"""Make a message object and return it."""msg = messaging.SensorMsg()msg.fov = 2.0return msgdef parent_method(self, name):"""Retrieve a message object by name."""self.sensorMsg = self.make_message()return

常见陷阱

如果灭有适当的保留,您可能遇到以下问题:看似以正确连接但未传输数据的消息,无法按预期通信的模拟组件,C代码中存在神秘的分段错误或访问冲突,通过使用注册表模式或确保消息对象存储在长期变量中,您可以避免这些问题并创建更加模块化、可维护的模拟代码。

http://www.lryc.cn/news/589102.html

相关文章:

  • QT——QList的详细讲解
  • SpringBoot3.0 +GraalVM17 + Docker
  • AI大模型训练相关函数知识补充
  • MongoDB基础增删改查命令
  • vscode配置运行完整C代码项目
  • B/S 架构通信原理详解
  • 高标准农田气象站的功能
  • 亚矩阵云手机:破解 Yandex 广告平台多账号风控难题的利器
  • 云服务器如何管理数据库(MySQL/MongoDB)?
  • 《大数据技术原理与应用》实验报告四 MapReduce初级编程实践
  • Keepalived双机热备概述
  • 死锁问题以及读写锁和自旋锁介绍【Linux操作系统】
  • Sersync和Rsync部署
  • 免杀学习篇(1)—— 工具使用
  • Dify的默认端口怎么修改
  • 算法学习day16----Python数据结构--模拟队列
  • Nuxt3宝塔PM2管理器部署
  • linux系统------LVS+KeepAlived+Nginx高可用方案
  • LVS(Linux Virtual Server)详细笔记(理论篇)
  • 李宏毅《生成式人工智能导论》 | 第9讲 AI Agent
  • Jfinal+SQLite java工具类复制mysql表数据到 *.sqlite
  • 设计模式笔记_结构型_适配器模式
  • Redis 中的持久化机制:RDB 与 AOF
  • 基于STM32设计的智能厨房
  • redis快速入门教程
  • JavaScript进阶篇——第四章 解构赋值(完全版)
  • Bash shell用法
  • 轻松管理多个Go版本:g工具安装与使用
  • 【自学linux】计算机体系结构和操作系统第二章
  • OpenCV 伽马校正函数gammaCorrection()