74 for (
int i = 0; i < m_nodes.size(); ++i) {
75 if (m_nodes[i].
id == nodeId) {
86 for (
int i = 0; i < m_nodes.size(); ++i) {
87 if (m_nodes[i].
id == nodeId) {
99 for (
int i = 0; i < m_nodes.size(); ++i) {
100 if (m_nodes[i].
id == nodeId) {
126 for (
const MnaNode& n : m_nodes) {
127 if (n.id == nodeId) {
139 const QString& dstNodeId,
const QString& dstPortName)
147 bool srcFound =
false;
149 if (p.
name == srcPortName) {
160 for (
int i = 0; i < dstNode.
inputs.size(); ++i) {
161 if (dstNode.
inputs[i].name == dstPortName) {
162 dstNode.
inputs[i].sourceNodeId = srcNodeId;
163 dstNode.
inputs[i].sourcePortName = srcPortName;
178 auto addError = [&](
const QString& msg) {
186 QMap<QString, QSet<QString>> adj;
187 QMap<QString, int> inDegree;
188 for (
const MnaNode& n : m_nodes) {
189 if (!adj.contains(n.id)) {
192 if (!inDegree.contains(n.id)) {
198 for (
const MnaNode& n : m_nodes) {
199 for (
const MnaPort& p : n.inputs) {
202 addError(QStringLiteral(
"Node '%1' input port '%2' references unknown source node '%3'")
215 QQueue<QString> queue;
216 for (
auto it = inDegree.constBegin(); it != inDegree.constEnd(); ++it) {
217 if (it.value() == 0) {
218 queue.enqueue(it.key());
223 while (!queue.isEmpty()) {
224 QString current = queue.dequeue();
226 for (
const QString& neighbor : adj.value(current)) {
227 inDegree[neighbor]--;
228 if (inDegree[neighbor] == 0) {
229 queue.enqueue(neighbor);
234 if (visited != m_nodes.size()) {
235 addError(QStringLiteral(
"Graph contains a cycle"));
240 for (
const MnaNode& n : m_nodes) {
241 if (!registry.
hasOp(n.opType)) {
242 addError(QStringLiteral(
"Node '%1' has unregistered op type '%2'")
243 .arg(n.id, n.opType));
248 QStringList schemaErrors;
249 if (!schema.
validate(n, &schemaErrors)) {
250 for (
const QString& e : schemaErrors) {
251 addError(QStringLiteral(
"Node '%1': %2").arg(n.id, e));
259 for (
const MnaPort& np : n.inputs) {
261 addError(QStringLiteral(
"Node '%1': required input port '%2' is not connected")
262 .arg(n.id, sp.
name));
269 for (
const MnaNode& n : m_nodes) {
270 for (
const MnaPort& inp : n.inputs) {
274 for (
const MnaNode& srcNode : m_nodes) {
277 for (
const MnaPort& srcOut : srcNode.outputs) {
282 addError(QStringLiteral(
"Edge %1.%2 -> %3.%4: data kind mismatch (%5 != %6)")
283 .arg(srcNode.id, srcOut.
name, n.id, inp.
name)
284 .arg(
static_cast<int>(srcOut.
dataKind))
285 .arg(
static_cast<int>(inp.
dataKind)));
303 QMap<QString, QSet<QString>> adj;
304 QMap<QString, int> inDegree;
306 for (
const MnaNode& n : m_nodes) {
311 for (
const MnaNode& n : m_nodes) {
312 for (
const MnaPort& p : n.inputs) {
322 QQueue<QString> queue;
323 for (
auto it = inDegree.constBegin(); it != inDegree.constEnd(); ++it) {
324 if (it.value() == 0) {
325 queue.enqueue(it.key());
330 while (!queue.isEmpty()) {
331 QString current = queue.dequeue();
332 sorted.append(current);
333 for (
const QString& neighbor : adj.value(current)) {
334 inDegree[neighbor]--;
335 if (inDegree[neighbor] == 0) {
336 queue.enqueue(neighbor);
350 QStringList upstream;
356 QSet<QString> visited;
357 QQueue<QString> queue;
366 while (!queue.isEmpty()) {
367 QString current = queue.dequeue();
368 upstream.append(current);
387 QStringList downstream;
393 QMap<QString, QSet<QString>> adj;
394 for (
const MnaNode& n : m_nodes) {
397 for (
const MnaNode& n : m_nodes) {
398 for (
const MnaPort& p : n.inputs) {
405 QSet<QString> visited;
406 QQueue<QString> queue;
407 for (
const QString& neighbor : adj.value(nodeId)) {
408 if (!visited.contains(neighbor)) {
409 visited.insert(neighbor);
410 queue.enqueue(neighbor);
414 while (!queue.isEmpty()) {
415 QString current = queue.dequeue();
416 downstream.append(current);
417 for (
const QString& neighbor : adj.value(current)) {
418 if (!visited.contains(neighbor)) {
419 visited.insert(neighbor);
420 queue.enqueue(neighbor);
433 for (
const MnaNode& n : m_nodes) {
451 for (
const MnaNode& n : m_nodes) {
452 nodesArr.append(n.toJson());
454 json[QStringLiteral(
"nodes")] = nodesArr;
460 arr.append(p.toJson());
462 json[QStringLiteral(
"graph_inputs")] = arr;
469 arr.append(p.toJson());
471 json[QStringLiteral(
"graph_outputs")] = arr;
476 if (!ptJson.isEmpty()) {
477 json[QStringLiteral(
"param_tree")] = ptJson;
489 const QJsonArray nodesArr = json.value(QStringLiteral(
"nodes")).toArray();
490 for (
const QJsonValue& v : nodesArr) {
494 const QJsonArray giArr = json.value(QStringLiteral(
"graph_inputs")).toArray();
495 for (
const QJsonValue& v : giArr) {
499 const QJsonArray goArr = json.value(QStringLiteral(
"graph_outputs")).toArray();
500 for (
const QJsonValue& v : goArr) {
504 if (json.contains(QStringLiteral(
"param_tree"))) {
518 for (
const MnaNode& n : m_nodes) {
519 nodesArr.append(n.toCbor());
521 cbor.insert(QStringLiteral(
"nodes"), nodesArr);
526 arr.append(p.toCbor());
528 cbor.insert(QStringLiteral(
"graph_inputs"), arr);
534 arr.append(p.toCbor());
536 cbor.insert(QStringLiteral(
"graph_outputs"), arr);
548 const QCborArray nodesArr = cbor.value(QStringLiteral(
"nodes")).toArray();
549 for (
const QCborValue& v : nodesArr) {
553 const QCborArray giArr = cbor.value(QStringLiteral(
"graph_inputs")).toArray();
554 for (
const QCborValue& v : giArr) {
558 const QCborArray goArr = cbor.value(QStringLiteral(
"graph_outputs")).toArray();
559 for (
const QCborValue& v : goArr) {
MnaOpRegistry class declaration — singleton catalog of operation schemas.
MnaGraph class declaration — directed acyclic graph of processing nodes.
MNE Analysis Container Format (mna/mnx).
@ Custom
User-defined data kind.
MnaNode & node(const QString &nodeId)
void addNode(const MnaNode &node)
bool validate(QStringList *errors=nullptr) const
QStringList dirtyNodes() const
bool hasNode(const QString &nodeId) const
QStringList downstreamNodes(const QString &nodeId) const
void removeNode(const QString &nodeId)
QList< MnaPort > graphInputs
Named, typed entry points.
QList< MnaPort > graphOutputs
Named, typed exit points.
MnaParamTree paramTree
Hierarchical parameter store with formula-driven bindings.
QList< MnaNode > & nodes()
QJsonObject toJson() const
QStringList topologicalSort() const
static MnaGraph fromJson(const QJsonObject &json)
QStringList upstreamNodes(const QString &nodeId) const
bool connect(const QString &srcNodeId, const QString &srcPortName, const QString &dstNodeId, const QString &dstPortName)
static MnaGraph fromCbor(const QCborMap &cbor)
Graph node representing a processing step.
QList< MnaPort > inputs
Input ports.
static MnaNode fromJson(const QJsonObject &json)
static MnaNode fromCbor(const QCborMap &cbor)
QList< MnaPort > outputs
Output ports.
Operation registry for the MNA graph model.
static MnaOpRegistry & instance()
bool hasOp(const QString &opType) const
MnaOpSchema schema(const QString &opType) const
bool required
Must be connected?
Operation schema for graph validation.
QList< MnaOpSchemaPort > inputPorts
Expected input ports.
bool validate(const MnaNode &node, QStringList *errors=nullptr) const
static MnaParamTree fromJson(const QJsonObject &obj)
QString name
Port name (unique within a node).
QString sourcePortName
Which output port on that node?
MnaDataKind dataKind
Data kind flowing through this port.
static MnaPort fromJson(const QJsonObject &json)
QString sourceNodeId
Which node produces this input? (empty → graph-level input).
static MnaPort fromCbor(const QCborMap &cbor)