cfl_bisimcta.cpp
Go to the documentation of this file.
1/** @file cfl_bisimcta.cpp Bisimulation relations (CTA)
2
3 Functions to compute bisimulation relations on dynamic systems (represented
4 by non-deterministic finite automata).
5
6 More specifically, we we implement algorithms to obtain ordinary/delayed/weak
7 bisimulations partitions based on change-tracking. In large, these implementations
8 are expected to perform better than the classical variants found in cfl_bisimulation.h".
9
10 This code was originally developed by Yiheng Tang in the context of compositional
11 verification in 2020/21.
12
13**/
14
15/* FAU Discrete Event Systems Library (libfaudes)
16
17 Copyright (C) 2020/21, Yiheng Tang
18 Copyright (C) 2021, Thomas Moor
19 Exclusive copyright is granted to Klaus Schmidt
20
21 This library is free software; you can redistribute it and/or
22 modify it under the terms of the GNU Lesser General Public
23 License as published by the Free Software Foundation; either
24 version 2.1 of the License, or (at your option) any later version.
25
26 This library is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
29 Lesser General Public License for more details.
30
31 You should have received a copy of the GNU Lesser General Public
32 License along with this library; if not, write to the Free Software
33 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
34
35#include "cfl_bisimcta.h"
36
37
38// *******************************
39// *******************************
40// Preliminaries
41// *******************************
42// *******************************
43
44#define BISIM_VERB1(msg) \
45 { if(faudes::ConsoleOut::G()->Verb() >=1 ) { \
46 std::ostringstream cfl_line; cfl_line << msg << std::endl; faudes::ConsoleOut::G()->Write(cfl_line.str(),0,0,0);} }
47#define BISIM_VERB2(msg) \
48 { if(faudes::ConsoleOut::G()->Verb() >=2 ) { \
49 std::ostringstream cfl_line; cfl_line << msg << std::endl; faudes::ConsoleOut::G()->Write(cfl_line.str(),0,0,0);} }
50#define BISIM_VERB0(msg) \
51 { if(faudes::ConsoleOut::G()->Verb() >=0 ) { \
52 std::ostringstream cfl_line; cfl_line << msg << std::endl; faudes::ConsoleOut::G()->Write(cfl_line.str(),0,0,0);} }
53
54namespace faudes {
55
56
57// ****************************************************************************************************************************
58// ****************************************************************************************************************************
59// Private part of header
60// ****************************************************************************************************************************
61// ****************************************************************************************************************************
62
63
64/*!
65 * \brief The Bisimulation class
66 * The following class implements a basic normal bisimulation partition algorithms and derives a class
67 * for partitioning w.r.t. delayed bisimulation and weak bisimulation . All these algorithms are introduced
68 * in "Computing Maximal Weak and Other Bisimulations" from Alexandre Boulgakov et. al. (2016).
69 * The utilised normal bisimulation algorithm originates from the "change tracking algorithm"
70 * from Stefan Blom and Simona Orzan (see ref. of cited paper). Besides, the current paper uses topological
71 * sorted states to avoid computing saturated transitions explicitly when computing delayed and weak bisimulation.
72 *
73 * IMPORTANT NOTE: both algorithms for delayed and weak bisimulation requires tau-loop free automaton. This shall
74 * be done extern beforehand by using e.g. FactorTauLoops(Generator &rGen, const Idx &rSilent).
75 */
76
78public:
79 BisimulationCTA(const Generator &rGen, const std::vector<StateSet> &rPrePartition) : mGen(&rGen), mPrePartition(rPrePartition){}
80 virtual ~BisimulationCTA() = default;
81
82 /*!
83 * \brief BisimulationPartition
84 * wrapper
85 */
86 virtual void ComputePartition(std::list<StateSet>& rResult);
87
88protected:
89
90 // encoded data structure
91 struct State {
92 Idx id; // source state idx
93 std::vector< std::vector<Idx> > suc; // successors by event
94 std::vector< Idx > pre; // predecessors (neglect event, only for figuring affected)
95 std::vector< std::set<Idx> > cafter; // cafter by event (the std::set<Idx> is the set of c-values)
96 std::vector< Idx > evs; // active event set, only for pre-partition. (indicates "delayed" active ev in case of daleyd- or weak-bisim)
97 Idx c = 0; // this shall be replaced by 1 for each state (except #0 state)
98
99 // only for derived classes, recored predececssors with silent event.
100 // in such cases, "pre" records only non-tau predecessors
101 std::vector< Idx > taupre;
102 };
103 std::vector<State> mStates; // vector of all states [starting with 1 -- use min-index for debugging]
104 std::vector<Idx> mEvents; // vector of all events [starting with 1]
105
106 /*!
107 * \brief EncodeData
108 * encode source generator into vector-form data structures to accelerate the iteration
109 *
110 * NOTE: this function is virtual since derived classes, in the context of
111 * abstraction, have different steps
112 */
113 virtual void EncodeData(void);
114
115 /*!
116 * \brief FirstStepApproximation (see Fig. 2 (b) in cited paper)
117 * Prepartition states according to their active events.
118 * If a prepartition already exists, this function will refine the prepartition
119 *
120 * NOTE: this function is also utilised by delayed- and weak-bisimulation since in both
121 * cases, State.evs neglect tau (as tau is always active)
122 *
123 */
124 void FirstStepApproximation(void);
125
126 /*!
127 * \brief RefineChanged
128 * refine equivalence classes contained affected nodes in the last iteration
129 * (see Fig. 5 in cited paper)
130 */
131 void RefineChanged(void);
132
133 /*!
134 * \brief GenerateResult
135 * generate partition result w.r.t. original state indices (without trivial classes)
136 * \param rResult partition w.r.t. original state indices without trivial classes
137 */
138 void GenerateResult(std::list<StateSet>& rResult);
139
140 /*! keep the original generator */
142
143 /*! state prepartition with original state idx (the same as state label as in cited paper). Empty for trivial prepartition. */
144 const std::vector<StateSet> mPrePartition;
145
146 /*! persisted data structures, see cited paper. */
147 std::vector<bool> mAffected;
148 std::vector<bool> mChanged;
149 Idx mCmax; // maximal value of c in the current partition
150
151 /*! constant parameters for encoding*/
152 Idx mStateSize; // state count, for vector resizing and iteration
153 Idx mAlphSize; // event count, for vector resizing and iteration
154
155 /*! the current (and also final) partition. Partition is represented
156 * by states sorted by c_value. Access mStates for c_value to get the exact partition.
157 */
158 std::vector<Idx> mPartition;
159
160private:
161
162 /*!
163 * \brief ComputeBisimulation
164 * perform the overall iteration
165 */
166 void ComputeBisimulation(void);
167
168 /*!
169 * \brief ComputeChangedAfters
170 * Compute changed afters of each affected state. (see Fig 5. in cited paper)
171 * Affected states are those having some successors that have changed class (namely c_value)
172 * in the last iteration
173 * NOTE: this function is made private in that derived classes compute cafters
174 * w.r.t. silent event and these are accomplished by ComputeChangedDelayedAfters
175 * and ComputeChangedObservedAfters, respectively
176 */
177 void ComputeChangedAfters(void);
178
179protected:
180 // TMoor 2026 --- explicit re;lations to avoid lambda expressions
181 bool order_evs_c(const Idx& state1, const Idx& state2) const {
182 if (mStates[state1].evs < mStates[state2].evs) return 1;
183 if (mStates[state1].evs > mStates[state2].evs) return 0;
184 if (mStates[state1].c < mStates[state2].c) return 1;
185 return 0;
186 };
187
188
189};
190
191
192
193/*!
194 * \brief The DelayedBisimulation class
195 * Derived from Bisimulation class. We implement the "two-pass change-tracking" algorithm from the cited paper.
196 */
198public:
199 AbstractBisimulationCTA (const Generator& rGen, const Idx& rSilent, const Idx& rTaskFlag, const std::vector<StateSet>& rPrePartition)
200 : BisimulationCTA(rGen,rPrePartition), mTau(rSilent), mTaskFlag(rTaskFlag){}
201
202 virtual void ComputePartition(std::list<StateSet>& rResult);
203
204private:
205 /*!
206 * \brief tau
207 * persist index of original silent event
208 */
209 const Idx mTau;
210
211 /*!
212 * \brief mTaskFlag
213 * flag for task:
214 * mTaskFlag == 1: delayed bisimulation
215 * mTaskFlag == 2: weak bisimulation
216 */
218
219 /*!
220 * \brief EncodeData
221 * basic preparation for bisimulation
222 * NOTE: this function is virtual since derived classes, in the context of
223 * abstraction, have different steps
224 */
225 virtual void EncodeData(void);
226
227 /*!
228 * \brief MarkTauAffected
229 * perform a recursive Depth-First-Search to mark all tau* predecessors as affected
230 * based on given state index rState.
231 * NOTE: rState is also a tau* predecessor of itself, thus also affected
232 * NOTE: as tau-loop-free is guaranteed, no need to use visited list to avoid duplet
233 * NOTE: in most cases, argument rAffected directly takes mAffected. The only
234 * exception is that in ComputeChangedObservedAfters, we need to buffer an intermediate
235 * affected-vector.
236 */
237 void MarkTauStarAffected(std::vector<bool>& rAffected, const Idx& rState);
238
239 /*!
240 * \brief ComputeDelayedBisimulation
241 * perform the overall iteration based on different task flag value, see mTaskFlag
242 */
244
245
246 /*!
247 * \brief ComputeChangedDelayedAfters
248 * see Fig. 10 of cited paper
249 */
251
252 /*!
253 * \brief ComputeChangedObservedAfters
254 * see Fig. 12 of cited paper
255 */
257
258};
259
260
261
262// *******************************
263// *******************************
264// topological sort
265// *******************************
266// *******************************
267
268
269/*!
270 * \brief The TopoSort class
271 * perform a topological sort on states of a given automaton. if s1 is before s2 in the sort
272 * then there is no path from s2 to s1. The algorithm can be found in
273 * https://en.wikipedia.org/wiki/Topological_sorting
274 * under depth-first search, which is considered as first invented by R. Tarjan in 1976.
275 */
277public:
278 struct State{
280 bool permanent = 0;
281 bool temporary = 0;
282 std::vector<Idx> succs;
283 };
284 TopoSort (const Generator& rGen, const EventSet& rEvs);
285
286 bool Sort(std::list<Idx>& result);
287
288 bool Visit(State& rNode);
289
290private:
293 std::vector<State> mStates;
294 std::list<Idx> mResult;
295};
296
297/*!
298 * \brief topoSort
299 * wrapper for topological sortation
300 * rEvs is the set of events which will be considered while sorting
301 */
302bool topoSort (const Generator& rGen, const EventSet& rEvs, std::list<Idx>& result);
303
304
305
306
307// ****************************************************************************************************************************
308// ****************************************************************************************************************************
309// Implementation part
310// ****************************************************************************************************************************
311// ****************************************************************************************************************************
312
313
314// *******************************
315// *******************************
316// Transition saturation
317// *******************************
318// *******************************
319
320
321// YT: note flag: 1 := delayed bisim; 2 := weak bisim
322void ExtendTransRel(Generator& rGen, const EventSet& rSilent, const Idx& rFlag) {
323
324 if (rSilent.Empty()) return;
325 // HELPERS:
327 TransSet::Iterator tit1_end;
329 TransSet::Iterator tit2_end;
330 TransSet newtrans;
331
332 // initialize result with original transitionrelation
333 TransSet xTrans=rGen.TransRel();
334 newtrans = rGen.TransRel(); // initialize iteration
335 // loop for fixpoint
336 while(!newtrans.Empty()) {
337 // store new transitions for next iteration
338 TransSet nextnewtrans;
339 // loop over all transitions in newtrans
340 tit1=newtrans.Begin();
341 tit1_end=newtrans.End();
342 for(;tit1!=tit1_end; ++tit1) {
343 // if it is silent add transition to non silent successor directly
344 if(rSilent.Exists(tit1->Ev)) {
345 tit2=xTrans.Begin(tit1->X2);
346 tit2_end=xTrans.End(tit1->X2);
347 for(;tit2!=tit2_end; ++tit2) {
348// if(!rSilentAlphabet.Exists(tit2->Ev)) // tmoor 18-09-2019
349 if (!xTrans.Exists(tit1->X1,tit2->Ev,tit2->X2))
350 nextnewtrans.Insert(tit1->X1,tit2->Ev,tit2->X2);
351 }
352 }
353 else if (rFlag == 2){ // in case for observed transition (flag == 2), add non-silent transition to silent successor
354 tit2=xTrans.Begin(tit1->X2);
355 tit2_end=xTrans.End(tit1->X2);
356 for(;tit2!=tit2_end; ++tit2) {
357 if(rSilent.Exists(tit2->Ev)) // silent successor
358 if (!xTrans.Exists(tit1->X1,tit1->Ev,tit2->X2))
359 nextnewtrans.Insert(tit1->X1,tit1->Ev,tit2->X2);
360 }
361 }
362 }
363 // insert new transitions
364 xTrans.InsertSet(nextnewtrans);
365 // update trans for next iteration.
366 newtrans = nextnewtrans;
367 }
368 rGen.InjectTransRel(xTrans);
369}
370
371void InstallSelfloops(Generator &rGen, const EventSet& rEvs){
372 if (rEvs.Empty()) return;
373 StateSet::Iterator sit = rGen.StatesBegin();
374 for(;sit!=rGen.StatesEnd();sit++){
375 EventSet::Iterator eit = rEvs.Begin();
376 for(;eit!=rEvs.End();eit++)
377 rGen.SetTransition(*sit,*eit,*sit);
378 }
379}
380
381
382// *******************************
383// *******************************
384// topological sort
385// *******************************
386// *******************************
387
388TopoSort::TopoSort (const Generator& rGen, const EventSet &rEvs) {
389 mGen = &rGen;
390 nxidx=1;
391
392 // encode transition relation [effectively buffer log-n search]
393 Idx max=0;
394 std::map<Idx,Idx> smap;
395 mStates.resize(mGen->States().Size()+1);
396 StateSet::Iterator sit=mGen->StatesBegin();
397 for(; sit != mGen->StatesEnd(); ++sit) {
398 smap[*sit]=++max;
399 mStates[max].id=*sit;
400 }
402 for(; tit != mGen->TransRelEnd(); ++tit) {
403 if (rEvs.Exists(tit->Ev))
404 mStates[smap[tit->X1]].succs.push_back(smap[tit->X2]);
405 }
406}
407
408bool TopoSort::Sort (std::list<Idx> &result){
409 std::vector<State>::iterator sit = mStates.begin();
410 sit++;
411 for(;sit!=mStates.end();sit++){
412 if ((*sit).permanent) continue;
413 if (!Visit(*sit)) return false;
414 }
415 result = mResult;
416 return true;
417}
418
419bool TopoSort::Visit(State &rState){
420 if (rState.permanent) return true;
421 if (rState.temporary) return false;
422 rState.temporary = 1;
423 std::vector<Idx>::iterator sit = rState.succs.begin();
424 for(;sit!=rState.succs.end();sit++){
425 if(!Visit(mStates[*sit])) return false;
426 }
427 rState.temporary = 0;
428 rState.permanent = 1;
429 mResult.push_front(rState.id);
430 return true;
431}
432
433bool topoSort(const Generator& rGen, const EventSet &rEvs, std::list<Idx>& result){
434 TopoSort topo(rGen, rEvs);
435 return topo.Sort(result);
436}
437
438
439
440// *****************************************************
441// *****************************************************
442// (strong) bisimulation
443// *****************************************************
444// *****************************************************
445
447 mStateSize = mGen->States().Size()+1;
448 mAlphSize = mGen->Alphabet().Size()+1;
449
450 // encode transition relation [effectively buffer log-n search]
451 std::map<Idx,Idx> smap;
452 std::map<Idx,Idx> emap;
453 Idx max=0;
454 mEvents.resize(mAlphSize);
455 EventSet::Iterator eit= mGen->AlphabetBegin();
456 for(; eit != mGen->AlphabetEnd(); ++eit) {
457 emap[*eit]=++max;
458 mEvents[max]=*eit;
459 }
460 if (mPrePartition.empty())
461 mCmax = 1;
462 else
463 mCmax = mPrePartition.size();
464 max=0;
465 mStates.resize(mStateSize);
466 mPartition.resize(mStateSize-1);
467 StateSet::Iterator sit=mGen->StatesBegin();
468 for(; sit != mGen->StatesEnd(); ++sit) {
469 mPartition[max] = max+1; // trivial partition
470 smap[*sit]=++max;
471 mStates[max].id=*sit;
472 mStates[max].suc.resize(mAlphSize);
473 mStates[max].cafter.resize(mAlphSize);
474 // initialize cafter
475 const EventSet& activeevs = mGen->ActiveEventSet(*sit);
476 EventSet::Iterator eit = activeevs.Begin();
477 for(;eit!=activeevs.End();eit++)
478 mStates[max].evs.push_back(emap[*eit]);
479 // initialize c value. c = 1 if no prepartition; c = (index of prepartion) + 1 if prepartition defined
480 if (mPrePartition.empty()) mStates[max].c = mCmax;
481 else{
482 Idx prepit = 0;
483 for(;prepit<mPrePartition.size();prepit++){
484 if (mPrePartition[prepit].Exists(*sit)) break;
485 }
486 if (prepit == mPrePartition.size())
487 throw Exception("EncodeData:: ", "invalide prepartition. State "+ ToStringInteger(*sit) + "is not allocated.", 100);
488 mStates[max].c = prepit+1;
489 }
490 }
492 for(; tit != mGen->TransRelEnd(); ++tit) {
493 mStates[smap[tit->X1]].suc[emap[tit->Ev]].push_back(smap[tit->X2]);
494 // since predecessors are recorded neglecting events, we (shall) check for
495 // duplication before inserting. This is actually optional.
496 std::vector<Idx>::const_iterator preit = mStates[smap[tit->X2]].pre.begin();
497 for (;preit!=mStates[smap[tit->X2]].pre.end();preit++){
498 if (*preit==smap[tit->X1]) break;
499 }
500 if (preit==mStates[smap[tit->X2]].pre.end())
501 mStates[smap[tit->X2]].pre.push_back(smap[tit->X1]);
502 }
503
504 // initialize affected and changed vector
505 mAffected.resize(mStateSize);
506 mAffected[0] = 0;
507 mChanged.resize(mStateSize);
508 mChanged[0] = 0;
509 Idx iit = 1;
510 for(;iit<mStateSize;iit++){
511 mAffected[iit] = 0;
512 mChanged[iit] = 1;
513 }
514}
515
517 // set up (modified) Phi as in the cited paper. pairs (state, (activeEvs,c-value)) by lex-sort
518 // v1: musing lambda expression (C++11, fails on old compilers
519 std::sort(mPartition.begin(),mPartition.end(), [this](const Idx& state1, const Idx& state2){
520 if (this->mStates[state1].evs < this->mStates[state2].evs) return 1;
521 if (this->mStates[state1].evs > this->mStates[state2].evs) return 0;
522 if (this->mStates[state1].c < this->mStates[state2].c) return 1;
523 return 0;
524 });
525 // v2: using explicit order (FAIL)
526 //std::sort(mPartition.begin(),mPartition.end(), order_evs_c);
527 // assign new c_values
528 std::vector<Idx> evs(1);
529 evs[0] = 0; //initialize invalid active event set
530 Idx c = 0; // initialize invalide c value
531 std::vector<Idx>::const_iterator partit = mPartition.begin();
532 for(;partit!=mPartition.end();partit++){
533 if(mStates[*partit].evs != evs || mStates[*partit].c != c){
534 evs = mStates[*partit].evs;
535 c = mStates[*partit].c;
536 mCmax++;
537 }
538 mStates[*partit].c = mCmax;
539 }
540}
541
543 // recompute mAffected
544 std::fill(mAffected.begin(),mAffected.end(),0);
545 Idx stateit = 1;
546 for(;stateit<mStateSize;stateit++){
547 if (!mChanged[stateit]) continue;
548 std::vector<Idx>::iterator predit = mStates[stateit].pre.begin();
549 for(;predit!=mStates[stateit].pre.end();predit++) mAffected[*predit] = 1;
550 }
551 // compute cafters
552 stateit = 1;
553 for (;stateit<mStateSize;stateit++){
554 if (!mAffected[stateit]) continue;
555 // clear cafter of this node
556 std::set<Idx> emptyset;
557 std::fill(mStates[stateit].cafter.begin(),mStates[stateit].cafter.end(),emptyset);
558 // compute new cafter
559 Idx sucevit = 1;
560 for( ; sucevit<mAlphSize; sucevit++){
561 std::vector<Idx>::const_iterator sucstateit = mStates[stateit].suc[sucevit].begin();
562 for(;sucstateit!=mStates[stateit].suc[sucevit].end();sucstateit++)
563 mStates[stateit].cafter[sucevit].insert(mStates[*sucstateit].c);
564 }
565 }
566}
567
569
570
571 std::fill(mChanged.begin(),mChanged.end(),0);
572 std::vector<Idx>::iterator partit = mPartition.begin();
573 while(partit!=mPartition.end()){
574 // treat affected node is merged into the following step
575
576 // get class containing affected node (line 25 in cited paper)
577 // iterate on mPartition, i.e. mPartition must already sorted by c
578 // we simply iterate forward to get the current class. if no states in class is affected, skip
579 Idx c = mStates[*partit].c;
580 std::list<Idx> eqclass;
581 //std::vector<Idx>::iterator partit_rec = partit;
582 bool affectedflag = false;
583 while(mStates[*partit].c == c){
584 eqclass.push_back(*partit);
585 if (mAffected[*partit]) affectedflag = true;
586 partit++;
587 if (partit==mPartition.end()) break;
588 }
589 if (eqclass.size()==1 || !affectedflag) continue; // skip trivial class or non-affected class
590
591 // reorder nodes in eqclasses by moving those in affected to the front (line 26 in cited paper)
592 std::list<Idx>::iterator bound = std::partition(eqclass.begin(), eqclass.end(),
593 [this](const Idx& state){return this->mAffected[state];});
594
595 // sort affected nodes by cafter (line 27. Note in cited paper line 27, it says sort "NOT" affected nodes.
596 // this shall be a typo since all unaffected nodes in this class shall have the same cafter, as their
597 // cafters are not changed from the last iteration)
598 std::list<Idx> eqclass_aff; // unhook elements before bound
599 eqclass_aff.splice(eqclass_aff.begin(),eqclass,eqclass.begin(),bound);
600 eqclass_aff.sort([this](const Idx& state1, const Idx& state2){
601 return this->mStates[state1].cafter < this->mStates[state2].cafter;});
602 eqclass.splice(eqclass.begin(),eqclass_aff);// hook the sorted eqclass_aff again to the front of eqclass
603
604 // delete the largest set of states with the same cafter (line 28). c_value of these states are preserved
605 std::list<Idx>::iterator eqclassit = eqclass.begin();
606 std::vector<std::set<Idx> > maxCafter; // cafter corresponding to most states in the current class
607 std::vector<std::set<Idx> > currentCafter; // initialize an invalid cafter for comparison
608 Idx maxSize = 0;
609 Idx currentSize = 0;
610 for(;eqclassit!=eqclass.end();eqclassit++){
611 if (mStates[*eqclassit].cafter!=currentCafter){
612 if (currentSize > maxSize){ // update max if the latest record is greater
613 maxSize = currentSize;
614 maxCafter = currentCafter;
615 }
616 currentSize = 1;
617 currentCafter = mStates[*eqclassit].cafter;
618 }
619 else{
620 currentSize++;
621 }
622 }
623 if (maxCafter.empty()) continue; // namely all states has the same cafter. no need to refine.
624
625 // refine this class. The greatest refined class is removed and all other refined classes
626 // will have new c_value (line 29-37)
627 eqclass.remove_if([this, maxCafter](const Idx& state){
628 return this->mStates[state].cafter==maxCafter;});
629 currentCafter.clear();
630 eqclassit = eqclass.begin();
631 for(;eqclassit!=eqclass.end();eqclassit++){
632 if(mStates[*eqclassit].cafter!=currentCafter){
633 currentCafter = mStates[*eqclassit].cafter;
634 mCmax++;
635 }
636 mStates[*eqclassit].c = mCmax;
637 mChanged[*eqclassit] = 1;
638 }
639 }
640
641 // sort by c
642 std::sort(mPartition.begin(),mPartition.end(), [this](const Idx& state1, const Idx& state2){
643 return this->mStates[state1].c<this->mStates[state2].c;
644 });
645}
646
647// the wrapper
649 BISIM_VERB2("Initializing data")
650 EncodeData();
651 BISIM_VERB2("Doing FirstStepApproximation")
653 while (std::find(mChanged.begin(),mChanged.end(),1)!=mChanged.end()){
654 BISIM_VERB2("Doing ComputeChangedAfters")
656 BISIM_VERB2("Doing RefineChanged")
658 }
659}
660
661void BisimulationCTA::GenerateResult(std::list<StateSet>& rResult){
662 rResult.clear();
663 Idx c = 0;
664 std::vector<Idx>::const_iterator partit = mPartition.begin();
665 StateSet eqclass;
666 for(;partit!=mPartition.end();partit++){
667 if (mStates[*partit].c != c){
668 if (eqclass.Size()>1) rResult.push_back(eqclass);
669 eqclass.Clear();
670 eqclass.Insert(mStates[*partit].id);
671 c = mStates[*partit].c;
672 }
673 else eqclass.Insert(mStates[*partit].id);
674 }
675 if (eqclass.Size()>1) rResult.push_back(eqclass); // insert the last partitio
676}
677
678void BisimulationCTA::ComputePartition(std::list<StateSet>& rResult){
680 GenerateResult(rResult);
681}
682
683// *****************************************************
684// *****************************************************
685// delayed and weak bisimulation
686// *****************************************************
687// *****************************************************
688
690 mStateSize = mGen->States().Size()+1;
691 mAlphSize = mGen->Alphabet().Size(); // Note the difference with BisimulationCTA::EncodeData(). Index 0 is for silent event
692
693 // encode transition relation [effectively buffer log-n search]
694 std::map<Idx,Idx> smap;
695 std::map<Idx,Idx> emap;
696
697 Idx max=0;
698 mEvents.resize(mAlphSize);
699 EventSet::Iterator eit= mGen->AlphabetBegin();
700 for(; eit != mGen->AlphabetEnd(); ++eit) {
701 if (*eit != mTau){
702 emap[*eit]=++max;
703 mEvents[max]=*eit;
704 }
705 else{
706 emap[*eit]=0;
707 mEvents[0]=*eit;
708 }
709 }
710
711 // perform topological sort, throw if with tau-loops
712 std::list<Idx> topo;
713 EventSet tau;
714 tau.Insert(mTau);
715 if (!topoSort(*mGen,tau,topo))
716 throw Exception("DelayedBisimulationCTA::EncodeData(): ", "input automaton shall not have tau loop. Please factor"
717 "tau-loops before partition", 500);
718
719 if (mPrePartition.empty())
720 mCmax = 1;
721 else
722 mCmax = mPrePartition.size();
723 max=0;
724 mStates.resize(mStateSize);
725 mPartition.resize(mStateSize-1);
726 // iterate over topo sorted states BACKWARDS and install into mStates
727 // mStates shall have the upstream order. this is a pure design feature
728 // as all iterations will start from the downstream-most state in the topo sort.
729 // By installing them backwards, we always start the iteration from the first state
730 std::list<Idx>::const_reverse_iterator sit=topo.rbegin();
731 for(; sit != topo.rend(); ++sit) {
732 mPartition[max] = max+1; // trivial partition
733 smap[*sit]=++max;
734 mStates[max].id=*sit;
735 mStates[max].suc.resize(mAlphSize);
736 mStates[max].cafter.resize(mAlphSize);
737 // install DELAYED active events
738 EventSet activeevs = mGen->ActiveEventSet(*sit); // direct active events
739 TransSet::Iterator tit = mGen->TransRelBegin(*sit); // active events of one-step tau successors
740 for(;tit!=mGen->TransRelEnd(*sit);tit++){
741 if (tit->Ev!=mTau) continue;
742 // since active events are encoded in vectors, iterate over active event vector of successor
743 std::vector<Idx>::const_iterator eit = mStates[smap[tit->X2]].evs.begin(); // the state smap[tit->X2] must have been encoded
744 for(;eit!=mStates[smap[tit->X2]].evs.end();eit++){
745 activeevs.Insert(mEvents[*eit]); // set operation to avoid duplication
746 }
747 }
748 EventSet::Iterator eit = activeevs.Begin();
749 for(;eit!=activeevs.End();eit++){
750 if (*eit == mTau) continue; // tau is always active, no need to take into consideration
751 mStates[max].evs.push_back(emap[*eit]);
752 }
753 // initialize c value. c = 1 if no prepartition; c = (index of prepartion) + 1 if prepartition defined
754 if (mPrePartition.empty()) mStates[max].c = mCmax;
755 else{
756 Idx prepit = 0;
757 for(;prepit<mPrePartition.size();prepit++){
758 if (mPrePartition[prepit].Exists(*sit)) break;
759 }
760 if (prepit == mPrePartition.size())
761 throw Exception("EncodeData:: ", "invalide prepartition. State "+ ToStringInteger(*sit) + "is not allocated.", 100);
762 mStates[max].c = prepit+1;
763 }
764 }
766 for(; tit != mGen->TransRelEnd(); ++tit) {
767 mStates[smap[tit->X1]].suc[emap[tit->Ev]].push_back(smap[tit->X2]);
768 // distinguish tau-predecessor and non-tau-predecessor
769 // since predecessors are recorded neglecting events, we (shall) check for
770 // duplication before inserting. This is actually optional.
771 if (emap[tit->Ev]!=0){
772
773 std::vector<Idx>::const_iterator preit = mStates[smap[tit->X2]].pre.begin();
774 for (;preit!=mStates[smap[tit->X2]].pre.end();preit++){
775 if (*preit==smap[tit->X1])
776 break;
777 }
778 if (preit==mStates[smap[tit->X2]].pre.end())
779 mStates[smap[tit->X2]].pre.push_back(smap[tit->X1]);
780 }
781 else{
782 std::vector<Idx>::const_iterator preit = mStates[smap[tit->X2]].taupre.begin();
783 for (;preit!=mStates[smap[tit->X2]].taupre.end();preit++){
784 if (*preit==smap[tit->X1])
785 break;
786 }
787 if (preit==mStates[smap[tit->X2]].taupre.end())
788 mStates[smap[tit->X2]].taupre.push_back(smap[tit->X1]);
789 }
790 }
791
792 // initialize affected and changed vector
793 mAffected.resize(mStateSize);
794 std::fill(mAffected.begin(),mAffected.end(),0);
795 mChanged.resize(mStateSize);
796 std::fill(mChanged.begin(),mChanged.end(),1);
797 mChanged[0] = 0;
798}
799
800void AbstractBisimulationCTA::MarkTauStarAffected(std::vector<bool> &rAffected, const Idx& rState){
801 rAffected[rState] = 1;
802 std::vector<Idx>::const_iterator taupredit = mStates[rState].taupre.begin();
803 for(;taupredit!=mStates[rState].taupre.end();taupredit++){
804 if(!rAffected[*taupredit]){
805 MarkTauStarAffected(rAffected, *taupredit);
806 }
807 }
808}
809
811 std::fill(mAffected.begin(),mAffected.end(),0);
812 Idx stateit = 1;
813
814 // figure out all affected
815 for(;stateit<mStateSize;stateit++){
816 if (!mChanged[stateit]) continue;
817 // all non-tau predecessors and their tau* predecessors
818 std::vector<Idx>::iterator predit = mStates[stateit].pre.begin();
819 for(;predit!=mStates[stateit].pre.end();predit++){
821 }
822 // this state and its tau* predecessors
824 }
825 stateit = 1;
826 for(;stateit<mStateSize;stateit++){
827 if(!mAffected[stateit]) continue;
828 std::set<Idx> emptyset;
829 std::fill(mStates[stateit].cafter.begin(),mStates[stateit].cafter.end(),emptyset);
830 // compute new cafter
831 // implicit selfloop (line 18)
832 mStates[stateit].cafter[0].insert(mStates[stateit].c);
833 // visible afters (line 19-21)
834 Idx sucevit = 0; // handle tau successor as well
835 for( ; sucevit<mAlphSize; sucevit++){
836 std::vector<Idx>::const_iterator sucstateit = mStates[stateit].suc[sucevit].begin();
837 for(;sucstateit!=mStates[stateit].suc[sucevit].end();sucstateit++){
838 mStates[stateit].cafter[sucevit].insert(mStates[*sucstateit].c);
839 }
840 }
841 // delayed afters (line 22-26)
842 std::vector<Idx>::const_iterator suctauit = mStates[stateit].suc[0].begin(); // iterate over tau successors
843 for(;suctauit!=mStates[stateit].suc[0].end();suctauit++){
844 // append all cafters of suctauit to stateit
845 Idx sucsucevit = 0;
846 for( ; sucsucevit<mAlphSize; sucsucevit++){
847 mStates[stateit].cafter[sucsucevit].insert(
848 mStates[*suctauit].cafter[sucsucevit].begin(),
849 mStates[*suctauit].cafter[sucsucevit].end());
850 }
851 }
852 }
853}
854
856 std::fill(mAffected.begin(),mAffected.end(),0);
857 Idx stateit = 1;
858
859 // figure out all affected
860 for(;stateit<mStateSize;stateit++){
861 if (!mChanged[stateit]) continue;
862 // buffer a vector of all tau* predecessors, including this state itself
863 std::vector<bool> taustarpred(mStateSize);
864 MarkTauStarAffected(taustarpred,stateit);
865 // for each taustarpred, mark itself and all its non-tau preds and their tau* preds as affected
866 Idx affectedit = 1;
867 for (;affectedit<mStateSize;affectedit++){
868 if (!taustarpred[affectedit]) continue;
869 std::vector<Idx>::iterator predit = mStates[affectedit].pre.begin();
870 for(;predit!=mStates[affectedit].pre.end();predit++){
872 }
873 mAffected[affectedit] = 1; // no need to mark all taupred of affectedit as they are all in taustarpred
874 }
875 }
876
877 stateit = 1;
878 for(;stateit<mStateSize;stateit++){
879 if(!mAffected[stateit]) continue;
880 std::set<Idx> emptyset;
881 std::fill(mStates[stateit].cafter.begin(),mStates[stateit].cafter.end(),emptyset);
882 // implicit selfloop (line 16)
883 mStates[stateit].cafter[0].insert(mStates[stateit].c);
884 // merge (tau-)cafter of tau successors
885 std::vector<Idx>::const_iterator tausucit = mStates[stateit].suc[0].begin();
886 for (;tausucit!=mStates[stateit].suc[0].end();tausucit++){
887 mStates[stateit].cafter[0].insert(mStates[*tausucit].cafter[0].begin(),mStates[*tausucit].cafter[0].end());
888 }
889 }
890
891 stateit = 1;
892 for(;stateit<mStateSize;stateit++){
893 if(!mAffected[stateit]) continue;
894 // merge (tau-)cafter of non-tau successor (line 23-25)
895 Idx sucevit = 1; // skip tau (0)
896 for (;sucevit<mAlphSize;sucevit++){
897 std::vector<Idx>::const_iterator sucstateit = mStates[stateit].suc[sucevit].begin();
898 for(;sucstateit!=mStates[stateit].suc[sucevit].end();sucstateit++){
899 mStates[stateit].cafter[sucevit].insert(mStates[*sucstateit].cafter[0].begin(),mStates[*sucstateit].cafter[0].end());
900 }
901 }
902 // merge (nontau-) cafter of tau sucessros (line 26-30)
903 Idx sucsucevit = 1; // non-tau suc-successors
904 for(;sucsucevit<mAlphSize;sucsucevit++){
905 std::vector<Idx>::const_iterator sucstateit = mStates[stateit].suc[0].begin(); // iterate over tau-succesor states
906 for(;sucstateit!=mStates[stateit].suc[0].end();sucstateit++){
907 mStates[stateit].cafter[sucsucevit].insert(mStates[*sucstateit].cafter[sucsucevit].begin(),mStates[*sucstateit].cafter[sucsucevit].end());
908 }
909 }
910 }
911}
912
913// the internal wrapper
915 BISIM_VERB2("Initializing data")
916 EncodeData();
917 BISIM_VERB2("Doing FirstStepApproximation")
919 while (std::find(mChanged.begin(),mChanged.end(),1)!=mChanged.end()){
920 if (mTaskFlag==1){
921 BISIM_VERB2("Doing ComputeChangedDelayedAfters")
923 }
924 else if (mTaskFlag==2){
925 BISIM_VERB2("Doing ComputeChangedObservedAfters")
927 }
928 BISIM_VERB2("Doing RefineChanged")
930 }
931}
932
933void AbstractBisimulationCTA::ComputePartition(std::list<StateSet>& rResult){
935 GenerateResult(rResult);
936}
937
938
939
940// wrappers
941void ComputeBisimulationCTA(const Generator& rGen, std::list<StateSet>& rResult){
942 std::vector<StateSet> trivial;
943 BisimulationCTA bisim(rGen,trivial);
944 bisim.ComputePartition(rResult);
945}
946
947void ComputeBisimulationCTA(const Generator& rGen, std::list<StateSet>& rResult, const std::vector<StateSet>& rPrePartition){
948 BisimulationCTA bisim(rGen,rPrePartition);
949 bisim.ComputePartition(rResult);
950}
951
952void ComputeDelayedBisimulationCTA(const Generator& rGen, const EventSet &rSilent, std::list<StateSet>& rResult){
953 if (rSilent.Empty()){
954 ComputeBisimulationCTA(rGen,rResult);
955 }
956 else if (rSilent.Size()==1){
957 std::vector<StateSet> trivial;
958 AbstractBisimulationCTA dbisim(rGen,*(rSilent.Begin()),1,trivial);
959 dbisim.ComputePartition(rResult);
960 }
961 else throw Exception("ComputeDelayedBisimulationCTA:","silent alphabet can contain at most one event", 100);
962
963}
964
965void ComputeBisimulationCTA(const Generator& rGen, const EventSet &rSilent, std::list<StateSet>& rResult, const std::vector<StateSet>& rPrePartition){
966 if (rSilent.Empty()){
967 ComputeBisimulationCTA(rGen,rResult);
968 }
969 else if (rSilent.Size()==1){
970 AbstractBisimulationCTA dbisim(rGen,*(rSilent.Begin()),1,rPrePartition);
971 dbisim.ComputePartition(rResult);
972 }
973 else throw Exception("ComputeDelayedBisimulationCTA:","silent alphabet can contain at most one event", 100);
974
975}
976
977void ComputeWeakBisimulationCTA(const Generator& rGen, const EventSet &rSilent, std::list<StateSet>& rResult){
978 if (rSilent.Empty()){
979 ComputeBisimulationCTA(rGen,rResult);
980 }
981 else if (rSilent.Size()==1){
982 std::vector<StateSet> trivial;
983 AbstractBisimulationCTA wbisim(rGen,*(rSilent.Begin()),2,trivial);
984 wbisim.ComputePartition(rResult);
985 }
986 else throw Exception("ComputeWeakBisimulation::","silent alphabet can contain at most one event", 100);
987}
988
989void ComputeWeakBisimulation(const Generator& rGen, const EventSet &rSilent, std::list<StateSet>& rResult, const std::vector<StateSet>& rPrePartition){
990 if (rSilent.Empty()){
991 ComputeBisimulationCTA(rGen,rResult);
992 }
993 else if (rSilent.Size()==1){
994 AbstractBisimulationCTA wbisim(rGen,*(rSilent.Begin()),2,rPrePartition);
995 wbisim.ComputePartition(rResult);
996 }
997 else throw Exception("ComputeWeakBisimulationCTA::","silent alphabet can contain at most one event", 100);
998}
999
1001 const Generator& rGen, const EventSet& rSilent, std::list<StateSet>& rResult, const Idx& rFlag, const std::vector<StateSet>& rPrePartition){
1002 if (rSilent.Empty()){
1003 ComputeBisimulationCTA(rGen,rResult);
1004 }
1005 else if (rSilent.Size()==1){
1006 Generator xg(rGen);
1007 ExtendTransRel(xg,rSilent,rFlag);
1008 InstallSelfloops(xg,rSilent);
1009 BisimulationCTA bisim(xg, rPrePartition);
1010 bisim.ComputePartition(rResult);
1011 }
1012 else throw Exception("ComputeAbstractBisimulationSatCTA::","silent alphabet can contain at most one event", 100);
1013}
1014
1015
1016void ComputeDelayedBisimulationSatCTA(const Generator& rGen, const EventSet& rSilent, std::list<StateSet>& rResult){
1017 std::vector<StateSet> trivial;
1018 ComputeAbstractBisimulationSatCTA(rGen,rSilent,rResult,1,trivial);
1019}
1020
1021void ComputeWeakBisimulationSatCTA(const Generator& rGen, const EventSet& rSilent, std::list<StateSet>& rResult){
1022 std::vector<StateSet> trivial;
1023 ComputeAbstractBisimulationSatCTA(rGen,rSilent,rResult,2,trivial);
1024}
1025
1026void ComputeDelayedBisimulationSatCTA(const Generator& rGen, const EventSet& rSilent, std::list<StateSet>& rResult, const std::vector<StateSet>& rPrePartition){
1027 ComputeAbstractBisimulationSatCTA(rGen,rSilent,rResult,1,rPrePartition);
1028}
1029
1030void ComputeWeakBisimulationSatCTA(const Generator& rGen, const EventSet& rSilent, std::list<StateSet>& rResult, const std::vector<StateSet>& rPrePartition){
1031 ComputeAbstractBisimulationSatCTA(rGen,rSilent,rResult,2,rPrePartition);
1032}
1033
1034} //namespace faudes
#define BISIM_VERB2(msg)
The DelayedBisimulation class Derived from Bisimulation class. We implement the "two-pass change-trac...
void ComputeChangedDelayedAfters(void)
ComputeChangedDelayedAfters see Fig. 10 of cited paper.
virtual void ComputePartition(std::list< StateSet > &rResult)
BisimulationPartition wrapper.
void ComputeChangedObservedAfters(void)
ComputeChangedObservedAfters see Fig. 12 of cited paper.
const Idx mTaskFlag
mTaskFlag flag for task: mTaskFlag == 1: delayed bisimulation mTaskFlag == 2: weak bisimulation
void ComputeAbstractBisimulation()
ComputeDelayedBisimulation perform the overall iteration based on different task flag value,...
void MarkTauStarAffected(std::vector< bool > &rAffected, const Idx &rState)
MarkTauAffected perform a recursive Depth-First-Search to mark all tau* predecessors as affected base...
const Idx mTau
tau persist index of original silent event
AbstractBisimulationCTA(const Generator &rGen, const Idx &rSilent, const Idx &rTaskFlag, const std::vector< StateSet > &rPrePartition)
virtual void EncodeData(void)
EncodeData basic preparation for bisimulation NOTE: this function is virtual since derived classes,...
The Bisimulation class The following class implements a basic normal bisimulation partition algorithm...
virtual ~BisimulationCTA()=default
std::vector< Idx > mEvents
const std::vector< StateSet > mPrePartition
void RefineChanged(void)
RefineChanged refine equivalence classes contained affected nodes in the last iteration (see Fig....
virtual void ComputePartition(std::list< StateSet > &rResult)
BisimulationPartition wrapper.
bool order_evs_c(const Idx &state1, const Idx &state2) const
virtual void EncodeData(void)
EncodeData encode source generator into vector-form data structures to accelerate the iteration.
void FirstStepApproximation(void)
FirstStepApproximation (see Fig. 2 (b) in cited paper) Prepartition states according to their active ...
void GenerateResult(std::list< StateSet > &rResult)
GenerateResult generate partition result w.r.t. original state indices (without trivial classes)
void ComputeBisimulation(void)
ComputeBisimulation perform the overall iteration.
const Generator * mGen
std::vector< bool > mAffected
BisimulationCTA(const Generator &rGen, const std::vector< StateSet > &rPrePartition)
std::vector< bool > mChanged
std::vector< State > mStates
std::vector< Idx > mPartition
void ComputeChangedAfters(void)
ComputeChangedAfters Compute changed afters of each affected state. (see Fig 5. in cited paper) Affec...
bool Exists(const Idx &rIndex) const
bool Insert(const Idx &rIndex)
Iterator Begin(void) const
Iterator End(void) const
TBaseSet< Transition, TransSort::X1EvX2 >::Iterator Iterator
bool Exists(const Transition &t) const
bool Insert(const Transition &rTransition)
The TopoSort class perform a topological sort on states of a given automaton. if s1 is before s2 in t...
std::vector< State > mStates
bool Visit(State &rNode)
const Generator * mGen
TopoSort(const Generator &rGen, const EventSet &rEvs)
std::list< Idx > mResult
bool Sort(std::list< Idx > &result)
StateSet::Iterator StatesBegin(void) const
const TransSet & TransRel(void) const
bool SetTransition(Idx x1, Idx ev, Idx x2)
const EventSet & Alphabet(void) const
EventSet ActiveEventSet(Idx x1) const
TransSet::Iterator TransRelBegin(void) const
EventSet::Iterator AlphabetBegin(void) const
StateSet::Iterator StatesEnd(void) const
TransSet::Iterator TransRelEnd(void) const
void InjectTransRel(const TransSet &rNewtransrel)
EventSet::Iterator AlphabetEnd(void) const
const StateSet & States(void) const
bool Empty(void) const
virtual void Clear(void)
Iterator End(void) const
virtual void InsertSet(const TBaseSet &rOtherSet)
Iterator Begin(void) const
Idx Size(void) const
uint32_t Idx
void ExtendTransRel(Generator &rGen, const EventSet &rSilent, const Idx &rFlag)
ExtendTransRel perform transition saturation w.r.t. silent evs. Two different saturation modes are se...
void InstallSelfloops(Generator &rGen, const EventSet &rEvs)
InstallSelfloops install selfloops on all states of given event set. intended to install silent event...
void ComputeAbstractBisimulationSatCTA(const Generator &rGen, const EventSet &rSilent, std::list< StateSet > &rResult, const Idx &rFlag, const std::vector< StateSet > &rPrePartition)
ComputeAbstractBisimulationSatCTA saturation and change-tracking based partition algorithm for either...
void ComputeWeakBisimulation(const Generator &rGen, const EventSet &rSilent, std::list< StateSet > &rResult, const std::vector< StateSet > &rPrePartition)
bool topoSort(const Generator &rGen, const EventSet &rEvs, std::list< Idx > &result)
topoSort wrapper for topological sortation rEvs is the set of events which will be considered while s...
void ComputeBisimulationCTA(const Generator &rGen, std::list< StateSet > &rResult)
ComputeBisimulationCTA basic bisimulation partition algorithm based on change-tracking algorithm.
void ComputeWeakBisimulationCTA(const Generator &rGen, const EventSet &rSilent, std::list< StateSet > &rResult)
ComputeWeakBisimulationCTA weak bisimulation (aka observation eq) partition based on change-tracking ...
std::string ToStringInteger(Int number)
Definition cfl_utils.cpp:43
void ComputeDelayedBisimulationCTA(const Generator &rGen, const EventSet &rSilent, std::list< StateSet > &rResult)
void ComputeWeakBisimulationSatCTA(const Generator &rGen, const EventSet &rSilent, std::list< StateSet > &rResult)
ComputeWeakBisimulationSatCTA weak bisimulation (aka observation eq) partition based on change-tracki...
void ComputeDelayedBisimulationSatCTA(const Generator &rGen, const EventSet &rSilent, std::list< StateSet > &rResult)
ComputeDelayedBisimulationSatCTA delayed bisimulation partition based on change-tracking algorithm an...
std::vector< std::set< Idx > > cafter
std::vector< std::vector< Idx > > suc
std::vector< Idx > succs

libFAUDES 2.34d --- 2026.03.11 --- c++ api documentaion by doxygen