wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Priorities.h
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
7
8#pragma once
9
10#include <algorithm>
11
12#include <QHash>
13#include <QSet>
14#include <QStack>
15#include <QVector>
16
28template<typename T>
30 const QVector<T *> &elements,
31 const QHash<T *, QVector<T *>> &successors)
32{
33 QSet<T *> feedbackNodes;
34
35 int indexCounter = 0;
36 QHash<T *, int> nodeIndex;
37 QHash<T *, int> lowlink;
38 QStack<T *> sccStack;
39 QSet<T *> onStack;
40
41 struct Frame {
42 T *node;
43 int successorIdx;
44 };
45
46 for (auto *element : elements) {
47 if (nodeIndex.contains(element)) {
48 continue;
49 }
50
51 QStack<Frame> callStack;
52 nodeIndex[element] = indexCounter;
53 lowlink[element] = indexCounter;
54 ++indexCounter;
55 sccStack.push(element);
56 onStack.insert(element);
57 callStack.push({element, 0});
58
59 while (!callStack.isEmpty()) {
60 auto &frame = callStack.top();
61 T *node = frame.node;
62
63 const auto it = successors.constFind(node);
64 const int succCount = (it != successors.constEnd()) ? static_cast<int>(it->size()) : 0;
65
66 if (frame.successorIdx < succCount) {
67 T *succ = (*it)[frame.successorIdx];
68 ++frame.successorIdx;
69
70 if (!nodeIndex.contains(succ)) {
71 nodeIndex[succ] = indexCounter;
72 lowlink[succ] = indexCounter;
73 ++indexCounter;
74 sccStack.push(succ);
75 onStack.insert(succ);
76 callStack.push({succ, 0});
77 } else if (onStack.contains(succ)) {
78 lowlink[node] = (std::min)(lowlink[node], nodeIndex[succ]);
79 }
80 } else {
81 // All successors processed — check if node is root of an SCC.
82 if (lowlink[node] == nodeIndex[node]) {
83 QVector<T *> scc;
84 T *w;
85 do {
86 w = sccStack.pop();
87 onStack.remove(w);
88 scc.append(w);
89 } while (w != node);
90
91 if (scc.size() > 1) {
92 for (auto *n : std::as_const(scc)) {
93 feedbackNodes.insert(n);
94 }
95 } else if ((it != successors.constEnd()) && it->contains(node)) {
96 feedbackNodes.insert(node); // self-loop
97 }
98 }
99
100 callStack.pop();
101
102 if (!callStack.isEmpty()) {
103 T *parent = callStack.top().node;
104 lowlink[parent] = (std::min)(lowlink[parent], lowlink[node]);
105 }
106 }
107 }
108 }
109
110 return feedbackNodes;
111}
112
114
130template<typename T>
132 const QVector<T *> &elements,
133 const QHash<T *, QVector<T *>> &successors,
134 QHash<T *, int> &outPriorities)
135{
136 QStack<T *> stack;
137 QSet<T *> inStack;
138
139 for (auto *element : elements) {
140 if (outPriorities.contains(element)) {
141 continue;
142 }
143
144 stack.push(element);
145 inStack.insert(element);
146
147 while (!stack.isEmpty()) {
148 auto *current = stack.top();
149
150 // Unreachable: a node only leaves `inStack` together with either
151 // getting a priority assigned (below, immediately followed by a pop
152 // of that same stack entry) or via this very check — and a node is
153 // only ever pushed while absent from both `outPriorities` and
154 // `inStack`, so `stack.top()` can never already hold a priority.
155 // The inner while loop always drains the stack fully before the
156 // outer `for` advances to the next top-level element, so no node
157 // can be re-discovered here after being resolved elsewhere either.
158 if (outPriorities.contains(current)) { // LCOV_EXCL_LINE
159 stack.pop(); // LCOV_EXCL_LINE
160 inStack.remove(current); // LCOV_EXCL_LINE
161 continue; // LCOV_EXCL_LINE
162 }
163
164 bool allProcessed = true;
165 int maxSuccessorPriority = 0;
166 bool hasFeedbackLoop = false;
167
168 const auto it = successors.find(current);
169 if (it != successors.end()) {
170 for (auto *successor : *it) {
171 if (!outPriorities.contains(successor)) {
172 if (!inStack.contains(successor)) {
173 stack.push(successor);
174 inStack.insert(successor);
175 allProcessed = false;
176 } else {
177 hasFeedbackLoop = true;
178 }
179 } else {
180 maxSuccessorPriority = (std::max)(maxSuccessorPriority, outPriorities.value(successor));
181 }
182 }
183 }
184
185 // Assign priority when all successors are computed, OR when a
186 // feedback loop is detected. The early assignment on feedback is
187 // intentional: it gives feedback-loop nodes a *lower* priority
188 // (based only on already-computed successors) so the simulation
189 // processes them *after* their non-cyclic inputs. Because
190 // GraphicElement::updateLogic() reads live predecessor outputs,
191 // processing feedback nodes late ensures they see fresh values
192 // from the current iteration rather than stale ones.
193 if (allProcessed || hasFeedbackLoop) {
194 outPriorities[current] = maxSuccessorPriority + 1;
195 stack.pop();
196 inStack.remove(current);
197 }
198 }
199 }
200}
201
202} // namespace PrioritiesInternal
203
231template<typename T>
233 const QVector<T *> &elements,
234 const QHash<T *, QVector<T *>> &successors,
235 QHash<T *, int> &outPriorities)
236{
237 if (!findFeedbackNodes(elements, successors).isEmpty()) {
238 PrioritiesInternal::legacyCalculatePriorities(elements, successors, outPriorities);
239 return;
240 }
241
242 // Cycle-free: two-phase iterative DFS (expand, then assign in post-order).
243 // On first visit a node pushes its unfinished successors and stays on the
244 // stack; when it surfaces again every successor pushed above it has been
245 // assigned (guaranteed acyclic), so its own priority is final. A node
246 // reached from several parents may sit on the stack more than once — the
247 // duplicate pops in O(1) via the priority check — keeping the whole pass
248 // linear in nodes + edges.
249 QSet<T *> expanded;
250
251 for (auto *element : elements) {
252 if (outPriorities.contains(element)) {
253 continue;
254 }
255
256 QStack<T *> stack;
257 stack.push(element);
258
259 while (!stack.isEmpty()) {
260 auto *current = stack.top();
261
262 if (outPriorities.contains(current)) {
263 stack.pop();
264 continue;
265 }
266
267 const auto it = successors.constFind(current);
268
269 if (!expanded.contains(current)) {
270 expanded.insert(current);
271 if (it != successors.constEnd()) {
272 for (auto *successor : *it) {
273 if (!outPriorities.contains(successor)) {
274 stack.push(successor);
275 }
276 }
277 }
278 continue;
279 }
280
281 int maxSuccessorPriority = 0;
282 if (it != successors.constEnd()) {
283 for (auto *successor : *it) {
284 maxSuccessorPriority = (std::max)(maxSuccessorPriority, outPriorities.value(successor));
285 }
286 }
287
288 outPriorities[current] = maxSuccessorPriority + 1;
289 stack.pop();
290 }
291 }
292}
QSet< T * > findFeedbackNodes(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors)
Finds all nodes that participate in feedback loops (cycles).
Definition Priorities.h:29
void calculatePriorities(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors, QHash< T *, int > &outPriorities)
Priority calculation for directed graphs.
Definition Priorities.h:232
void legacyCalculatePriorities(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors, QHash< T *, int > &outPriorities)
Legacy iterative DFS priority calculation, used for cyclic graphs only.
Definition Priorities.h:131