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 if (outPriorities.contains(current)) {
151 stack.pop();
152 inStack.remove(current);
153 continue;
154 }
155
156 bool allProcessed = true;
157 int maxSuccessorPriority = 0;
158 bool hasFeedbackLoop = false;
159
160 const auto it = successors.find(current);
161 if (it != successors.end()) {
162 for (auto *successor : *it) {
163 if (!outPriorities.contains(successor)) {
164 if (!inStack.contains(successor)) {
165 stack.push(successor);
166 inStack.insert(successor);
167 allProcessed = false;
168 } else {
169 hasFeedbackLoop = true;
170 }
171 } else {
172 maxSuccessorPriority = (std::max)(maxSuccessorPriority, outPriorities.value(successor));
173 }
174 }
175 }
176
177 // Assign priority when all successors are computed, OR when a
178 // feedback loop is detected. The early assignment on feedback is
179 // intentional: it gives feedback-loop nodes a *lower* priority
180 // (based only on already-computed successors) so the simulation
181 // processes them *after* their non-cyclic inputs. Because
182 // LogicElement::updateLogic() reads live predecessor outputs,
183 // processing feedback nodes late ensures they see fresh values
184 // from the current iteration rather than stale ones.
185 if (allProcessed || hasFeedbackLoop) {
186 outPriorities[current] = maxSuccessorPriority + 1;
187 stack.pop();
188 inStack.remove(current);
189 }
190 }
191 }
192}
193
194} // namespace PrioritiesInternal
195
223template<typename T>
225 const QVector<T *> &elements,
226 const QHash<T *, QVector<T *>> &successors,
227 QHash<T *, int> &outPriorities)
228{
229 if (!findFeedbackNodes(elements, successors).isEmpty()) {
230 PrioritiesInternal::legacyCalculatePriorities(elements, successors, outPriorities);
231 return;
232 }
233
234 // Cycle-free: two-phase iterative DFS (expand, then assign in post-order).
235 // On first visit a node pushes its unfinished successors and stays on the
236 // stack; when it surfaces again every successor pushed above it has been
237 // assigned (guaranteed acyclic), so its own priority is final. A node
238 // reached from several parents may sit on the stack more than once — the
239 // duplicate pops in O(1) via the priority check — keeping the whole pass
240 // linear in nodes + edges.
241 QSet<T *> expanded;
242
243 for (auto *element : elements) {
244 if (outPriorities.contains(element)) {
245 continue;
246 }
247
248 QStack<T *> stack;
249 stack.push(element);
250
251 while (!stack.isEmpty()) {
252 auto *current = stack.top();
253
254 if (outPriorities.contains(current)) {
255 stack.pop();
256 continue;
257 }
258
259 const auto it = successors.constFind(current);
260
261 if (!expanded.contains(current)) {
262 expanded.insert(current);
263 if (it != successors.constEnd()) {
264 for (auto *successor : *it) {
265 if (!outPriorities.contains(successor)) {
266 stack.push(successor);
267 }
268 }
269 }
270 continue;
271 }
272
273 int maxSuccessorPriority = 0;
274 if (it != successors.constEnd()) {
275 for (auto *successor : *it) {
276 maxSuccessorPriority = (std::max)(maxSuccessorPriority, outPriorities.value(successor));
277 }
278 }
279
280 outPriorities[current] = maxSuccessorPriority + 1;
281 stack.pop();
282 }
283 }
284}
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:224
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