本節書摘來自華章出版社《資訊實體融合系統(cps)設計、模組化與仿真——基于 ptolemy ii 平台》一書中的第3章,第3.2節,作者:[美]愛德華·阿什福德·李(edward ashford lee),更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視
雖然保證有界緩沖區和排除死鎖是很有價值的,但是這是有代價的: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-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-11 實作了資料依賴疊代的一個ddf模型
圖3-11中所示的模式是非常有用的,它可能被反複使用。幸運的是,ptolemy ii包括了一個可以對設計模式(design pattern)進行存儲并重用的機制,這個機制由feng(2009)提出。比如圖3-12中所示的模式,位于morelibraries→designpatterns庫中,它就是一個可以被重用的單元。事實上,任何一個ptolemy ii模型都可以作為一個設計模式導出到一個庫中,然後作為一個單元重新導入到另一個模型中,導入時僅需将它拖拽到模型中。
圖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-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角色(以及它們的布爾形式),如下所示。
每次點火,switch消耗一個輸入的令牌和控制(control)端口(在底部)的一個整數值令牌,并按照控制令牌的訓示為輸入令牌路由,以便将其傳送到相應的輸出通道。所有其他的輸出通道在這次點火中不産生令牌。select 則相反,它消耗一個來自于由控制令牌指定通道的令牌,并将它傳送到輸出。其他的輸入通道不消耗令牌。booleanswitch (booleanselect)是switch的變體,它的輸出或輸入的數量被限制為兩個,并且控制令牌是布爾型而不是整數。
switch 和select可以和以下角色相比較,它們的功能是相關的:
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-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-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中的元素”。
函數“minimax(d)”傳回d的一個子集,這個子集的元素都滿足目的角色要求的輸出通道上令牌數的最大值的下限。這将包括sink角色(無輸出端口的角色)。
補充閱讀:字元串操作角色
string庫提供了一些角色以便對字元串進行操作:
stringcompare角色對兩個字元串進行比較,确定它們是否相等,或者其中一個字元串是否是以另一個字元串開始、結尾或者包含另一個字元串。換行stringmatches角色檢查一個字元串是否與一個給定的以普通表達式表達的模式相比對。換行stringfunction函數可以删除一個字元串附近的空白空間或者将它轉為大寫字母或小寫字母。換行stringindexof角色查找一個字元串的子串并傳回這個子串的索引。換行stringlength角色輸出一個字元串的長度。換行stringreplace函數将一個滿足某種模式的子串替換為一個特定的替換字元串。換行stringsplit将一個字元串從指定分隔符處分開。換行stringsubstring根據給定的起始和終止索引在一個字元串中提取子串。
補充閱讀:回歸測試建構
當開發一個重要模型或者擴充ptolemy ii時,良好的工程經驗要求建立回歸測試(regression test)。未來的很多變化可能會造成早期的應用程式失效,進而造成系統行為的改變,回歸測試可以防止這些改變。幸運的是,在ptolemy ii中,建立回歸測試十分容易。在morelibraries→regressiontest中可以找到其核心元件。
test角色将輸入值和由correctvalues參數指定的值相比較。這個角色有一個trainingmode參數,當它設定為true時,它記錄它接收的輸入值。是以,它的一個典型應用就是将角色放入訓練模式中,執行模型,然後将角色從訓練模型中取出,并且将模型儲存在某個目錄中。在這個目錄中所有模型将作為日常測試的一部分被執行。(這就是ptolemy ii如何為自己建立出大量的回歸測試的原因。)如果test角色接收到任何不同于它記錄的輸入,模型就抛出異常。注意,确定性(determinate)模型的一個關鍵好處就是,它有建構這樣的回歸測試的能力。
nonstricttest也同樣,除了它容忍(并忽視)缺失的輸入,并且在它執行中的後點火(postfire)階段測試輸入,而不是在點火階段。這對sr域和continuous域這兩個疊代終止值是固定值的域來說是很有用的。
有時候,希望模型抛出異常。對于這樣的模型,回歸測試應當包含一個testexceptionattribute的執行個體,testexceptionattribute也有一個訓練模式。如果模型執行中不抛出異常,或者如果抛出的異常不符合預期,模型的這種執行個體會使得模型抛出異常。