Skip to content

Commit 58e34d4

Browse files
Refactor KSP heuristic to make it less dense in text
Co-authored-by: Lasse Westh-Nielsen <lassewesth@gmail.com>
1 parent 8bcb76d commit 58e34d4

File tree

1 file changed

+60
-34
lines changed

1 file changed

+60
-34
lines changed

alpha/alpha-algo/src/main/java/org/neo4j/gds/impl/spanningtree/KSpanningTree.java

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private SpanningTree growApproach(SpanningTree spanningTree) {
117117
// is actually better
118118
if (spanningTree.effectiveNodeCount() < k)
119119
return spanningTree;
120-
120+
121121
HugeLongArray outDegree = HugeLongArray.newArray(graph.nodeCount());
122122

123123
HugeLongArray parent = HugeLongArray.newArray(graph.nodeCount());
@@ -152,10 +152,7 @@ private SpanningTree growApproach(SpanningTree spanningTree) {
152152
nodesInTree++;
153153
nodeAdded = true;
154154
} else {
155-
while (!exterior.get(toTrim.top())) { //not valid frontier nodes anymore, just ignore
156-
toTrim.pop(); //as we said, pq does not have a direct remove method
157-
}
158-
var nodeToTrim = toTrim.top(); //a leaf node with worst cost
155+
var nodeToTrim = findNextValidLeaf(toTrim, exterior); //a leaf node with currently theworst cost
159156
if (parent.get(node) == nodeToTrim) {
160157
//we cannot add it, if we're supposed to remove its parent
161158
//TODO: should be totally feasible to consider the 2nd worst then.
@@ -197,13 +194,7 @@ private SpanningTree growApproach(SpanningTree spanningTree) {
197194
}
198195
}
199196
if (affectedNode != -1) { //if a node has been converted to a leaf
200-
if (!toTrim.containsElement(affectedNode)) {
201-
toTrim.add(affectedNode, affectedCost); //add it to pq
202-
} else {
203-
//it is still in the queue, but it is not a leaf anymore, so it's value is obsolete
204-
toTrim.set(affectedNode, affectedCost);
205-
}
206-
exterior.set(affectedNode); //and mark it in the exterior
197+
updateExterior(affectedNode, affectedCost, toTrim, exterior);
207198
}
208199
} else {
209200
//the root is removed, long live the new root!
@@ -212,14 +203,9 @@ private SpanningTree growApproach(SpanningTree spanningTree) {
212203
var newRoot = rootNodeAdjacent.nextSetBit(0);
213204
rootNodeAdjacent.clear(); //empty everything
214205
//find the children of the new root (this can happen once per node)
215-
graph.forEachRelationship(newRoot, (s, t) -> {
216-
//relevant are only those nodes which are currently
217-
//in the k-tree
218-
if (parent.get(t) == s && included.get(t)) {
219-
rootNodeAdjacent.set(t);
220-
}
221-
return true;
222-
});
206+
207+
fillChildren(newRoot, rootNodeAdjacent, parent, included);
208+
223209
root = newRoot;
224210
//set it as root
225211
clearNode(root, parent, costToParent);
@@ -247,32 +233,26 @@ private SpanningTree growApproach(SpanningTree spanningTree) {
247233
//then the node (being a leaf) is added to the trimming priority queu
248234
toTrim.add(node, associatedCost);
249235
exterior.set(node); //and the exterior
236+
relaxNode(node, priorityQueue, parent, spanningTree);
250237

251-
graph.forEachRelationship(node, (s, t) -> {
252-
if (parent.get(t) == s) {
253-
//TODO: work's only on mst edges for now (should be doable to re-find an k-MST from whole graph)
254-
if (!priorityQueue.containsElement(t)) {
255-
priorityQueue.add(t, spanningTree.costToParent(t));
256-
}
257-
258-
}
259-
return true;
260-
});
261238
} else {
262239
clearNode(node, parent, costToParent);
263-
264240
}
265241
}
266242
//post-processing step: anything not touched is reset to -1
243+
pruneUntouchedNodes(parent, costToParent, included);
244+
progressTracker.endSubTask();
245+
return new SpanningTree(root, graph.nodeCount(), k, parent, costToParent, totalCost);
246+
247+
}
248+
249+
private void pruneUntouchedNodes(HugeLongArray parent, HugeDoubleArray costToParent, BitSet included) {
267250
graph.forEachNode(nodeId -> {
268251
if (!included.get(nodeId)) {
269252
clearNode(nodeId, parent, costToParent);
270253
}
271254
return true;
272255
});
273-
progressTracker.endSubTask();
274-
return new SpanningTree(root, graph.nodeCount(), k, parent, costToParent, totalCost);
275-
276256
}
277257

278258
private void clearNode(long node, HugeLongArray parent, HugeDoubleArray costToParent) {
@@ -288,6 +268,52 @@ private boolean moveMakesSense(double cost1, double cost2, DoubleUnaryOperator m
288268
}
289269
}
290270

271+
private void updateExterior(long affectedNode, double affectedCost, HugeLongPriorityQueue toTrim, BitSet exterior) {
272+
if (!toTrim.containsElement(affectedNode)) {
273+
toTrim.add(affectedNode, affectedCost); //add it to pq
274+
} else {
275+
//it is still in the queue, but it is not a leaf anymore, so it's value is obsolete
276+
toTrim.set(affectedNode, affectedCost);
277+
}
278+
exterior.set(affectedNode); //and mark it in the exterior
279+
}
280+
281+
private long findNextValidLeaf(HugeLongPriorityQueue toTrim, BitSet exterior) {
282+
while (!exterior.get(toTrim.top())) { //not valid frontier nodes anymore, just ignore
283+
toTrim.pop(); //as we said, pq does not have a direct remove method
284+
}
285+
return toTrim.top();
286+
}
287+
288+
private void fillChildren(long newRoot, BitSet rootNodeAdjacent, HugeLongArray parent, BitSet included) {
289+
graph.forEachRelationship(newRoot, (s, t) -> {
290+
//relevant are only those nodes which are currently
291+
//in the k-tree
292+
if (parent.get(t) == s && included.get(t)) {
293+
rootNodeAdjacent.set(t);
294+
}
295+
return true;
296+
});
297+
}
298+
299+
private void relaxNode(
300+
long node,
301+
HugeLongPriorityQueue priorityQueue,
302+
HugeLongArray parent,
303+
SpanningTree spanningTree
304+
) {
305+
graph.forEachRelationship(node, (s, t) -> {
306+
if (parent.get(t) == s) {
307+
//TODO: work's only on mst edges for now (should be doable to re-find an k-MST from whole graph)
308+
if (!priorityQueue.containsElement(t)) {
309+
priorityQueue.add(t, spanningTree.costToParent(t));
310+
}
311+
312+
}
313+
return true;
314+
});
315+
}
316+
291317
}
292318

293319

0 commit comments

Comments
 (0)