From 594dc8d6372d7a0169743e774d08ef5db8a9c30b Mon Sep 17 00:00:00 2001 From: ioannispan Date: Wed, 11 Dec 2024 14:45:14 +0100 Subject: [PATCH] Bridges report size of components left Co-authored-by: Veselin Nikolov --- ...ulationPointsMemoryEstimateDefinition.java | 4 +- .../java/org/neo4j/gds/bridges/Bridge.java | 6 +-- .../java/org/neo4j/gds/bridges/Bridges.java | 47 +++++++++++++++---- .../BridgesMemoryEstimateDefinition.java | 14 ++++++ .../neo4j/gds/bridges/TreeSizeTracker.java | 42 +++++++++++++++++ ...ionPointsMemoryEstimateDefinitionTest.java | 2 +- .../neo4j/gds/bridges/BridgesLargestTest.java | 23 ++++----- .../BridgesMemoryEstimateDefinitionTest.java | 23 +++++++-- .../neo4j/gds/bridges/SmallBridgesTest.java | 29 +++++++++--- .../centrality/CentralityAlgorithms.java | 4 +- ...lgorithmsEstimationModeBusinessFacade.java | 8 ++-- ...ityAlgorithmsStreamModeBusinessFacade.java | 8 ++-- .../pages/algorithms/articulation-points.adoc | 2 +- .../ROOT/pages/algorithms/bridges.adoc | 14 +++--- .../gds/bridges/BridgesStreamProcTest.java | 3 ++ .../BridgesResultBuilderForStreamMode.java | 10 +++- .../LocalCentralityProcedureFacade.java | 7 ++- .../centrality/BridgesStreamResult.java | 4 +- 18 files changed, 190 insertions(+), 60 deletions(-) create mode 100644 algo/src/main/java/org/neo4j/gds/bridges/TreeSizeTracker.java diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java index 97c22a2ac9..9d0ad14f48 100644 --- a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java @@ -19,7 +19,6 @@ */ package org.neo4j.gds.articulationpoints; -import org.neo4j.gds.bridges.Bridges; import org.neo4j.gds.collections.ha.HugeLongArray; import org.neo4j.gds.collections.ha.HugeObjectArray; import org.neo4j.gds.mem.Estimate; @@ -32,7 +31,7 @@ public class ArticulationPointsMemoryEstimateDefinition implements MemoryEstimat @Override public MemoryEstimation memoryEstimation() { - var builder = MemoryEstimations.builder(Bridges.class); + var builder = MemoryEstimations.builder(ArticulationPoints.class); builder .perNode("tin", HugeLongArray::memoryEstimation) .perNode("low", HugeLongArray::memoryEstimation) @@ -46,7 +45,6 @@ public MemoryEstimation memoryEstimation() { HugeObjectArray.memoryEstimation(relationshipCount, Estimate.sizeOfInstance(ArticulationPoints.StackEvent.class)) ); - })); return builder.build(); diff --git a/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java b/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java index 65dbf1f538..039ff424af 100644 --- a/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java +++ b/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java @@ -19,9 +19,9 @@ */ package org.neo4j.gds.bridges; -public record Bridge(long from, long to) { +public record Bridge(long from, long to,long[] remainingSizes) { - static Bridge create(long from, long to){ - return new Bridge(Math.min(from,to), Math.max(from,to)); + static Bridge create(long from, long to, long[] remainingSizes){ + return new Bridge(Math.min(from,to), Math.max(from,to), remainingSizes); } } diff --git a/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java b/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java index 1c57b19592..3d97edfbca 100644 --- a/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java +++ b/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java @@ -28,7 +28,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; public class Bridges extends Algorithm { @@ -39,14 +41,26 @@ public class Bridges extends Algorithm { private long timer; private long stackIndex = -1; private List result = new ArrayList<>(); + private final Optional treeSizeTracker; - public Bridges(Graph graph, ProgressTracker progressTracker){ + public static Bridges create(Graph graph, ProgressTracker progressTracker, boolean shouldComputeComponents){ + + if (shouldComputeComponents) { + var treeSizeTracker = new TreeSizeTracker(graph.nodeCount()); + return new Bridges(graph, progressTracker, Optional.of(treeSizeTracker)); + }else{ + return new Bridges(graph,progressTracker,Optional.empty()); + } + } + + private Bridges(Graph graph, ProgressTracker progressTracker, Optional treeSizeTracker){ super(progressTracker); this.graph = graph; this.visited = new BitSet(graph.nodeCount()); this.tin = HugeLongArray.newArray(graph.nodeCount()); this.low = HugeLongArray.newArray(graph.nodeCount()); + this.treeSizeTracker = treeSizeTracker; } @Override @@ -59,28 +73,40 @@ public BridgeResult compute() { //each edge may have at most one event to the stack at the same time var stack = HugeObjectArray.newArray(StackEvent.class, graph.relationshipCount()); + BiConsumer onLastChildVisit = (treeSizeTracker.isPresent()) ? treeSizeTracker.get()::recordTreeChild : (a,b)->{}; + int listIndex=0; var n = graph.nodeCount(); - for (int i = 0; i < n; ++i) { - if (!visited.get(i)) - dfs(i, stack); + for (long i = 0; i < n; ++i) { + if (!visited.get(i)) { + dfs(i, stack,onLastChildVisit); + + if (treeSizeTracker.isPresent()) { + var tracker =treeSizeTracker.get(); + var listEndIndex = result.size(); + for (var j = listIndex; j < listEndIndex; ++j) { + var currentBridge = result.get(j); + var remainingSizes = tracker.recordBridge(currentBridge.to(),i); + result.set(j,new Bridge(currentBridge.from(), currentBridge.to(),remainingSizes)); + } + listIndex = listEndIndex; + } + } } progressTracker.endSubTask("Bridges"); return new BridgeResult(result); } - - - private void dfs(long node, HugeObjectArray stack) { + private void dfs(long node, HugeObjectArray stack, BiConsumer onLastChildVisit) { stack.set(++stackIndex, StackEvent.upcomingVisit(node,-1)); while (stackIndex >= 0) { var stackEvent = stack.get(stackIndex--); - visitEvent(stackEvent, stack); + visitEvent(stackEvent, stack,onLastChildVisit); } progressTracker.logProgress(); } - private void visitEvent(StackEvent event, HugeObjectArray stack) { + private void visitEvent(StackEvent event, HugeObjectArray stack, BiConsumer onLastChildVisit) { if (event.lastVisit()) { var to = event.eventNode(); var v = event.triggerNode(); @@ -88,8 +114,9 @@ private void visitEvent(StackEvent event, HugeObjectArray stack) { var lowTo = low.get(to); low.set(v, Math.min(lowV, lowTo)); var tinV = tin.get(v); + onLastChildVisit.accept(v,to); if (lowTo > tinV) { - result.add(new Bridge(v, to)); + result.add(new Bridge(v, to, null)); } progressTracker.logProgress(); return; diff --git a/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java b/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java index 12ae7e327e..59747b57f5 100644 --- a/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java +++ b/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java @@ -28,6 +28,13 @@ import org.neo4j.gds.mem.MemoryRange; public class BridgesMemoryEstimateDefinition implements MemoryEstimateDefinition { + + private final boolean shouldComputeComponents; + + public BridgesMemoryEstimateDefinition(boolean shouldComputeComponents) { + this.shouldComputeComponents = shouldComputeComponents; + } + @Override public MemoryEstimation memoryEstimation() { @@ -38,6 +45,13 @@ public MemoryEstimation memoryEstimation() { .perNode("visited", Estimate::sizeOfBitset) .perNode("bridge", (v)-> v * Estimate.sizeOfInstance(Bridge.class)); + if (shouldComputeComponents){ + builder.add( + "component split", + MemoryEstimations.builder(TreeSizeTracker.class) + .perNode("treeSize", HugeLongArray::memoryEstimation) + .build()); + } builder.rangePerGraphDimension("stack", ((graphDimensions, concurrency) -> { long relationshipCount = graphDimensions.relCountUpperBound(); return MemoryRange.of( diff --git a/algo/src/main/java/org/neo4j/gds/bridges/TreeSizeTracker.java b/algo/src/main/java/org/neo4j/gds/bridges/TreeSizeTracker.java new file mode 100644 index 0000000000..2b5b6c82ef --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/bridges/TreeSizeTracker.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.gds.bridges; + +import org.neo4j.gds.collections.ha.HugeLongArray; + + class TreeSizeTracker { + + private final HugeLongArray subTreeSize; + + TreeSizeTracker(long nodeCount){ + subTreeSize = HugeLongArray.newArray(nodeCount); + subTreeSize.setAll( v -> 1); + } + void recordTreeChild(long parent, long child) { + subTreeSize.addTo(parent,subTreeSize.get(child)); + } + + public long[] recordBridge( long child, long root) { + var childSubTree = subTreeSize.get(child); + var rootSubTree= subTreeSize.get(root) - childSubTree; + return new long[]{rootSubTree, childSubTree}; + } + +} diff --git a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java index 2c14d75581..122d0b2ad2 100644 --- a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java +++ b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java @@ -30,6 +30,6 @@ void shouldEstimateMemoryAccurately() { MemoryEstimationAssert.assertThat(memoryEstimation) .memoryRange(100, 6000, new Concurrency(1)) - .hasSameMinAndMaxEqualTo(218752); + .hasSameMinAndMaxEqualTo(218760); } } diff --git a/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java b/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java index ace03c7201..f9a98b1206 100644 --- a/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java +++ b/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java @@ -74,31 +74,32 @@ class BridgesLargestTest { @Inject private TestGraph graph; - private Bridge bridge(String from, String to) { - return new Bridge(graph.toOriginalNodeId(from), graph.toOriginalNodeId(to)); + private Bridge bridge(String from, String to, long[] remainingSizes) { + return new Bridge(graph.toOriginalNodeId(from), graph.toOriginalNodeId(to), remainingSizes); } @Test void shouldFindAllBridges() { - var bridges = new Bridges(graph, ProgressTracker.NULL_TRACKER); + var bridges = Bridges.create(graph, ProgressTracker.NULL_TRACKER,true); var result = bridges.compute().bridges().stream() .map(b -> new Bridge( graph.toOriginalNodeId(b.from()), - graph.toOriginalNodeId(b.to()) + graph.toOriginalNodeId(b.to()), + b.remainingSizes() )).toList(); - assertThat(result) .isNotNull() + .usingRecursiveFieldByFieldElementComparator() .containsExactlyInAnyOrder( - bridge("a1", "a2"), - bridge("a3", "a4"), - bridge("a3", "a7"), - bridge("a7", "a8"), - bridge("a10", "a11"), - bridge("a14", "a13") + bridge("a1", "a2", new long[]{1,1}), + bridge("a3", "a4", new long[]{3,1}), + bridge("a3", "a7", new long[]{2,2}), + bridge("a7", "a8", new long[]{3,1}), + bridge("a10", "a11", new long[]{5,4}), + bridge("a14", "a13", new long[]{8,1}) ); } } diff --git a/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java b/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java index 57c054b3ae..cfa3a3b3cd 100644 --- a/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java +++ b/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java @@ -19,18 +19,31 @@ */ package org.neo4j.gds.bridges; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.neo4j.gds.assertions.MemoryEstimationAssert; import org.neo4j.gds.core.concurrency.Concurrency; +import java.util.stream.Stream; + class BridgesMemoryEstimateDefinitionTest { - @Test - void shouldEstimateMemoryAccurately() { - var memoryEstimation = new BridgesMemoryEstimateDefinition().memoryEstimation(); + static Stream memoryEstimationTuples() { + return Stream.of( + Arguments.of(false, 221064L), + Arguments.of(true, 221920L) + ); + } + + @ParameterizedTest + @MethodSource("memoryEstimationTuples") + void shouldEstimateMemoryAccurately(boolean should, long expectedMemory) { + var memoryEstimation = new BridgesMemoryEstimateDefinition(should).memoryEstimation(); MemoryEstimationAssert.assertThat(memoryEstimation) .memoryRange(100, 6000, new Concurrency(1)) - .hasSameMinAndMaxEqualTo(221056L); + .hasSameMinAndMaxEqualTo(expectedMemory); } + } diff --git a/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java b/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java index 5ab0e72931..16ddc12d1f 100644 --- a/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java +++ b/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java @@ -65,15 +65,32 @@ class GraphWithBridges { @Test - void shouldFindBridges() { - var bridges = new Bridges(graph, ProgressTracker.NULL_TRACKER); + void shouldFindJustBridges() { + var bridges = Bridges.create(graph, ProgressTracker.NULL_TRACKER,false); + var result = bridges.compute().bridges(); + + assertThat(result) + .isNotNull() + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder( + Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d"),null), + Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e"),null) + ); + } + + + + @Test + void shouldFindBridgesAndComponentSizes() { + var bridges = Bridges.create(graph, ProgressTracker.NULL_TRACKER,true); var result = bridges.compute().bridges(); assertThat(result) .isNotNull() + .usingRecursiveFieldByFieldElementComparator() .containsExactlyInAnyOrder( - Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d")), - Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e")) + Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d"),new long[]{3,2}), + Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e"),new long[]{4,1}) ); } @@ -84,7 +101,7 @@ void shouldLogProgress(){ var log = new GdsTestLog(); var progressTracker = new TaskProgressTracker(progressTask, log, new Concurrency(1), EmptyTaskRegistryFactory.INSTANCE); - var bridges = new Bridges(graph, progressTracker); + var bridges = Bridges.create(graph, progressTracker,false); bridges.compute(); Assertions.assertThat(log.getMessages(TestLog.INFO)) @@ -130,7 +147,7 @@ class GraphWithoutBridges { @Test void shouldFindBridges() { - var bridges = new Bridges(graph,ProgressTracker.NULL_TRACKER); + var bridges = Bridges.create(graph,ProgressTracker.NULL_TRACKER,false); var result = bridges.compute().bridges(); assertThat(result) diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithms.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithms.java index 7216cf4605..36118fe50a 100644 --- a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithms.java +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithms.java @@ -181,12 +181,12 @@ public BetwennessCentralityResult betweennessCentrality( ); } - BridgeResult bridges(Graph graph, AlgoBaseConfig configuration) { + BridgeResult bridges(Graph graph, AlgoBaseConfig configuration, boolean shouldComputeComponents) { var task = BridgeProgressTaskCreator.progressTask(graph.nodeCount()); var progressTracker = progressTrackerCreator.createProgressTracker(configuration, task); - var algorithm = new Bridges(graph, progressTracker); + var algorithm = Bridges.create(graph, progressTracker,shouldComputeComponents); return algorithmMachinery.runAlgorithmsAndManageProgressTracker( algorithm, diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsEstimationModeBusinessFacade.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsEstimationModeBusinessFacade.java index a2c5446a66..016e56dae1 100644 --- a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsEstimationModeBusinessFacade.java +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsEstimationModeBusinessFacade.java @@ -82,12 +82,12 @@ public MemoryEstimateResult betweennessCentrality( memoryEstimation ); } - MemoryEstimation bridges() { - return new BridgesMemoryEstimateDefinition().memoryEstimation(); + MemoryEstimation bridges(boolean shouldComputeComponents) { + return new BridgesMemoryEstimateDefinition(shouldComputeComponents).memoryEstimation(); } - public MemoryEstimateResult bridges(BridgesBaseConfig configuration, Object graphNameOrConfiguration) { - var memoryEstimation = bridges(); + public MemoryEstimateResult bridges(BridgesBaseConfig configuration, Object graphNameOrConfiguration, boolean shouldComputeComponents) { + var memoryEstimation = bridges(shouldComputeComponents); return algorithmEstimationTemplate.estimate( configuration, diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStreamModeBusinessFacade.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStreamModeBusinessFacade.java index 4fc05363ec..d29b273d28 100644 --- a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStreamModeBusinessFacade.java +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStreamModeBusinessFacade.java @@ -123,14 +123,16 @@ public Stream articulationPoints( public Stream bridges( GraphName graphName, BridgesStreamConfig configuration, - StreamResultBuilder streamResultBuilder + StreamResultBuilder streamResultBuilder, + boolean shouldComputeComponents ) { + return algorithmProcessingTemplateConvenience.processRegularAlgorithmInStreamMode( graphName, configuration, Bridges, - estimationFacade::bridges, - (graph, __) -> centralityAlgorithms.bridges(graph, configuration), + ()-> estimationFacade.bridges(shouldComputeComponents), + (graph, __) -> centralityAlgorithms.bridges(graph, configuration,shouldComputeComponents), streamResultBuilder ); } diff --git a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc index c485122f59..4ab71eed7c 100644 --- a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc +++ b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc @@ -241,7 +241,7 @@ YIELD nodeCount, relationshipCount, bytesMin, bytesMax, requiredMemory [opts="header",cols="1,1,1,1,1"] |=== | nodeCount | relationshipCount | bytesMin | bytesMax | requiredMemory -| 6 | 14 | 984 | 984 | "984 Bytes" +| 6 | 14 | 992 | 992 | "992 Bytes" |=== -- diff --git a/doc/modules/ROOT/pages/algorithms/bridges.adoc b/doc/modules/ROOT/pages/algorithms/bridges.adoc index 82fdae123f..5691d848c9 100644 --- a/doc/modules/ROOT/pages/algorithms/bridges.adoc +++ b/doc/modules/ROOT/pages/algorithms/bridges.adoc @@ -43,6 +43,7 @@ CALL gds.bridges.stream( YIELD from: Integer, to: Integer + remainingSizes: List of Long ---- include::partial$/algorithms/common-configuration/common-parameters.adoc[] @@ -60,6 +61,7 @@ include::partial$/algorithms/common-configuration/common-stream-stats-configurat | Name | Type | Description | from | Integer | Start node ID. | to | Integer | End node ID. +| splitComponens| List of Long| A list with the resulting sizes of the components after removing the relationship. |=== ====== @@ -134,7 +136,7 @@ YIELD nodeCount, relationshipCount, bytesMin, bytesMax, requiredMemory [opts="header",cols="1,1,1,1,1"] |=== | nodeCount | relationshipCount | bytesMin | bytesMax | requiredMemory -| 6 | 14 | 1040 | 1040 | "1040 Bytes" +| 6 | 14 | 1152 | 1152 | "1152 Bytes" |=== -- @@ -150,15 +152,15 @@ include::partial$/algorithms/shared/examples-stream-intro.adoc[] [source, cypher, role=noplay] ---- CALL gds.bridges.stream('myGraph') -YIELD from, to -RETURN gds.util.asNode(from).name AS fromName, gds.util.asNode(to).name AS toName +YIELD from, to, remainingSizes +RETURN gds.util.asNode(from).name AS fromName, gds.util.asNode(to).name AS toName, remainingSizes ORDER BY fromName ASC, toName ASC ---- .Results -[opts="header",cols="1,1"] +[opts="header",cols="1,1,1"] |=== -| fromName | toName -| "Alice" | "Doug" +| fromName | toName | remainingSizes +| "Alice" | "Doug" | [3, 3] |=== -- diff --git a/proc/centrality/src/integrationTest/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java b/proc/centrality/src/integrationTest/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java index f4ba712d8d..48b1d8956a 100644 --- a/proc/centrality/src/integrationTest/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java +++ b/proc/centrality/src/integrationTest/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java @@ -78,10 +78,13 @@ void shouldStreamBackResults(){ var rowCount = runQueryWithRowConsumer(query, (resultRow) -> { var fromId = resultRow.getNumber("from"); var toId = resultRow.getNumber("to"); + assertThat(fromId.longValue()).isNotEqualTo(toId.longValue()); assertThat(fromId.longValue()).isIn(expectedNode1, expectedNode2); assertThat(toId.longValue()).isIn(expectedNode1, expectedNode2); + assertThat(resultRow.get("remainingSizes")).isNotNull(); + }); assertThat(rowCount).isEqualTo(1l); } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesResultBuilderForStreamMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesResultBuilderForStreamMode.java index 29058e10f8..fa0e0b123a 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesResultBuilderForStreamMode.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesResultBuilderForStreamMode.java @@ -24,6 +24,7 @@ import org.neo4j.gds.applications.algorithms.machinery.StreamResultBuilder; import org.neo4j.gds.bridges.BridgeResult; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -42,7 +43,12 @@ public Stream build( return bridges .stream() - .map( b -> new BridgesStreamResult(graph.toOriginalNodeId(b.from()), graph.toOriginalNodeId(b.to()))); - + .map( b -> + new BridgesStreamResult( + graph.toOriginalNodeId(b.from()), + graph.toOriginalNodeId(b.to()), + List.of(b.remainingSizes()[0],b.remainingSizes()[1]) + ) + ); } } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/LocalCentralityProcedureFacade.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/LocalCentralityProcedureFacade.java index 0a160b0699..6d1a1b88b6 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/LocalCentralityProcedureFacade.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/LocalCentralityProcedureFacade.java @@ -671,7 +671,8 @@ public Stream bridgesStream( return streamModeBusinessFacade.bridges( GraphName.parse(graphName), parsedConfiguration, - resultBuilder + resultBuilder, + procedureReturnColumns.contains("remainingSizes") ); } @@ -680,6 +681,7 @@ public Stream bridgesStreamEstimate( Object graphNameOrConfiguration, Map algorithmConfiguration ) { + var parsedConfiguration = configurationParser.parseConfiguration( algorithmConfiguration, BridgesStreamConfig::of @@ -687,7 +689,8 @@ public Stream bridgesStreamEstimate( return Stream.of(estimationModeBusinessFacade.bridges( parsedConfiguration, - graphNameOrConfiguration + graphNameOrConfiguration, + true )); } diff --git a/procedures/facade-api/centrality-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesStreamResult.java b/procedures/facade-api/centrality-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesStreamResult.java index e05f208d1e..5d2bc4ac04 100644 --- a/procedures/facade-api/centrality-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesStreamResult.java +++ b/procedures/facade-api/centrality-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesStreamResult.java @@ -19,5 +19,7 @@ */ package org.neo4j.gds.procedures.algorithms.centrality; -public record BridgesStreamResult(long from, long to) { +import java.util.List; + +public record BridgesStreamResult(long from, long to, List remainingSizes) { }