Skip to content

Commit 8bcb76d

Browse files
Drop first heuristic as being theoretically useful
1 parent 6334550 commit 8bcb76d

File tree

3 files changed

+19
-170
lines changed

3 files changed

+19
-170
lines changed

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

Lines changed: 4 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
package org.neo4j.gds.impl.spanningtree;
2121

2222
import com.carrotsearch.hppc.BitSet;
23-
import org.apache.commons.lang3.mutable.MutableDouble;
24-
import org.apache.commons.lang3.mutable.MutableLong;
2523
import org.jetbrains.annotations.NotNull;
2624
import org.neo4j.gds.Algorithm;
2725
import org.neo4j.gds.api.Graph;
@@ -78,7 +76,7 @@ public SpanningTree compute() {
7876
prim.setTerminationFlag(getTerminationFlag());
7977
SpanningTree spanningTree = prim.compute();
8078

81-
var outputTree = combineApproach(spanningTree);
79+
var outputTree = growApproach(spanningTree);
8280
progressTracker.endSubTask();
8381
return outputTree;
8482
}
@@ -111,97 +109,15 @@ private double init(HugeLongArray parent, HugeDoubleArray costToParent, Spanning
111109
return spanningTree.totalWeight();
112110
}
113111

114-
private SpanningTree cutLeafApproach(SpanningTree spanningTree) {
115-
//this approach cuts a leaf at each step (remaining graph is always corrected)
116-
//so we can just cut the most expensive leaf at each step
117-
var priorityQueue = createPriorityQueue(graph.nodeCount(), true);
118-
HugeLongArray degree = HugeLongArray.newArray(graph.nodeCount());
119-
long root = startNodeId;
120-
double rootCost = -1.0;
121-
long rootChild = -1;
122-
123-
HugeLongArray parent = HugeLongArray.newArray(graph.nodeCount());
124-
HugeDoubleArray costToParent = HugeDoubleArray.newArray(graph.nodeCount());
125-
126-
double totalCost = init(parent, costToParent, spanningTree);
127-
long numberOfDeletions = spanningTree.effectiveNodeCount() - k;
128-
129-
progressTracker.beginSubTask(numberOfDeletions);
130-
//calculate degree of each node in MST
131-
for (long nodeId = 0; nodeId < graph.nodeCount(); ++nodeId) {
132-
var nodeParent = parent.get(nodeId);
133-
if (nodeParent != -1) {
134-
degree.set(nodeParent, degree.get(nodeParent) + 1);
135-
degree.set(nodeId, degree.get(nodeId) + 1);
136-
137-
if (nodeParent == root) { //root nodes needs special care because parent is -1
138-
rootChild = nodeId;
139-
rootCost = costToParent.get(nodeId);
140-
}
141-
}
142-
}
143-
//add all leafs in priority queue
144-
for (long nodeId = 0; nodeId < graph.nodeCount(); ++nodeId) {
145-
if (degree.get(nodeId) == 1) {
146-
double relevantCost = (nodeId == root) ?
147-
rootCost :
148-
costToParent.get(nodeId);
149-
priorityQueue.add(nodeId, relevantCost);
150-
}
151-
}
152-
153-
for (long i = 0; i < numberOfDeletions; ++i) {
154-
var nextNode = priorityQueue.pop();
155-
long affectedNode;
156-
157-
if (nextNode == root) { //the affecte node is its single child
158-
affectedNode = rootChild;
159-
totalCost -= rootCost;
160-
clearNode(rootChild, parent, costToParent);
161-
clearNode(root, parent, costToParent);
162-
root = affectedNode;
163-
} else { //the affected node is its paret
164-
affectedNode = parent.get(nextNode);
165-
totalCost -= costToParent.get(nextNode);
166-
clearNode(nextNode, parent, costToParent);
167-
}
168-
169-
degree.set(affectedNode, degree.get(affectedNode) - 1);
170-
double associatedCost = -1;
171-
if (degree.get(affectedNode) == 1) { //it becomes a leaf
172-
if (affectedNode == root) {
173-
//if it is root, we loop at its neighbors to find its single alive child
174-
MutableDouble mutRootCost = new MutableDouble();
175-
MutableLong mutRootChild = new MutableLong();
176-
graph.forEachRelationship(root, (s, t) -> {
177-
if (parent.get(t) == s) {
178-
mutRootChild.setValue(t);
179-
mutRootCost.setValue(costToParent.get(t));
180-
return false;
181-
}
182-
return true;
183-
});
184-
rootChild = mutRootChild.longValue();
185-
rootCost = mutRootCost.doubleValue();
186-
associatedCost = rootCost;
187-
} else {
188-
//otherwise we just get the info from parent
189-
associatedCost = costToParent.get(affectedNode);
190-
}
191-
priorityQueue.add(affectedNode, associatedCost);
192-
}
193-
progressTracker.logProgress();
194-
}
195-
progressTracker.endSubTask();
196-
return new SpanningTree(root, graph.nodeCount(), k, parent, costToParent, totalCost);
197-
}
198112

