Spaces:
Running
Running
| /** | |
| * 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; | |