19#ifndef HEDGEHOG_CYCLE_TEST_H_
20#define HEDGEHOG_CYCLE_TEST_H_
36template<tool::HedgehogDynamicGraphForStaticAnalysis GraphType>
37class CycleTest :
public hh_cx::AbstractTest<GraphType> {
40 class SimpleSubGraph {
42 using pointer_const_abstract_node = hh_cx::behavior::AbstractNode
const *;
44 std::vector<pointer_const_abstract_node>
47 std::vector<std::vector<bool>>
52 constexpr SimpleSubGraph() =
default;
57 constexpr SimpleSubGraph(hh_cx::Graph<GraphType>
const *graph,
size_t minNodeId) {
58 for (
size_t nodeIdSrc = minNodeId; nodeIdSrc < graph->numberNodesRegistered(); ++nodeIdSrc) {
59 for (
size_t nodeIdDest = minNodeId; nodeIdDest < graph->numberNodesRegistered(); ++nodeIdDest) {
60 if (graph->isLinked(nodeIdSrc, nodeIdDest)) {
61 this->addEdge(graph->node(nodeIdSrc), graph->node(nodeIdDest));
68 virtual ~SimpleSubGraph() =
default;
72 [[nodiscard]]
constexpr std::vector<pointer_const_abstract_node>
const ®isteredNodes()
const {
73 return registeredNodes_;
78 [[nodiscard]]
constexpr size_t numberNodes()
const {
return registeredNodes_.size(); }
83 [[nodiscard]]
constexpr std::vector<pointer_const_abstract_node>
84 adjacentNodes(pointer_const_abstract_node origin)
const {
85 std::vector<pointer_const_abstract_node> adjacentNodes{};
86 auto senderId = nodeId(origin);
87 for (
size_t receiverId = 0; receiverId < numberNodes(); ++receiverId) {
88 if (isLinked(senderId, receiverId)) {
89 adjacentNodes.push_back(node(receiverId));
97 [[nodiscard]]
constexpr size_t numberNodesRegistered()
const {
return registeredNodes_.size(); }
103 [[nodiscard]]
constexpr bool isLinked(pointer_const_abstract_node sender,
104 pointer_const_abstract_node receiver)
const {
106 idSender = nodeId(sender),
107 idReceiver = nodeId(receiver);
109 if (idSender == numberNodes() || idReceiver == numberNodes()) {
return false; }
110 return adjacencyMatrix_[idSender][idReceiver];
118 [[nodiscard]]
constexpr bool isLinked(
size_t idSender,
size_t idReceiver)
const {
119 if (idSender >= numberNodes() || idReceiver >= numberNodes()) {
120 throw (std::runtime_error(
"The nodes you are trying to test do not exist in the graph."));
122 return adjacencyMatrix_[idSender][idReceiver];
128 [[nodiscard]]
constexpr size_t nodeId(pointer_const_abstract_node node)
const {
129 if (!isNodeRegistered(node)) {
130 throw (std::runtime_error(
"The node you are trying to get does not exist in the graph."));
133 for (
auto registeredNode : registeredNodes_) {
134 if (registeredNode == node) {
return nodeId; }
143 [[nodiscard]]
constexpr pointer_const_abstract_node node(
size_t id)
const {
144 if (
id >= registeredNodes_.size()) {
145 throw (std::runtime_error(
"The node you are requesting does not exist."));
147 return registeredNodes_.at(
id);
154 constexpr void addEdge(pointer_const_abstract_node sender, pointer_const_abstract_node receiver) {
155 registerNode(sender);
156 registerNode(receiver);
157 adjacencyMatrix_.at(nodeId(sender)).at(nodeId(receiver)) =
true;
163 constexpr bool isNodeRegistered(pointer_const_abstract_node node)
const {
164 return std::find(registeredNodes_.cbegin(), registeredNodes_.cend(), node) != registeredNodes_.cend();
169 constexpr void registerNode(pointer_const_abstract_node node) {
170 if (!isNodeRegistered(node)) { registeredNodes_.push_back(node); }
171 for (
auto &line : adjacencyMatrix_) {
172 line.push_back(
false);
174 adjacencyMatrix_.emplace_back(adjacencyMatrix_.size() + 1,
false);
179 tarjanNodeNumbers_{},
184 johnsonBlockedSetArray_{};
186 std::vector<hh_cx::behavior::AbstractNode const *>
190 std::vector<std::vector<hh_cx::behavior::AbstractNode const *>>
191 tarjanConnectedComponent_{},
193 johnsonBlockedMap_{};
199 constexpr explicit CycleTest() : AbstractTest<GraphType>(
"Cycle test") {}
202 constexpr ~CycleTest()
override =
default;
206 constexpr void test(hh_cx::Graph<GraphType>
const *graph)
override {
207 numberOfNodes_ = graph->numberNodesRegistered();
208 findAllCycles(graph);
209 removeCyclesWhereNodesRedefineCanTerminate();
210 if (johnsonCycles_.empty()) {
211 this->graphValid(
true);
213 this->graphValid(
false);
214 this->appendErrorMessage(
"Cycles found, the canTerminate() method needs to be defined for each of these cycles.");
215 for (
const auto &cycle : johnsonCycles_) {
216 this->appendErrorMessage(
"\n\t");
217 auto origin = cycle.at(0);
218 for (
const auto &node : cycle) {
219 this->appendErrorMessage(node->name());
220 this->appendErrorMessage(
" -> ");
222 this->appendErrorMessage(origin->name());
230 constexpr void findAllCycles(hh_cx::Graph<GraphType>
const *graph) {
231 size_t startIndex = 0;
232 while (startIndex < graph->numberNodesRegistered()) {
234 SimpleSubGraph subGraph(graph, startIndex);
235 tarjanAllStrongConnect(subGraph);
237 auto [leastIndex, minSubGraph] = leastIndexStrongConnectedComponents(subGraph);
239 if (leastIndex == numberOfNodes_) {
243 auto node = subGraph.node(leastIndex);
244 findCyclesInSCC(node, node, minSubGraph);
251 constexpr void initTarjan() {
252 tarjanNodeNumbers_ = std::vector<size_t>(numberOfNodes_);
253 tarjanLowLink_ = std::vector<size_t>(numberOfNodes_);
254 tarjanIsOnStack_ = std::vector<bool>(numberOfNodes_,
false);
255 tarjanStack_.clear();
256 tarjanConnectedComponent_.clear();
260 constexpr void initJohnson() {
261 johnsonStack_.clear();
262 johnsonBlockedSetArray_ = std::vector<bool>(numberOfNodes_,
false);
263 for (
size_t nodeId = 0; nodeId < numberOfNodes_; ++nodeId) {
264 johnsonBlockedMap_.emplace_back();
270 constexpr void tarjanAllStrongConnect(SimpleSubGraph
const &subGraph) {
272 for (
size_t nodeId = 0; nodeId < subGraph.numberNodesRegistered(); ++nodeId) {
273 if (tarjanNodeNumbers_[nodeId] == 0) { tarjanStrongConnect(num, nodeId, subGraph); }
281 constexpr void tarjanStrongConnect(
size_t &num,
size_t &nodeId, SimpleSubGraph
const &subGraph) {
282 auto node = subGraph.node(nodeId);
283 tarjanNodeNumbers_[nodeId] = num;
284 tarjanLowLink_[nodeId] = num;
285 tarjanIsOnStack_[nodeId] =
true;
286 tarjanStack_.push_back(node);
289 for (
auto &neighbor : subGraph.adjacentNodes(node)) {
290 size_t neighborId = subGraph.nodeId(neighbor);
291 if (tarjanNodeNumbers_[neighborId] == 0) {
292 tarjanStrongConnect(num, neighborId, subGraph);
293 tarjanLowLink_[nodeId] = std::min(tarjanLowLink_[nodeId], tarjanLowLink_[neighborId]);
294 }
else if (tarjanIsOnStack_[neighborId]) {
295 tarjanLowLink_[nodeId] = std::min(tarjanLowLink_[nodeId], tarjanNodeNumbers_[neighborId]);
299 if (tarjanLowLink_[nodeId] == tarjanNodeNumbers_[nodeId]) {
300 std::vector<hh_cx::behavior::AbstractNode const *> component{};
301 hh_cx::behavior::AbstractNode
const *nodeStack =
nullptr;
303 nodeStack = tarjanStack_.back();
304 tarjanStack_.pop_back();
305 tarjanIsOnStack_[subGraph.nodeId(nodeStack)] =
false;
306 component.push_back(nodeStack);
307 }
while (node != nodeStack);
308 tarjanConnectedComponent_.push_back(component);
315 constexpr std::pair<size_t, SimpleSubGraph> leastIndexStrongConnectedComponents(SimpleSubGraph
const &subGraph) {
316 size_t min = numberOfNodes_;
317 std::vector<hh_cx::behavior::AbstractNode const *> minSCC(numberOfNodes_,
nullptr);
319 for (
auto connectedComponent : tarjanConnectedComponent_) {
320 if (connectedComponent.size() == 1) {
321 auto node = connectedComponent.at(0);
322 if (subGraph.isLinked(node, node)) {
323 johnsonCycles_.push_back({node});
326 for (
auto node : connectedComponent) {
327 if (subGraph.nodeId(node) < min) {
328 min = subGraph.nodeId(node);
329 minSCC = connectedComponent;
335 if (min == numberOfNodes_) {
return {numberOfNodes_, SimpleSubGraph{}}; }
337 SimpleSubGraph minSubGraph{};
339 std::vector<size_t> minIndexes{};
340 for (
auto &sender : subGraph.registeredNodes()) {
341 for (
auto &receiver : subGraph.registeredNodes()) {
342 if (subGraph.isLinked(sender, receiver)) {
343 if (std::find(minSCC.cbegin(), minSCC.cend(), sender) != minSCC.cend()
344 && std::find(minSCC.cbegin(), minSCC.cend(), receiver) != minSCC.cend()) {
345 minSubGraph.addEdge(sender, receiver);
346 minIndexes.push_back(subGraph.nodeId(sender));
347 minIndexes.push_back(subGraph.nodeId(receiver));
352 return {*std::min_element(minIndexes.cbegin(), minIndexes.cend()), minSubGraph};
361 constexpr bool findCyclesInSCC(
362 hh_cx::behavior::AbstractNode
const *startNode,
363 hh_cx::behavior::AbstractNode
const *currentNode,
364 SimpleSubGraph
const &subGraph) {
365 bool cycleFound =
false;
366 johnsonStack_.push_back(currentNode);
367 johnsonBlockedSetArray_.at(subGraph.nodeId(currentNode)) =
true;
369 for (
auto neighbor : subGraph.adjacentNodes(currentNode)) {
370 if (neighbor == startNode) {
371 std::vector<hh_cx::behavior::AbstractNode const *> cycle{};
372 auto tempStack(johnsonStack_);
373 while (!tempStack.empty()) {
374 cycle.push_back(tempStack.back());
375 tempStack.pop_back();
377 std::reverse(cycle.begin(), cycle.end());
379 bool johnsonCycleFound =
false;
380 for (
auto const &johnsonCycle : johnsonCycles_) {
381 if (johnsonCycle.size() == cycle.size()) {
382 bool cycleSame =
true;
383 for (
size_t nodeId = 0; nodeId < johnsonCycle.size(); ++nodeId) {
384 cycleSame &= (johnsonCycle.at(nodeId) == cycle.at(nodeId));
386 johnsonCycleFound |= cycleSame;
390 if (!johnsonCycleFound) {
391 johnsonCycles_.push_back(cycle);
394 }
else if (!johnsonBlockedSetArray_.at(subGraph.nodeId(neighbor))) {
395 cycleFound |= findCyclesInSCC(startNode, neighbor, subGraph);
399 if (cycleFound) { unblock(currentNode, subGraph); }
401 for (
auto neighbors : subGraph.adjacentNodes(currentNode)) {
402 auto nodes = johnsonBlockedMap_.at(subGraph.nodeId(neighbors));
403 if (std::find(nodes.cbegin(), nodes.cend(), currentNode) == nodes.cend()) {
404 nodes.push_back(currentNode);
405 johnsonBlockedMap_.at(subGraph.nodeId(neighbors)) = nodes;
410 johnsonStack_.pop_back();
415 constexpr void removeCyclesWhereNodesRedefineCanTerminate() {
416 std::vector<std::vector<hh_cx::behavior::AbstractNode const *>> temp{};
417 for (
const auto &cycle : johnsonCycles_) {
418 if (!std::any_of(cycle.cbegin(), cycle.cend(),
419 [](
auto const &node) { return node->isCanTerminateOverloaded(); })) {
420 temp.push_back(cycle);
423 johnsonCycles_ = temp;
429 constexpr void unblock(hh_cx::behavior::AbstractNode
const *node, SimpleSubGraph
const &subGraph) {
430 johnsonBlockedSetArray_.at(subGraph.nodeId(node)) =
false;
431 for (
auto nodeBlocked : johnsonBlockedMap_.at(subGraph.nodeId(node))) {
432 if (johnsonBlockedSetArray_.at(subGraph.nodeId(nodeBlocked))) {
433 unblock(nodeBlocked, subGraph);
436 johnsonBlockedMap_.at(subGraph.nodeId(node)) = {};