Skip to content

Commit 2636703

Browse files
Mats-SXs1ckFlorentinD
committed
Fix an additional bug with degree filtering
- Extract duplicated code into private helper - Use `NOT_FOUND` constant - Check for degree filter as early as possible - Improve variable names and code comments somewhat Co-authored-by: Martin Junghanns <[email protected]> Co-authored-by: Florentin Dörre <[email protected]>
1 parent e6a8773 commit 2636703

File tree

2 files changed

+85
-31
lines changed

2 files changed

+85
-31
lines changed

algo/src/test/java/org/neo4j/graphalgo/triangle/IntersectingTriangleCountTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,40 @@ void triangleWhenFirstMemberHasMoreNeighbours() {
319319
assertEquals(0, result.localTriangles().get(6));
320320
}
321321

322+
@Test
323+
void filterMaxDegreeFirstCNode() {
324+
var graph = fromGdl(
325+
"CREATE " +
326+
" (n0)-[:REL]->(n1)" +
327+
", (n1)-[:REL]->(n2)" +
328+
", (n2)-[:REL]->(n3)" +
329+
", (n2)-[:REL]->(n4)" +
330+
", (n2)-[:REL]->(n5)" +
331+
", (n3)-[:REL]->(n4)" +
332+
", (n1)-[:REL]->(n6)" +
333+
", (n0)-[:REL]->(n2)" +
334+
", (n0)-[:REL]->(n6)",
335+
UNDIRECTED
336+
);
337+
338+
TriangleCountBaseConfig config = ImmutableTriangleCountBaseConfig
339+
.builder()
340+
.maxDegree(3)
341+
.build();
342+
343+
TriangleCountResult result = compute(graph, config);
344+
345+
assertEquals(1, result.globalTriangles());
346+
assertEquals(7, result.localTriangles().size());
347+
assertEquals(1, result.localTriangles().get(0));
348+
assertEquals(1, result.localTriangles().get(1));
349+
assertEquals(EXCLUDED_NODE_TRIANGLE_COUNT, result.localTriangles().get(2));
350+
assertEquals(0, result.localTriangles().get(3));
351+
assertEquals(0, result.localTriangles().get(4));
352+
assertEquals(0, result.localTriangles().get(5));
353+
assertEquals(1, result.localTriangles().get(6));
354+
}
355+
322356
@Test
323357
void filterMaxDegreeSecondCNode() {
324358
var graph = fromGdl(

core/src/main/java/org/neo4j/graphalgo/core/huge/HugeGraphIntersectImpl.java

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
import java.util.function.LongPredicate;
2727

28+
import static org.neo4j.graphalgo.core.huge.AdjacencyList.DecompressingCursor.NOT_FOUND;
29+
2830
/**
2931
* An instance of this is not thread-safe; Iteration/Intersection on multiple threads will
3032
* throw misleading {@link NullPointerException}s.
@@ -69,78 +71,96 @@ public void intersectAll(long nodeA, IntersectionConsumer consumer) {
6971
// find first neighbour B of A with id > A
7072
long nodeB = neighboursAMain.skipUntil(nodeA);
7173
// if there is no such neighbour -> no triangle (or we already found it)
72-
if (nodeA > nodeB) {
74+
if (nodeB == NOT_FOUND) {
7375
return;
7476
}
7577

7678
// iterates over neighbours of A
7779
AdjacencyList.DecompressingCursor neighboursA = cacheA;
7880
// current neighbour of A
79-
long nodeCa;
81+
long nodeCfromA = NOT_FOUND;
8082
// iterates over neighbours of B
8183
AdjacencyList.DecompressingCursor neighboursB = cacheB;
8284
// current neighbour of B
83-
long nodeCb;
85+
long nodeCfromB;
8486

8587
// last node where Ca = Cb
8688
// prevents counting a new triangle for parallel relationships
8789
long triangleC;
8890

89-
// for all neighbours of A
91+
// for all neighbors of A
9092
while (neighboursAMain.hasNextVLong()) {
9193
// we have not yet seen a triangle
92-
triangleC = -1;
94+
triangleC = NOT_FOUND;
9395
// check the second node's degree
9496
if (degreeFilter.test(nodeB)) {
9597
neighboursB = cursor(nodeB, neighboursB, offsets, adjacency);
9698
// find first neighbour Cb of B with id > B
97-
nodeCb = neighboursB.skipUntil(nodeB);
99+
nodeCfromB = neighboursB.skipUntil(nodeB);
98100

99-
// check the third node's degree
100-
if (nodeCb > nodeB && degreeFilter.test(nodeCb)) {
101+
// if B had no neighbors, find a new B
102+
if (nodeCfromB != NOT_FOUND) {
101103
// copy the state of A's cursor
102104
neighboursA.copyFrom(neighboursAMain);
103-
// find the first neighbour Ca of A with id >= Cb
104-
nodeCa = neighboursA.advance(nodeCb);
105-
106-
// if Ca = Cb we have found a triangle
107-
// we only submit one triangle per parallel relationship
108-
if (nodeCa == nodeCb && nodeCa > triangleC) {
109-
consumer.accept(nodeA, nodeB, nodeCa);
110-
triangleC = nodeCa;
105+
106+
if (degreeFilter.test(nodeCfromB)) {
107+
// find the first neighbour Ca of A with id >= Cb
108+
nodeCfromA = neighboursA.advance(nodeCfromB);
109+
triangleC = checkForAndEmitTriangle(consumer, nodeA, nodeB, nodeCfromA, nodeCfromB, triangleC);
111110
}
112111

113112
// while both A and B have more neighbours
114-
while (neighboursB.hasNextVLong() && neighboursA.hasNextVLong()) {
113+
while (neighboursA.hasNextVLong() && neighboursB.hasNextVLong()) {
115114
// take the next neighbour Cb of B
116-
nodeCb = neighboursB.nextVLong();
117-
if (nodeCb > nodeCa) {
118-
// if Cb > Ca, take the next neighbour Ca of A with id >= Cb
119-
nodeCa = neighboursA.advance(nodeCb);
120-
}
121-
// check for triangle
122-
if (nodeCa == nodeCb && nodeCa > triangleC && degreeFilter.test(nodeCa)) {
123-
consumer.accept(nodeA, nodeB, nodeCa);
124-
triangleC = nodeCa;
115+
nodeCfromB = neighboursB.nextVLong();
116+
if (degreeFilter.test(nodeCfromB)) {
117+
if (nodeCfromB > nodeCfromA) {
118+
// if Cb > Ca, take the next neighbour Ca of A with id >= Cb
119+
nodeCfromA = neighboursA.advance(nodeCfromB);
120+
}
121+
triangleC = checkForAndEmitTriangle(
122+
consumer,
123+
nodeA,
124+
nodeB,
125+
nodeCfromA,
126+
nodeCfromB,
127+
triangleC
128+
);
125129
}
126130
}
127131

128132
// it is possible that the last Ca > Cb, but there are no more neighbours Ca of A
129133
// so if there are more neighbours Cb of B
130134
if (neighboursB.hasNextVLong()) {
131135
// we take the next neighbour Cb of B with id >= Ca
132-
nodeCb = neighboursB.advance(nodeCa);
133-
// check for triangle
134-
if (nodeCa == nodeCb && nodeCa > triangleC && degreeFilter.test(nodeCa)) {
135-
consumer.accept(nodeA, nodeB, nodeCa);
136+
nodeCfromB = neighboursB.advance(nodeCfromA);
137+
if (degreeFilter.test(nodeCfromB)) {
138+
checkForAndEmitTriangle(consumer, nodeA, nodeB, nodeCfromA, nodeCfromB, triangleC);
136139
}
137140
}
138141
}
139142
}
140143

141144
// skip until the next neighbour B of A with id > (current) B
142-
nodeB = skipUntil(neighboursAMain, nodeB);
145+
nodeB = neighboursAMain.skipUntil(nodeB);
146+
}
147+
}
148+
149+
private long checkForAndEmitTriangle(
150+
IntersectionConsumer consumer,
151+
long nodeA,
152+
long nodeB,
153+
long nodeCa,
154+
long nodeCb,
155+
long triangleC
156+
) {
157+
// if Ca = Cb there exists a triangle
158+
// if Ca = triangleC we have already counted it
159+
if (nodeCa == nodeCb && nodeCa > triangleC) {
160+
consumer.accept(nodeA, nodeB, nodeCa);
161+
return nodeCa;
143162
}
163+
return triangleC;
144164
}
145165

146166
private int degree(long node) {

0 commit comments

Comments
 (0)