Hedgehog  3.1.0
A library to generate hybrid pipeline workflow systems
Loading...
Searching...
No Matches
cycle_test.h
Go to the documentation of this file.
1// NIST-developed software is provided by NIST as a public service. You may use, copy and distribute copies of the
2// software in any medium, provided that you keep intact this entire notice. You may improve, modify and create
3// derivative works of the software or any portion of the software, and you may copy and distribute such modifications
4// or works. Modified works should carry a notice stating that you changed the software and should note the date and
5// nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the
6// source of the software. NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND,
7// EXPRESS, IMPLIED, IN FACT OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
8// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR
9// WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE
10// CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS
11// THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE. You
12// are solely responsible for determining the appropriateness of using and distributing the software and you assume
13// all risks associated with its use, including but not limited to the risks and costs of program errors, compliance
14// with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of
15// operation. This software is not intended to be used in any situation where a failure could cause risk of injury or
16// damage to property. The software developed by NIST employees is not subject to copyright protection within the
17// United States.
18
19#ifndef HEDGEHOG_CYCLE_TEST_H_
20#define HEDGEHOG_CYCLE_TEST_H_
21
22#ifdef HH_ENABLE_HH_CX
23#include "abstract_test.h"
24
26namespace hh_cx {
27
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 *;
43
44 std::vector<pointer_const_abstract_node>
45 registeredNodes_{};
46
47 std::vector<std::vector<bool>>
48 adjacencyMatrix_{};
49
50 public:
52 constexpr SimpleSubGraph() = default;
53
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));
62 }
63 }
64 }
65 }
66
68 virtual ~SimpleSubGraph() = default;
69
72 [[nodiscard]] constexpr std::vector<pointer_const_abstract_node> const &registeredNodes() const {
73 return registeredNodes_;
74 }
75
78 [[nodiscard]] constexpr size_t numberNodes() const { return registeredNodes_.size(); }
79
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));
90 }
91 }
92 return adjacentNodes;
93 }
94
97 [[nodiscard]] constexpr size_t numberNodesRegistered() const { return registeredNodes_.size(); }
98
103 [[nodiscard]] constexpr bool isLinked(pointer_const_abstract_node sender,
104 pointer_const_abstract_node receiver) const {
105 auto
106 idSender = nodeId(sender),
107 idReceiver = nodeId(receiver);
108
109 if (idSender == numberNodes() || idReceiver == numberNodes()) { return false; }
110 return adjacencyMatrix_[idSender][idReceiver];
111 }
112
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."));
121 }
122 return adjacencyMatrix_[idSender][idReceiver];
123 }
124
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."));
131 }
132 size_t nodeId = 0;
133 for (auto registeredNode : registeredNodes_) {
134 if (registeredNode == node) { return nodeId; }
135 else { ++nodeId; }
136 }
137 return nodeId;
138 }
139
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."));
146 } else {
147 return registeredNodes_.at(id);
148 }
149 }
150
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;
158 }
159 private:
163 constexpr bool isNodeRegistered(pointer_const_abstract_node node) const {
164 return std::find(registeredNodes_.cbegin(), registeredNodes_.cend(), node) != registeredNodes_.cend();
165 }
166
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);
173 }
174 adjacencyMatrix_.emplace_back(adjacencyMatrix_.size() + 1, false);
175 }
176 };
177
178 std::vector<size_t>
179 tarjanNodeNumbers_{},
180 tarjanLowLink_{};
181
182 std::vector<bool>
183 tarjanIsOnStack_{},
184 johnsonBlockedSetArray_{};
185
186 std::vector<hh_cx::behavior::AbstractNode const *>
187 tarjanStack_{},
188 johnsonStack_{};
189
190 std::vector<std::vector<hh_cx::behavior::AbstractNode const *>>
191 tarjanConnectedComponent_{},
192 johnsonCycles_{},
193 johnsonBlockedMap_{};
194
195 size_t
196 numberOfNodes_{};
197 public:
199 constexpr explicit CycleTest() : AbstractTest<GraphType>("Cycle test") {}
200
202 constexpr ~CycleTest() override = default;
203
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);
212 } else {
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(" -> ");
221 }
222 this->appendErrorMessage(origin->name());
223 }
224 }
225 }
226
227 private:
230 constexpr void findAllCycles(hh_cx::Graph<GraphType> const *graph) {
231 size_t startIndex = 0;
232 while (startIndex < graph->numberNodesRegistered()) {
233 initTarjan();
234 SimpleSubGraph subGraph(graph, startIndex);
235 tarjanAllStrongConnect(subGraph);
236
237 auto [leastIndex, minSubGraph] = leastIndexStrongConnectedComponents(subGraph);
238
239 if (leastIndex == numberOfNodes_) {
240 break;
241 } else {
242 initJohnson();
243 auto node = subGraph.node(leastIndex);
244 findCyclesInSCC(node, node, minSubGraph);
245 ++startIndex;
246 }
247 }
248 }
249
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();
257 }
258
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();
265 }
266 }
267
270 constexpr void tarjanAllStrongConnect(SimpleSubGraph const &subGraph) {
271 size_t num = 1;
272 for (size_t nodeId = 0; nodeId < subGraph.numberNodesRegistered(); ++nodeId) {
273 if (tarjanNodeNumbers_[nodeId] == 0) { tarjanStrongConnect(num, nodeId, subGraph); }
274 }
275 }
276
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);
287 num += 1;
288
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]);
296 }
297 }
298
299 if (tarjanLowLink_[nodeId] == tarjanNodeNumbers_[nodeId]) {
300 std::vector<hh_cx::behavior::AbstractNode const *> component{};
301 hh_cx::behavior::AbstractNode const *nodeStack = nullptr;
302 do {
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);
309 }
310 }
311
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);
318
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});
324 }
325 } else {
326 for (auto node : connectedComponent) {
327 if (subGraph.nodeId(node) < min) {
328 min = subGraph.nodeId(node);
329 minSCC = connectedComponent;
330 }
331 }
332 }
333 }
334
335 if (min == numberOfNodes_) { return {numberOfNodes_, SimpleSubGraph{}}; }
336 else {
337 SimpleSubGraph minSubGraph{};
338
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));
348 }
349 }
350 }
351 }
352 return {*std::min_element(minIndexes.cbegin(), minIndexes.cend()), minSubGraph};
353 }
354 }
355
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;
368
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();
376 }
377 std::reverse(cycle.begin(), cycle.end());
378
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));
385 }
386 johnsonCycleFound |= cycleSame;
387 }
388 }
389
390 if (!johnsonCycleFound) {
391 johnsonCycles_.push_back(cycle);
392 }
393 cycleFound = true;
394 } else if (!johnsonBlockedSetArray_.at(subGraph.nodeId(neighbor))) {
395 cycleFound |= findCyclesInSCC(startNode, neighbor, subGraph);
396 }
397 }
398
399 if (cycleFound) { unblock(currentNode, subGraph); }
400 else {
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;
406 }
407 }
408 }
409
410 johnsonStack_.pop_back();
411 return cycleFound;
412 }
413
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);
421 }
422 }
423 johnsonCycles_ = temp;
424 }
425
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);
434 }
435 }
436 johnsonBlockedMap_.at(subGraph.nodeId(node)) = {};
437 }
438
439};
440
441}
442#endif //HH_ENABLE_HH_CX
443#endif //HEDGEHOG_CYCLE_TEST_H_