《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——3.2 动态数据流-阿里云开发者社区

开发者社区> 华章出版社> 正文

《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——3.2 动态数据流

简介: 与在其他数据流MoC(如SDF)中一样,当DDF角色有足够的输入数据时,它们才开始点火。对于一个要点火的角色,在角色点火前必须遵守点火规则(firing rule)(即角色需要满足点火条件才能进行点火)。

本节书摘来自华章出版社《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》一书中的第3章,第3.2节,作者:[美]爱德华·阿什福德·李(Edward Ashford Lee),更多章节内容可以访问云栖社区“华章计算机”公众号查看

3.2 动态数据流

虽然保证有界缓冲区和排除死锁是很有价值的,但是这是有代价的:SDF的表达能力不够好。它不能直接表达有条件的点火行为,比如,如何让一个令牌有特定值时角色才点火。
现在已经开发出大量的可以不受SDF约束的数据流变体。我们在这一节中介绍一个称为动态数据流(Dynamic Data Fow,DDF)的变体。DDF比SDF更灵活,因为角色在每次点火时可以产生和消耗不同数量的令牌。
3.2.1 点火规则
与在其他数据流MoC(如SDF)中一样,当DDF角色有足够的输入数据时,它们才开始点火。对于一个要点火的角色,在角色点火前必须遵守点火规则(firing rule)(即角色需要满足点火条件才能进行点火)。在SDF模型中,角色的点火规则是恒定的。规则明确指定了角色可以进行点火之前,其每个输入端口需要的令牌数。但是在DDF域中,点火规则更复杂,可能会为每次点火规定不同令牌数。
例3.8 例3.6的SampleDelay 角色直接为DDF计算模型,不需要任何对初始令牌的特殊处理。则SampleDelay的点火规则特别指出,在首次点火时不需要输入令牌;在后续的点火中,需要一个输入令牌。
另一个区别是,在SDF中,角色在每个输出端口都产生固定数量的令牌。而在DDF中,产生的令牌数量是可以变化的。
例3.9 在首次点火时,SampleDelay角色产生一定数量的令牌,该个数量由它的initialOutputs参数指定。在后续的点火中,它仅产生一个令牌,这与它消耗的令牌数相等。
点火规则本身不必是恒定不变的。点火时,DDF角色可能会改变下一次点火的点火规则。
BooleanSelect是一个重要的DDF角色,它根据一布尔值控制令牌流,将两个输入流归并为一个流。该角色有3个点火规则。初始情况下,要求control(底部)端口有一个令牌,其他两个端口没有令牌。当角色点火时,记录控制令牌的值并改变它的点火规则,即要求在trueInput端口(标有“T”)或者falseInput端口(标有“F”)有一个令牌,这取决于控制令牌的值。当这个角色下一次点火时,它消耗相应端口的令牌并将它发送到输出端口。这样,它点火两次产生一个输出。当产生一个输出后,它的点火规则变为要求在control端口有一个令牌。
BooleanSelect角色的一个更通用的版本是Select角色,它根据一个使用整数值的控制令牌流,将任意数量的(而不仅限于两个)输入流合并为一个输出流。
BooleanSelect角色和 Select角色将输入流进行合并,而BooleanSwitch角色和 Switch角色的作用正好与之相反,它们将一个单输入流分成多个。同样,对于每一个输入令牌,控制令牌流决定它将发送到哪一个输出流。这些Switch 和 Select角色完成对令牌的条件路由,如图3-10所示。
例3.10 图3-10使用BooleanSwitch和BooleanSelect角色完成有条件点火,相当于命令式程序语言中的if-then-else。在这个图中,Bernoulli角色产生一个随机的布尔值令牌流。这个控制流控制Ramp角色产生的令牌的路由。当Bernoulli角色产生true时,Ramp角色的输出使用Scale角色进行乘以-1的操作。当Bernoulli角色产生false时,就使用Scale2,它将输入不加改变地进行传递。BooleanSelect角色使用相同的控制流来选择合适的Scale输出。
3

