sp_pexecutor.cpp
Go to the documentation of this file.
1 /** @file sp_pexecutor.cpp Executor for multiple synchronized timed generators */
2 
3 /*
4  FAU Discrete Event Systems Library (libFAUDES)
5 
6  Copyright (C) 2007, 2008, 2024 Thomas Moor
7  Copyright (C) 2007 Ruediger Berndt
8  Exclusive copyright is granted to Klaus Schmidt
9 
10 */
11 
12 
13 #include "sp_pexecutor.h"
14 
15 namespace faudes {
16 
17 
18 // std faudes type
19 FAUDES_TYPE_IMPLEMENTATION(ParallelExecutor,ParallelExecutor,Type)
20 
21 // ParallelExecutor(void)
23  FD_DX("ParallelExecutor(" << this << ")::ParallelExecutor()");
24 }
25 
26 // ParallelExecutor(other)
28  FD_DX("ParallelExecutor(" << this << ")::ParallelExecutor(other)");
29  Assign(rOther);
30 }
31 
32 // ParallelExecutor(rFileName)
33 ParallelExecutor::ParallelExecutor(const std::string& rFileName) : Type() {
34  FD_DX("ParallelExecutor(" << this << ")::ParallelExecutor(" << rFileName << ")");
35  Read(rFileName);
36 }
37 
38 // ParallelExecutor(void)
40  FD_DX("ParallelExecutor(" << this << ")::~ParallelExecutor()");
41 }
42 
43 // DoAssign(other)
45  FD_DX("ParallelExecutor(" << this << ")::DoAssign(other)");
46  mExecutors=rOther.mExecutors;
48  Compile();
49 }
50 
51 //UpdateParallelTimedState()
55  for(Iterator xit=Begin(); xit!=End(); xit++){
56  mCurrentParallelTimedState.State.push_back(xit->CurrentTimedState().State);
57  mCurrentParallelTimedState.Clock.push_back(xit->CurrentTimedState().ClockValue);
58  }
59 }
60 
61 // Compile()
63  iterator xit;
64  FD_DX("ParallelExecutor(" << this << ")::Compile(): with #" << Size() << " generators");
65  // reset executors, incl compile
66  for(xit=mExecutors.begin(); xit!=mExecutors.end(); xit++)
67  xit->Reset();
68  // update state
70  // compute alphabet
71  mAlphabet.Clear();
72  mAlphabet.Name("Alphabet");
73  for(xit=mExecutors.begin(); xit!=mExecutors.end(); xit++)
74  mAlphabet.InsertSet(xit->Generator().Alphabet());
75  // reset other members
76  mCurrentTime=0;
77  mCurrentStep=0;
78  mRecentEvent=0;
79  mEValid=false;
80  FD_DX("ParallelExecutor(" << this << ")::Compile(): done");
81 }
82 
83 // Reset()
85  FD_DX("ParallelExecutor(" << this << ")::Reset()");
86  // compile includes reset
87  Compile();
88 }
89 
90 // Insert(rFileName)
91 void ParallelExecutor::Insert(const std::string& rFileName) {
92  FD_DX("ParallelExecutor(" << this << ")::Insert(" << rFileName << ")" );
93  TokenReader tr(rFileName);
94  // create executor and read generator
95  mExecutors.push_back(Executor());
96  mExecutorNames.push_back(rFileName);
97  // read generator
98  mExecutors.back().Read(tr);
99  FD_DX("ParallelExecutor(" << this << ")::Insert(" << rFileName
100  << "): found " << mExecutors.back().Name());
101  // compile (incl check determinism)
102  Compile();
103 }
104 
105 // Insert(rGen)
107  FD_DX("ParallelExecutor(" << this << ")::Insert(rGen): " << rGen.Name() << " at #" << Size());
108  mExecutors.push_back(Executor(rGen));
109  mExecutorNames.push_back("");
110  // compile (incl check determinism)
111  Compile();
112 }
113 
114 // Clear()
116  FD_DX("ParallelExecutor(" << this << ")::Clear()");
117  mExecutors.clear();
118  mExecutorNames.clear();
119  Compile();
120 }
121 
122 // Size()
124  return (Idx) mExecutors.size();
125 }
126 
127 
128 
129 //DoWrite(rTr,rLabel)
130 void ParallelExecutor::DoWrite(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const {
131  (void) pContext;
132  std::string label=rLabel;
133  if(label=="") label = "Executor";
134  rTw.WriteBegin(label);
135  DoWriteGenerators(rTw);
136  rTw.WriteEnd(label);
137 }
138 
139 // DoXWrite()
140 /*
141 void ParallelExecutor::DoXWrite(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const {
142  (void) pContext;
143  // Set up outer tag
144  std::string label=rLabel;
145  std::string ftype=TypeName();
146  if(label=="") label="Executor";
147  Token btag;
148  btag.SetBegin(label);
149  if(Name()!=label && Name()!="") btag.InsAttributeString("name",Name());
150  if(ftype!=label && ftype!="") btag.InsAttributeString("ftype",ftype);
151  FD_DG("vGenerator(" << this << ")::DoXWrite(..): section " << btag.StringValue() << " #" << Size());
152  rTw.Write(btag);
153  DoWriteGenerators(rTw);
154  rTw.WriteEnd(label);
155 }
156 */
157 
158 //DoWriteGenerators(rTr,rLabel)
160  rTw.WriteBegin("Generators");
161  for(Idx i=0; i<Size(); i++) {
162  // write filename
163  if(mExecutorNames.at(i)!="") {
164  rTw.WriteString(mExecutorNames.at(i));
165  }
166  // write generator
167  if(mExecutorNames.at(i)=="") {
168  mExecutors.at(i).Write(rTw);
169  }
170  }
171  rTw.WriteEnd("Generators");
172 }
173 
174 //DoRead(rTr,rLabel)
175 void ParallelExecutor::DoRead(TokenReader& rTr, const std::string& rLabel, const Type* pContext) {
176  (void) pContext;
177  // report
178  FD_DX("ParallelExecutor(" << this << ")::DoRead(tr, " << rLabel << ")");
179  // get my section
180  std::string label=rLabel;
181  if(label=="") label = "Executor";
182  // read section
183  rTr.ReadBegin(label);
184  // read generators
185  DoReadGenerators(rTr);
186  // done
187  rTr.ReadEnd(label);
188  // reset
189  Reset();
190 }
191 
192 
193 //DoReadGenerators(rTr,rLabel)
195  // get relevant directory
196  std::string dirname="";
197  if(rTr.SourceMode()==TokenReader::File)
198  dirname=ExtractDirectory(rTr.FileName());
199  // report
200  FD_DX("ParallelExecutor(" << this << ")::DoReadGenerators(tr): dirname " << dirname);
201  // read section
202  rTr.ReadBegin("Generators");
203  Token token;
204  while(!rTr.Eos("Generators")) {
205  rTr.Peek(token);
206  // is it a file name?
207  if(token.Type()==Token::String) {
208  Insert(PrependPath(dirname,token.StringValue()));
209  rTr.Get(token);
210  continue;
211  }
212  // is it a generator?
213  if(token.Type()==Token::Begin)
214  if(token.StringValue()=="Generator") {
215  TimedGenerator gen;
216  gen.Read(rTr);
217  Insert(gen);
218  continue;
219  }
220  // else report error
221  std::stringstream errstr;
222  errstr << "Invalid token" << rTr.FileLine();
223  throw Exception("ParallelExecutor::DoReadGenerators", errstr.str(), 502);
224  }
225  rTr.ReadEnd("Generators");
226 }
227 
228 // Alphabet()
230  return mAlphabet;
231 }
232 
233 // ComputeEnabled() fake const
235  ParallelExecutor* fakeconst = const_cast<ParallelExecutor*>(this);
236  fakeconst->ComputeEnabledNonConst();
237 }
238 
239 // ComputeEnabled()
241  iterator xit;
242  // compute members (may remove this)
243  FD_DX("ParallelExecutor(" << this << ")::ComputeEnabled(): members");
244  for(xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
245  xit->IsDeadlocked();
246  // compute etime
247  FD_DX("ParallelExecutor(" << this << ")::ComputeEnabled(): time");
249  for(xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
250  mETime.Intersect(xit->EnabledTime());
251  // compute e/d events
252  FD_DX("ParallelExecutor(" << this << ")::ComputeEnabled(): e/d events");
253  mDEvents.Name("DisabledEvents");
254  mDEvents.Clear();
255  for(xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
256  mDEvents.InsertSet(xit->DisabledEvents());
258  mEEvents.Name("EnabledEvents");
260  // compute einterval // TODO: this is conservative
261  FD_DX("ParallelExecutor(" << this << ")::ComputeEnabled(): interval");
263  for(xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
264  mEInterval.Intersect(xit->EnabledInterval());
265  // done
266  mEValid=true;
267 }
268 
269 // EnabledTime(void)
271  if(!mEValid) ComputeEnabled();
272  return mETime;
273 
274 }
275 
276 // EnabledEvents(void)
278  if(!mEValid) ComputeEnabled();
279  return mEEvents;
280 }
281 
282 // DisabledEvents(void)
284  if(!mEValid) ComputeEnabled();
285  return mDEvents;
286 }
287 
288 // EnabledInterval(void)
290  if(!mEValid) ComputeEnabled();
291  return mEInterval;
292 }
293 
294 // EnabledEventInterval(event)
296  TimeInterval retInterval;
297  retInterval.SetPositive();
298  Iterator xit;
299  for(xit=Begin(); xit != End(); xit++){
300  retInterval.Intersect(xit->EnabledEventTime(event));
301  }
302  FD_DX("ParalelExecutor(" << this << ")::EnabledEventTime(" << event << "):"<< retInterval.Str());
303  return retInterval;
304 }
305 
306 // EnabledGuardInterval(event)
308  TimeInterval retInterval;
309  retInterval.SetPositive();
310  Iterator xit;
311  for(xit=Begin(); xit != End(); xit++){
312  retInterval.Intersect(xit->EnabledGuardTime(event));
313  }
314  FD_DX("ParalelExecutor(" << this << ")::EnabledGuardTime(" << event << "):"<< retInterval.Str());
315  return retInterval;
316 }
317 
318 //GetCurrentStateVec
321 }
322 
323 // get current state
326 }
327 
328 // set current state
330  FD_DX("ParalelExecutor(" << this << ")::CurrentParallelState(ptstate): set " << PTSStr(ptstate));
331  // prepare
332  if(ptstate.State.size()!=Size()) return false;
333  if(ptstate.Clock.size()!=Size()) return false;
334  bool res=true;
336  // loop and set for all executors
337  int i=0;
338  for(iterator xit=mExecutors.begin(); xit!=mExecutors.end(); xit++, i++){
339  TimedState tstate;
340  tstate.State=ptstate.State[i];
341  tstate.ClockValue=ptstate.Clock[i];
342  res = res && xit->CurrentTimedState(tstate);
343  }
344  // reject
345  if(!res) {
347  return false;
348  }
349  // reset time (dont call the virtual fncts here)
352  // fix state rep
354  // invalidate
355  mEValid=false;
356  mRecentEvent=0;
357  FD_DX("ParalelExecutor(" << this << ")::CurrentParallelState(ptstate): done");
358  return true;
359 }
360 
361 
362 
363 // ExecuteTime(time)
365  if(mCurrentTime>=Time::Max()) return false;
366  if(time==0) return true;
367  if(!mEValid) ComputeEnabled();
368  if(!mETime.In(time) && !((time==Time::Max()) && mETime.UBinf()) ) {
369  FD_DX("ParalelExecutor(" << this << ")::ExecuteTime(time): execution of " << time
370  << " conflicts with enabled status " );
371  return false;
372  }
373  // progress current time
374  mCurrentTime += time;
375  // fix infinity
376  if(time==Time::Max()) mCurrentTime=Time::Max();
377  // progress members
378  bool success=true;
379  for(iterator xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
380  success &= xit->ExecuteTime(time);
381  // update state
383  // indicate invalid (conservative)
384  mEValid=false;
385  return success;
386 }
387 
388 // ExecuteEvent(event)
390  if(!mEValid) ComputeEnabled();
391  if(!mEEvents.Exists(event)) {
392  FD_DX("ParallelExecutor(" << this << ")::ExecuteEvent(): execution of event " << EStr(event)
393  << " conflicts with enabled status " );
394  return false;
395  }
396  // progress members
397  bool success=true;
398  for(iterator xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
399  if(xit->Generator().ExistsEvent(event))
400  success &= xit->ExecuteEvent(event);
401  if(!success) {
402  // should throw exception
403  FD_DX("ParallelExecutor(" << this << ")::ExecuteEvent(): execution of event " << EStr(event)
404  << " conflicts with internal state data " );
405  return false;
406  }
407  // progress current time
408  mCurrentStep += 1;
409  // update state
411  // record event
412  mRecentEvent=event;
413  // invalidate
414  mEValid=false;
415  return true;
416 }
417 
418 // CurrentTime(time)
420  mCurrentTime=time;
421  for(iterator xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
422  xit->CurrentTime(time);
423  mEValid=false;
424 }
425 
426 // CurrentTime()
428  return mCurrentTime;
429 }
430 
431 // CurrentStep(step)
433  mCurrentStep=step;
434  for(iterator xit=mExecutors.begin(); xit != mExecutors.end(); xit++)
435  xit->CurrentStep(0);
436  mEValid=false;
437 }
438 
439 // CurrentStep()
441  return mCurrentStep;
442 }
443 
444 // IsDeadlocked()
446  if(!mEValid) ComputeEnabled();
447  if(!mEEvents.Empty()) return false;
448  if(!(mETime.UB()<=0)) return false;
449  return true;
450 }
451 
452 // PTSStr(ptstate)
453 std::string ParallelExecutor::PTSStr(const ParallelTimedState& ptstate) const {
454  if(Size()!=ptstate.State.size()) return("(undef)");
455  std::stringstream res;
456  res << "(state ";
457  for(unsigned int i=0; i< Size(); i++){
458  res << mExecutors[i].Generator().SStr(ptstate.State[i]);
459  if(i+1<Size()) res << " x ";
460  }
461  res << ") (clocks (";
462  for(unsigned int i=0; i< Size(); i++){
463  const Executor* execp=&mExecutors[i];
464  ClockSet::Iterator cit;
465  for(cit=execp->Generator().ClocksBegin();cit!=execp->Generator().ClocksEnd();cit++){
466  if(cit!=execp->Generator().ClocksBegin()) res << " ";
467  res << CStr(*cit) << "=";
468  std::map<Idx,Time::Type>::const_iterator cvit=ptstate.Clock[i].find(*cit);
469  if(cvit!=ptstate.Clock[i].end()) res << cvit->second;
470  else res << "undef";
471  }
472  res<<")";
473  if(i+1<Size()) res << " x (";
474  }
475  res << ")";
476  return res.str();
477 }
478 
479 // PSStr(pstate)
480 std::string ParallelExecutor::PSStr(const ParallelState& pstate) const {
481  if(Size()!=pstate.size()) return("(undef)");
482  std::stringstream res;
483  res << "(state ";
484  for(unsigned int i=0; i< Size(); i++){
485  res << mExecutors[i].Generator().SStr(pstate[i]);
486  if(i+1<Size()) res << " x ";
487  }
488  res << ")";
489  return res.str();
490 }
491 
492 // TEStr(tevent)
493 std::string ParallelExecutor::TEStr(const TimedEvent& tevent) const {
494  if(Size()==0) return "(undef)";
495  return Begin()->TEStr(tevent);
496 }
497 
498 // EStr(event)
499 std::string ParallelExecutor::EStr(Idx event) const {
500  if(Size()==0) return "(undef)";
501  return Begin()->EStr(event);
502 }
503 
504 // CStr(clock)
505 std::string ParallelExecutor::CStr(Idx clock) const {
506  if(Size()==0) return "(undef)";
507  return Begin()->CStr(clock);
508 }
509 
510 // CurrentParallelTimedStateStr()
513 }
514 
515 // CurrentParallelTimedStateStr()
518 }
519 
520 //ActiveEventSet
522  EventSet retEventSet=Alphabet();
523  Iterator xit;
524  int i;
525  for(i=0, xit=Begin(); xit!=End(); xit++, i++) {
526  retEventSet.EraseSet( xit->Generator().Alphabet() - xit->Generator().ActiveEventSet(stateVec[i]));
527  }
528  return retEventSet;
529 }
530 
531 //Active(Idx)
534 }
535 
536 //Active(Idx, ParallelState)
537 bool ParallelExecutor::Active(Idx ev, const ParallelState& stateVec) const {
538  Iterator xit;
539  int i;
540  for(xit=Begin(), i=0; xit!=End(); ++xit, i++){
541  if(xit->Generator().Alphabet().Exists(ev))
542  if(xit->Generator().TransRelBegin(stateVec[i],ev)
543  == xit->Generator().TransRelEnd(stateVec[i],ev))
544  return false;
545  }
546  return true;
547 }
548 
549 
550 //DoWrite(rTr,rLabel,pContext)
551 void ParallelExecutor::ParallelTimedState::DoWrite(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const {
552  // allow for two versions only
553  std::string label=rLabel;
554  if(label!="DiscreteState")
555  if(label!="TimedState")
556  label="TimedState";
557  FD_DC("ParallelExecutor::ParallelTimedState::DoWrite(): section " << rLabel << " context " << pContext);
558  // figure context
559  const ParallelExecutor* exe=dynamic_cast<const ParallelExecutor*>(pContext);
560  if(exe) if(exe->Size()!=State.size()) exe=0;
561  if(exe) if(exe->Size()!=Clock.size()) exe=0;
562  // do write
563  if(rLabel=="TimedState") rTw.WriteBegin("TimedState");
564  // discrete state
565  rTw.WriteBegin("DiscreteState");
566  for(unsigned int i=0; i< State.size(); i++) {
567  std::string name="";
568  if(exe) name=exe->At(i).StateName(State.at(i));
569  if(name!="") rTw.WriteString(name);
570  else rTw.WriteInteger(State.at(i));
571  };
572  rTw.WriteEnd("DiscreteState");
573  // figure whether to write clocks
574  bool doclocks=false;
575  if(rLabel=="TimedState")
576  for(unsigned int i=0; i< Clock.size(); i++)
577  if(Clock.at(i).size()>0) doclocks=true;
578  // write clocks
579  if(doclocks) {
580  rTw.WriteBegin("ClockMaps");
581  for(unsigned int i=0; i< Clock.size(); i++) {
582  rTw.WriteBegin("ClockMap");
583  std::map<Idx,Time::Type>::const_iterator cit;
584  for(cit=Clock.at(i).begin(); cit!=Clock.at(i).end(); cit++) {
585  std::string name="";
586  if(exe) name=exe->At(i).Generator().ClockName(cit->first);
587  if(name!="") rTw.WriteString(name);
588  else rTw.WriteInteger(cit->first);
589  rTw.WriteInteger(cit->second);
590  }
591  rTw.WriteEnd("ClockMap");
592  }
593  rTw.WriteEnd("ClockMaps");
594  }
595  // do write end
596  if(rLabel=="TimedState") rTw.WriteEnd("TimedState");
597 }
598 
599 //DoRead(rTr,rLabel,pContext)
600  void ParallelExecutor::ParallelTimedState::DoRead(TokenReader& rTr, const std::string& rLabel, const Type* pContext) {
601  (void) rLabel; (void) pContext; (void) rTr;
602  FD_DC("ParallelExecutor::ParallelTimedState::DoRead()");
603 }
604 
605 
606 } // namespace faudes
607 
608 
#define FD_DC(message)
#define FAUDES_TYPE_IMPLEMENTATION(ftype, ctype, cbase)
Definition: cfl_types.h:951
void Generator(const TimedGenerator &rGen)
Definition: sp_executor.cpp:46
std::string StateName(Idx idx) const
Definition: sp_executor.h:167
bool Exists(const Idx &rIndex) const
virtual void InsertSet(const NameSet &rOtherSet)
void EraseSet(const NameSet &rOtherSet)
virtual void DoWrite(TokenWriter &rTw, const std::string &rLabel="", const Type *pContext=0) const
virtual void DoRead(TokenReader &rTr, const std::string &rLabel="", const Type *pContext=0)
std::string TEStr(const TimedEvent &tevent) const
virtual void DoWrite(TokenWriter &rTw, const std::string &rLabel="", const Type *pContext=0) const
std::vector< Executor >::iterator iterator
Definition: sp_pexecutor.h:578
Time::Type CurrentTime(void) const
std::string PSStr(const ParallelState &pstate) const
const EventSet & EnabledEvents() const
const ParallelState & CurrentParallelState(void) const
void ComputeEnabled(void) const
virtual bool ExecuteTime(Time::Type duration)
virtual void DoRead(TokenReader &rTr, const std::string &rLabel="", const Type *pContext=0)
EventSet ActiveEventSet(const ParallelState &stateVec) const
const ParallelTimedState & CurrentParallelTimedState(void) const
int CurrentStep(void) const
std::string PTSStr(const ParallelTimedState &ptstate) const
void Insert(const std::string &rFileName)
std::vector< Executor >::const_iterator Iterator
Definition: sp_pexecutor.h:180
bool Active(Idx ev, const ParallelState &stateVec) const
ParallelTimedState mCurrentParallelTimedState
Definition: sp_pexecutor.h:605
std::vector< Idx > ParallelState
Definition: sp_pexecutor.h:70
const TimeInterval & EnabledInterval() const
const EventSet & DisabledEvents() const
Iterator Begin(void) const
Definition: sp_pexecutor.h:181
std::string CurrentParallelTimedStateStr(void) const
virtual void DoWriteGenerators(TokenWriter &rTw) const
std::vector< Executor > mExecutors
Definition: sp_pexecutor.h:572
const TimeInterval & EnabledTime() const
TimeInterval EnabledGuardTime(Idx event) const
virtual void Clear(void)
virtual bool ExecuteEvent(Idx event)
void UpdateParallelTimedState(void)
virtual ~ParallelExecutor(void)
std::vector< std::string > mExecutorNames
Definition: sp_pexecutor.h:575
virtual void Reset(void)
std::string EStr(Idx event) const
std::string CurrentParallelStateStr(void) const
virtual void DoReadGenerators(TokenReader &rTr)
Iterator End(void) const
Definition: sp_pexecutor.h:182
const EventSet & Alphabet(void) const
std::string CStr(Idx clock) const
void DoAssign(const ParallelExecutor &rSrc)
TimeInterval EnabledEventTime(Idx event) const
const Executor & At(int i) const
Definition: sp_pexecutor.h:183
bool UBinf(void) const
bool In(Time::Type time) const
std::string Str(void) const
void UB(Time::Type time)
void Intersect(const TimeInterval &rOtherInterval)
static Type Max(void)
std::string FileLine(void) const
bool Eos(const std::string &rLabel)
void ReadEnd(const std::string &rLabel)
void ReadBegin(const std::string &rLabel)
bool Get(Token &token)
bool Peek(Token &token)
std::string FileName(void) const
Mode SourceMode(void) const
void WriteString(const std::string &rString)
void WriteEnd(const std::string &rLabel)
void WriteInteger(Idx index)
void WriteBegin(const std::string &rLabel)
const std::string & StringValue(void) const
Definition: cfl_token.cpp:178
@ Begin
<label> (begin of section)
Definition: cfl_token.h:84
@ String
any string, space separated or quoted, must start with a letter
Definition: cfl_token.h:86
TokenType Type(void) const
Definition: cfl_token.cpp:199
void Read(const std::string &rFileName, const std::string &rLabel="", const Type *pContext=0)
Definition: cfl_types.cpp:262
virtual Type & Assign(const Type &rSrc)
Definition: cfl_types.cpp:77
void Name(const std::string &rName)
bool Empty(void) const
Definition: cfl_baseset.h:1841
virtual void Clear(void)
Definition: cfl_baseset.h:1919
const std::string & Name(void) const
Definition: cfl_baseset.h:1772
uint32_t Idx
std::string ExtractDirectory(const std::string &rFullPath)
Definition: cfl_utils.cpp:272
std::string PrependPath(const std::string &rLeft, const std::string &rRight)
Definition: cfl_utils.cpp:319
#define FD_DX(message)
Definition: sp_executor.h:27
std::map< Idx, Time::Type > ClockValue
Definition: sp_executor.h:103

libFAUDES 2.33c --- 2025.05.15 --- c++ api documentaion by doxygen