Skip to content

Commit f87bac6

Browse files
committed
Add support for inverse index in NodeFilteredGraph
1 parent 15d3ffe commit f87bac6

File tree

2 files changed

+103
-10
lines changed

2 files changed

+103
-10
lines changed

core/src/main/java/org/neo4j/gds/core/huge/NodeFilteredGraph.java

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.neo4j.gds.core.huge;
2121

2222
import org.apache.commons.lang3.mutable.MutableInt;
23+
import org.jetbrains.annotations.Nullable;
2324
import org.neo4j.gds.NodeLabel;
2425
import org.neo4j.gds.api.CSRGraph;
2526
import org.neo4j.gds.api.CSRGraphAdapter;
@@ -32,10 +33,10 @@
3233
import org.neo4j.gds.api.RelationshipWithPropertyConsumer;
3334
import org.neo4j.gds.api.properties.nodes.NodePropertyValues;
3435
import org.neo4j.gds.api.schema.GraphSchema;
36+
import org.neo4j.gds.collections.ha.HugeIntArray;
3537
import org.neo4j.gds.collections.primitive.PrimitiveLongIterable;
3638
import org.neo4j.gds.config.ConcurrencyConfig;
3739
import org.neo4j.gds.core.concurrency.RunWithConcurrency;
38-
import org.neo4j.gds.collections.ha.HugeIntArray;
3940
import org.neo4j.gds.core.utils.partition.Partition;
4041
import org.neo4j.gds.core.utils.partition.PartitionUtils;
4142
import org.neo4j.gds.utils.CloseableThreadLocal;
@@ -56,16 +57,30 @@ public class NodeFilteredGraph extends CSRGraphAdapter implements FilteredIdMap
5657
private final FilteredIdMap filteredIdMap;
5758
private long relationshipCount;
5859
private final HugeIntArray degreeCache;
60+
private final HugeIntArray degreeInverseCache;
5961
private final CloseableThreadLocal<Graph> threadLocalGraph;
6062

6163
public NodeFilteredGraph(CSRGraph originalGraph, FilteredIdMap filteredIdMap) {
62-
this(originalGraph, filteredIdMap, emptyDegreeCache(filteredIdMap), -1);
64+
this(
65+
originalGraph,
66+
filteredIdMap,
67+
emptyDegreeCache(filteredIdMap),
68+
originalGraph.characteristics().isInverseIndexed() ? emptyDegreeCache(filteredIdMap) : null,
69+
-1
70+
);
6371
}
6472