图3-10 实现了有条件点火的一个DDF模型
例3.11 如图3-11所示为一个DDF模型,它使用BooleanSwitch和BooleanSelect角色,通过一个反馈回路来实现有数据依赖的迭代。Ramp角色用一个递增的整数序列“0, 1, 2, 3…”对回路进行反馈。SampleDelay角色对回路进行初始化,方法是向BooleanSelect的Control(控制)端口提供一个false令牌。在整个循环中,每个输入的整数被反复地乘以0.5 ,直到结果值小于0.5。令牌可以是被返回到回路中以便参与另一次迭代,也可以被路由到回路外面而到达Discard角色(图的右边,那个类似电路图中接地图标的符号,可在Sinks→GenericSinks库中找到),这是由Comparator 角色(在Logic库中)控制的。Discard角色接收并丢弃它的输入,但是这种情况下,它也被用来控制一次迭代的含义。参数requiredFiringsPerIteration被加到该角色上,并赋值为1(见3.2.2节)。这样,模型的一次迭代包含了很多所需的循环迭代,以便产生Discard的一次点火。这种结构可以类比于命令式程序语言中的do-while循环。
3

图3-11 实现了数据依赖迭代的一个DDF模型
图3-11中所示的模式是非常有用的,它可能被反复使用。幸运的是,Ptolemy II包括了一个可以对设计模式(design pattern)进行存储并重用的机制,这个机制由Feng(2009)提出。比如图3-12中所示的模式,位于MoreLibraries→DesignPatterns库中,它就是一个可以被重用的单元。事实上,任何一个Ptolemy II模型都可以作为一个设计模式导出到一个库中,然后作为一个单元重新导入到另一个模型中,导入时仅需将它拖拽到模型中。
3

图3-12 作为库中单元部件存储的设计模式
Switch和Select角色(以及它们布尔形式的版本)是DDF域中的一部分,这部分相对于SDF域更加的灵活和具有更好的表达能力,但是使用它们也意味着有可能无法确定一个使用有界缓冲区的调度,也不能确保该模型不会出现死锁。事实上,Buck(1993)证明,对于DDF模型来说,有界缓冲区和死锁是不可判定的(undecidable)。因为这个原因,导致DDF模型的分析变得相当困难。
例3.12 图3-10中的if-then-else模型的一个变体如图3-13所示。在这个例子中,BooleanSelect 角色的输入被反转。不同于以前的模型,这个模型没有确保有界缓冲区的调度方法。Bernoulli角色有可能产生一个任意长的值为true的令牌序列,在此期间,这个任意长的令牌序列为BooleanSelect的false端口建立在输入缓存区上,因此有可能发生缓冲区溢出。
3

图3-13 没有有界缓冲区调度的DDF模型
Switch 和 Select角色以及它们的布尔形式,是类似于命令式程序语言中goto语句的数据流。它们通过对令牌进行有条件的路由以便提供对模型执行的初级控制。与goto语句一样,它们在模型中的使用可能导致理解困难。这个问题可以使用结构化数据流(structured data?ow)来解决,它在Ptolemy II中可用高阶角色来实现,这点在2.7节有描述。
3.2.2 DDF中的迭代
SDF的一个优点是一个完整迭代的定义是唯一的。它由模型中每个角色固定数量的点火行为组成。因此比较容易通过设置SDF指示器(iteration)的参数来控制模型总体执行的持续时间,这个参数控制每个角色将被执行的次数。
DDF指示器也有一个iterations参数,但是定义一次迭代不是这么简单的。可以通过以下方法定义一次迭代:向一个或多个requiredFiringsPerIteration 角色添加参数,并给这个参数赋一个整数值,如例3.13所示:
补充阅读:令牌流控制的角色
Ptolemy II提供了一些可以在模型中对令牌进行路由的角色。其中最基础的就是Switch和Select角色(以及它们的布尔形式),如下所示。
3

每次点火,Switch消耗一个输入的令牌和控制(control)端口(在底部)的一个整数值令牌,并按照控制令牌的指示为输入令牌路由,以便将其传送到相应的输出通道。所有其他的输出通道在这次点火中不产生令牌。Select 则相反,它消耗一个来自于由控制令牌指定通道的令牌,并将它传送到输出。其他的输入通道不消耗令牌。BooleanSwitch (BooleanSelect)是Switch的变体,它的输出或输入的数量被限制为两个,并且控制令牌是布尔型而不是整数。
Switch 和Select可以和以下角色相比较,它们的功能是相关的:
3

