Skip to content

Commit f1d27ba

Browse files
mbastianCopilot
andauthored
Add Spliterator on NodesQuadTree (#243)
* Change default config for block size and default dict size * Replace LinkedHashSet with arrays and implement spliterator * Add multi-node edge iterator * Refactoring to remove duplicate getNodes methods in quadtree * Add edge iteration support in quadtree * Add global quad tree edge iterator option * Refactor to configure edge inout iterator locking and test all edges in quadtree spliterator * Formatting * Git ignore * Non approximate support for global iterator and bugfix * Add additional tests * Quadtree versioning * Tweak boundaries * Documentation * Update src/main/java/org/gephi/graph/impl/NodesQuadTree.java Remove distinct from spliterator as edges can be returned twice Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix locking function name clash in view decorator * Fix toArray * Reduce memory overhead of quad node array init --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 63dc531 commit f1d27ba

17 files changed

+1884
-235
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ nbactions.xml
77
/store/graphstore/target/
88
.idea
99
*.iml
10+
.vscode/**

src/main/java/org/gephi/graph/api/Rect2D.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ public String toString() {
114114
}
115115

116116
private String toString(NumberFormat formatter) {
117-
return "(" + formatter.format(minX) + " " + formatter.format(minY) + ") < " + "(" + formatter
118-
.format(maxX) + " " + formatter.format(maxY) + ")";
117+
return "min(x:" + formatter.format(minX) + " y:" + formatter.format(minY) + ") < " + "max(x:" + formatter
118+
.format(maxX) + " y:" + formatter.format(maxY) + ")";
119119
}
120120

121121
/**
@@ -174,6 +174,44 @@ public boolean intersects(float minX, float minY, float maxX, float maxY) {
174174
return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY;
175175
}
176176

177+
/**
178+
* Returns true if this rectangle contains or intersects with the given
179+
* rectangle. This is equivalent to checking
180+
* {@code this.contains(rect) || this.intersects(rect)} but more efficient as it
181+
* performs the check in a single operation.
182+
*
183+
* @param rect the rectangle to check
184+
* @return true if this rectangle contains or intersects with the given
185+
* rectangle, false otherwise
186+
*/
187+
public boolean containsOrIntersects(Rect2D rect) {
188+
if (rect == this) {
189+
return true;
190+
}
191+
192+
return containsOrIntersects(rect.minX, rect.minY, rect.maxX, rect.maxY);
193+
}
194+
195+
/**
196+
* Returns true if this rectangle contains or intersects with the given
197+
* rectangle. This is equivalent to checking
198+
* {@code this.contains(minX, minY, maxX, maxY) || this.intersects(minX, minY, maxX, maxY)}
199+
* but more efficient as it performs the check in a single operation.
200+
*
201+
* @param minX the x coordinate of the minimum corner
202+
* @param minY the y coordinate of the minimum corner
203+
* @param maxX the x coordinate of the maximum corner
204+
* @param maxY the y coordinate of the maximum corner
205+
*
206+
* @return true if this rectangle contains or intersects with the given
207+
* rectangle, false otherwise
208+
*/
209+
public boolean containsOrIntersects(float minX, float minY, float maxX, float maxY) {
210+
// Two rectangles have overlap if they intersect - containment is a subset of
211+
// intersection
212+
return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY;
213+
}
214+
177215
@Override
178216
public boolean equals(Object obj) {
179217
if (this == obj) {

src/main/java/org/gephi/graph/api/SpatialIndex.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@
1616
package org.gephi.graph.api;
1717

1818
/**
19-
* Object to query the nodes and edges of the graph in a spatial context.
19+
* Query the (quadtree-based) index based on the given rectangle area.
20+
* <p>
21+
* The spatial index is not enabled by default. To enable it, set the
22+
* appropriate configuration:
23+
* <code>@{@link Configuration.Builder#enableSpatialIndex(boolean)}</code>.
24+
* <p>
25+
* When nodes are moved, added or removed, the spatial index is automatically
26+
* updated. Edges are not indexed, but they are queried based on whether their
27+
* source or target nodes are in the given area.
28+
* <p>
29+
* The Z position is not taken into account when querying the spatial index,
30+
* only X/Y are supported.
31+
* </p>
2032
*
2133
* @author Eduardo Ramos
2234
*/
@@ -31,18 +43,56 @@ public interface SpatialIndex {
3143
NodeIterable getNodesInArea(Rect2D rect);
3244

3345
/**
34-
* Returns the edges in the given area.
46+
* Returns the nodes in the given area using a faster, but approximate method.
47+
* <p>
48+
* All nodes in the provided area are guaranteed to be returned, but some nodes
49+
* outside the area may also be returned.
50+
*
51+
* @param rect area to query
52+
* @return nodes in the area
53+
*/
54+
NodeIterable getApproximateNodesInArea(Rect2D rect);
55+
56+
/**
57+
* Returns the edges in the given area. Edges may be returned twice.
3558
*
3659
* @param rect area to query
3760
* @return edges in the area
3861
*/
3962
EdgeIterable getEdgesInArea(Rect2D rect);
4063

64+
/**
65+
* Returns the edges in the given area using a faster, but approximate method.
66+
* <p>
67+
* All edges in the provided area are guaranteed to be returned, but some edges
68+
* outside the area may also be returned. Edges may also be returned twice.
69+
*
70+
* @param rect area to query
71+
* @return edges in the area
72+
*/
73+
EdgeIterable getApproximateEdgesInArea(Rect2D rect);
74+
4175
/**
4276
* Returns the bounding rectangle that contains all nodes in the graph. The
4377
* boundaries are calculated based on each node's position and size.
4478
*
4579
* @return the bounding rectangle, or null if there are no nodes
4680
*/
4781
Rect2D getBoundaries();
82+
83+
/**
84+
* Acquires a read lock on the spatial index. This is recommended when using the
85+
* query functions in a stream context, to avoid the spatial index being
86+
* modified while being queried.
87+
* <p>
88+
* Every call to this method must be matched with a call to
89+
* {@link #spatialIndexReadUnlock()}.
90+
*/
91+
void spatialIndexReadLock();
92+
93+
/**
94+
* Releases a read lock on the spatial index. This must be called after a call
95+
* to {@link #spatialIndexReadLock()}.
96+
*/
97+
void spatialIndexReadUnlock();
4898
}

src/main/java/org/gephi/graph/impl/EdgeStore.java

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -419,14 +419,18 @@ public EdgeInIterator edgeInIterator(final Node node) {
419419
return new EdgeInIterator((NodeImpl) node);
420420
}
421421

422-
public EdgeInOutIterator edgeIterator(final Node node) {
422+
public EdgeInOutIterator edgeIterator(final Node node, boolean locking) {
423423
checkValidNodeObject(node);
424-
return new EdgeInOutIterator((NodeImpl) node);
424+
return new EdgeInOutIterator((NodeImpl) node, locking);
425425
}
426426

427-
public Iterator<Edge> edgeUndirectedIterator(final Node node) {
427+
public EdgeInOutMultiIterator edgeIterator(final Iterator<NodeImpl> nodeIterator, boolean locking) {
428+
return new EdgeInOutMultiIterator(nodeIterator, locking);
429+
}
430+
431+
public Iterator<Edge> edgeUndirectedIterator(final Node node, boolean locking) {
428432
checkValidNodeObject(node);
429-
return undirectedIterator(new EdgeInOutIterator((NodeImpl) node));
433+
return undirectedIterator(new EdgeInOutIterator((NodeImpl) node, locking));
430434
}
431435

432436
public EdgeTypeOutIterator edgeOutIterator(final Node node, int type) {
@@ -471,7 +475,7 @@ public NeighborsIterator neighborInIterator(final Node node, int type) {
471475

472476
public NeighborsIterator neighborIterator(Node node) {
473477
checkValidNodeObject(node);
474-
return new NeighborsUndirectedIterator((NodeImpl) node, new EdgeInOutIterator((NodeImpl) node));
478+
return new NeighborsUndirectedIterator((NodeImpl) node, new EdgeInOutIterator((NodeImpl) node, true));
475479
}
476480

477481
public NeighborsIterator neighborIterator(final Node node, int type) {
@@ -1285,6 +1289,9 @@ private void incrementVersion() {
12851289
if (version != null) {
12861290
version.incrementAndGetEdgeVersion();
12871291
}
1292+
if (spatialIndex != null) {
1293+
spatialIndex.incrementVersion();
1294+
}
12881295
}
12891296

12901297
boolean isUndirectedToIgnore(EdgeImpl edge) {
@@ -1491,28 +1498,52 @@ public boolean hasNext() {
14911498
}
14921499
}
14931500

1494-
protected final class EdgeInOutIterator implements Iterator<Edge> {
1501+
/**
1502+
* Abstract base class for iterating over edges connected to nodes. Provides
1503+
* common logic for handling both incoming and outgoing edges.
1504+
*/
1505+
protected abstract class AbstractEdgeInOutIterator implements Iterator<Edge> {
14951506

1496-
protected final int outTypeLength;
1497-
protected final int inTypeLength;
1507+
protected final boolean locking;
1508+
protected int outTypeLength;
1509+
protected int inTypeLength;
14981510
protected EdgeImpl[] outArray;
14991511
protected EdgeImpl[] inArray;
15001512
protected int typeIndex = 0;
15011513
protected EdgeImpl pointer;
15021514
protected EdgeImpl lastEdge;
15031515
protected boolean out = true;
15041516

1505-
public EdgeInOutIterator(NodeImpl node) {
1506-
readLock();
1517+
protected AbstractEdgeInOutIterator(boolean locking) {
1518+
this.locking = locking;
1519+
if (locking) {
1520+
readLock();
1521+
}
1522+
}
1523+
1524+
/**
1525+
* Initialize arrays for the current node. Called when starting iteration for a
1526+
* new node.
1527+
*/
1528+
protected void initializeForNode(NodeImpl node) {
15071529
outArray = node.headOut;
15081530
outTypeLength = outArray.length;
15091531
inArray = node.headIn;
15101532
inTypeLength = inArray.length;
1533+
typeIndex = 0;
1534+
pointer = null;
1535+
out = true;
15111536
}
15121537

1538+
/**
1539+
* Called when the current node has no more edges. Should return true if there
1540+
* are more nodes to process, false otherwise.
1541+
*/
1542+
protected abstract boolean moveToNextNode();
1543+
15131544
@Override
15141545
public boolean hasNext() {
1515-
if (pointer == null) {
1546+
while (pointer == null) {
15161547
if (out) {
15171548
while (pointer == null && typeIndex < outTypeLength) {
15181549
pointer = outArray[typeIndex++];
@@ -1537,8 +1568,13 @@ public boolean hasNext() {
15371568
}
15381569

15391570
if (pointer == null) {
1540-
readUnlock();
1541-
return false;
1571+
// No more edges for current node, try next node
1572+
if (!moveToNextNode()) {
1573+
if (locking) {
1574+
readUnlock();
1575+
}
1576+
return false;
1577+
}
15421578
}
15431579
}
15441580
return true;
@@ -1579,6 +1615,53 @@ public void remove() {
15791615
}
15801616
}
15811617

1618+
/**
1619+
* Iterator for edges connected to a single node (both incoming and outgoing).
1620+
*/
1621+
protected final class EdgeInOutIterator extends AbstractEdgeInOutIterator {
1622+
1623+
public EdgeInOutIterator(NodeImpl node, boolean locking) {
1624+
super(locking);
1625+
initializeForNode(node);
1626+
}
1627+
1628+
@Override
1629+
protected boolean moveToNextNode() {
1630+
// Single node iterator - no more nodes to process
1631+
return false;
1632+
}
1633+
}
1634+
1635+
/**
1636+
* Iterator for edges connected to multiple nodes (both incoming and outgoing).
1637+
* Iterates through all edges of all provided nodes without creating separate
1638+
* iterators.
1639+
*/
1640+
protected final class EdgeInOutMultiIterator extends AbstractEdgeInOutIterator {
1641+
1642+
private final Iterator<NodeImpl> nodeIterator;
1643+
1644+
public EdgeInOutMultiIterator(Iterator<NodeImpl> nodeIterator, boolean locking) {
1645+
super(locking);
1646+
this.nodeIterator = nodeIterator;
1647+
// Initialize with first node if available
1648+
if (nodeIterator.hasNext()) {
1649+
NodeImpl node = nodeIterator.next();
1650+
checkValidNodeObject(node);
1651+
initializeForNode(node);
1652+
}
1653+
}
1654+
1655+
@Override
1656+
protected boolean moveToNextNode() {
1657+
if (nodeIterator.hasNext()) {
1658+
initializeForNode(nodeIterator.next());
1659+
return true;
1660+
}
1661+
return false;
1662+
}
1663+
}
1664+
15821665
protected final class EdgeOutIterator implements Iterator<Edge> {
15831666

15841667
protected final int typeLength;

src/main/java/org/gephi/graph/impl/GraphStore.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ public boolean removeNode(final Node node) {
277277
autoWriteLock();
278278
try {
279279
nodeStore.checkNonNullNodeObject(node);
280-
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node); edgeIterator.hasNext();) {
280+
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node, false); edgeIterator
281+
.hasNext();) {
281282
edgeIterator.next();
282283
edgeIterator.remove();
283284
}
@@ -303,7 +304,8 @@ public boolean removeAllNodes(Collection<? extends Node> nodes) {
303304
try {
304305
for (Node node : nodes) {
305306
nodeStore.checkNonNullNodeObject(node);
306-
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node); edgeIterator.hasNext();) {
307+
for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node, false); edgeIterator
308+
.hasNext();) {
307309
edgeIterator.next();
308310
edgeIterator.remove();
309311
}
@@ -434,7 +436,7 @@ public NodeIterable getSuccessors(final Node node, final int type) {
434436

435437
@Override
436438
public EdgeIterable getEdges(final Node node) {
437-
return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node), getAutoLock());
439+
return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node, true), getAutoLock());
438440
}
439441

440442
@Override
@@ -575,7 +577,7 @@ public boolean isIncident(final Node node, final Edge edge) {
575577
public void clearEdges(final Node node) {
576578
autoWriteLock();
577579
try {
578-
EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(node);
580+
EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(node, false);
579581
for (; itr.hasNext();) {
580582
itr.next();
581583
itr.remove();

src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ public final class GraphStoreConfiguration {
4040
public static final int NODESTORE_DEFAULT_DICTIONARY_SIZE = 8192;
4141
public final static float NODESTORE_DICTIONARY_LOAD_FACTOR = .7f;
4242
// EdgeStore
43-
public static final int EDGESTORE_BLOCK_SIZE = 8192;
44-
public static final int EDGESTORE_DEFAULT_BLOCKS = 10;
43+
public static final int EDGESTORE_BLOCK_SIZE = 32768;
44+
public static final int EDGESTORE_DEFAULT_BLOCKS = 5;
4545
public static final int EDGESTORE_DEFAULT_TYPE_COUNT = 1;
46-
public static final int EDGESTORE_DEFAULT_DICTIONARY_SIZE = 1000;
46+
public static final int EDGESTORE_DEFAULT_DICTIONARY_SIZE = 32768;
4747
public static final float EDGESTORE_DICTIONARY_LOAD_FACTOR = .7f;
4848
// GraphView
4949
public static final int VIEW_DEFAULT_TYPE_COUNT = 1;
@@ -83,8 +83,10 @@ public final class GraphStoreConfiguration {
8383
public static final TimeRepresentation DEFAULT_TIME_REPRESENTATION = TimeRepresentation.TIMESTAMP;
8484
// Spatial index
8585
public static final int SPATIAL_INDEX_MAX_LEVELS = 16;
86-
public static final int SPATIAL_INDEX_MAX_OBJECTS_PER_NODE = 5000;
86+
public static final int SPATIAL_INDEX_MAX_OBJECTS_PER_NODE = 8192;
8787
public static final float SPATIAL_INDEX_DIMENSION_BOUNDARY = 1e6f;
88+
public static final boolean SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH = false;
89+
public static final float SPATIAL_INDEX_LOCAL_ITERATOR_THRESHOLD = 0.3f;
8890
// Miscellaneous
8991
public static final double TIMESTAMP_STORE_GROWING_FACTOR = 1.1;
9092
public static final double INTERVAL_STORE_GROWING_FACTOR = 1.1;

0 commit comments

Comments
 (0)