65-
private NodeFilteredGraph(CSRGraph originalGraph, FilteredIdMap filteredIdMap, HugeIntArray degreeCache, long relationshipCount) {
73+
private NodeFilteredGraph(
74+
CSRGraph originalGraph,
75+
FilteredIdMap filteredIdMap,
76+
HugeIntArray degreeCache,
77+
@Nullable HugeIntArray degreeInverseCache,
78+
long relationshipCount
79+
) {
6680
super(originalGraph);
6781

6882
this.degreeCache = degreeCache;
83+
this.degreeInverseCache = degreeInverseCache;
6984
this.filteredIdMap = filteredIdMap;
7085
this.relationshipCount = relationshipCount;
7186
this.threadLocalGraph = CloseableThreadLocal.withInitial(this::concurrentCopy);
@@ -104,18 +119,18 @@ public void forEachNode(LongPredicate consumer) {
104119

105120
@Override
106121
public int degree(long nodeId) {
107-
int cachedDegree = degreeCache.get(nodeId);
122+
int cachedDegree = this.degreeCache.get(nodeId);
108123
if (cachedDegree != NO_DEGREE) {
109124
return cachedDegree;
110125
}
111126

112127
var degree = new MutableInt();
113128

114-
threadLocalGraph.get().forEachRelationship(nodeId, (s, t) -> {
129+
this.threadLocalGraph.get().forEachRelationship(nodeId, (s, t) -> {
115130
degree.increment();
116131
return true;
117132
});
118-
degreeCache.set(nodeId, degree.intValue());
133+
this.degreeCache.set(nodeId, degree.intValue());
119134

120135
return degree.intValue();
121136
}
@@ -130,6 +145,24 @@ public int degreeWithoutParallelRelationships(long nodeId) {
130145
return degreeCounter.degree;
131146
}
132147

148+
@Override
149+
public int degreeInverse(long nodeId) {
150+
int cachedDegree = this.degreeInverseCache.get(nodeId);
151+
if (cachedDegree != NO_DEGREE) {
152+
return cachedDegree;
153+
}
154+
155+
var degree = new MutableInt();
156+
157+
this.threadLocalGraph.get().forEachInverseRelationship(nodeId, (s, t) -> {
158+
degree.increment();
159+
return true;
160+
});
161+
this.degreeInverseCache.set(nodeId, degree.intValue());
162+
163+
return degree.intValue();
164+
}
165+
133166
@Override
134167
public long nodeCount() {
135168
return filteredIdMap.nodeCount();
@@ -218,9 +251,30 @@ public void forEachRelationship(long nodeId, double fallbackValue, RelationshipW
218251
);
219252
}
220253

254+
@Override
255+
public void forEachInverseRelationship(long nodeId, RelationshipConsumer consumer) {
256+
super.forEachInverseRelationship(
257+
filteredIdMap.toRootNodeId(nodeId),
258+
(s, t) -> filterAndConsume(s, t, consumer)
259+
);
260+
}
261+
262+
@Override
263+
public void forEachInverseRelationship(
264+
long nodeId,
265+
double fallbackValue,
266+
RelationshipWithPropertyConsumer consumer
267+
) {
268+
super.forEachInverseRelationship(
269+
filteredIdMap.toRootNodeId(nodeId),
270+
fallbackValue,
271+
(s, t, p) -> filterAndConsume(s, t, p, consumer)
272+
);
273+
}
274+
221275
@Override
222276
public Stream<RelationshipCursor> streamRelationships(long nodeId, double fallbackValue) {
223-
if (! filteredIdMap.containsRootNodeId(filteredIdMap.toRootNodeId(nodeId))) {
277+
if (!filteredIdMap.containsRootNodeId(filteredIdMap.toRootNodeId(nodeId))) {
224278
return Stream.empty();
225279
}
226280

@@ -266,7 +320,13 @@ public double relationshipProperty(long sourceNodeId, long targetNodeId) {
266320

267321
@Override
268322
public CSRGraph concurrentCopy() {
269-
return new NodeFilteredGraph(csrGraph.concurrentCopy(), filteredIdMap, degreeCache, relationshipCount);
323+
return new NodeFilteredGraph(
324+
csrGraph.concurrentCopy(),
325+
filteredIdMap,
326+
degreeCache,
327+
degreeInverseCache,
328+
relationshipCount
329+
);
270330
}
271331

272332
@Override
@@ -312,7 +372,12 @@ private boolean filterAndConsume(long source, long target, RelationshipConsumer
312372
return true;
313373
}
314374

315-
private boolean filterAndConsume(long source, long target, double propertyValue, RelationshipWithPropertyConsumer consumer) {
375+
private boolean filterAndConsume(
376+
long source,
377+
long target,
378+
double propertyValue,
379+
RelationshipWithPropertyConsumer consumer
380+
) {
316381
if (filteredIdMap.containsRootNodeId(source) && filteredIdMap.containsRootNodeId(target)) {
317382
long internalSourceId = filteredIdMap.toFilteredNodeId(source);
318383
long internalTargetId = filteredIdMap.toFilteredNodeId(target);

core/src/test/java/org/neo4j/gds/core/huge/NodeFilteredGraphTest.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,18 @@
4444
@GdlExtension
4545
class NodeFilteredGraphTest {
4646

47-
@GdlGraph(idOffset = 1337)
47+
@GdlGraph(idOffset = 1337, indexInverse = true)
4848
static String GDL = " (x:Ignore)," +
4949
" (a:Person)," +
5050
" (b:Ignore:Person)," +
5151
" (c:Ignore:Person)," +
5252
" (d:Person)," +
5353
" (e:Ignore)," +
54+
" (x)-->(d)," + // ignored for index inverse
5455
" (a)-->(b)," +
5556
" (a)-->(e)," +
5657
" (b)-->(c)," +
58+
" (x)-->(c)," + // ignored for index inverse
5759
" (b)-->(d)," +
5860
" (c)-->(e)";
5961

@@ -122,6 +124,32 @@ void filterDegreeWithoutParallelRelationships() {
122124
assertThat(graph.degreeWithoutParallelRelationships(filteredIdFunction(graph).apply("a"))).isEqualTo(1L);
123125
}
124126

127+
@Test
128+
void filterDegreeInverse() {
129+
var graph = graphStore.getGraph(
130+
NodeLabel.of("Person"),
131+
RelationshipType.ALL_RELATIONSHIPS,
132+
Optional.empty()
133+
);
134+
135+
assertThat(graph.degreeInverse(filteredIdFunction(graph).apply("d"))).isEqualTo(1L);
136+
}
137+
138+
@Test
139+
void foreachInverseRelationship() {
140+
var graph = graphStore.getGraph(
141+
NodeLabel.of("Person"),
142+
RelationshipType.ALL_RELATIONSHIPS,
143+
Optional.empty()
144+
);
145+
146+
graph.forEachInverseRelationship(filteredIdFunction(graph).apply("d"), (source, target) -> {
147+
assertThat(source).isEqualTo(filteredIdFunction(graph).apply("d"));
148+
assertThat(target).isEqualTo(filteredIdFunction(graph).apply("b"));
149+
return true;
150+
});
151+
}
152+
125153
@Test
126154
void filterStreamRelationships() {
127155
var graph = graphStore.getGraph(

0 commit comments

Comments
 (0)