本节书摘来自华章出版社《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》一书中的第2章,第2.2节,作者:[美]爱德华·阿什福德·李(Edward Ashford Lee),更多章节内容可以访问云栖社区“华章计算机”公众号查看
2.2 令牌和数据类型
在图2-7的例子中,Const角色在它的输出端口创建一个值序列。序列中的每个值称为一个令牌(token)。令牌由角色创建,然后通过输出端口传送出去,最后被一个或多个目标角色(destination actor)接收。每个目标角色(destination actor)通过输入端口接收令牌。在本例中,Display角色将接收由Const角色创建的令牌并将它们显示在窗口中。一个令牌就是一个数据单元,就像一个字符串或者数值,令牌在两个角色之间通过端口进行通信。
一个由Const角色创建的令牌,它的值可以是Ptolemy II表达式语言(见第13章)能表达的所有值。可将该值设为1(整数1),或者1.0(浮点数1),或者{1.0}(包含1.0的单元素数组),或{value = 1,name = "one"}(一条由两个元素组成的记录:一个名为value的整数和一个名为name的字符串),或者[1,0;0,1] (一个2×2单位矩阵)。这些都是Const角色的有效表达式。
Const角色可以产生不同类型(type)的数据,Display角色可以显示不同类型的数据。角色库中的大多数角色是多态角色(polymorphic actor),也就是说它们可以对多种类型的数据进行操作,或者产生多种类型的数据,即便它们的具体处理行为可能会因类型的不同而不同。比如,矩阵乘法与整数乘法的操作并不相同,但是它们都可以使用Math库中的MultiplyDivide角色来完成。Ptolemy II包含了一个很复杂的类型系统,它允许角色有效而安全地处理不同的数据类型。该类型系统是由Xiong(2002)创建的,这个系统将在第14章中描述。
为了更进一步对数据类型的探讨,我们使用SDF Director创建如图2-12中所示的模型。Ramp角色的位置在Sources→SequenceSources子库中,AddSubtract 角色在Math库中(见2.2节的补充阅读)。将Const的value(值)参数设为0,指示器的迭代(iteration)参数设为5。运行此模型,结果会显示从0~4的5个连续数,如图2-12所示。这些值是由Ramp的当前值减去Const角色的常数输出而产生的。请读者自己做实验,改变Const角色的值,观察输出端的5个数是如何变化的。
图2-12 另一个例子,用以解释数据类型
现在将Const角色的值改回“Hello World”。当你执行这个模型时,你应该会看到图2-13所示的异常(exception)窗口。出现这个错误是因为试图将一个整数值减去一个字符串值。这是一种类型错误(type error)。
图2-13 当试图执行该例时引起了一个异常。整数不可以减去字符串。对于这样的异常检查,栈跟踪是非常有用的,尤其在包含自定义角色时
引起异常的角色被高亮显示,并且异常窗口中将显示角色的名称。在Ptolemy II模型中,所有对象都有一个加“.”的名称。句号“.”将不同层中的元素分隔开。这样,“.helloWorldSubtractError.AddSubtract”就表示一个包含.hello World AddSubtractError错误的“AddSubtract”对象。(此模型保存为名为helloWorldAddSubtract.xml的文件。)
异常(Exceptions)是很有用的调试工具,尤其当用Java进行组件开发时。为了说明如何使用它们,单击图2-13异常窗口中的Display Stack Trace按钮。这个窗口显示了造成异常的执行序列。比如说,如果你向下滚动窗口,就会出现类似如下的显示:
在String Token. Jave源代码中的359行出现异常是由ptolemy.data.StringToken,类中的subtract方法引起的。因为Ptolemy II是和源代码一起发行的(大多数安装机制都支持源代码安装),因此这是非常有用的信息。对于类型错误,也许不需要查看栈追踪,但是如果想用自己的Java代码扩展该系统或者遇到了一个无法解释的问题,那么查看栈追踪会得到启发。
为了寻找上面提到的StringToken.java文件,请查找Ptolemy II 安装目录。如果目录是$PTII,那么文件的位置由完整的类名给出,但是点“.”换成了斜杠“/,这种情况下,它在:
在Windows系统中这些“/”会变为“”“/”。
因此可以尝试对模型进行修改来排除异常。将Const角色从AddSubtract角色的端(-)口断开,并将它连接到端口(+),如图2-14所示。步骤为:1)选中连接;2)删除(用delete键);3)增加一条新连接或者选中它并从它的一个端点拖曳到另一个新位置。注意,高端口是空心三角形,这表明你可以对它建立一个以上的连接。当模型执行时,得到如图2-14所示的从“0Hello World”起的一个字符串序列。按照Java的惯例,字符串可以进行整数加(使用级联),但不能进行整数减。
图2-14 整数加字符串
所有连接到一个多重端口的连接必须具有相同的类型。在这种情况下,多重端口的输入为一个整数序列(来自Ramp)和一个字符串序列(来自Const)。Ramp整数自动转换为字符串,且与“Hello World”进行级联,以生成输出序列。
如上例所示,当角色需要且类型转换可行时,Ptolemy II自动进行类型转换。通常,Ptolemy II将在信息没有丢失的情况下自动进行类型转换。比如说,int可以转换为string,反之则不行。int可以转换成double,反之不行。int可以转换成long,反之则不行。在第14章将详细解释,但通常读者无须记住转换法则。一般情况下,数据类型转换和结果计算会如所期望的那样进行。
为进一步探索数据类型,可通过修改Ramp角色,使其参数为不同的类型。比如,试着将init型变成step型字符串。
补充阅读:Math库
Actors→Math库中的角色进行数学运算,如下图所示(除了最多功能的表达式外,其他的将在第13中解释)。
它们的意义大多可从名称上来直观理解:
AbsoluteValue 计算输入的绝对值。
AddSubtract在加法端口加上令牌,在减法端口减去令牌。
Accumulator 输出到达的所有令牌之和。
Average 输出到达的所有令牌的平均值。
Counter当令牌到达两个输入端口时向上或向下计数。
Differential输出当前输入和上次输入的差。
DotProduct计算两个数组或矩阵输入的内积。
Limiter 将输入的值限制在某一范围内。
LookupTable根据输入查找表中的数组作为输出。
Maximum 输出当前有效输入令牌的最大值。
Minimum输出当前有效输入令牌的最小值。
MovingAverage输出最近输入的平均值。
MultiplyDivide 对乘法输入端的输入做乘法,对除法输入端的输入做除法。
Quantizer 从指定列表中选出最接近输入值的值进行输出。
Remainder 将进行输入值除以dirisor参数后的余数输出。
RunningMaximum 将目前所有输入中的最大值进行输出。
RunningMinimum 将目前所有输入中的最小值进行输出。
Scale 将输入乘以一个常量参数。
TrigFunction 计算三角函数,包括正弦、余弦、正切、反余弦、反正弦和反正切。
UnaryMathFunction 能够执行一元变量的函数,包括求幂、求对数、求符号函数、求平方值、求平方根。
还有一些细节需要注意 。一些有多输入端口(AddSubtract、Counter和MultiplyDivide)
的角色,或者一些有多重输入(Maximum和Minimum)的角色,并不要求所有的输入信道都有输入令牌。当这些角色点火时,一旦输入令牌可用,它们就开始执行。比如说,若AddSubtract端口的+(plus)信道上没有任何令牌,只有-(minus)信道上有一个令
牌,那么输出将是这个令牌取负所得的值。当角色点火时,输入是否可用取决于指示器和模型本身。若是SDF指示器,每次点火时,每个输入信道上只严格地提供一个输入令牌。
这些角色是多态的(polymorphic),它们可以作用于多种不同的数据类型。比如说,AbsoluteValue 角色接收任何标量类型的输入(见第14章)。如果输入类型是复数,则它计算模值。对于其他标量类型,它计算绝对值。
Accumulator 和 Average 都有复位(reset)输入端口(在图标的底部)。Accumulator 和Average 计算输入序列的和或者均值,且它们都可以被复位。