Skip to content

Commit 4c58d9b

Browse files
committed
Merge branch 'smpls-mg' of https://github.com/Model-Based-Design-Lab/maxpluslib into smpls-mg
2 parents c3ce63a + c8ef033 commit 4c58d9b

File tree

3 files changed

+219
-54
lines changed

3 files changed

+219
-54
lines changed

include/maxplus/base/fsm/fsm.h

Lines changed: 139 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@
4444
#include "maxplus/base/basic_types.h"
4545
#include "maxplus/base/exception/exception.h"
4646
#include "maxplus/base/string/cstring.h"
47+
#include <functional>
4748
#include <list>
4849
#include <map>
4950
#include <memory>
5051
#include <set>
52+
#include <utility>
5153

5254
namespace FSM {
5355

@@ -210,6 +212,7 @@ class FiniteStateMachine {
210212
[[nodiscard]] virtual const SetOfStateRefs &getInitialStates() const = 0;
211213
[[nodiscard]] virtual const SetOfStateRefs &getFinalStates() const = 0;
212214
[[nodiscard]] virtual const SetOfStates &getStates() const = 0;
215+
[[nodiscard]] virtual SetOfStateRefs getStateRefs() const = 0;
213216
[[nodiscard]] virtual const SetOfEdges &getEdges() const = 0;
214217
};
215218

@@ -250,8 +253,9 @@ class DepthFirstSearch {
250253
SetOfEdgeRefs::CIter iter;
251254
};
252255

253-
protected:
254256
using DfsStack = std::list<DFSStackItem>;
257+
258+
protected:
255259
DfsStack dfsStack;
256260

257261
public:
@@ -266,60 +270,147 @@ class DepthFirstSearch {
266270

267271
virtual void onSimpleCycle(DfsStack &stack){};
268272

273+
const FiniteStateMachine &getFSM() { return this->fsm; }
274+
269275
explicit DepthFirstSearch(const FiniteStateMachine &targetFsm) : fsm(targetFsm){};
270276

271277
// Execute the depth first search
272-
void DoDepthFirstSearch(const StateRef &startingState, bool fullDFS = false) {
278+
void DoDepthFirstSearch(const SetOfStateRefs &startingStates, bool fullDFS = false) {
273279
// store visited states
274280
SetOfStateRefs visitedStates;
275281
SetOfStateRefs statesOnStack;
276282

277-
// put initial state on the stack
278-
dfsStack.emplace_back(startingState);
279-
this->onEnterState(startingState);
280-
281-
while (!(dfsStack.empty())) {
282-
DFSStackItem &si = dfsStack.back();
283-
284-
// current item complete?
285-
if (si.atEnd()) {
286-
// pop it from stack
287-
this->onLeaveState(si.getState());
288-
const auto *const s = si.getState();
289-
statesOnStack.erase(s);
290-
if (fullDFS) {
291-
assert(visitedStates.includesState(s));
292-
visitedStates.erase(s);
283+
this->_abort = false;
284+
285+
// for each of the starting states
286+
auto nextStartingState = startingStates.begin();
287+
while (nextStartingState != startingStates.end()) {
288+
289+
// skip states we have already visited
290+
while (nextStartingState != startingStates.end()
291+
&& visitedStates.includesState(*nextStartingState)) {
292+
nextStartingState++;
293+
}
294+
// if we did not find any state anymore
295+
if (nextStartingState == startingStates.end()) {
296+
break;
297+
}
298+
299+
statesOnStack.clear();
300+
StateRef s = *nextStartingState;
301+
dfsStack.emplace_back(s);
302+
statesOnStack.insert(s);
303+
visitedStates.insert(s);
304+
this->onEnterState(s);
305+
306+
while (!this->_abort && !(dfsStack.empty())) {
307+
DFSStackItem &si = dfsStack.back();
308+
if (!visitedStates.includesState(si.getState())) {
309+
this->onEnterState(si.getState());
310+
visitedStates.insert(si.getState());
293311
}
294-
dfsStack.pop_back();
295-
} else {
296-
// goto next edge
297-
const auto *e = *(si.getIter());
298-
si.advance();
299-
StateRef dest = e->getDestination();
300-
bool revisit = statesOnStack.includesState(dest);
301-
if (revisit) {
302-
// cycle found
303-
this->onSimpleCycle(dfsStack);
312+
// current item complete?
313+
if (si.atEnd()) {
314+
// pop it from stack
315+
this->onLeaveState(si.getState());
316+
const auto *const s = si.getState();
317+
statesOnStack.erase(s);
318+
if (fullDFS) {
319+
assert(visitedStates.includesState(s));
320+
visitedStates.erase(s);
321+
}
322+
dfsStack.pop_back();
304323
} else {
305-
// if target state not visited before
306-
dfsStack.emplace_back(dest);
307-
this->onTransition(*e);
308-
this->onEnterState(dest);
309-
visitedStates.insert(dest);
310-
statesOnStack.insert(dest);
324+
// goto next edge
325+
const auto *e = *(si.getIter());
326+
si.advance();
327+
StateRef dest = e->getDestination();
328+
bool revisit = statesOnStack.includesState(dest);
329+
if (revisit) {
330+
// cycle found
331+
this->onSimpleCycle(dfsStack);
332+
} else {
333+
if (!visitedStates.includesState(dest)) {
334+
// if target state not visited before
335+
dfsStack.emplace_back(dest);
336+
this->onTransition(*e);
337+
this->onEnterState(dest);
338+
visitedStates.insert(dest);
339+
statesOnStack.insert(dest);
340+
}
341+
}
311342
}
312343
}
313344
}
314345
}
315346

347+
void DoDepthFirstSearch(const StateRef &startingState, bool fullDFS = false) {
348+
SetOfStateRefs stateSet;
349+
stateSet.insert(startingState);
350+
return this->DoDepthFirstSearch(stateSet, fullDFS);
351+
}
352+
316353
// Execute the depth first search
317354
void DoDepthFirstSearch(bool fullDFS = false) {
318-
this->DoDepthFirstSearch(this->fsm.getInitialState(), fullDFS);
355+
this->DoDepthFirstSearch(this->fsm.getInitialStates(), fullDFS);
319356
}
320357

321-
protected:
358+
void abortDFS() { this->_abort = true; }
359+
360+
private:
322361
const FiniteStateMachine &fsm;
362+
bool _abort{};
363+
};
364+
365+
class DepthFirstSearchLambda : public DepthFirstSearch {
366+
367+
public:
368+
DepthFirstSearchLambda(const DepthFirstSearchLambda &) = delete;
369+
DepthFirstSearchLambda &operator=(const DepthFirstSearchLambda &other) = delete;
370+
DepthFirstSearchLambda(DepthFirstSearch &&) = delete;
371+
DepthFirstSearchLambda &operator=(DepthFirstSearchLambda &&) = delete;
372+
373+
private:
374+
using TOnEnterLambda = std::function<void(StateRef s)>;
375+
TOnEnterLambda _onEnterStateLambda;
376+
using TOnLeaveLambda = std::function<void(StateRef s)>;
377+
TOnLeaveLambda _onLeaveStateLambda;
378+
using TOnTransitionLambda = std::function<void(const Edge &e)>;
379+
TOnTransitionLambda _onTransitionLambda;
380+
using TOnSimpleCycleLambda = std::function<void(const DepthFirstSearch::DfsStack &stack)>;
381+
TOnSimpleCycleLambda _onSimpleCycleLambda;
382+
383+
public:
384+
~DepthFirstSearchLambda() override = default;
385+
386+
void onEnterState(StateRef s) override { this->_onEnterStateLambda(s); };
387+
388+
void onLeaveState(StateRef s) override { this->_onLeaveStateLambda(s); };
389+
390+
void onTransition(const Edge &e) override { this->_onTransitionLambda(e); };
391+
392+
void onSimpleCycle(DepthFirstSearch::DfsStack &stack) override {
393+
this->_onSimpleCycleLambda(stack);
394+
};
395+
396+
explicit DepthFirstSearchLambda(const FiniteStateMachine &targetFsm) :
397+
_onEnterStateLambda([](StateRef) {}),
398+
_onLeaveStateLambda([](StateRef) {}),
399+
_onTransitionLambda([](const Edge &) {}),
400+
_onSimpleCycleLambda([](const DepthFirstSearch::DfsStack &) {}),
401+
DepthFirstSearch(targetFsm){};
402+
403+
void setOnEnterLambda(TOnEnterLambda lambda) { this->_onEnterStateLambda = std::move(lambda); }
404+
405+
void setOnLeaveLambda(TOnLeaveLambda lambda) { this->_onLeaveStateLambda = std::move(lambda); }
406+
407+
void setOnTransitionLambda(TOnTransitionLambda lambda) {
408+
this->_onTransitionLambda = std::move(lambda);
409+
}
410+
411+
void setOnSimpleCycleLambda(TOnSimpleCycleLambda lambda) {
412+
this->_onSimpleCycleLambda = std::move(lambda);
413+
}
323414
};
324415

325416
// Check for cycles
@@ -339,30 +430,15 @@ class DetectCycle : public DepthFirstSearch {
339430
bool checkForCycles() { return this->checkForCycles(nullptr); }
340431

341432
bool checkForCycles(ListOfStateRefs *cycle) {
342-
this->visitedStates.clear();
343433
this->cycle = cycle;
344-
const SetOfStates &states = this->fsm.getStates();
345-
auto nextStartingState = states.begin();
346-
while (nextStartingState != states.end()) {
347-
this->DoDepthFirstSearch((*nextStartingState).second->getReference());
348-
if (this->hasCycle) {
349-
return true;
350-
}
351-
while (nextStartingState != states.end()
352-
&& this->visitedStates.includesState(
353-
(*nextStartingState).second->getReference())) {
354-
nextStartingState++;
355-
}
356-
}
357-
return false;
434+
const SetOfStateRefs states = this->getFSM().getStateRefs();
435+
this->DoDepthFirstSearch(states);
436+
return this->hasCycle;
358437
}
359438

360439
private:
361-
SetOfStateRefs visitedStates;
362440
ListOfStateRefs *cycle = nullptr;
363441

364-
void onEnterState(StateRef s) override { this->visitedStates.insert(s); }
365-
366442
void onSimpleCycle(DfsStack &stack) override {
367443
if (!this->hasCycle) {
368444
if (this->cycle != nullptr) {
@@ -371,6 +447,7 @@ class DetectCycle : public DepthFirstSearch {
371447
}
372448
}
373449
this->hasCycle = true;
450+
this->abortDFS();
374451
}
375452
}
376453
};
@@ -657,6 +734,14 @@ class FiniteStateMachine : public Abstract::FiniteStateMachine {
657734
return this->states;
658735
};
659736

737+
[[nodiscard]] FSM::Abstract::SetOfStateRefs getStateRefs() const override {
738+
FSM::Abstract::SetOfStateRefs result;
739+
for (const auto &s : this->states) {
740+
result.insert(s.second->getReference());
741+
}
742+
return result;
743+
};
744+
660745
[[nodiscard]] const SetOfEdges<StateLabelType, EdgeLabelType> &getEdges() const override {
661746
return this->edges;
662747
};

src/testbench/graph/mpautomatontest.cc

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void MPAutomatonTest::Run() {
2222
testCreateFSM();
2323
testDeterminizeFSM();
2424
testMinimizeFSM();
25+
testDFSFSM();
2526
testDetectCycleFSM();
2627
}
2728

@@ -128,6 +129,84 @@ void MPAutomatonTest::testMinimizeFSM() {
128129
ASSERT_EQUAL(fsaMin->getEdges().size(), 2);
129130
}
130131

132+
void MPAutomatonTest::testDFSFSM() {
133+
134+
std::cout << "Running test: Depth-First Search FSM\n";
135+
136+
{
137+
FSM::Labeled::FiniteStateMachine<int, int> fsa;
138+
139+
const auto *s0 = fsa.addState(0);
140+
const auto *s1 = fsa.addState(1);
141+
const auto *s2 = fsa.addState(2);
142+
const auto *s3 = fsa.addState(3);
143+
const auto *s4 = fsa.addState(4);
144+
145+
fsa.setInitialState(*s0);
146+
fsa.addInitialState(*s2);
147+
148+
fsa.addEdge(*s0, 2, *s1);
149+
fsa.addEdge(*s1, 2, *s1);
150+
fsa.addEdge(*s2, 2, *s1);
151+
fsa.addEdge(*s2, 2, *s3);
152+
fsa.addEdge(*s3, 2, *s2);
153+
fsa.addEdge(*s4, 2, *s3);
154+
155+
bool foundCycle = false;
156+
FSM::Abstract::SetOfStateRefs statesFound;
157+
158+
FSM::Abstract::DepthFirstSearchLambda DFS(fsa);
159+
160+
DFS.setOnEnterLambda([&statesFound](FSM::Abstract::StateRef s) {
161+
ASSERT(!statesFound.includesState(s));
162+
statesFound.insert(s);
163+
});
164+
DFS.setOnSimpleCycleLambda(
165+
[&foundCycle](const FSM::Abstract::DepthFirstSearch::DfsStack &) {
166+
foundCycle = true;
167+
});
168+
169+
// from all initial states
170+
171+
DFS.DoDepthFirstSearch();
172+
ASSERT(foundCycle);
173+
ASSERT(statesFound.size() == 4);
174+
}
175+
176+
{
177+
FSM::Labeled::FiniteStateMachine<int, int> fsa;
178+
179+
const auto *s2 = fsa.addState(5);
180+
const auto *s3 = fsa.addState(4);
181+
const auto *s0 = fsa.addState(3);
182+
const auto *s1 = fsa.addState(5);
183+
184+
fsa.addEdge(*s0, 2, *s1);
185+
fsa.addEdge(*s1, 2, *s2);
186+
fsa.addEdge(*s2, 2, *s3);
187+
188+
bool foundCycle = false;
189+
FSM::Abstract::SetOfStateRefs statesFound;
190+
191+
FSM::Abstract::DepthFirstSearchLambda DFS(fsa);
192+
193+
DFS.setOnEnterLambda([&statesFound](FSM::Abstract::StateRef s) {
194+
ASSERT(!statesFound.includesState(s));
195+
statesFound.insert(s);
196+
});
197+
DFS.setOnSimpleCycleLambda(
198+
[&foundCycle](const FSM::Abstract::DepthFirstSearch::DfsStack &) {
199+
foundCycle = true;
200+
});
201+
202+
// from all initial states
203+
204+
DFS.DoDepthFirstSearch(s2);
205+
ASSERT(!foundCycle);
206+
ASSERT(statesFound.size() == 2);
207+
}
208+
}
209+
131210
void MPAutomatonTest::testDetectCycleFSM() {
132211

133212
std::cout << "Running test: DetectCycles\n";

src/testbench/graph/mpautomatontest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ class MPAutomatonTest : public ::testing::Test {
1919
void testDeterminizeFSM();
2020
void testMinimizeFSM();
2121
void testDetectCycleFSM();
22+
void testDFSFSM();
2223
};

0 commit comments

Comments
 (0)