Con?gurationSwitch 和BooleanSwitch相似,除了它将一个控制(control)输入端口替换为一个参数(parameter),这个参数决定了将把数据传送到哪个输出。如果在模型执行期间参数值没有改变,那么这个角色就是个SDF角色,它总是在一个输出产生零个令牌而在另一个输出产生一个令牌。Con?gurationSelect与BooleanSelect 的相似之处也和这个类似。
BooleanMultiplexor和Multiplexor与 BooleanSelect 和 Select相似,但是它们从所有的输入通道中只消耗一个令牌。这些角色只保留一个输入令牌,其他的输入令牌都丢弃,并将这一个令牌传送给输出。因为这两个角色严格地在每个通道上消耗和产生一个令牌,所以它们是同构SDF角色。
补充阅读:结构化数据流
在一种命令式语言中,结构化程序设计使用嵌套的for循环、if-then-else、do-while和递归语句替代了goto语句(在Dijkstra(1968)中指出goto语句可能有问题)。在结构化数据流中,这样的概念同样适用于数据流建模环境。
图3-14展示了可以完成图3-10条件点火的替代方法。结果是,SDF模型优于图3-10所示的DDF模型。与2.7节讨论的一样,Case角色就是一个高阶角色的例子。其包含两个子模型(细化模型):一个名为true,它包含一个参数为-1的Scale角色;一个名为default,它包含一个参数为1的Scale角色。当给Case角色的控制输入是true时,true细化模型执行一次迭代。对于其他的控制输入执行默认细化模型。
3

图3-14 结构化的数据流方法用于条件点火
条件点火类型的数据流叫作结构化数据流(structured dataflow),因为与许多结构化程序一样,控制结构都是分层嵌套的。这种方法避免了有任意的数据依赖的令牌路由(这也类似于避免了使用goto语句产生的任意分支),此外,Case角色的使用可以使所有模型都是SDF模型。在图3-14的例子中,每一个角色在每一个端口都消耗并产生一个令牌。所以,这个模型是否存在死锁和有界缓冲区是可分析的。
结构化数据流的类型在LabVIEW中有详细的介绍,LabVIEW是National Instruments
(Kodosky et al.,1991)开发的一个设计工具。除了提供一个和图3-14类似的条件操作外,LabVIEW也为迭代(与命令式语言中的for和do-while循环类似)和序列(它在一个子模型的有限集中循环)提供结构化数据流设计。Ptolemy?II中的迭代可以使用2.7节的高阶角色来实现。序列(以及更复杂的控制结构)可以使用第8章所述的模态模型来实现。Ptolemy II支持递归使用ActorRecursion角色,其位于DomainSpecific→DynamicDataflow中(见练习3)。然而,如果不加注意,递归中的有界性又将变得不可确定了(Lee and Parks,1995)。
例3.13 在例3.10中讨论过的图3-10中的if-then-else的例子。指示器的iterations
参数设置为40,图上也确实有40个点。这是因为一个名为requiredFiringsPerIteration的参数被添加到SequencePlotter角色中并被赋值为1。因此,每次迭代都必须包括至少一次SequencePlotter角色的点火。这种情况下,模型中没有其他角色有名为requiredFiringsPerIteration的参数,所以该参数决定了一次迭代的内容。
当模型中的多个角色有名为requiredFiringsPerIteration的参数时,或者当没有这样的参数时,情况就更微妙。在这些情况下,DDF仍有良好定义的迭代,但是其定义的复杂度可能会让设计者大吃一惊。
例3.14 再次考虑图3-10的if-then-else例子。若从SequencePlotter角色中删去requiredFiringsPerIteration 参数,那么模型的40次迭代将只产生9个输出。为什么?回想3.2.1节的BooleanSelect角色,对于它的每个输出,它都要点火两次。缺少了模型中的任何约束,在一次迭代中DDF指示器点火任何角色都将不会超过一次。
例3.15 如图3-15所示为一个DDF模型,对于在一个目录的所有Ptolemy 模型,它将所有SequencePlotter角色的实例替换为Test角色的实例。这个模型使用DirectoryListing角色来为特定目录下的角色构建一个文件名数组。Ptolemy模型查找名称为“*.xml”的文件形式。DirectoryListing角色的?ringCountLimit参数保证了这个角色只点火一次。它将在输出产生一个数组令牌,然后拒绝再次点火。一旦这个数组中的数据被处理完,就没有需要处理的令牌,所以这个模型会陷入死锁,并将停止执行。
3

