Skip to content

Commit 93b68c3

Browse files
committed
cycle detection
1 parent 8c285f2 commit 93b68c3

File tree

4 files changed

+167
-25
lines changed

4 files changed

+167
-25
lines changed

include/maxplus/base/fsm/fsm.h

Lines changed: 104 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#ifndef MAXPLUS_BASE_FSM_FSM_H
4242
#define MAXPLUS_BASE_FSM_FSM_H
4343

44+
#include "base/fsm/fsm.h"
4445
#include "maxplus/base/basic_types.h"
4546
#include "maxplus/base/exception/exception.h"
4647
#include "maxplus/base/string/cstring.h"
@@ -150,6 +151,11 @@ class State : public WithUniqueID {
150151
return this->outgoingEdges;
151152
}
152153

154+
// as a reference
155+
[[nodiscard]] virtual const StateRef getReference() const {
156+
return this;
157+
}
158+
153159
void insertOutgoingEdge(Edge &e) { this->outgoingEdges.insert(&e); }
154160

155161
void removeOutgoingEdge(EdgeRef e) { this->outgoingEdges.erase(e); }
@@ -174,13 +180,20 @@ struct StateRefCompareLessThan {
174180
};
175181

176182
// A set of references to states
177-
class SetOfStateRefs : public std::set<const State *, StateRefCompareLessThan> {
183+
class SetOfStateRefs : public std::set<StateRef, StateRefCompareLessThan> {
178184
public:
179-
using CIter = SetOfEdgeRefs::const_iterator;
185+
using CIter = SetOfStateRefs::const_iterator;
180186
bool includesState(const State *s) { return this->find(s) != this->end(); }
181187
virtual ~SetOfStateRefs() = default;
182188
};
183189

190+
// A list of references to states
191+
class ListOfStateRefs : public std::list<StateRef> {
192+
public:
193+
using CIter = ListOfStateRefs::const_iterator;
194+
virtual ~ListOfStateRefs() = default;
195+
};
196+
184197
// forward declaration of reachable states strategy
185198
class ReachableStates;
186199

@@ -199,8 +212,10 @@ class FiniteStateMachine {
199212
[[nodiscard]] virtual StateRef getInitialState() const = 0;
200213
[[nodiscard]] virtual const SetOfStateRefs &getInitialStates() const = 0;
201214
[[nodiscard]] virtual const SetOfStateRefs &getFinalStates() const = 0;
215+
[[nodiscard]] virtual const SetOfStates &getStates() const = 0;
202216
};
203217

218+
204219
//
205220
// A generic DFS strategy on the target FSM
206221
// overwrite the methods onEnterState, onLeaveState, onTransition and onSimpleCycle with
@@ -223,9 +238,9 @@ class DepthFirstSearch {
223238
};
224239

225240
// access state
226-
inline const StateRef getState() { return this->state; }
241+
StateRef getState() { return this->state; }
227242

228-
inline SetOfEdgeRefs::CIter getIter() { return this->iter; }
243+
SetOfEdgeRefs::CIter getIter() { return this->iter; }
229244

230245
// test if all outgoing edges have been done
231246
bool atEnd() { return this->iter == this->state->getOutgoingEdges().end(); }
@@ -238,7 +253,7 @@ class DepthFirstSearch {
238253
SetOfEdgeRefs::CIter iter;
239254
};
240255

241-
private:
256+
protected:
242257
using DfsStack = std::list<DFSStackItem>;
243258
DfsStack dfsStack;
244259

@@ -257,12 +272,14 @@ class DepthFirstSearch {
257272
explicit DepthFirstSearch(FiniteStateMachine &targetFsm) : fsm(targetFsm){};
258273

259274
// Execute the depth first search
260-
void DoDepthFirstSearch(bool fullDFS = false) {
275+
void DoDepthFirstSearch(const StateRef &startingState, bool fullDFS = false) {
261276
// store visited states
262277
SetOfStateRefs visitedStates;
278+
SetOfStateRefs statesOnStack;
263279

264280
// put initial state on the stack
265-
dfsStack.emplace_back(this->fsm.getInitialState());
281+
dfsStack.emplace_back(startingState);
282+
this->onEnterState(startingState);
266283

267284
while (!(dfsStack.empty())) {
268285
DFSStackItem &si = dfsStack.back();
@@ -271,35 +288,98 @@ class DepthFirstSearch {
271288
if (si.atEnd()) {
272289
// pop it from stack
273290
this->onLeaveState(si.getState());
291+
const auto *const s = si.getState();
292+
statesOnStack.erase(s);
274293
if (fullDFS) {
275-
const auto s = si.getState();
276294
assert(visitedStates.includesState(s));
277295
visitedStates.erase(s);
278296
}
279297
dfsStack.pop_back();
280298
} else {
281299
// goto next edge
282-
auto *e = *(si.getIter());
300+
const auto *e = *(si.getIter());
283301
si.advance();
284-
bool revisit = visitedStates.includesState(e->getDestination());
302+
StateRef dest = e->getDestination();
303+
bool revisit = statesOnStack.includesState(dest);
285304
if (revisit) {
286-
// if target state not visited before
287-
dfsStack.emplace_back(e->getDestination());
288-
this->onTransition(*e);
289-
this->onEnterState(e->getDestination());
290-
visitedStates.insert(e->getDestination());
291-
} else {
292305
// cycle found
293306
this->onSimpleCycle(dfsStack);
307+
} else {
308+
// if target state not visited before
309+
dfsStack.emplace_back(dest);
310+
this->onTransition(*e);
311+
this->onEnterState(dest);
312+
visitedStates.insert(dest);
313+
statesOnStack.insert(dest);
294314
}
295315
}
296316
}
297317
}
298318

299-
private:
319+
// Execute the depth first search
320+
void DoDepthFirstSearch(bool fullDFS = false) {
321+
this->DoDepthFirstSearch(this->fsm.getInitialState(), fullDFS);
322+
}
323+
324+
protected:
300325
FiniteStateMachine &fsm;
301326
};
302327

328+
// Check for cycles
329+
class DetectCycle : public DepthFirstSearch {
330+
public:
331+
bool hasCycle = false;
332+
333+
explicit DetectCycle(FiniteStateMachine &targetFsm) : DepthFirstSearch(targetFsm){};
334+
335+
~DetectCycle() override = default;
336+
337+
DetectCycle(const DetectCycle &) = delete;
338+
DetectCycle &operator=(const DetectCycle &other) = delete;
339+
DetectCycle(DetectCycle &&) = delete;
340+
DetectCycle &operator=(DetectCycle &&) = delete;
341+
342+
bool checkForCycles() {
343+
return this->checkForCycles(nullptr);
344+
}
345+
346+
bool checkForCycles(ListOfStateRefs *cycle) {
347+
this->visitedStates.clear();
348+
this->cycle = cycle;
349+
const SetOfStates &states = this->fsm.getStates();
350+
auto nextStartingState = states.begin();
351+
while (nextStartingState != states.end()) {
352+
this->DoDepthFirstSearch((*nextStartingState).second->getReference());
353+
if (this->hasCycle) {
354+
return true;
355+
}
356+
while (nextStartingState != states.end() && this->visitedStates.includesState((*nextStartingState).second->getReference())) {
357+
nextStartingState++;
358+
}
359+
}
360+
return false;
361+
}
362+
363+
private:
364+
SetOfStateRefs visitedStates;
365+
ListOfStateRefs *cycle = nullptr;
366+
367+
void onEnterState(StateRef s) override {
368+
this->visitedStates.insert(s);
369+
}
370+
371+
void onSimpleCycle(DfsStack &stack) override {
372+
if (!this->hasCycle) {
373+
if (this->cycle != nullptr) {
374+
for (auto si : stack) {
375+
this->cycle->push_back(si.getState());
376+
}
377+
}
378+
this->hasCycle = true;
379+
}
380+
}
381+
};
382+
303383
// Reachable states strategy based on DFS
304384
class ReachableStates : public DepthFirstSearch {
305385
public:
@@ -573,7 +653,7 @@ class FiniteStateMachine : public Abstract::FiniteStateMachine {
573653
return false;
574654
};
575655

576-
[[nodiscard]] const SetOfStates<StateLabelType, EdgeLabelType> &getStates() const {
656+
[[nodiscard]] const SetOfStates<StateLabelType, EdgeLabelType> &getStates() const override {
577657
return this->states;
578658
};
579659
Abstract::SetOfStateRefs getStateRefs() {
@@ -871,7 +951,7 @@ class FiniteStateMachine : public Abstract::FiniteStateMachine {
871951
const auto *s = *(cli->begin());
872952
auto es = s->getOutgoingEdges();
873953
// for every outgoing edge
874-
for (auto *edi : es) {
954+
for (const auto *edi : es) {
875955
auto ed = dynamic_cast<EdgeRef<StateLabelType, EdgeLabelType>>(edi);
876956
result->addEdge(*(newStateMap[cli]),
877957
ed->getLabel(),
@@ -885,6 +965,11 @@ class FiniteStateMachine : public Abstract::FiniteStateMachine {
885965
return result;
886966
}
887967

968+
bool hasDirectedCycle() {
969+
FSM::Abstract::DetectCycle DC(*this);
970+
return DC.checkForCycles(nullptr);
971+
}
972+
888973
private:
889974
void insertOutgoingLabels(const State<StateLabelType, EdgeLabelType> *s,
890975
std::set<EdgeLabelType> &labels) {

src/testbench/graph/mpautomatontest.cc

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

2728
void MPAutomatonTest::testCreateFSM() {
@@ -103,21 +104,21 @@ void MPAutomatonTest::testDeterminizeFSM() {
103104

104105
void MPAutomatonTest::testMinimizeFSM() {
105106

106-
std::cout << "Running test: MinimizeFSM" << std::endl;
107+
std::cout << "Running test: MinimizeFSM\n";
107108

108109
FSM::Labeled::FiniteStateMachine<int, int> fsa;
109110

110-
auto s0 = fsa.addState(3);
111-
auto s1 = fsa.addState(5);
112-
auto s2 = fsa.addState(5);
111+
const auto *s0 = fsa.addState(3);
112+
const auto *s1 = fsa.addState(5);
113+
const auto *s2 = fsa.addState(5);
113114

114115
fsa.addEdge(*s0, 2, *s1);
115116
fsa.addEdge(*s1, 2, *s2);
116117
fsa.addEdge(*s2, 2, *s2);
117118

118119
fsa.setInitialState(*s0);
119120

120-
std::shared_ptr<FSM::Labeled::FiniteStateMachine<int, int>> fsaMin =
121+
const auto fsaMin =
121122
std::dynamic_pointer_cast<FSM::Labeled::FiniteStateMachine<int, int>>(
122123
fsa.minimizeEdgeLabels());
123124

@@ -126,4 +127,48 @@ void MPAutomatonTest::testMinimizeFSM() {
126127

127128
ASSERT_EQUAL(fsaMin->getStates().size(), 2);
128129
ASSERT_EQUAL(fsaMin->getEdges().size(), 2);
129-
}
130+
}
131+
132+
void MPAutomatonTest::testDetectCycleFSM() {
133+
134+
std::cout << "Running test: DetectCycles\n";
135+
136+
{
137+
FSM::Labeled::FiniteStateMachine<int, int> fsa;
138+
139+
const auto *s0 = fsa.addState(3);
140+
const auto *s1 = fsa.addState(5);
141+
const auto *s2 = fsa.addState(5);
142+
const auto *s3 = fsa.addState(4);
143+
144+
fsa.addEdge(*s3, 2, *s1);
145+
fsa.addEdge(*s1, 2, *s2);
146+
fsa.addEdge(*s2, 2, *s0);
147+
fsa.addEdge(*s3, 2, *s2);
148+
fsa.addEdge(*s1, 2, *s0);
149+
150+
FSM::Abstract::DetectCycle DC(fsa);
151+
bool hasCycle = DC.checkForCycles();
152+
153+
ASSERT(! hasCycle);
154+
}
155+
156+
{
157+
FSM::Labeled::FiniteStateMachine<int, int> fsa;
158+
159+
const auto *s2 = fsa.addState(5);
160+
const auto *s3 = fsa.addState(4);
161+
const auto *s0 = fsa.addState(3);
162+
const auto *s1 = fsa.addState(5);
163+
164+
fsa.addEdge(*s0, 2, *s1);
165+
fsa.addEdge(*s1, 2, *s2);
166+
fsa.addEdge(*s2, 2, *s3);
167+
fsa.addEdge(*s3, 2, *s1);
168+
169+
FSM::Abstract::DetectCycle DC(fsa);
170+
bool hasCycle = DC.checkForCycles();
171+
172+
ASSERT(hasCycle);
173+
}
174+
}

src/testbench/graph/mpautomatontest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ class MPAutomatonTest : public ::testing::Test {
1818
void testCreateFSM();
1919
void testDeterminizeFSM();
2020
void testMinimizeFSM();
21+
void testDetectCycleFSM();
2122
};

src/testbench/graph/testing.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ namespace testing {
1212
} \
1313
}
1414

15+
#define ASSERT(x) \
16+
{ \
17+
if (!(x)) { \
18+
throw std::runtime_error( \
19+
std::string("Assert condition violated.") + std::string("\nIn:") \
20+
+ std::string(__FILE__) + std::string(":") + std::to_string(__LINE__) \
21+
+ std::string(" in ") + std::string(__FUNCTION__) + std::string(": ") \
22+
+ std::to_string((x))); \
23+
} \
24+
}
25+
1526
#define ASSERT_EQUAL(x, y) \
1627
{ \
1728
if ((x) != (y)) { \

0 commit comments

Comments
 (0)