199113
private SpanningTree growApproach(SpanningTree spanningTree) {
200114

201115
//this approach grows gradually the MST found in the previous step
202116
//when it is about to get larger than K, we crop the current worst leaf if the new value to be added
203117
// is actually better
204-
118+
if (spanningTree.effectiveNodeCount() < k)
119+
return spanningTree;
120+
205121
HugeLongArray outDegree = HugeLongArray.newArray(graph.nodeCount());
206122

207123
HugeLongArray parent = HugeLongArray.newArray(graph.nodeCount());
@@ -372,21 +288,6 @@ private boolean moveMakesSense(double cost1, double cost2, DoubleUnaryOperator m
372288
}
373289
}
374290

375-
private SpanningTree combineApproach(SpanningTree tree) {
376-
if (tree.effectiveNodeCount() < k) {
377-
return tree;
378-
}
379-
var spanningTree1 = cutLeafApproach(tree);
380-
var spanningTree2 = growApproach(tree);
381-
System.out.println("Prune Leaf: " + spanningTree1.totalWeight() + " Grow Mst:" + spanningTree2.totalWeight());
382-
if (spanningTree1.totalWeight() > spanningTree2.totalWeight()) {
383-
return (minMax == Prim.MAX_OPERATOR) ? spanningTree1 : spanningTree2;
384-
} else {
385-
return (minMax == Prim.MAX_OPERATOR) ? spanningTree2 : spanningTree1;
386-
387-
}
388-
389-
}
390291
}
391292

392293

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ public Task progressTask(
5353
return Tasks.task(
5454
taskName(),
5555
Tasks.leaf("SpanningTree", graph.relationshipCount()),
56-
Tasks.leaf("Remove relationships 1"),
57-
Tasks.leaf("Remove relationships 2")
58-
56+
Tasks.leaf("Remove relationships")
5957
);
6058
}
6159

alpha/alpha-algo/src/test/java/org/neo4j/gds/impl/spanningtree/KSpanningTreeTest.java

Lines changed: 14 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,13 @@ void shouldProduceSingleConnectedTree() {
133133
", (c:Node)" +
134134
", (d:Node)" +
135135
", (e:Node)" +
136-
", (a)-[:TYPE {cost: 1.0}]->(b)" +
137-
", (b)-[:TYPE {cost: 20.0}]->(c)" +
138-
", (c)-[:TYPE {cost: 30.0}]->(d)" +
136+
", (b)-[:TYPE {cost: 1.0}]->(a)" +
137+
", (c)-[:TYPE {cost: 20.0}]->(b)" +
138+
", (d)-[:TYPE {cost: 30.0}]->(c)" +
139139
", (d)-[:TYPE {cost: 1.0}]->(e)"
140140
);
141141
var graph = factory.build().getUnion();
142-
var startNode = factory.nodeId("a");
142+
var startNode = factory.nodeId("d");
143143