图3-15 一个DDF模型,对于一个目录下的所有Ptolemy模型,它将所有SequencePlotter角色的实例均替换为Test角色的实例
ArrayToSequence角色,将一个文件名数组转换为一个令牌序列,每个文件名有一个值为字符串类型的令牌。注意enforceArrayLength角色参数在这里被设置为false。如果我们知道问题中XML文件的准确数目,我们就可以将这个参数保留为默认值,然后将数组长度参数arrayLength设为文件数目,并使用SDF指示器代替DDF指示器。ArrayToSequence 角色会消耗一个令牌并产生一个固定的、已知的输出令牌,因此它是一个SDF角色。但是,因为我们一般情况下不知道目录中会有多少匹配文件,所以还是DDF指示器更有用。
FileReader角色读取XML文件并将其内容以字符串的形式输出。StringReplace角色将所有的具有完整类名的SequencePlotter角色的实例都替换为Test角色的完整类名。
第二个StringReplace角色名为StringReplace2,用于从原始文件名创建一个新文件名。比如说,文件名Foo.xml会变成FooTest.xml,然后FileWriter角色将修改后的文件名写入一个具有新文件名的新文件中。
注意,可以用IterateOverArray角色和SDF指示器作为替代完成该操作(见2.7.2节)。我们将其留作练习,由读者自行研究(见本章末的练习2)。
3.2.3 将DDF与其他域结合
虽然一个系统整体上被建模为DDF是最好的,但是它也可能包含一些可以被建模为SDF的子系统。这样,一个DDF模型可能包含一个具有SDF指示器的不透明复合角色。这种方法可以提高效率并且可以更好地控制迭代中的计算量。
反过来,如果一个DDF模型在它的输入/输出边界上的行为类似于SDF,那么这个DDF模型也有可能和一个SDF模型放在一起。为了能在SDF模型中使用,一个不透明的DDF复合角色应当消耗并产生固定数目的令牌。通常由DDF监视器决定边界上有多少令牌是不可能的(这通常是个不确定性的问题),所以它要由模型设计者来声明消耗速率和产生速率。如果它不等于1(不必明确声明),那么模型设计者可以在每个输入端口创建一个名为tokenConsumptionRate的参数并将它设定为一个整数值,以此来声明消耗和产生率。相似地,输出端口应该被赋予一个名为tokenProductionRate的参数。
一旦边界上的速率确定了,则应该由设计者确保在运行期间遵守这些速率。这可以使用requiredFiringsPerIteration参数来完成,如3.2.2节解释的那样。另外,DDF监视器有一个runUntilDeadlockInOneIteration参数,当该参数设置为true时,就定义了一个被基本迭代反复调用直到死锁的迭代。如果使用该参数,则它将优写于模型中可能出现的requiredFiringsPerIteration参数。
DDF符合松散角色语义(loose actor semantics),意味着若DDF指示器用在不透明的复合角色中,那么当调用它的点火(fire)方法时,它的状态将改变。特别地,在它们的fire方法中数据流角色会消耗(consume)输入令牌。一旦令牌被消耗,它们在输入缓冲区中就不再可用。这样,第二次点火就会使用新数据,而不管是否调用后点火(postfire)方法。由于这个原因,DDF和SDF复合角色不应在要求严格角色语义(如SR域和连续)的域中使用,除非模型的设计者可以保证这些复合角色在Continuous容器的SR的一次迭代中点火次数不超过一次。
注意,任何SDF模型都可以与DDF指示器一起运行。但是,迭代的概念是不同的。有时候,即使在有数据依赖迭代情况下,一个DDF模型仍能与SDF指示器一起运行。图3-14展示了一个例子,Case角色促成了这个组合。但是有时候,即使在使用Switch的情况下,使用该组合仍是可能的。SDF调度器将假设Switch在每个输出通道上产生一个令牌,然后依次来构建一个调度机制。当执行这个调度时,指示器会遭遇这样的角色:指示器期望它已经做好点火准备,但是角色并没有足够的输入以供点火。它们的prefire(预点火)方法返回false,向指示器表明角色没有准备好点火。SDF指示器会遵守这一点,并且会在调度中跳过这个角色。但是,这个技巧相当取巧,不推荐使用。因为它可能会导致意料之外的角色执行顺序。
补充阅读:定义DDF迭代
DDF迭代(iteration)由基本迭代(basic interation)的最小数量组成(见下文),它满足requiredFiringsPerIteration参数所要求的全部约束条件。
在一次基本迭代中,DDF指示器将对所有被使能的(enabled)且不可延迟的(non-deferable)角色进行一次点火。一个被使能的角色是指这个角色在输入端口有足够的数据,或者没有输入端口。一个可延迟的角色是指这个角色的执行可以被延迟,因为下游角色不会要求它立即执行。这种情况的出现要么是因为下游角色在连接它和可延迟角色的通道上已经有了足够的令牌,要么是因为下游角色正在等待另一个通道或端口的令牌。如果没有被使能且不可延迟的角色,那么指示器会对那些被使能且可延迟的角色进行点火,这些可延迟角色的输出通道上有满足目的角色要求的最大令牌值的下限。如果没有被使能的角色,那么已经出现死锁(deadlock)。Parks(1995)提出以上策略,可以保证在无限执行中,缓冲区依然是有界缓冲区(bounded buffer)(如果出现有界缓冲区无限执行的情况)。
实现一次基本迭代的算法如下所示。用E表示被使能的角色集合,D表示可延迟的被使能角色集合。那么一次基本(默认)迭代的组成如下,这里ED表示“在E中且不在D中的元素”。
3

