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