144144
var k = 3;
145145
var spanningTree = new KSpanningTree(
@@ -172,14 +172,14 @@ void shouldProduceSingleTreeWithKMinusOneEdges(int k, double expected) {
172172
", (d:Node)" +
173173
", (e:Node)" +
174174
", (f:Node)" +
175-
", (a)-[:TYPE {cost: 1.0}]->(b)" +
176-
", (b)-[:TYPE {cost: 20.0}]->(c)" +
177-
", (c)-[:TYPE {cost: 30.0}]->(d)" +
175+
", (b)-[:TYPE {cost: 1.0}]->(a)" +
176+
", (c)-[:TYPE {cost: 20.0}]->(b)" +
177+
", (d)-[:TYPE {cost: 30.0}]->(c)" +
178178
", (d)-[:TYPE {cost: 1.0}]->(e)" +
179179
", (e)-[:TYPE {cost: 1.0}]->(f)"
180180
);
181181
var graph = factory.build().getUnion();
182-
var startNode = factory.nodeId("a");
182+
var startNode = factory.nodeId("d");
183183

184184

185185
var spanningTree = new KSpanningTree(
@@ -201,52 +201,6 @@ void shouldProduceSingleTreeWithKMinusOneEdges(int k, double expected) {
201201
assertThat(spanningTree.totalWeight()).isEqualTo(expected);
202202
}
203203

204-
@Test
205-
void worstCaseForCuttingHeaviest() {
206-
var factory = GdlFactory.of("CREATE" +
207-
" (a:Node)" +
208-
", (b:Node)" +
209-
", (c:Node)" +
210-
", (d:Node)" +
211-
", (e:Node)" +
212-
", (f:Node)" +
213-
", (g:Node)" +
214-
", (h:Node)" +
215-
", (a)-[:TYPE {cost: 9.0}]->(b)" +
216-
", (b)-[:TYPE {cost: 9.0}]->(c)" +
217-
", (c)-[:TYPE {cost: 0.0}]->(d)" +
218-
", (d)-[:TYPE {cost: 10.0}]->(e)" +
219-
", (e)-[:TYPE {cost: 0.0}]->(f)" +
220-
", (f)-[:TYPE {cost: 9.0}]->(g)" +
221-
", (g)-[:TYPE {cost: 9.0}]->(h)"
222-
223-
);
224-
var graph = factory.build().getUnion();
225-
var startNode = factory.nodeId("a");
226-
227-
228-
var spanningTree = new KSpanningTree(
229-
graph,
230-
Prim.MIN_OPERATOR,
231-
startNode,
232-
4,
233-
ProgressTracker.NULL_TRACKER
234-
).compute();
235-
236-
var counter = new MutableLong(0);
237-
spanningTree.forEach((__, ___, ____) -> {
238-
counter.add(1);
239-
return true;
240-
});
241-
242-
assertThat(counter.getValue()).isEqualTo(4 - 1);
243-
assertThat(spanningTree.totalWeight()).isEqualTo(10.0);
244-
//here a 'cut-heaviest' approach would begin by cutting edge 10.
245-
//this would prune the tree into two smaller subtrees of 4 nodes each
246-
//adn thus return a 18 despite the optimal being 10.
247-
//this is one of the many situations where cut-heavy-fails
248-
249-
}
250204

251205
@Test
252206
void worstCaseForPruningLeaves() {
@@ -344,16 +298,12 @@ void shouldLogProgress() {
344298
"KSpanningTree :: SpanningTree 80%",
345299
"KSpanningTree :: SpanningTree 100%",
346300
"KSpanningTree :: SpanningTree :: Finished",
347-
"KSpanningTree :: Remove relationships 1 :: Start",
348-
"KSpanningTree :: Remove relationships 1 50%",
349-
"KSpanningTree :: Remove relationships 1 100%",
350-
"KSpanningTree :: Remove relationships 1 :: Finished",
351-
"KSpanningTree :: Remove relationships 2 :: Start",
352-
"KSpanningTree :: Remove relationships 2 20%",
353-
"KSpanningTree :: Remove relationships 2 40%",
354-
"KSpanningTree :: Remove relationships 2 60%",
355-
"KSpanningTree :: Remove relationships 2 100%",
356-
"KSpanningTree :: Remove relationships 2 :: Finished",
301+
"KSpanningTree :: Remove relationships :: Start",
302+
"KSpanningTree :: Remove relationships 20%",
303+
"KSpanningTree :: Remove relationships 40%",
304+
"KSpanningTree :: Remove relationships 60%",
305+
"KSpanningTree :: Remove relationships 100%",
306+
"KSpanningTree :: Remove relationships :: Finished",
357307
"KSpanningTree :: Finished"
358308
);
359309
}

0 commit comments

Comments
 (0)