函数“minimax(D)”返回D的一个子集,这个子集的元素都满足目的角色要求的输出通道上令牌数的最大值的下限。这将包括sink角色(无输出端口的角色)。
补充阅读:字符串操作角色
String库提供了一些角色以便对字符串进行操作:
3

StringCompare角色对两个字符串进行比较,确定它们是否相等,或者其中一个字符串是否是以另一个字符串开始、结尾或者包含另一个字符串。换行StringMatches角色检查一个字符串是否与一个给定的以普通表达式表达的模式相匹配。换行StringFunction函数可以删除一个字符串附近的空白空间或者将它转为大写字母或小写字母。换行StringIndexOf角色查找一个字符串的子串并返回这个子串的索引。换行StringLength角色输出一个字符串的长度。换行StringReplace函数将一个满足某种模式的子串替换为一个特定的替换字符串。换行StringSplit将一个字符串从指定分隔符处分开。换行StringSubstring根据给定的起始和终止索引在一个字符串中提取子串。
补充阅读:回归测试构建
当开发一个重要模型或者扩展Ptolemy II时,良好的工程经验要求建立回归测试(regression test)。未来的很多变化可能会造成早期的应用程序失效,从而造成系统行为的改变,回归测试可以防止这些改变。幸运的是,在Ptolemy II中,创建回归测试十分容易。在MoreLibraries→RegressionTest中可以找到其核心组件。


a3a5d978e4e62d88afc5547c9d0e7b9d07d28552

Test角色将输入值和由correctValues参数指定的值相比较。这个角色有一个trainingMode参数,当它设置为true时,它记录它接收的输入值。因此,它的一个典型应用就是将角色放入训练模式中,执行模型,然后将角色从训练模型中取出,并且将模型保存在某个目录中。在这个目录中所有模型将作为日常测试的一部分被执行。(这就是Ptolemy II如何为自己创建出大量的回归测试的原因。)如果Test角色接收到任何不同于它记录的输入,模型就抛出异常。注意,确定性(determinate)模型的一个关键好处就是,它有构建这样的回归测试的能力。
NonStrictTest也同样,除了它容忍(并忽视)缺失的输入,并且在它执行中的后点火(postfire)阶段测试输入,而不是在点火阶段。这对SR域和Continuous域这两个迭代终止值是固定值的域来说是很有用的。
有时候,希望模型抛出异常。对于这样的模型,回归测试应当包含一个TestExceptionAttribute的实例,TestExceptionAttribute也有一个训练模式。如果模型执行中不抛出异常,或者如果抛出的异常不符合预期,模型的这种实例会使得模型抛出异常。

版权声明:如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developerteam@list.alibaba-inc.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接