File size: 5,894 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import { error } from '../../util';

const sqrt2 = Math.sqrt(2);

// Function which colapses 2 (meta) nodes into one
// Updates the remaining edge lists
// Receives as a paramater the edge which causes the collapse
const collapse = function( edgeIndex, nodeMap, remainingEdges ){
  if( remainingEdges.length === 0 ){
    error(`Karger-Stein must be run on a connected (sub)graph`);
  }

  let edgeInfo = remainingEdges[ edgeIndex ];
  let sourceIn = edgeInfo[1];
  let targetIn = edgeInfo[2];
  let partition1 = nodeMap[ sourceIn ];
  let partition2 = nodeMap[ targetIn ];
  let newEdges = remainingEdges; // re-use array

  // Delete all edges between partition1 and partition2
  for( let i = newEdges.length - 1; i >=0; i-- ){
    let edge = newEdges[i];
    let src = edge[1];
    let tgt = edge[2];

    if(
      ( nodeMap[ src ] === partition1 && nodeMap[ tgt ] === partition2 ) ||
      ( nodeMap[ src ] === partition2 && nodeMap[ tgt ] === partition1 )
    ){
      newEdges.splice(i, 1);
    }
  }

  // All edges pointing to partition2 should now point to partition1
  for( let i = 0; i < newEdges.length; i++ ){
    let edge = newEdges[i];

    if( edge[1] === partition2 ){ // Check source
      newEdges[i] = edge.slice(); // copy
      newEdges[i][1] = partition1;
    } else if( edge[2] === partition2 ){ // Check target
      newEdges[i] = edge.slice(); // copy
      newEdges[i][2] = partition1;
    }
  }

  // Move all nodes from partition2 to partition1
  for( let i = 0; i < nodeMap.length; i++ ){
    if( nodeMap[i] === partition2 ){
      nodeMap[i] = partition1;
    }
  }

  return newEdges;
};

// Contracts a graph until we reach a certain number of meta nodes
const contractUntil = function( metaNodeMap, remainingEdges, size, sizeLimit ){
  while( size > sizeLimit ){
    // Choose an edge randomly
    let edgeIndex = Math.floor( (Math.random() * remainingEdges.length) );

    // Collapse graph based on edge
    remainingEdges = collapse( edgeIndex, metaNodeMap, remainingEdges );

    size--;
  }

  return remainingEdges;
};

const elesfn = ({

  // Computes the minimum cut of an undirected graph
  // Returns the correct answer with high probability
  kargerStein: function(){
    let { nodes, edges } = this.byGroup();
    edges.unmergeBy(edge => edge.isLoop());

    let numNodes = nodes.length;
    let numEdges = edges.length;
    let numIter = Math.ceil( Math.pow( Math.log( numNodes ) / Math.LN2, 2 ) );
    let stopSize = Math.floor( numNodes / sqrt2 );

    if( numNodes < 2 ){
      error( 'At least 2 nodes are required for Karger-Stein algorithm' );
      return undefined;
    }

    // Now store edge destination as indexes
    // Format for each edge (edge index, source node index, target node index)
    let edgeIndexes = [];
    for( let i = 0; i < numEdges; i++ ){
      let e = edges[ i ];
      edgeIndexes.push([ i, nodes.indexOf(e.source()), nodes.indexOf(e.target()) ]);
    }

    // We will store the best cut found here
    let minCutSize = Infinity;
    let minCutEdgeIndexes = [];
    let minCutNodeMap = new Array(numNodes);

    // Initial meta node partition
    let metaNodeMap = new Array(numNodes);
    let metaNodeMap2 = new Array(numNodes);

    let copyNodesMap = (from, to) => {
      for( let i = 0; i < numNodes; i++ ){
        to[i] = from[i];
      }
    };

    // Main loop
    for( let iter = 0; iter <= numIter; iter++ ){
      // Reset meta node partition
      for( let i = 0; i < numNodes; i++ ){ metaNodeMap[i] = i; }

      // Contract until stop point (stopSize nodes)
      let edgesState = contractUntil( metaNodeMap, edgeIndexes.slice(), numNodes, stopSize );
      let edgesState2 = edgesState.slice(); // copy

      // Create a copy of the colapsed nodes state
      copyNodesMap(metaNodeMap, metaNodeMap2);

      // Run 2 iterations starting in the stop state
      let res1 = contractUntil( metaNodeMap, edgesState, stopSize, 2 );
      let res2 = contractUntil( metaNodeMap2, edgesState2, stopSize, 2 );

      // Is any of the 2 results the best cut so far?
      if( res1.length <= res2.length && res1.length < minCutSize ){
        minCutSize = res1.length;
        minCutEdgeIndexes = res1;
        copyNodesMap(metaNodeMap, minCutNodeMap);
      } else if( res2.length <= res1.length && res2.length < minCutSize ){
        minCutSize = res2.length;
        minCutEdgeIndexes = res2;
        copyNodesMap(metaNodeMap2, minCutNodeMap);
      }
    } // end of main loop


    // Construct result
    let cut = this.spawn( minCutEdgeIndexes.map(e => edges[e[0]]) );
    let partition1 = this.spawn();
    let partition2 = this.spawn();

    // traverse metaNodeMap for best cut
    let witnessNodePartition = minCutNodeMap[0];
    for( let i = 0; i < minCutNodeMap.length; i++ ){
      let partitionId = minCutNodeMap[i];
      let node = nodes[i];

      if( partitionId === witnessNodePartition ){
        partition1.merge( node );
      } else {
        partition2.merge( node );
      }
    }

    // construct components corresponding to each disjoint subset of nodes
    const constructComponent = (subset) => {
      const component = this.spawn();

      subset.forEach(node => {
        component.merge(node);

        node.connectedEdges().forEach(edge => {
          // ensure edge is within calling collection and edge is not in cut
          if (this.contains(edge) && !cut.contains(edge)) {
            component.merge(edge);
          }
        });
      });

      return component;
    };

    const components = [
      constructComponent(partition1),
      constructComponent(partition2)
    ];

    let ret = {
      cut,
      components,

      // n.b. partitions are included to be compatible with the old api spec
      // (could be removed in a future major version)
      partition1,
      partition2
    };

    return ret;
  }
}); // elesfn


export default elesfn;