diff --git a/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java b/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java
new file mode 100644
index 0000000000..65dbf1f538
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/Bridge.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public record Bridge(long from, long to) {
+
+ static Bridge create(long from, long to){
+ return new Bridge(Math.min(from,to), Math.max(from,to));
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/bridges/BridgeProgressTaskCreator.java b/algo/src/main/java/org/neo4j/gds/bridges/BridgeProgressTaskCreator.java
new file mode 100644
index 0000000000..f82da42547
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/BridgeProgressTaskCreator.java
@@ -0,0 +1,31 @@
+/*
+ * 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.api.Graph;
+import org.neo4j.gds.core.utils.progress.tasks.Task;
+import org.neo4j.gds.core.utils.progress.tasks.Tasks;
+
+public class BridgeProgressTaskCreator {
+
+ public static Task progressTask(Graph graph) {
+ return Tasks.leaf("Bridges", graph.nodeCount());
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/bridges/BridgeResult.java b/algo/src/main/java/org/neo4j/gds/bridges/BridgeResult.java
new file mode 100644
index 0000000000..1d53ee423f
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/BridgeResult.java
@@ -0,0 +1,25 @@
+/*
+ * 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 java.util.List;
+
+public record BridgeResult(List bridges){
+}
diff --git a/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java b/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java
new file mode 100644
index 0000000000..8866d72d5d
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/Bridges.java
@@ -0,0 +1,138 @@
+/*
+ * 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 com.carrotsearch.hppc.BitSet;
+import org.neo4j.gds.Algorithm;
+import org.neo4j.gds.api.Graph;
+import org.neo4j.gds.collections.ha.HugeLongArray;
+import org.neo4j.gds.collections.ha.HugeObjectArray;
+import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class Bridges extends Algorithm {
+
+ private final Graph graph;
+ private final BitSet visited;
+ private final HugeLongArray tin;
+ private final HugeLongArray low;
+ private long timer;
+ private long stackIndex = -1;
+ private List result = new ArrayList<>();
+
+ public Bridges(Graph graph, ProgressTracker progressTracker){
+ super(progressTracker);
+
+ this.graph = graph;
+ this.visited = new BitSet(graph.nodeCount());
+ this.tin = HugeLongArray.newArray(graph.nodeCount());
+ this.low = HugeLongArray.newArray(graph.nodeCount());
+ }
+
+ @Override
+ public BridgeResult compute() {
+ timer = 0;
+ visited.clear();
+ tin.setAll(__ -> -1);
+ low.setAll(__ -> -1);
+ progressTracker.beginSubTask("Bridges");
+ //each edge may have at most one event to the stack at the same time
+ var stack = HugeObjectArray.newArray(StackEvent.class, graph.relationshipCount());
+
+ var n = graph.nodeCount();
+ for (int i = 0; i < n; ++i) {
+ if (!visited.get(i))
+ dfs(i, stack);
+ }
+ progressTracker.endSubTask("Bridges");
+ return new BridgeResult(result);
+
+ }
+
+
+
+ private void dfs(long node, HugeObjectArray stack) {
+ stack.set(++stackIndex, StackEvent.upcomingVisit(node,-1));
+ while (stackIndex >= 0) {
+ var stackEvent = stack.get(stackIndex--);
+ visitEvent(stackEvent, stack);
+ }
+ progressTracker.logProgress();
+ }
+
+ private void visitEvent(StackEvent event, HugeObjectArray stack) {
+ if (event.lastVisit()) {
+ var to = event.eventNode();
+ var v = event.triggerNode();
+ var lowV = low.get(v);
+ var lowTo = low.get(to);
+ low.set(v, Math.min(lowV, lowTo));
+ var tinV = tin.get(v);
+ if (lowTo > tinV) {
+ result.add(new Bridge(v, to));
+ }
+ progressTracker.logProgress();
+ return;
+ }
+
+ if (!visited.get(event.eventNode())) {
+ var v = event.eventNode();
+ visited.set(v);
+ var p = event.triggerNode();
+ tin.set(v, timer);
+ low.set(v, timer++);
+ var parent_skipped = new AtomicBoolean(false);
+ ///add post event (Should be before everything)
+ if (p != -1) {
+ stack.set(++stackIndex, StackEvent.lastVisit(v, p));
+ }
+ graph.forEachRelationship(v, (s, to) -> {
+ if (to == p && !parent_skipped.get()) {
+ parent_skipped.set(true);
+ return true;
+ }
+ stack.set(++stackIndex, StackEvent.upcomingVisit(to, v));
+
+ return true;
+ });
+
+ } else {
+ long v = event.triggerNode();
+ long to = event.eventNode();
+ var lowV = low.get(v);
+ var tinTo = tin.get(to);
+ low.set(v, Math.min(lowV, tinTo));
+ }
+ }
+
+
+ public record StackEvent(long eventNode, long triggerNode, boolean lastVisit) {
+ static StackEvent upcomingVisit(long node, long triggerNode) {
+ return new StackEvent(node, triggerNode, false);
+ }
+
+ static StackEvent lastVisit(long node, long triggerNode) {
+ return new StackEvent(node, triggerNode, true);
+ }
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/bridges/BridgesBaseConfig.java b/algo/src/main/java/org/neo4j/gds/bridges/BridgesBaseConfig.java
new file mode 100644
index 0000000000..19046bdf64
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/BridgesBaseConfig.java
@@ -0,0 +1,50 @@
+/*
+ * 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.NodeLabel;
+import org.neo4j.gds.RelationshipType;
+import org.neo4j.gds.annotation.Configuration;
+import org.neo4j.gds.api.GraphStore;
+import org.neo4j.gds.config.AlgoBaseConfig;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.neo4j.gds.utils.StringFormatting.formatWithLocale;
+
+public interface BridgesBaseConfig extends AlgoBaseConfig {
+
+ @Configuration.GraphStoreValidationCheck
+ default void validateTargetRelIsUndirected(
+ GraphStore graphStore,
+ Collection ignored,
+ Collection selectedRelationshipTypes
+ ) {
+ if (!graphStore.schema().filterRelationshipTypes(Set.copyOf(selectedRelationshipTypes)).isUndirected()) {
+ throw new IllegalArgumentException(formatWithLocale(
+ "Bridges requires relationship projections to be UNDIRECTED. " +
+ "Selected relationships `%s` are not all undirected.",
+ selectedRelationshipTypes.stream().map(RelationshipType::name).collect(Collectors.toSet())
+ ));
+ }
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java b/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java
new file mode 100644
index 0000000000..12ae7e327e
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+import org.neo4j.gds.collections.ha.HugeObjectArray;
+import org.neo4j.gds.mem.Estimate;
+import org.neo4j.gds.mem.MemoryEstimateDefinition;
+import org.neo4j.gds.mem.MemoryEstimation;
+import org.neo4j.gds.mem.MemoryEstimations;
+import org.neo4j.gds.mem.MemoryRange;
+
+public class BridgesMemoryEstimateDefinition implements MemoryEstimateDefinition {
+ @Override
+ public MemoryEstimation memoryEstimation() {
+
+ var builder = MemoryEstimations.builder(Bridges.class);
+ builder
+ .perNode("tin", HugeLongArray::memoryEstimation)
+ .perNode("low", HugeLongArray::memoryEstimation)
+ .perNode("visited", Estimate::sizeOfBitset)
+ .perNode("bridge", (v)-> v * Estimate.sizeOfInstance(Bridge.class));
+
+ builder.rangePerGraphDimension("stack", ((graphDimensions, concurrency) -> {
+ long relationshipCount = graphDimensions.relCountUpperBound();
+ return MemoryRange.of(
+ HugeObjectArray.memoryEstimation(relationshipCount, Estimate.sizeOfInstance(Bridges.StackEvent.class))
+ );
+
+
+ }));
+
+ return builder.build();
+ }
+}
diff --git a/algo/src/main/java/org/neo4j/gds/bridges/BridgesStreamConfig.java b/algo/src/main/java/org/neo4j/gds/bridges/BridgesStreamConfig.java
new file mode 100644
index 0000000000..471a7cd55e
--- /dev/null
+++ b/algo/src/main/java/org/neo4j/gds/bridges/BridgesStreamConfig.java
@@ -0,0 +1,32 @@
+/*
+ * 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.annotation.Configuration;
+import org.neo4j.gds.core.CypherMapWrapper;
+
+@Configuration
+public interface BridgesStreamConfig extends BridgesBaseConfig {
+
+ static BridgesStreamConfig of(CypherMapWrapper userInput) {
+ return new BridgesStreamConfigImpl(userInput);
+ }
+
+}
diff --git a/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java b/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java
new file mode 100644
index 0000000000..ace03c7201
--- /dev/null
+++ b/algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.junit.jupiter.api.Test;
+import org.neo4j.gds.Orientation;
+import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
+import org.neo4j.gds.extension.GdlExtension;
+import org.neo4j.gds.extension.GdlGraph;
+import org.neo4j.gds.extension.Inject;
+import org.neo4j.gds.extension.TestGraph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@GdlExtension
+class BridgesLargestTest {
+
+ // https://upload.wikimedia.org/wikipedia/commons/d/df/Graph_cut_edges.svg
+ @GdlGraph(orientation = Orientation.UNDIRECTED, idOffset = 1)
+ private static final String GRAPH =
+
+ """
+ CREATE
+ (a1:Node),
+ (a2:Node),
+ (a3:Node),
+ (a4:Node),
+ (a5:Node),
+ (a6:Node),
+ (a7:Node),
+ (a8:Node),
+ (a9:Node),
+ (a10:Node),
+ (a11:Node),
+ (a12:Node),
+ (a13:Node),
+ (a14:Node),
+ (a15:Node),
+ (a16:Node),
+ (a1)-[:R]->(a2),
+ (a3)-[:R]->(a4),
+ (a3)-[:R]->(a7),
+ (a7)-[:R]->(a8),
+ (a5)-[:R]->(a9),
+ (a5)-[:R]->(a10),
+ (a9)-[:R]->(a10),
+ (a9)-[:R]->(a14),
+ (a10)-[:R]->(a11),
+ (a11)-[:R]->(a12),
+ (a10)-[:R]->(a14),
+ (a11)-[:R]->(a15),
+ (a12)-[:R]->(a16),
+ (a13)-[:R]->(a14),
+ (a15)-[:R]->(a16)
+ """;
+
+ @Inject
+ private TestGraph graph;
+
+ private Bridge bridge(String from, String to) {
+ return new Bridge(graph.toOriginalNodeId(from), graph.toOriginalNodeId(to));
+ }
+
+
+ @Test
+ void shouldFindAllBridges() {
+ var bridges = new Bridges(graph, ProgressTracker.NULL_TRACKER);
+
+ var result = bridges.compute().bridges().stream()
+ .map(b -> new Bridge(
+ graph.toOriginalNodeId(b.from()),
+ graph.toOriginalNodeId(b.to())
+ )).toList();
+
+
+ assertThat(result)
+ .isNotNull()
+ .containsExactlyInAnyOrder(
+ bridge("a1", "a2"),
+ bridge("a3", "a4"),
+ bridge("a3", "a7"),
+ bridge("a7", "a8"),
+ bridge("a10", "a11"),
+ bridge("a14", "a13")
+ );
+ }
+}
diff --git a/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java b/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java
new file mode 100644
index 0000000000..042c6f70e0
--- /dev/null
+++ b/algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.junit.jupiter.api.Test;
+import org.neo4j.gds.assertions.MemoryEstimationAssert;
+import org.neo4j.gds.core.concurrency.Concurrency;
+
+class BridgesMemoryEstimateDefinitionTest {
+
+ @Test
+ void shouldEstimateMemoryAccurately() {
+ var memoryEstimation = new BridgesMemoryEstimateDefinition().memoryEstimation();
+
+ MemoryEstimationAssert.assertThat(memoryEstimation)
+ .memoryRange(100, 6000, new Concurrency(1))
+ .hasMin(221056L)
+ .hasMax(221056L);
+ }
+}
diff --git a/algo/src/test/java/org/neo4j/gds/bridges/BridgesStreamConfigTest.java b/algo/src/test/java/org/neo4j/gds/bridges/BridgesStreamConfigTest.java
new file mode 100644
index 0000000000..371f0606ec
--- /dev/null
+++ b/algo/src/test/java/org/neo4j/gds/bridges/BridgesStreamConfigTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.junit.jupiter.api.Test;
+import org.neo4j.gds.Orientation;
+import org.neo4j.gds.RelationshipType;
+import org.neo4j.gds.api.GraphStore;
+import org.neo4j.gds.core.CypherMapWrapper;
+import org.neo4j.gds.extension.GdlExtension;
+import org.neo4j.gds.extension.GdlGraph;
+import org.neo4j.gds.extension.Inject;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+@GdlExtension
+class BridgesStreamConfigTest {
+
+ @GdlGraph(orientation = Orientation.NATURAL)
+ private static final String GRAPH =
+ """
+ CREATE
+ (a:N),
+ (b:N),
+ (a)-[:R]->(b)
+ """;
+
+ @Inject
+ private GraphStore graphStore;
+
+ @Test
+ void shouldRaiseAnExceptionIfGraphIsNotUndirected() {
+ var bridgesConfiguration = BridgesStreamConfig.of(CypherMapWrapper.empty());
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() ->
+ bridgesConfiguration.validateTargetRelIsUndirected(
+ graphStore,
+ List.of(),
+ List.of(RelationshipType.of("R"))
+ ))
+ .withMessageContaining("Bridges requires relationship projections to be UNDIRECTED.");
+ }
+}
diff --git a/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java b/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java
new file mode 100644
index 0000000000..6ce150081f
--- /dev/null
+++ b/algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.neo4j.gds.Orientation;
+import org.neo4j.gds.compat.TestLog;
+import org.neo4j.gds.core.concurrency.Concurrency;
+import org.neo4j.gds.core.utils.progress.EmptyTaskRegistryFactory;
+import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
+import org.neo4j.gds.core.utils.progress.tasks.TaskProgressTracker;
+import org.neo4j.gds.extension.GdlExtension;
+import org.neo4j.gds.extension.GdlGraph;
+import org.neo4j.gds.extension.Inject;
+import org.neo4j.gds.extension.TestGraph;
+import org.neo4j.gds.logging.GdsTestLog;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.neo4j.gds.assertj.Extractors.removingThreadId;
+import static org.neo4j.gds.assertj.Extractors.replaceTimings;
+
+@GdlExtension
+class SmallBridgesTest {
+
+ @GdlExtension
+ @Nested
+ class GraphWithBridges {
+ @GdlGraph(orientation = Orientation.UNDIRECTED)
+ private static final String GRAPH =
+ """
+ CREATE
+ (a:Node {id: 0}),
+ (b:Node {id: 1}),
+ (c:Node {id: 2}),
+ (d:Node {id: 3}),
+ (e:Node {id: 4}),
+ (a)-[:R]->(d),
+ (b)-[:R]->(a),
+ (c)-[:R]->(a),
+ (c)-[:R]->(b),
+ (d)-[:R]->(e)
+ """;
+
+ @Inject
+ private TestGraph graph;
+
+
+ @Test
+ void shouldFindBridges() {
+ var bridges = new Bridges(graph, ProgressTracker.NULL_TRACKER);
+ var result = bridges.compute().bridges();
+
+ assertThat(result)
+ .isNotNull()
+ .containsExactlyInAnyOrder(
+ Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d")),
+ Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e"))
+ );
+ }
+
+ @Test
+ void shouldLogProgress(){
+
+ var progressTask = BridgeProgressTaskCreator.progressTask(graph);
+ var log = new GdsTestLog();
+ var progressTracker = new TaskProgressTracker(progressTask, log, new Concurrency(1), EmptyTaskRegistryFactory.INSTANCE);
+
+ var bridges = new Bridges(graph, progressTracker);
+ bridges.compute();
+
+ Assertions.assertThat(log.getMessages(TestLog.INFO))
+ .extracting(removingThreadId())
+ .extracting(replaceTimings())
+ .containsExactly(
+ "Bridges :: Start",
+ "Bridges 20%",
+ "Bridges 40%",
+ "Bridges 60%",
+ "Bridges 80%",
+ "Bridges 100%",
+ "Bridges :: Finished"
+ );
+ }
+
+ }
+
+ @GdlExtension
+ @Nested
+ class GraphWithoutBridges {
+
+ @GdlGraph(orientation = Orientation.UNDIRECTED)
+ private static final String GRAPH =
+ """
+ CREATE
+ (a:Node {id: 0}),
+ (b:Node {id: 1}),
+ (c:Node {id: 2}),
+ (d:Node {id: 3}),
+ (e:Node {id: 4}),
+ (a)-[:R]->(d),
+ (a)-[:R]->(e),
+ (b)-[:R]->(a),
+ (c)-[:R]->(a),
+ (c)-[:R]->(b),
+ (d)-[:R]->(e)
+ """;
+
+ @Inject
+ private TestGraph graph;
+
+
+ @Test
+ void shouldFindBridges() {
+ var bridges = new Bridges(graph,ProgressTracker.NULL_TRACKER);
+ var result = bridges.compute().bridges();
+
+ assertThat(result)
+ .isNotNull()
+ .isEmpty();
+ }
+ }
+
+
+
+}
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 a955215779..ea3b5ef873 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
@@ -33,6 +33,10 @@
import org.neo4j.gds.betweenness.ForwardTraverser;
import org.neo4j.gds.betweenness.FullSelectionStrategy;
import org.neo4j.gds.betweenness.RandomDegreeSelectionStrategy;
+import org.neo4j.gds.bridges.BridgeProgressTaskCreator;
+import org.neo4j.gds.bridges.BridgeResult;
+import org.neo4j.gds.bridges.Bridges;
+import org.neo4j.gds.bridges.BridgesBaseConfig;
import org.neo4j.gds.closeness.ClosenessCentrality;
import org.neo4j.gds.closeness.ClosenessCentralityBaseConfig;
import org.neo4j.gds.closeness.ClosenessCentralityResult;
@@ -172,6 +176,16 @@ DegreeCentralityResult degreeCentrality(Graph graph, DegreeCentralityConfig conf
return algorithmMachinery.runAlgorithmsAndManageProgressTracker(algorithm, progressTracker, true);
}
+ BridgeResult bridges(Graph graph, BridgesBaseConfig configuration) {
+
+ var task = BridgeProgressTaskCreator.progressTask(graph);
+ var progressTracker = progressTrackerCreator.createProgressTracker(configuration, task);
+
+ var algorithm = new Bridges(graph,progressTracker);
+
+ return algorithmMachinery.runAlgorithmsAndManageProgressTracker(algorithm, progressTracker, true);
+ }
+
PageRankResult eigenVector(Graph graph, PageRankConfig configuration) {
return pagerank(graph, configuration, LabelForProgressTracking.EigenVector, EIGENVECTOR);
}
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 f78fb81861..7a3af25020 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
@@ -23,6 +23,8 @@
import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult;
import org.neo4j.gds.betweenness.BetweennessCentralityBaseConfig;
import org.neo4j.gds.betweenness.BetweennessCentralityMemoryEstimateDefinition;
+import org.neo4j.gds.bridges.BridgesBaseConfig;
+import org.neo4j.gds.bridges.BridgesMemoryEstimateDefinition;
import org.neo4j.gds.closeness.ClosenessCentralityBaseConfig;
import org.neo4j.gds.config.RelationshipWeightConfig;
import org.neo4j.gds.degree.DegreeCentralityAlgorithmEstimateDefinition;
@@ -57,6 +59,19 @@ public MemoryEstimateResult betweennessCentrality(
memoryEstimation
);
}
+ MemoryEstimation bridges() {
+ return new BridgesMemoryEstimateDefinition().memoryEstimation();
+ }
+
+ public MemoryEstimateResult bridges(BridgesBaseConfig configuration, Object graphNameOrConfiguration) {
+ var memoryEstimation = bridges();
+
+ return algorithmEstimationTemplate.estimate(
+ configuration,
+ graphNameOrConfiguration,
+ memoryEstimation
+ );
+ }
public MemoryEstimation celf(InfluenceMaximizationBaseConfig configuration) {
return new CELFMemoryEstimateDefinition(configuration.toParameters()).memoryEstimation();
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 8dc4eed08e..684e813192 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
@@ -24,6 +24,8 @@
import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTemplateConvenience;
import org.neo4j.gds.applications.algorithms.machinery.ResultBuilder;
import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig;
+import org.neo4j.gds.bridges.BridgeResult;
+import org.neo4j.gds.bridges.BridgesStreamConfig;
import org.neo4j.gds.closeness.ClosenessCentralityStreamConfig;
import org.neo4j.gds.degree.DegreeCentralityStreamConfig;
import org.neo4j.gds.harmonic.HarmonicCentralityStreamConfig;
@@ -34,6 +36,7 @@
import org.neo4j.gds.pagerank.PageRankStreamConfig;
import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticleRank;
+import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.BRIDGES;
import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.BetweennessCentrality;
import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.CELF;
import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ClosenessCentrality;
@@ -87,6 +90,21 @@ public RESULT betweennessCentrality(
);
}
+ public RESULT bridges(
+ GraphName graphName,
+ BridgesStreamConfig configuration,
+ ResultBuilder resultBuilder
+ ) {
+ return algorithmProcessingTemplateConvenience.processRegularAlgorithmInStatsOrStreamMode(
+ graphName,
+ configuration,
+ BRIDGES,
+ estimationFacade::bridges,
+ (graph, __) -> centralityAlgorithms.bridges(graph, configuration),
+ resultBuilder
+ );
+ }
+
public RESULT celf(
GraphName graphName,
InfluenceMaximizationStreamConfig configuration,
diff --git a/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/LabelForProgressTracking.java b/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/LabelForProgressTracking.java
index 49b5f0f031..3dd54cfdba 100644
--- a/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/LabelForProgressTracking.java
+++ b/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/LabelForProgressTracking.java
@@ -28,6 +28,7 @@ public enum LabelForProgressTracking {
BetaClosenessCentrality("Closeness Centrality (beta)"),
BetweennessCentrality("Betweenness Centrality"),
BFS("BFS"),
+ BRIDGES("Bridges"),
CELF("CELF"),
ClosenessCentrality("Closeness Centrality"),
CollapsePath("CollapsePath"),
diff --git a/doc-test/src/test/java/org/neo4j/gds/doc/BridgesDocTest.java b/doc-test/src/test/java/org/neo4j/gds/doc/BridgesDocTest.java
new file mode 100644
index 0000000000..6988cbd208
--- /dev/null
+++ b/doc-test/src/test/java/org/neo4j/gds/doc/BridgesDocTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.doc;
+
+import org.neo4j.gds.bridges.BridgesStreamProc;
+import org.neo4j.gds.functions.AsNodeFunc;
+
+import java.util.List;
+
+class BridgesDocTest extends SingleFileDocTestBase {
+
+ @Override
+ protected List> functions() {
+ return List.of(AsNodeFunc.class);
+ }
+
+ @Override
+ protected List> procedures() {
+ return List.of(
+ BridgesStreamProc.class
+ );
+ }
+
+ @Override
+ protected String adocFile() {
+ return "pages/algorithms/bridges.adoc";
+ }
+
+}
diff --git a/doc/modules/ROOT/content-nav.adoc b/doc/modules/ROOT/content-nav.adoc
index 58931cc763..90710f105d 100644
--- a/doc/modules/ROOT/content-nav.adoc
+++ b/doc/modules/ROOT/content-nav.adoc
@@ -58,6 +58,7 @@
** xref:algorithms/centrality.adoc[]
*** xref:algorithms/article-rank.adoc[]
*** xref:algorithms/betweenness-centrality.adoc[]
+*** xref:algorithms/bridges.adoc[]
*** xref:algorithms/celf.adoc[]
*** xref:algorithms/closeness-centrality.adoc[]
*** xref:algorithms/degree-centrality.adoc[]
diff --git a/doc/modules/ROOT/images/example-graphs/bridges.svg b/doc/modules/ROOT/images/example-graphs/bridges.svg
new file mode 100644
index 0000000000..f6222ac710
--- /dev/null
+++ b/doc/modules/ROOT/images/example-graphs/bridges.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/doc/modules/ROOT/pages/algorithms/bridges.adoc b/doc/modules/ROOT/pages/algorithms/bridges.adoc
new file mode 100644
index 0000000000..82fdae123f
--- /dev/null
+++ b/doc/modules/ROOT/pages/algorithms/bridges.adoc
@@ -0,0 +1,164 @@
+[[algorithms-bridges]]
+= Bridges
+:description: This section describes the Bridges algorithm in the Neo4j Graph Data Science library.
+:entity: relationship
+:result: bridge
+:algorithm: Bridges
+:sequential: true
+
+:undirected:
+include::partial$/algorithms/shared/algorithm-traits.adoc[]
+
+[[algorithms-bridges-intro]]
+== Introduction
+
+Given a graph, a bridge is a relationship whose removal increases the number of connected components in the graph.
+Equivalently, a relationship can only be a bridge if and only if it is not contained in any cycle.
+The Neo4j GDS Library provides an efficient linear time sequential algorithm to compute all bridges in a graph.
+
+For more information on this algorithm, see:
+
+* https://en.wikipedia.org/wiki/Bridge_(graph_theory)[Bridge (graph theory)^]
+
+
+[[algorithms-briges-syntax]]
+== Syntax
+
+include::partial$/algorithms/shared/syntax-intro-named-graph.adoc[]
+
+.Bridges syntax per mode
+[.tabbed-example, caption = ]
+====
+
+[.include-with-stream]
+======
+
+.Run Bridges in stream mode on a named graph.
+[source, cypher, role=noplay]
+----
+CALL gds.bridges.stream(
+ graphName: String,
+ configuration: Map
+)
+YIELD
+ from: Integer,
+ to: Integer
+----
+
+include::partial$/algorithms/common-configuration/common-parameters.adoc[]
+
+.Configuration
+[opts="header",cols="3,2,3m,2,8"]
+|===
+| Name | Type | Default | Optional | Description
+include::partial$/algorithms/common-configuration/common-stream-stats-configuration-entries.adoc[]
+|===
+
+.Results
+[opts="header"]
+|===
+| Name | Type | Description
+| from | Integer | Start node ID.
+| to | Integer | End node ID.
+|===
+
+======
+====
+
+
+[[algorithms-bridges-examples]]
+== Examples
+
+include::partial$/algorithms/shared/examples-named-native-note.adoc[]
+
+:algorithm-name: {algorithm}
+:graph-description: social network
+:image-file: bridges.svg
+include::partial$/algorithms/shared/examples-intro.adoc[]
+
+.The following Cypher statement will create the example graph in the Neo4j database:
+[source, cypher, role=noplay setup-query]
+----
+CREATE
+ (nAlice:User {name: 'Alice'}),
+ (nBridget:User {name: 'Bridget'}),
+ (nCharles:User {name: 'Charles'}),
+ (nDoug:User {name: 'Doug'}),
+ (nMark:User {name: 'Mark'}),
+ (nMichael:User {name: 'Michael'}),
+
+ (nAlice)-[:LINK]->(nBridget),
+ (nAlice)-[:LINK]->(nCharles),
+ (nCharles)-[:LINK]->(nBridget),
+
+ (nAlice)-[:LINK]->(nDoug),
+
+ (nMark)-[:LINK]->(nDoug),
+ (nMark)-[:LINK]->(nMichael),
+ (nMichael)-[:LINK]->(nDoug);
+----
+
+This graph has two clusters of _Users_, that are closely connected.
+Between those clusters there is one single edge.
+
+.The following statement will project a graph using a Cypher projection and store it in the graph catalog under the name 'myGraph'.
+[source, cypher, role=noplay graph-project-query]
+----
+MATCH (source:User)-[r:LINK]->(target:User)
+RETURN gds.graph.project(
+ 'myGraph',
+ source,
+ target,
+ {},
+ { undirectedRelationshipTypes: ['*'] }
+)
+----
+
+
+[[algorithms-bridges-examples-memory-estimation]]
+=== Memory Estimation
+
+:mode: stream
+include::partial$/algorithms/shared/examples-estimate-intro.adoc[]
+
+[role=query-example]
+--
+.The following will estimate the memory requirements for running the algorithm:
+[source, cypher, role=noplay]
+----
+CALL gds.bridges.stream.estimate('myGraph', {})
+YIELD nodeCount, relationshipCount, bytesMin, bytesMax, requiredMemory
+----
+
+.Results
+[opts="header",cols="1,1,1,1,1"]
+|===
+| nodeCount | relationshipCount | bytesMin | bytesMax | requiredMemory
+| 6 | 14 | 1040 | 1040 | "1040 Bytes"
+|===
+--
+
+
+[[algorithms-bridges-examples-stream]]
+=== Stream
+
+include::partial$/algorithms/shared/examples-stream-intro.adoc[]
+
+[role=query-example]
+--
+.The following will run the algorithm in `stream` mode:
+[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
+ORDER BY fromName ASC, toName ASC
+----
+
+.Results
+[opts="header",cols="1,1"]
+|===
+| fromName | toName
+| "Alice" | "Doug"
+|===
+--
diff --git a/doc/modules/ROOT/pages/algorithms/centrality.adoc b/doc/modules/ROOT/pages/algorithms/centrality.adoc
index 15e62b3817..de54fbddb6 100644
--- a/doc/modules/ROOT/pages/algorithms/centrality.adoc
+++ b/doc/modules/ROOT/pages/algorithms/centrality.adoc
@@ -9,6 +9,7 @@ The Neo4j GDS library includes the following centrality algorithms, grouped by q
* Production-quality
** xref:algorithms/article-rank.adoc[Article Rank]
** xref:algorithms/betweenness-centrality.adoc[Betweenness Centrality]
+** xref:algorithms/bridges.adoc[Bridges]
** xref:algorithms/celf.adoc[CELF]
** xref:algorithms/closeness-centrality.adoc[Closeness Centrality]
** xref:algorithms/degree-centrality.adoc[Degree Centrality]
diff --git a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc
index 6c7e6223cd..4451eff35e 100644
--- a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc
+++ b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc
@@ -61,6 +61,9 @@
| `gds.bfs.stream.estimate` label:procedure[Procedure]
| `gds.bfs.stats` label:procedure[Procedure]
| `gds.bfs.stats.estimate` label:procedure[Procedure]
+.2+<.^|xref:algorithms/bridges.adoc[Bridges]
+| `gds.bridges.stream` label:procedure[Procedure]
+| `gds.bridges.stream.estimate` label:procedure[Procedure]
.4+<.^|xref:algorithms/closeness-centrality.adoc[Closeness Centrality]
| `gds.closeness.mutate` label:procedure[Procedure]
| `gds.closeness.stats` label:procedure[Procedure]
diff --git a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java
index 756b8e832f..8933f0490e 100644
--- a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java
+++ b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java
@@ -63,6 +63,9 @@ class OpenGdsProcedureSmokeTest extends BaseProcTest {
"gds.bellmanFord.write",
"gds.bellmanFord.write.estimate",
+ "gds.bridges.stream",
+ "gds.bridges.stream.estimate",
+
"gds.collapsePath.mutate",
"gds.conductance.stream",
@@ -573,7 +576,7 @@ void countShouldMatch() {
);
// If you find yourself updating this count, please also update the count in SmokeTest.kt
- int expectedCount = 418;
+ int expectedCount = 420;
assertEquals(
expectedCount,
returnedRows,
diff --git a/proc/centrality/src/main/java/org/neo4j/gds/bridges/BridgesStreamProc.java b/proc/centrality/src/main/java/org/neo4j/gds/bridges/BridgesStreamProc.java
new file mode 100644
index 0000000000..8b2b301a23
--- /dev/null
+++ b/proc/centrality/src/main/java/org/neo4j/gds/bridges/BridgesStreamProc.java
@@ -0,0 +1,58 @@
+/*
+ * 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.applications.algorithms.machinery.MemoryEstimateResult;
+import org.neo4j.gds.procedures.GraphDataScienceProcedures;
+import org.neo4j.procedure.Context;
+import org.neo4j.procedure.Description;
+import org.neo4j.procedure.Name;
+import org.neo4j.procedure.Procedure;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.neo4j.gds.bridges.Constants.BRIDGES_DESCRIPTION;
+import static org.neo4j.gds.procedures.ProcedureConstants.MEMORY_ESTIMATION_DESCRIPTION;
+import static org.neo4j.procedure.Mode.READ;
+
+public class BridgesStreamProc {
+
+ @Context
+ public GraphDataScienceProcedures facade;
+
+ @Procedure(value = "gds.bridges.stream", mode = READ)
+ @Description(BRIDGES_DESCRIPTION)
+ public Stream stream(
+ @Name(value = "graphName") String graphName,
+ @Name(value = "configuration", defaultValue = "{}") Map configuration
+ ) {
+ return facade.algorithms().centrality().bridgesStream(graphName, configuration);
+ }
+
+ @Procedure(value = "gds.bridges.stream.estimate", mode = READ)
+ @Description(MEMORY_ESTIMATION_DESCRIPTION)
+ public Stream estimate(
+ @Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration,
+ @Name(value = "algoConfiguration") Map algoConfiguration
+ ) {
+ return facade.algorithms().centrality().bridgesStreamEstimate(graphNameOrConfiguration, algoConfiguration);
+ }
+}
diff --git a/proc/centrality/src/main/java/org/neo4j/gds/bridges/Constants.java b/proc/centrality/src/main/java/org/neo4j/gds/bridges/Constants.java
new file mode 100644
index 0000000000..2cff6a8b11
--- /dev/null
+++ b/proc/centrality/src/main/java/org/neo4j/gds/bridges/Constants.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+class Constants {
+ static final String BRIDGES_DESCRIPTION = "Bridges find relationships that disconnect components if removed";
+}
diff --git a/proc/centrality/src/test/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java
new file mode 100644
index 0000000000..9f1b9a8f4a
--- /dev/null
+++ b/proc/centrality/src/test/java/org/neo4j/gds/bridges/BridgesStreamProcTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.neo4j.gds.BaseProcTest;
+import org.neo4j.gds.GdsCypher;
+import org.neo4j.gds.Orientation;
+import org.neo4j.gds.catalog.GraphProjectProc;
+import org.neo4j.gds.extension.IdFunction;
+import org.neo4j.gds.extension.Inject;
+import org.neo4j.gds.extension.Neo4jGraph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class BridgesStreamProcTest extends BaseProcTest {
+
+ @Neo4jGraph
+ private static final String DB_CYPHER =
+ "CREATE" +
+ " (a:Node {name: 'a'})" +
+ ", (b:Node {name: 'b'})" +
+ ", (c:Node {name: 'c'})" +
+ ", (d:Node {name: 'd'})" +
+ ", (e:Node {name: 'e'})" +
+ ", (a)-[:REL]->(b)" +
+ ", (b)-[:REL]->(c)" +
+ ", (c)-[:REL]->(d)" +
+ ", (d)-[:REL]->(b)";
+
+
+ @Inject
+ private IdFunction idFunction;
+
+ @BeforeEach
+ void setup() throws Exception {
+ registerProcedures(
+ BridgesStreamProc.class,
+ GraphProjectProc.class
+ );
+
+ runQuery(
+ GdsCypher.call(DEFAULT_GRAPH_NAME)
+ .graphProject()
+ .loadEverything(Orientation.UNDIRECTED)
+ .yields()
+ );
+ }
+
+ @Test
+ void shouldStreamBackResults(){
+ var query = GdsCypher.call(DEFAULT_GRAPH_NAME)
+ .algo("gds.bridges")
+ .streamMode()
+ .yields();
+
+ var expectedFrom = idFunction.of("a");
+ var expectedTo = idFunction.of("b");
+
+ var rowCount = runQueryWithRowConsumer(query, (resultRow) -> {
+
+ var fromId = resultRow.getNumber("from");
+ var toId = resultRow.getNumber("to");
+
+ assertThat(fromId.longValue()).isEqualTo(expectedFrom);
+ assertThat(toId.longValue()).isEqualTo(expectedTo);
+
+ });
+ 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
new file mode 100644
index 0000000000..9daf95c81a
--- /dev/null
+++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/BridgesResultBuilderForStreamMode.java
@@ -0,0 +1,54 @@
+/*
+ * 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.procedures.algorithms.centrality;
+
+import org.neo4j.gds.api.Graph;
+import org.neo4j.gds.api.GraphStore;
+import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTimings;
+import org.neo4j.gds.applications.algorithms.machinery.ResultBuilder;
+import org.neo4j.gds.bridges.Bridge;
+import org.neo4j.gds.bridges.BridgeResult;
+import org.neo4j.gds.bridges.BridgesStreamConfig;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+class BridgesResultBuilderForStreamMode implements ResultBuilder, Void> {
+
+
+ @Override
+ public Stream build(
+ Graph graph,
+ GraphStore graphStore,
+ BridgesStreamConfig bridgesStreamConfig,
+ Optional result,
+ AlgorithmProcessingTimings timings,
+ Optional unused
+ ) {
+ if (result.isEmpty()) return Stream.empty();
+
+ var bridges = result.get().bridges();
+
+ return bridges
+ .stream()
+ .map( b -> new Bridge(graph.toOriginalNodeId(b.from()), graph.toOriginalNodeId(b.to())));
+
+ }
+}
diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/CentralityProcedureFacade.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/CentralityProcedureFacade.java
index aa92c65ffa..088c35ab81 100644
--- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/CentralityProcedureFacade.java
+++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/CentralityProcedureFacade.java
@@ -29,6 +29,8 @@
import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig;
import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig;
import org.neo4j.gds.betweenness.BetweennessCentralityWriteConfig;
+import org.neo4j.gds.bridges.Bridge;
+import org.neo4j.gds.bridges.BridgesStreamConfig;
import org.neo4j.gds.closeness.ClosenessCentralityStatsConfig;
import org.neo4j.gds.closeness.ClosenessCentralityStreamConfig;
import org.neo4j.gds.closeness.ClosenessCentralityWriteConfig;
@@ -387,6 +389,8 @@ public Stream betweennessCentralityStreamEstimate(
return Stream.of(result);
}
+
+
public Stream betweennessCentralityWrite(
String graphNameAsString,
Map rawConfiguration
@@ -419,6 +423,37 @@ public Stream betweennessCentralityWriteEstimate(
return Stream.of(result);
}
+ public Stream bridgesStream(
+ String graphName,
+ Map configuration
+ ) {
+ var resultBuilder = new BridgesResultBuilderForStreamMode();
+
+ return algorithmExecutionScaffoldingForStreamMode.runAlgorithm(
+ graphName,
+ configuration,
+ BridgesStreamConfig::of,
+ streamMode()::bridges,
+ resultBuilder
+ );
+ }
+
+ public Stream bridgesStreamEstimate(
+ Object graphNameOrConfiguration,
+ Map algorithmConfiguration
+ ) {
+ var result = estimationMode.runEstimation(
+ algorithmConfiguration,
+ BridgesStreamConfig::of,
+ configuration -> estimationMode().bridges(
+ configuration,
+ graphNameOrConfiguration
+ )
+ );
+
+ return Stream.of(result);
+ }
+
public CelfMutateStub celfMutateStub() {
return celfMutateStub;
}