model-explorer / js /core /graphProcessor.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* GraphProcessor - Bộ Xử Lý Đồ Thị
* Chuyển đổi dữ liệu đồ thị từ ONNXParser sang định dạng Cytoscape.js
* Requirements: 4.2, 4.3, 4.6, 15.5
*/
class GraphProcessor {
/**
* Xử lý mô hình đã phân tích và tạo cấu trúc dữ liệu cho Cytoscape.js
* @param {Object} parsedModel - Mô hình đã phân tích từ ONNXParser
* @returns {{ elements: Array, stats: Object }}
*/
processGraph(parsedModel) {
if (!parsedModel || !parsedModel.graph) {
return {
elements: [],
stats: {
nodeCount: 0,
edgeCount: 0,
initializerCount: 0,
inputCount: 0,
outputCount: 0
}
};
}
const { graph, inputs = [], outputs = [], initializers = [] } = parsedModel;
const { nodes = [], edges = [], initializers: graphInitializers = [] } = graph;
// Tập hợp tên đầu vào và đầu ra để gán class
const inputNames = new Set((inputs || []).map(i => i.name));
const outputNames = new Set((outputs || []).map(o => o.name));
// Xây dựng map: tên tensor → nodeId (để tìm nút đầu vào/đầu ra)
const outputTensorToNodeId = new Map();
for (const node of nodes) {
for (const out of (node.outputs || [])) {
if (out) outputTensorToNodeId.set(out, node.id);
}
}
const elements = [];
// ── Nút đầu vào mô hình (input-node) ──────────────────────────────────
for (const input of inputs) {
elements.push({
data: {
id: `input_${input.name}`,
label: input.name,
name: input.name,
opType: 'Input',
shape: input.shape,
dataType: input.dataType,
description: input.description || '',
nodeType: 'input'
},
classes: 'input-node'
});
}
// ── Nút đầu ra mô hình (output-node) ──────────────────────────────────
for (const output of outputs) {
elements.push({
data: {
id: `output_${output.name}`,
label: output.name,
name: output.name,
opType: 'Output',
shape: output.shape,
dataType: output.dataType,
description: output.description || '',
nodeType: 'output'
},
classes: 'output-node'
});
}
// ── Nút bộ khởi tạo (initializer-node) ────────────────────────────────
const allInitializers = initializers.length > 0 ? initializers : graphInitializers;
for (const init of allInitializers) {
elements.push({
data: {
id: `init_${init.name}`,
label: init.name,
name: init.name,
opType: 'Initializer',
shape: init.shape,
dataType: init.dataType,
size: init.size,
elementCount: init.elementCount,
nodeType: 'initializer'
},
classes: 'initializer-node'
});
}
// ── Kiểm tra đồ thị lớn → clustering ──────────────────────────────────
const threshold = (typeof CONFIG !== 'undefined' && CONFIG.PERFORMANCE)
? CONFIG.PERFORMANCE.LAZY_LOAD_THRESHOLD
: 1000;
if (nodes.length > threshold) {
return this._processLargeGraph(
nodes, edges, inputs, outputs, allInitializers, elements, threshold
);
}
// ── Nút toán tử (op-node) ──────────────────────────────────────────────
for (const node of nodes) {
elements.push({
data: {
id: node.id,
label: node.opType || node.name || node.id,
name: node.name,
opType: node.opType || '',
attributes: node.attributes || {},
inputs: node.inputs || [],
outputs: node.outputs || [],
domain: node.domain || '',
nodeType: 'op'
},
classes: 'op-node'
});
}
// ── Cạnh giữa các nút toán tử ─────────────────────────────────────────
let edgeIndex = 0;
for (const edge of edges) {
elements.push({
data: {
id: `edge_${edgeIndex++}`,
source: edge.source,
target: edge.target,
label: edge.label || ''
},
classes: 'edge'
});
}
// ── Cạnh từ input-node → op-node ──────────────────────────────────────
for (const node of nodes) {
for (const inputTensor of (node.inputs || [])) {
if (inputNames.has(inputTensor)) {
elements.push({
data: {
id: `edge_${edgeIndex++}`,
source: `input_${inputTensor}`,
target: node.id,
label: inputTensor
},
classes: 'edge'
});
}
}
}
// ── Cạnh từ op-node → output-node ─────────────────────────────────────
for (const node of nodes) {
for (const outputTensor of (node.outputs || [])) {
if (outputNames.has(outputTensor)) {
elements.push({
data: {
id: `edge_${edgeIndex++}`,
source: node.id,
target: `output_${outputTensor}`,
label: outputTensor
},
classes: 'edge'
});
}
}
}
const stats = {
nodeCount: nodes.length,
edgeCount: edges.length,
initializerCount: allInitializers.length,
inputCount: inputs.length,
outputCount: outputs.length
};
return { elements, stats };
}
// ─── Private Helpers ────────────────────────────────────────────────────────
/**
* Xử lý đồ thị lớn bằng cách nhóm các nút theo opType (clustering)
* @private
*/
_processLargeGraph(nodes, edges, inputs, outputs, initializers, baseElements, threshold) {
// Nhóm nút theo opType
const clusters = new Map();
for (const node of nodes) {
const opType = node.opType || 'Unknown';
if (!clusters.has(opType)) {
clusters.set(opType, []);
}
clusters.get(opType).push(node);
}
const elements = [...baseElements];
let edgeIndex = 0;
// Tạo nút cluster cho mỗi opType
for (const [opType, clusterNodes] of clusters) {
const clusterId = `cluster_${opType}`;
elements.push({
data: {
id: clusterId,
label: `${opType} (${clusterNodes.length})`,
name: opType,
opType: opType,
nodeCount: clusterNodes.length,
nodeIds: clusterNodes.map(n => n.id),
nodeType: 'cluster',
isCluster: true
},
classes: 'op-node cluster-node'
});
}
// Xây dựng map: nodeId → clusterId
const nodeToCluster = new Map();
for (const [opType, clusterNodes] of clusters) {
const clusterId = `cluster_${opType}`;
for (const node of clusterNodes) {
nodeToCluster.set(node.id, clusterId);
}
}
// Tạo cạnh giữa các cluster (tránh trùng lặp)
const clusterEdgeSet = new Set();
for (const edge of edges) {
const srcCluster = nodeToCluster.get(edge.source);
const tgtCluster = nodeToCluster.get(edge.target);
if (srcCluster && tgtCluster && srcCluster !== tgtCluster) {
const key = `${srcCluster}${tgtCluster}`;
if (!clusterEdgeSet.has(key)) {
clusterEdgeSet.add(key);
elements.push({
data: {
id: `edge_${edgeIndex++}`,
source: srcCluster,
target: tgtCluster,
label: ''
},
classes: 'edge'
});
}
}
}
// Cạnh từ input-node → cluster
const inputNames = new Set((inputs || []).map(i => i.name));
const outputNames = new Set((outputs || []).map(o => o.name));
const inputClusterEdgeSet = new Set();
for (const node of nodes) {
const clusterId = nodeToCluster.get(node.id);
for (const inputTensor of (node.inputs || [])) {
if (inputNames.has(inputTensor)) {
const key = `input_${inputTensor}${clusterId}`;
if (!inputClusterEdgeSet.has(key)) {
inputClusterEdgeSet.add(key);
elements.push({
data: {
id: `edge_${edgeIndex++}`,
source: `input_${inputTensor}`,
target: clusterId,
label: inputTensor
},
classes: 'edge'
});
}
}
}
for (const outputTensor of (node.outputs || [])) {
if (outputNames.has(outputTensor)) {
const key = `${clusterId}→output_${outputTensor}`;
if (!inputClusterEdgeSet.has(key)) {
inputClusterEdgeSet.add(key);
elements.push({
data: {
id: `edge_${edgeIndex++}`,
source: clusterId,
target: `output_${outputTensor}`,
label: outputTensor
},
classes: 'edge'
});
}
}
}
}
const stats = {
nodeCount: nodes.length,
edgeCount: edges.length,
initializerCount: initializers.length,
inputCount: inputs.length,
outputCount: outputs.length,
clusterCount: clusters.size,
isClustered: true
};
return { elements, stats };
}
}
// Export as global for browser usage
window.GraphProcessor = GraphProcessor;