File size: 4,607 Bytes
9bd422a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
 * Unit tests for LayerStats.compute()
 * Validates: Requirements 19.1, 19.2, 19.4, 19.5
 */

import { describe, it, expect } from 'vitest';

// Extract the pure compute logic for testing (mirrors LayerStats.compute)
function computeLayerStats(parsedModel) {
  const nodes = (parsedModel && parsedModel.graph && parsedModel.graph.nodes) ? parsedModel.graph.nodes : [];
  const edges = (parsedModel && parsedModel.graph && parsedModel.graph.edges) ? parsedModel.graph.edges : [];
  const initializers = (parsedModel && parsedModel.initializers) ? parsedModel.initializers : [];

  const totalNodes = nodes.length;
  const totalEdges = edges.length;
  const totalInitializers = initializers.length;

  const countMap = {};
  nodes.forEach((node) => {
    const op = node.opType || 'Unknown';
    countMap[op] = (countMap[op] || 0) + 1;
  });

  const opTypeCounts = Object.entries(countMap)
    .map(([opType, count]) => ({
      opType,
      count,
      percentage: totalNodes > 0 ? parseFloat(((count / totalNodes) * 100).toFixed(1)) : 0,
    }))
    .sort((a, b) => b.count - a.count);

  return { opTypeCounts, totalNodes, totalEdges, totalInitializers };
}

describe('LayerStats.compute', () => {
  it('should count operator types correctly', () => {
    const model = {
      graph: {
        nodes: [
          { id: '1', opType: 'Conv' },
          { id: '2', opType: 'ReLU' },
          { id: '3', opType: 'Conv' },
          { id: '4', opType: 'Conv' },
          { id: '5', opType: 'ReLU' },
        ],
        edges: [{ source: '1', target: '2' }],
      },
      initializers: [{ name: 'w1' }, { name: 'w2' }],
    };

    const stats = computeLayerStats(model);

    expect(stats.totalNodes).toBe(5);
    expect(stats.totalEdges).toBe(1);
    expect(stats.totalInitializers).toBe(2);
    expect(stats.opTypeCounts).toHaveLength(2);
    // Sorted descending by count
    expect(stats.opTypeCounts[0]).toEqual({ opType: 'Conv', count: 3, percentage: 60 });
    expect(stats.opTypeCounts[1]).toEqual({ opType: 'ReLU', count: 2, percentage: 40 });
  });

  it('should return sum of counts equal to totalNodes', () => {
    const model = {
      graph: {
        nodes: [
          { id: '1', opType: 'Conv' },
          { id: '2', opType: 'BatchNorm' },
          { id: '3', opType: 'Conv' },
          { id: '4', opType: 'Add' },
        ],
        edges: [],
      },
      initializers: [],
    };

    const stats = computeLayerStats(model);
    const sumCounts = stats.opTypeCounts.reduce((s, e) => s + e.count, 0);
    expect(sumCounts).toBe(stats.totalNodes);
  });

  it('should handle empty model gracefully', () => {
    const stats = computeLayerStats({ graph: { nodes: [], edges: [] }, initializers: [] });
    expect(stats.totalNodes).toBe(0);
    expect(stats.totalEdges).toBe(0);
    expect(stats.totalInitializers).toBe(0);
    expect(stats.opTypeCounts).toHaveLength(0);
  });

  it('should handle null/undefined model', () => {
    const stats = computeLayerStats(null);
    expect(stats.totalNodes).toBe(0);
    expect(stats.opTypeCounts).toHaveLength(0);
  });

  it('should label nodes without opType as Unknown', () => {
    const model = {
      graph: { nodes: [{ id: '1' }, { id: '2', opType: '' }], edges: [] },
      initializers: [],
    };
    const stats = computeLayerStats(model);
    expect(stats.opTypeCounts).toHaveLength(1);
    expect(stats.opTypeCounts[0].opType).toBe('Unknown');
    expect(stats.opTypeCounts[0].count).toBe(2);
  });

  it('should sort by count descending', () => {
    const model = {
      graph: {
        nodes: [
          { id: '1', opType: 'A' },
          { id: '2', opType: 'B' },
          { id: '3', opType: 'B' },
          { id: '4', opType: 'C' },
          { id: '5', opType: 'C' },
          { id: '6', opType: 'C' },
        ],
        edges: [],
      },
      initializers: [],
    };
    const stats = computeLayerStats(model);
    expect(stats.opTypeCounts[0].opType).toBe('C');
    expect(stats.opTypeCounts[1].opType).toBe('B');
    expect(stats.opTypeCounts[2].opType).toBe('A');
  });

  it('should compute percentages that sum close to 100', () => {
    const model = {
      graph: {
        nodes: [
          { id: '1', opType: 'Conv' },
          { id: '2', opType: 'Conv' },
          { id: '3', opType: 'ReLU' },
        ],
        edges: [],
      },
      initializers: [],
    };
    const stats = computeLayerStats(model);
    const sumPct = stats.opTypeCounts.reduce((s, e) => s + e.percentage, 0);
    // Allow small rounding tolerance
    expect(Math.abs(sumPct - 100)).toBeLessThan(1);
  });
});