From f115e66445b78804d953c066dc94e40e47caadbc Mon Sep 17 00:00:00 2001 From: Veselin Nikolov Date: Wed, 7 Aug 2024 13:27:56 +0100 Subject: [PATCH 1/6] Add ArticulationPoints algorithm Co-authored-by: Ioannis Panagiotas --- .../ArticulationPoints.java | 143 ++++++++++++++++++ .../ArticulationPointsLargerGraphTest.java | 91 +++++++++++ .../ArticulationPointsTest.java | 76 ++++++++++ 3 files changed, 310 insertions(+) create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java create mode 100644 algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsLargerGraphTest.java create mode 100644 algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsTest.java diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java new file mode 100644 index 0000000000..a5863926fb --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java @@ -0,0 +1,143 @@ +/* + * 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.articulationpoints; + +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; + +public class ArticulationPoints extends Algorithm { + private final Graph graph; + + private final BitSet visited; + private final HugeLongArray tin; + private final HugeLongArray low; + private final HugeLongArray children; + private long timer; + private long stackIndex = -1; + + private final BitSet articulationPoints; + + public ArticulationPoints(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()); + this.children = HugeLongArray.newArray(graph.nodeCount()); + + this.articulationPoints = new BitSet(graph.nodeCount()); + } + + @Override + public BitSet compute() { + timer = 0; + visited.clear(); + tin.setAll(__ -> -1); + low.setAll(__ -> -1); + progressTracker.beginSubTask("ArticulationPoints"); + //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("ArticulationPoints"); + return this.articulationPoints; + } + + 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); + } + if (children.get(node) > 1) { + articulationPoints.set(node); + } else { + articulationPoints.clear(node); + } + 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) { + articulationPoints.set(v); + } + children.addTo(v, 1); + progressTracker.logProgress(); + return; + } + + if (!visited.get(event.eventNode())) { + var v = event.eventNode(); + visited.set(v); + children.set(v, 0); + var p = event.triggerNode(); + tin.set(v, timer); + low.set(v, timer++); + ///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) { + 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)); + } + } + + + private 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/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsLargerGraphTest.java b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsLargerGraphTest.java new file mode 100644 index 0000000000..3502a7ea9c --- /dev/null +++ b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsLargerGraphTest.java @@ -0,0 +1,91 @@ +/* + * 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.articulationpoints; + +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 ArticulationPointsLargerGraphTest { + + // 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; + + @Test + void articulationPoints() { + var articulationPoints = new ArticulationPoints(graph, ProgressTracker.NULL_TRACKER); + var result = articulationPoints.compute(); + + assertThat(result.cardinality()) + .isEqualTo(5L); + + assertThat(result.get(graph.toMappedNodeId("a3"))).isTrue(); + assertThat(result.get(graph.toMappedNodeId("a7"))).isTrue(); + assertThat(result.get(graph.toMappedNodeId("a10"))).isTrue(); + assertThat(result.get(graph.toMappedNodeId("a11"))).isTrue(); + assertThat(result.get(graph.toMappedNodeId("a14"))).isTrue(); + } +} diff --git a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsTest.java b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsTest.java new file mode 100644 index 0000000000..83ab38628b --- /dev/null +++ b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsTest.java @@ -0,0 +1,76 @@ +/* + * 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.articulationpoints; + +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 ArticulationPointsTest { + + @GdlGraph(orientation = Orientation.UNDIRECTED) + private static String GRAPH = + """ + CREATE + (a:Node), + (b:Node), + (c:Node), + (d:Node), + (e:Node), + (f:Node), + + (a)-[:REL]->(c), + (c)-[:REL]->(b), + (d)-[:REL]->(a), + (e)-[:REL]->(b), + (e)-[:REL]->(c), + (f)-[:REL]->(a), + (f)-[:REL]->(c), + (f)-[:REL]->(d) + """; + + @Inject + private TestGraph graph; + + @Test + void articulationPoints() { + + var articulationPoints = new ArticulationPoints(graph, ProgressTracker.NULL_TRACKER); + var result = articulationPoints.compute(); + + assertThat(result) + .isNotNull() + .satisfies(bitSet -> { + assertThat(bitSet.get(graph.toMappedNodeId("c"))) + .as("Node `c` should be an articulation point.") + .isTrue(); + assertThat(bitSet.cardinality()) + .as("There should be only one articulation point.") + .isEqualTo(1L); + }); + } +} From faf116d854c3178f305355ea934ba6ba7623038b Mon Sep 17 00:00:00 2001 From: Veselin Nikolov Date: Wed, 7 Aug 2024 14:19:08 +0100 Subject: [PATCH 2/6] Add stream procedure for Articulation Points Co-authored-by: Ioannis Panagiotas --- .../ArticulationPoints.java | 2 +- .../ArticulationPointsBaseConfig.java | 50 ++++++ ...ulationPointsMemoryEstimateDefinition.java | 54 ++++++ ...ArticulationPointsProgressTaskCreator.java | 32 ++++ .../ArticulationPointsStreamConfig.java | 36 ++++ ...ionPointsMemoryEstimateDefinitionTest.java | 35 ++++ .../ArticulationPointsStreamConfigTest.java | 64 +++++++ .../centrality/CentralityAlgorithms.java | 19 ++- ...lgorithmsEstimationModeBusinessFacade.java | 21 +++ ...ityAlgorithmsStreamModeBusinessFacade.java | 20 ++- .../gds/doc/ArticulationPointsDocTest.java | 46 +++++ doc/modules/ROOT/content-nav.adoc | 1 + .../pages/algorithms/articulation-points.adoc | 158 ++++++++++++++++++ .../ROOT/pages/algorithms/centrality.adoc | 1 + .../algorithm-references.adoc | 3 + .../neo4j/gds/OpenGdsProcedureSmokeTest.java | 5 +- .../ArticulationPointsStreamProc.java | 60 +++++++ .../gds/articulationpoints/Constants.java | 26 +++ .../ArticulationPointsStreamProcTest.java | 114 +++++++++++++ .../centrality/ArticulationPoint.java | 23 +++ ...ationPointsResultBuilderForStreamMode.java | 55 ++++++ .../centrality/CentralityProcedureFacade.java | 31 ++++ 22 files changed, 849 insertions(+), 7 deletions(-) create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfig.java create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsProgressTaskCreator.java create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfig.java create mode 100644 algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java create mode 100644 algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java create mode 100644 doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java create mode 100644 doc/modules/ROOT/pages/algorithms/articulation-points.adoc create mode 100644 proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java create mode 100644 proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/Constants.java create mode 100644 proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProcTest.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPoint.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStreamMode.java diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java index a5863926fb..47846031b8 100644 --- a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPoints.java @@ -131,7 +131,7 @@ private void visitEvent(StackEvent event, HugeObjectArray stack) { } - private record StackEvent(long eventNode, long triggerNode, boolean lastVisit) { + record StackEvent(long eventNode, long triggerNode, boolean lastVisit) { static StackEvent upcomingVisit(long node, long triggerNode) { return new StackEvent(node, triggerNode, false); } diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfig.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfig.java new file mode 100644 index 0000000000..f02e2545ea --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfig.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.articulationpoints; + +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 ArticulationPointsBaseConfig extends AlgoBaseConfig { + + @Configuration.GraphStoreValidationCheck + default void requireUndirectedGraph( + GraphStore graphStore, + Collection ignored, + Collection selectedRelationshipTypes + ) { + if (!graphStore.schema().filterRelationshipTypes(Set.copyOf(selectedRelationshipTypes)).isUndirected()) { + throw new IllegalArgumentException(formatWithLocale( + "Articulation Points algorithm 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/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java new file mode 100644 index 0000000000..97c22a2ac9 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.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.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; +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 ArticulationPointsMemoryEstimateDefinition implements MemoryEstimateDefinition { + @Override + public MemoryEstimation memoryEstimation() { + + var builder = MemoryEstimations.builder(Bridges.class); + builder + .perNode("tin", HugeLongArray::memoryEstimation) + .perNode("low", HugeLongArray::memoryEstimation) + .perNode("children", HugeLongArray::memoryEstimation) + .perNode("visited", Estimate::sizeOfBitset) + .perNode("articulationPoints", Estimate::sizeOfBitset); + + builder.rangePerGraphDimension("stack", ((graphDimensions, concurrency) -> { + long relationshipCount = graphDimensions.relCountUpperBound(); + return MemoryRange.of( + HugeObjectArray.memoryEstimation(relationshipCount, Estimate.sizeOfInstance(ArticulationPoints.StackEvent.class)) + ); + + + })); + + return builder.build(); + } +} diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsProgressTaskCreator.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsProgressTaskCreator.java new file mode 100644 index 0000000000..c5d6b1b3f9 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsProgressTaskCreator.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.articulationpoints; + +import org.neo4j.gds.core.utils.progress.tasks.Task; +import org.neo4j.gds.core.utils.progress.tasks.Tasks; + +public final class ArticulationPointsProgressTaskCreator { + + private ArticulationPointsProgressTaskCreator() {} + + public static Task progressTask(long nodeCount) { + return Tasks.leaf("ArticulationPoints", nodeCount); + } +} diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfig.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfig.java new file mode 100644 index 0000000000..129c76d664 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfig.java @@ -0,0 +1,36 @@ +/* + * 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.articulationpoints; + +import org.neo4j.gds.annotation.Configuration; +import org.neo4j.gds.core.CypherMapWrapper; + +import java.util.Map; + +@Configuration +public interface ArticulationPointsStreamConfig extends ArticulationPointsBaseConfig { + static ArticulationPointsStreamConfig of(CypherMapWrapper cypherMapWrapper) { + return new ArticulationPointsStreamConfigImpl(cypherMapWrapper); + } + + static ArticulationPointsStreamConfig of(Map rawMapConfiguration) { + return of(CypherMapWrapper.create(rawMapConfiguration)); + } +} diff --git a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java new file mode 100644 index 0000000000..2c14d75581 --- /dev/null +++ b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java @@ -0,0 +1,35 @@ +/* + * 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.articulationpoints; + +import org.junit.jupiter.api.Test; +import org.neo4j.gds.assertions.MemoryEstimationAssert; +import org.neo4j.gds.core.concurrency.Concurrency; + +class ArticulationPointsMemoryEstimateDefinitionTest { + @Test + void shouldEstimateMemoryAccurately() { + var memoryEstimation = new ArticulationPointsMemoryEstimateDefinition().memoryEstimation(); + + MemoryEstimationAssert.assertThat(memoryEstimation) + .memoryRange(100, 6000, new Concurrency(1)) + .hasSameMinAndMaxEqualTo(218752); + } +} diff --git a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java new file mode 100644 index 0000000000..ca449d0d98 --- /dev/null +++ b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java @@ -0,0 +1,64 @@ +/* + * 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.articulationpoints; + +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 ArticulationPointsStreamConfigTest { + + @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 articulationPointsConfiguration = ArticulationPointsStreamConfig.of(CypherMapWrapper.empty()); + + assertThatIllegalArgumentException() + .isThrownBy(() -> + articulationPointsConfiguration.requireUndirectedGraph( + graphStore, + List.of(), + List.of(RelationshipType.of("R")) + )) + .withMessageContaining("Articulation Points") + .withMessageContaining("requires relationship projections to be UNDIRECTED."); + } +} 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 03a921d357..8cd9692775 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 @@ -19,12 +19,15 @@ */ package org.neo4j.gds.applications.algorithms.centrality; +import com.carrotsearch.hppc.BitSet; import com.carrotsearch.hppc.LongScatterSet; import org.neo4j.gds.Orientation; import org.neo4j.gds.api.Graph; import org.neo4j.gds.applications.algorithms.machinery.AlgorithmMachinery; import org.neo4j.gds.applications.algorithms.machinery.ProgressTrackerCreator; import org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking; +import org.neo4j.gds.articulationpoints.ArticulationPoints; +import org.neo4j.gds.articulationpoints.ArticulationPointsProgressTaskCreator; import org.neo4j.gds.beta.pregel.Pregel; import org.neo4j.gds.beta.pregel.PregelComputation; import org.neo4j.gds.betweenness.BetweennessCentrality; @@ -36,12 +39,12 @@ 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; import org.neo4j.gds.closeness.DefaultCentralityComputer; import org.neo4j.gds.closeness.WassermanFaustCentralityComputer; +import org.neo4j.gds.config.AlgoBaseConfig; import org.neo4j.gds.core.concurrency.Concurrency; import org.neo4j.gds.core.concurrency.DefaultPool; import org.neo4j.gds.core.concurrency.ParallelUtil; @@ -177,12 +180,22 @@ DegreeCentralityResult degreeCentrality(Graph graph, DegreeCentralityConfig conf } - BridgeResult bridges(Graph graph, BridgesBaseConfig configuration) { + BitSet articulationPoints(Graph graph, AlgoBaseConfig configuration) { + + var task = ArticulationPointsProgressTaskCreator.progressTask(graph.nodeCount()); + var progressTracker = progressTrackerCreator.createProgressTracker(configuration, task); + + var algorithm = new ArticulationPoints(graph, progressTracker); + + return algorithmMachinery.runAlgorithmsAndManageProgressTracker(algorithm, progressTracker, true); + } + + BridgeResult bridges(Graph graph, AlgoBaseConfig configuration) { var task = BridgeProgressTaskCreator.progressTask(graph.nodeCount()); var progressTracker = progressTrackerCreator.createProgressTracker(configuration, task); - var algorithm = new Bridges(graph,progressTracker); + var algorithm = new Bridges(graph, progressTracker); return algorithmMachinery.runAlgorithmsAndManageProgressTracker(algorithm, progressTracker, true); } 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 7a3af25020..6add8bb12c 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 @@ -21,6 +21,8 @@ import org.neo4j.gds.applications.algorithms.machinery.AlgorithmEstimationTemplate; import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.articulationpoints.ArticulationPointsMemoryEstimateDefinition; +import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityBaseConfig; import org.neo4j.gds.betweenness.BetweennessCentralityMemoryEstimateDefinition; import org.neo4j.gds.bridges.BridgesBaseConfig; @@ -43,6 +45,24 @@ public CentralityAlgorithmsEstimationModeBusinessFacade(AlgorithmEstimationTempl this.algorithmEstimationTemplate = algorithmEstimationTemplate; } + public MemoryEstimation articulationPoints() { + return new ArticulationPointsMemoryEstimateDefinition().memoryEstimation(); + } + + public MemoryEstimateResult articulationPoints( + ArticulationPointsStreamConfig configuration, + Object graphNameOrConfiguration + ) { + var memoryEstimation = articulationPoints(); + + return algorithmEstimationTemplate.estimate( + configuration, + graphNameOrConfiguration, + memoryEstimation + ); + } + + public MemoryEstimation betweennessCentrality(RelationshipWeightConfig configuration) { return new BetweennessCentralityMemoryEstimateDefinition(configuration.hasRelationshipWeightProperty()).memoryEstimation(); } @@ -125,4 +145,5 @@ public MemoryEstimateResult pageRank(PageRankConfig configuration, Object graphN 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 684e813192..a5ff3eb399 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 @@ -19,10 +19,12 @@ */ package org.neo4j.gds.applications.algorithms.centrality; +import com.carrotsearch.hppc.BitSet; import org.neo4j.gds.algorithms.centrality.CentralityAlgorithmResult; import org.neo4j.gds.api.GraphName; import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTemplateConvenience; import org.neo4j.gds.applications.algorithms.machinery.ResultBuilder; +import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig; import org.neo4j.gds.bridges.BridgeResult; import org.neo4j.gds.bridges.BridgesStreamConfig; @@ -36,6 +38,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.ArticulationPoints; 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; @@ -89,10 +92,23 @@ public RESULT betweennessCentrality( resultBuilder ); } - + public RESULT articulationPoints( + GraphName graphName, + ArticulationPointsStreamConfig configuration, + ResultBuilder resultBuilder + ) { + return algorithmProcessingTemplateConvenience.processRegularAlgorithmInStatsOrStreamMode( + graphName, + configuration, + ArticulationPoints, + estimationFacade::articulationPoints, + (graph, __) -> centralityAlgorithms.articulationPoints(graph, configuration), + resultBuilder + ); + } public RESULT bridges( GraphName graphName, - BridgesStreamConfig configuration, + BridgesStreamConfig configuration, ResultBuilder resultBuilder ) { return algorithmProcessingTemplateConvenience.processRegularAlgorithmInStatsOrStreamMode( diff --git a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java new file mode 100644 index 0000000000..c58641a004 --- /dev/null +++ b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.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.articulationpoints.ArticulationPointsStreamProc; +import org.neo4j.gds.functions.AsNodeFunc; + +import java.util.List; + +class ArticulationPointsDocTest extends SingleFileDocTestBase { + + @Override + protected List> functions() { + return List.of(AsNodeFunc.class); + } + + @Override + protected List> procedures() { + return List.of( + ArticulationPointsStreamProc.class + ); + } + + @Override + protected String adocFile() { + return "pages/algorithms/articulation-points.adoc"; + } + +} diff --git a/doc/modules/ROOT/content-nav.adoc b/doc/modules/ROOT/content-nav.adoc index 90710f105d..04132ae9d7 100644 --- a/doc/modules/ROOT/content-nav.adoc +++ b/doc/modules/ROOT/content-nav.adoc @@ -57,6 +57,7 @@ ** xref:algorithms/syntax.adoc[] ** xref:algorithms/centrality.adoc[] *** xref:algorithms/article-rank.adoc[] +*** xref:algorithms/articulation-points.adoc[] *** xref:algorithms/betweenness-centrality.adoc[] *** xref:algorithms/bridges.adoc[] *** xref:algorithms/celf.adoc[] diff --git a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc new file mode 100644 index 0000000000..413868ac43 --- /dev/null +++ b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc @@ -0,0 +1,158 @@ +[[algorithms-articulation-points]] += Bridges +:description: This section describes the Articulation Points algorithm in the Neo4j Graph Data Science library. +:entity: relationship +:result: node +:algorithm: Articulation Points +:sequential: true + +:undirected: +include::partial$/algorithms/shared/algorithm-traits.adoc[] + +[[algorithms-articulation-points-intro]] +== Introduction + +Given a graph, an articulation point is a node whose removal increases the number of connected components in the graph. +The Neo4j GDS Library provides an efficient linear time sequential algorithm to compute all articulation points in a graph. + + +[[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.articulationPoints.stream( + graphName: String, + configuration: Map +) +YIELD + nodeId: 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 +| nodeId | Integer | The ID of the node representing an articulation point. +|=== + +====== +==== + + +[[algorithms-articulation-points-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-articulation-points-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.articulationPoints.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 | 984 | 984 | "984 Bytes" +|=== +-- + + +[[algorithms-articulation-points-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.articulationPoints.stream('myGraph') +YIELD nodeId +RETURN gds.util.asNode(nodeId).name AS name +ORDER BY name ASC +---- + +.Results +[opts="header"] +|=== +| name +| "Alice" +| "Doug" +|=== +-- diff --git a/doc/modules/ROOT/pages/algorithms/centrality.adoc b/doc/modules/ROOT/pages/algorithms/centrality.adoc index de54fbddb6..a12eb290ba 100644 --- a/doc/modules/ROOT/pages/algorithms/centrality.adoc +++ b/doc/modules/ROOT/pages/algorithms/centrality.adoc @@ -8,6 +8,7 @@ The Neo4j GDS library includes the following centrality algorithms, grouped by q * Production-quality ** xref:algorithms/article-rank.adoc[Article Rank] +** xref:algorithms/articulation-points.adoc[Articulation Points] ** xref:algorithms/betweenness-centrality.adoc[Betweenness Centrality] ** xref:algorithms/bridges.adoc[Bridges] ** xref:algorithms/celf.adoc[CELF] diff --git a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc index 4451eff35e..1c6cc6938c 100644 --- a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc +++ b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc @@ -36,6 +36,9 @@ | `gds.articleRank.stream.estimate` label:procedure[Procedure] | `gds.articleRank.stats` label:procedure[Procedure] | `gds.articleRank.stats.estimate` label:procedure[Procedure] +.2+<.^|xref:algorithms/articulation-points.adoc[Articulation Points] +| `gds.articulationPoints.stream` label:procedure[Procedure] +| `gds.articulationPoints.stream.estimate` label:procedure[Procedure] .8+<.^| xref:algorithms/bellman-ford-single-source.adoc[Bellman-Ford] | `gds.bellmanFord.mutate` label:procedure[Procedure] | `gds.bellmanFord.mutate.estimate` 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 8933f0490e..8e858212da 100644 --- a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java +++ b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java @@ -54,6 +54,9 @@ class OpenGdsProcedureSmokeTest extends BaseProcTest { "gds.allShortestPaths.stream", + "gds.articulationPoints.stream", + "gds.articulationPoints.stream.estimate", + "gds.bellmanFord.stats", "gds.bellmanFord.stats.estimate", "gds.bellmanFord.stream", @@ -576,7 +579,7 @@ void countShouldMatch() { ); // If you find yourself updating this count, please also update the count in SmokeTest.kt - int expectedCount = 420; + int expectedCount = 422; assertEquals( expectedCount, returnedRows, diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java new file mode 100644 index 0000000000..cd49bfb8e0 --- /dev/null +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java @@ -0,0 +1,60 @@ +/* + * 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.articulationpoints; + +import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.procedures.GraphDataScienceProcedures; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPoint; +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.articulationpoints.Constants.PROC_DESCRIPTION; +import static org.neo4j.gds.procedures.ProcedureConstants.MEMORY_ESTIMATION_DESCRIPTION; +import static org.neo4j.procedure.Mode.READ; + +public class ArticulationPointsStreamProc { + + @Context + public GraphDataScienceProcedures facade; + + @Procedure(value = "gds.articulationPoints.stream", mode = READ) + @Description(PROC_DESCRIPTION) + public Stream stream( + @Name(value = "graphName") String graphName, + @Name(value = "configuration", defaultValue = "{}") Map configuration + ) { + return facade.algorithms().centrality().articulationPoints(graphName, configuration); + } + + @Procedure(value = "gds.articulationPoints.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().articulationPointsStreamEstimate(graphNameOrConfiguration, algoConfiguration); + } + +} diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/Constants.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/Constants.java new file mode 100644 index 0000000000..31f49634b1 --- /dev/null +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/Constants.java @@ -0,0 +1,26 @@ +/* + * 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.articulationpoints; + +public final class Constants { + public static final String PROC_DESCRIPTION = "Algorithm that finds nodes that disconnect components if removed"; + + private Constants() {} +} diff --git a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProcTest.java new file mode 100644 index 0000000000..2267ad9a2b --- /dev/null +++ b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProcTest.java @@ -0,0 +1,114 @@ +/* + * 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.articulationpoints; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.neo4j.gds.BaseTest; +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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LONG; +import static org.neo4j.gds.compat.GraphDatabaseApiProxy.registerProcedures; + +class ArticulationPointsStreamProcTest extends BaseTest { + + @Neo4jGraph + private static final String DB_CYPHER = + """ + 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 IdFunction idFunction; + + @BeforeEach + void setup() throws Exception { + registerProcedures( + db, + ArticulationPointsStreamProc.class, + GraphProjectProc.class + ); + + runQuery("CALL gds.graph.project('graph', 'Node', {R: {orientation: 'UNDIRECTED'}})"); + } + + @Test + void shouldStreamBackResults() { + var expectedArticulationPoints = List.of( + idFunction.of("a3"), + idFunction.of("a7"), + idFunction.of("a10"), + idFunction.of("a11"), + idFunction.of("a14") + ); + + var resultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.stream('graph')", + row -> { + var nodeId = row.getNumber("nodeId"); + assertThat(nodeId) + .asInstanceOf(LONG) + .isIn(expectedArticulationPoints); + } + ); + + assertThat(resultRowCount) + .as("There should be five articulation points.") + .isEqualTo(5L); + } + +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPoint.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPoint.java new file mode 100644 index 0000000000..3b14df4aec --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPoint.java @@ -0,0 +1,23 @@ +/* + * 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; + +public record ArticulationPoint(long nodeId) { +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStreamMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStreamMode.java new file mode 100644 index 0000000000..1451a1192f --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStreamMode.java @@ -0,0 +1,55 @@ +/* + * 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 com.carrotsearch.hppc.BitSet; +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.articulationpoints.ArticulationPointsStreamConfig; + +import java.util.Optional; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +class ArticulationPointsResultBuilderForStreamMode implements ResultBuilder, Void> { + + @Override + public Stream build( + Graph graph, + GraphStore graphStore, + ArticulationPointsStreamConfig bridgesStreamConfig, + Optional result, + AlgorithmProcessingTimings timings, + Optional unused + ) { + if (result.isEmpty()) return Stream.empty(); + + var bridges = result.get(); + + var nodeCount = graph.nodeCount(); + return LongStream.range(0, nodeCount) + .filter(bridges::get) + .map(graph::toOriginalNodeId) + .mapToObj(ArticulationPoint::new); + + } +} 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 a1597084f5..a2fe161393 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 @@ -26,6 +26,7 @@ import org.neo4j.gds.applications.algorithms.centrality.CentralityAlgorithmsStreamModeBusinessFacade; import org.neo4j.gds.applications.algorithms.centrality.CentralityAlgorithmsWriteModeBusinessFacade; import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityWriteConfig; @@ -424,6 +425,36 @@ public Stream betweennessCentralityWriteEstimate( } + public Stream articulationPoints( + String graphName, + Map configuration + ) { + var resultBuilder = new ArticulationPointsResultBuilderForStreamMode(); + + return algorithmExecutionScaffoldingForStreamMode.runAlgorithm( + graphName, + configuration, + ArticulationPointsStreamConfig::of, + streamMode()::articulationPoints, + resultBuilder + ); + } + + public Stream articulationPointsStreamEstimate( + Object graphNameOrConfiguration, + Map algorithmConfiguration + ) { + var result = estimationMode.runEstimation( + algorithmConfiguration, + ArticulationPointsStreamConfig::of, + configuration -> estimationMode().articulationPoints( + configuration, + graphNameOrConfiguration + ) + ); + + return Stream.of(result); } + public Stream bridgesStream( String graphName, Map configuration From 28c71d99f88b60a1abf9d87c8eb2267899140853 Mon Sep 17 00:00:00 2001 From: ioannispan Date: Thu, 8 Aug 2024 11:48:56 +0200 Subject: [PATCH 3/6] Add mutate procedure for Articulation Points Co-authored-by: Veselin Nikolov --- .../ArticulationPointsMutateConfig.java | 37 +++++ .../ArticulationPointsMutateStep.java | 69 +++++++++ ...lgorithmsEstimationModeBusinessFacade.java | 4 +- ...ityAlgorithmsMutateModeBusinessFacade.java | 22 +++ .../algorithms/metadata/Algorithm.java | 1 + .../metadata/LabelForProgressTracking.java | 2 + .../gds/doc/ArticulationPointsDocTest.java | 4 +- .../pages/algorithms/articulation-points.adoc | 73 +++++++++- .../ConfigurationParsersForMutateMode.java | 2 + .../pipeline/MutateModeAlgorithmLibrary.java | 1 + .../neo4j/gds/ml/pipeline/StubbyHolder.java | 2 + .../stubs/ArticulationPointsStub.java | 31 +++++ .../ArticulationPointsMutateProc.java | 60 ++++++++ .../ArticulationPointsMutateProcTest.java | 131 ++++++++++++++++++ .../ArticulationPointsMutateResult.java | 76 ++++++++++ .../centrality/CentralityProcedureFacade.java | 29 ++++ .../stubs/ArticulationPointsMutateStub.java | 87 ++++++++++++ ...ationPointsResultBuilderForMutateMode.java | 57 ++++++++ 18 files changed, 682 insertions(+), 6 deletions(-) create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateConfig.java create mode 100644 applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsMutateStep.java create mode 100644 pipeline/src/main/java/org/neo4j/gds/ml/pipeline/stubs/ArticulationPointsStub.java create mode 100644 proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java create mode 100644 proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsMutateStub.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateConfig.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateConfig.java new file mode 100644 index 0000000000..4a13c6f8f0 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateConfig.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.articulationpoints; + +import org.neo4j.gds.annotation.Configuration; +import org.neo4j.gds.config.MutateNodePropertyConfig; +import org.neo4j.gds.core.CypherMapWrapper; + +import java.util.Map; + +@Configuration +public interface ArticulationPointsMutateConfig extends ArticulationPointsBaseConfig, MutateNodePropertyConfig { + static ArticulationPointsMutateConfig of(CypherMapWrapper cypherMapWrapper) { + return new ArticulationPointsMutateConfigImpl(cypherMapWrapper); + } + + static ArticulationPointsMutateConfig of(Map rawMapConfiguration) { + return of(CypherMapWrapper.create(rawMapConfiguration)); + } +} diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsMutateStep.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsMutateStep.java new file mode 100644 index 0000000000..89d31aa1f5 --- /dev/null +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsMutateStep.java @@ -0,0 +1,69 @@ +/* + * 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.applications.algorithms.centrality; + +import com.carrotsearch.hppc.BitSet; +import org.neo4j.gds.api.Graph; +import org.neo4j.gds.api.GraphStore; +import org.neo4j.gds.api.ResultStore; +import org.neo4j.gds.api.properties.nodes.LongNodePropertyValues; +import org.neo4j.gds.applications.algorithms.machinery.MutateNodeProperty; +import org.neo4j.gds.applications.algorithms.machinery.MutateOrWriteStep; +import org.neo4j.gds.applications.algorithms.metadata.NodePropertiesWritten; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; +import org.neo4j.gds.core.utils.progress.JobId; + +class ArticulationPointsMutateStep implements MutateOrWriteStep { + private final MutateNodeProperty mutateNodeProperty; + private final ArticulationPointsMutateConfig configuration; + + ArticulationPointsMutateStep(MutateNodeProperty mutateNodeProperty, ArticulationPointsMutateConfig configuration) { + this.mutateNodeProperty = mutateNodeProperty; + this.configuration = configuration; + } + + @Override + public NodePropertiesWritten execute( + Graph graph, + GraphStore graphStore, + ResultStore resultStore, + BitSet result, + JobId jobId + ) { + var nodeProperties = new LongNodePropertyValues() { + @Override + public long longValue(long nodeId) { + return result.get(nodeId) ? 1 : 0; + } + + @Override + public long nodeCount() { + return graph.nodeCount(); + } + }; + + return mutateNodeProperty.mutateNodeProperties( + graph, + graphStore, + configuration, + nodeProperties + ); + } +} 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 6add8bb12c..cc04548354 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 @@ -21,8 +21,8 @@ import org.neo4j.gds.applications.algorithms.machinery.AlgorithmEstimationTemplate; import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.articulationpoints.ArticulationPointsBaseConfig; import org.neo4j.gds.articulationpoints.ArticulationPointsMemoryEstimateDefinition; -import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityBaseConfig; import org.neo4j.gds.betweenness.BetweennessCentralityMemoryEstimateDefinition; import org.neo4j.gds.bridges.BridgesBaseConfig; @@ -50,7 +50,7 @@ public MemoryEstimation articulationPoints() { } public MemoryEstimateResult articulationPoints( - ArticulationPointsStreamConfig configuration, + ArticulationPointsBaseConfig configuration, Object graphNameOrConfiguration ) { var memoryEstimation = articulationPoints(); diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsMutateModeBusinessFacade.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsMutateModeBusinessFacade.java index cd049755f5..71da216fbf 100644 --- a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsMutateModeBusinessFacade.java +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsMutateModeBusinessFacade.java @@ -19,11 +19,13 @@ */ package org.neo4j.gds.applications.algorithms.centrality; +import com.carrotsearch.hppc.BitSet; import org.neo4j.gds.api.GraphName; import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTemplateConvenience; import org.neo4j.gds.applications.algorithms.machinery.MutateNodeProperty; import org.neo4j.gds.applications.algorithms.machinery.ResultBuilder; import org.neo4j.gds.applications.algorithms.metadata.NodePropertiesWritten; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; import org.neo4j.gds.betweenness.BetweennessCentralityMutateConfig; import org.neo4j.gds.betweenness.BetwennessCentralityResult; import org.neo4j.gds.closeness.ClosenessCentralityMutateConfig; @@ -38,6 +40,7 @@ import org.neo4j.gds.pagerank.PageRankResult; import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticleRank; +import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticulationPoints; 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; @@ -82,6 +85,25 @@ public RESULT articleRank( ); } + public RESULT articulationPoints( + GraphName graphName, + ArticulationPointsMutateConfig configuration, + ResultBuilder resultBuilder + ) { + var mutateStep = new ArticulationPointsMutateStep(mutateNodeProperty, configuration); + + return algorithmProcessingTemplateConvenience.processRegularAlgorithmInMutateOrWriteMode( + graphName, + configuration, + ArticulationPoints, + estimation::articulationPoints, + (graph, __) -> algorithms.articulationPoints(graph, configuration), + mutateStep, + resultBuilder + ); + } + + public RESULT betweennessCentrality( GraphName graphName, BetweennessCentralityMutateConfig configuration, diff --git a/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/Algorithm.java b/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/Algorithm.java index e06c154df7..3156892a07 100644 --- a/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/Algorithm.java +++ b/applications/algorithms/machinery/src/main/java/org/neo4j/gds/applications/algorithms/metadata/Algorithm.java @@ -30,6 +30,7 @@ public enum Algorithm { AllShortestPaths, ApproximateMaximumKCut, ArticleRank, + ArticulationPoints, AStar, BellmanFord, BetaClosenessCentrality, 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 3dd54cfdba..e1aa9283b4 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 @@ -23,6 +23,7 @@ public enum LabelForProgressTracking { AllShortestPaths("All Shortest Paths"), ApproximateMaximumKCut("ApproxMaxKCut"), ArticleRank("ArticleRank"), + ArticulationPoints("ArticulationPoints"), AStar("AStar"), BellmanFord("Bellman-Ford"), BetaClosenessCentrality("Closeness Centrality (beta)"), @@ -81,6 +82,7 @@ public static LabelForProgressTracking from(Algorithm algorithm) { case AllShortestPaths -> AllShortestPaths; case ApproximateMaximumKCut -> ApproximateMaximumKCut; case ArticleRank -> ArticleRank; + case ArticulationPoints -> ArticulationPoints; case AStar -> AStar; case BellmanFord -> BellmanFord; case BetaClosenessCentrality -> BetaClosenessCentrality; diff --git a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java index c58641a004..f3ec90e0d4 100644 --- a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java +++ b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java @@ -19,6 +19,7 @@ */ package org.neo4j.gds.doc; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateProc; import org.neo4j.gds.articulationpoints.ArticulationPointsStreamProc; import org.neo4j.gds.functions.AsNodeFunc; @@ -34,7 +35,8 @@ protected List> functions() { @Override protected List> procedures() { return List.of( - ArticulationPointsStreamProc.class + ArticulationPointsStreamProc.class, + ArticulationPointsMutateProc.class ); } diff --git a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc index 413868ac43..b0b3fd308e 100644 --- a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc +++ b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc @@ -1,8 +1,8 @@ [[algorithms-articulation-points]] = Bridges :description: This section describes the Articulation Points algorithm in the Neo4j Graph Data Science library. -:entity: relationship -:result: node +:entity: node +:result: node property :algorithm: Articulation Points :sequential: true @@ -28,7 +28,7 @@ include::partial$/algorithms/shared/syntax-intro-named-graph.adoc[] [.include-with-stream] ====== -.Run Bridges in stream mode on a named graph. +.Run Articulation points in stream mode on a named graph. [source, cypher, role=noplay] ---- CALL gds.articulationPoints.stream( @@ -55,6 +55,46 @@ include::partial$/algorithms/common-configuration/common-stream-stats-configurat | nodeId | Integer | The ID of the node representing an articulation point. |=== +====== +[.include-with-mutate] +====== + +.Run Articulation points in mutate mode on a named graph. +[source, cypher, role=noplay] +---- +CALL gds.articulationPoints.mutate( + graphName: String, + configuration: Map +) +YIELD + YIELD + mutateMillis: Integer, + nodePropertiesWritten: Integer, + computeMillis: Integer, + numberOfArticulationPoints: Integer, + configuration: Map +---- + +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-mutate-configuration-entries.adoc +|=== + +.Results +[opts="header"] +|=== +| Name | Type | Description +| mutateMillis | Integer | Milliseconds for adding properties to the projected graph. +| nodePropertiesWritten | Integer | Number of properties added to the projected graph. +| computeMillis | Integer | Milliseconds for running the algorithm. +| numberOfArticulationPoints | Integer | Number of articulation points in the graph. +| configuration | Map | The configuration used for running the algorithm. +|=== + ====== ==== @@ -156,3 +196,30 @@ ORDER BY name ASC | "Doug" |=== -- + +[[algorithms-articulation-points-examples-mutate]] +=== Mutate + +The `mutate` mode updates the named graph with a new {entity} property that denotes whether a node is an articulation point or not. +This is achieved through setting `0,1` values, where `1` denotes that the node is an articulation point. +The name of the new property is specified using the mandatory configuration parameter `mutateProperty`. +The result is a single summary row, similar to `stats`, but with some additional metrics. +The `mutate` mode is especially useful when multiple algorithms are used in conjunction. + +[role=query-example] +-- +.The following will run the algorithm in `mutate` mode: +[source, cypher, role=noplay] +---- +CALL gds.articulationPoints.mutate('myGraph', { mutateProperty: 'articulationPoint'}) +YIELD numberOfArticulationPoints +---- + +.Results +[opts="header"] +|=== +| numberOfArticulationPoints +| 2 +|=== +-- + diff --git a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/ConfigurationParsersForMutateMode.java b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/ConfigurationParsersForMutateMode.java index 981d2b3c56..de00cb6139 100644 --- a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/ConfigurationParsersForMutateMode.java +++ b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/ConfigurationParsersForMutateMode.java @@ -21,6 +21,7 @@ import org.neo4j.gds.applications.algorithms.metadata.Algorithm; import org.neo4j.gds.approxmaxkcut.config.ApproxMaxKCutMutateConfig; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; import org.neo4j.gds.betweenness.BetweennessCentralityMutateConfig; import org.neo4j.gds.closeness.ClosenessCentralityMutateConfig; import org.neo4j.gds.config.AlgoBaseConfig; @@ -76,6 +77,7 @@ public Function lookup(Algorithm algorithm) { case AllShortestPaths -> null; case ApproximateMaximumKCut -> ApproxMaxKCutMutateConfig::of; case ArticleRank -> PageRankMutateConfig::of; + case ArticulationPoints -> ArticulationPointsMutateConfig::of; case AStar -> ShortestPathAStarMutateConfig::of; case BellmanFord -> AllShortestPathsBellmanFordMutateConfig::of; case BetaClosenessCentrality -> ClosenessCentralityMutateConfig::of; diff --git a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/MutateModeAlgorithmLibrary.java b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/MutateModeAlgorithmLibrary.java index db9cec2f7f..57f462a7bd 100644 --- a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/MutateModeAlgorithmLibrary.java +++ b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/MutateModeAlgorithmLibrary.java @@ -61,6 +61,7 @@ static CanonicalProcedureName algorithmToName(Algorithm algorithm) { case AllShortestPaths -> null; case ApproximateMaximumKCut -> CanonicalProcedureName.parse("gds.maxkcut"); case ArticleRank -> CanonicalProcedureName.parse("gds.articleRank"); + case ArticulationPoints -> CanonicalProcedureName.parse("gds.articulationPoints"); case AStar -> CanonicalProcedureName.parse("gds.shortestPath.astar"); case BellmanFord -> CanonicalProcedureName.parse("gds.bellmanFord"); case BetaClosenessCentrality -> CanonicalProcedureName.parse("gds.beta.closeness"); diff --git a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/StubbyHolder.java b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/StubbyHolder.java index 6c37d8e3bd..7672a61e6f 100644 --- a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/StubbyHolder.java +++ b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/StubbyHolder.java @@ -22,6 +22,7 @@ import org.neo4j.gds.applications.algorithms.metadata.Algorithm; import org.neo4j.gds.ml.pipeline.stubs.ApproximateMaximumKCutStub; import org.neo4j.gds.ml.pipeline.stubs.ArticleRankStub; +import org.neo4j.gds.ml.pipeline.stubs.ArticulationPointsStub; import org.neo4j.gds.ml.pipeline.stubs.BellmanFordStub; import org.neo4j.gds.ml.pipeline.stubs.BetaClosenessCentralityStub; import org.neo4j.gds.ml.pipeline.stubs.BetweennessCentralityStub; @@ -76,6 +77,7 @@ Stub get(Algorithm algorithm) { case AllShortestPaths -> null; case ApproximateMaximumKCut -> new ApproximateMaximumKCutStub(); case ArticleRank -> new ArticleRankStub(); + case ArticulationPoints -> new ArticulationPointsStub(); case AStar -> new SinglePairShortestPathAStarStub(); case BellmanFord -> new BellmanFordStub(); case BetaClosenessCentrality -> new BetaClosenessCentralityStub(); diff --git a/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/stubs/ArticulationPointsStub.java b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/stubs/ArticulationPointsStub.java new file mode 100644 index 0000000000..22ac23fd2e --- /dev/null +++ b/pipeline/src/main/java/org/neo4j/gds/ml/pipeline/stubs/ArticulationPointsStub.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.ml.pipeline.stubs; + +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; +import org.neo4j.gds.procedures.algorithms.AlgorithmsProcedureFacade; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPointsMutateResult; +import org.neo4j.gds.procedures.algorithms.stubs.MutateStub; + +public class ArticulationPointsStub extends AbstractStub { + protected MutateStub stub(AlgorithmsProcedureFacade facade) { + return facade.centrality().articulationPointsMutateStub(); + } +} diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java new file mode 100644 index 0000000000..65bfe08634 --- /dev/null +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java @@ -0,0 +1,60 @@ +/* + * 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.articulationpoints; + +import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.procedures.GraphDataScienceProcedures; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPointsMutateResult; +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.articulationpoints.Constants.PROC_DESCRIPTION; +import static org.neo4j.gds.procedures.ProcedureConstants.MEMORY_ESTIMATION_DESCRIPTION; +import static org.neo4j.procedure.Mode.READ; + +public class ArticulationPointsMutateProc { + + @Context + public GraphDataScienceProcedures facade; + + @Procedure(value = "gds.articulationPoints.mutate", mode = READ) + @Description(PROC_DESCRIPTION) + public Stream mutate( + @Name(value = "graphName") String graphName, + @Name(value = "configuration", defaultValue = "{}") Map configuration + ) { + return facade.algorithms().centrality().articulationPointsMutateStub().execute(graphName, configuration); + } + + @Procedure(value = "gds.articulationPoints.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().articulationPointsMutateEstimate(graphNameOrConfiguration, algoConfiguration); + } + +} diff --git a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java new file mode 100644 index 0000000000..c76abfdaa0 --- /dev/null +++ b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java @@ -0,0 +1,131 @@ +/* + * 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.articulationpoints; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.neo4j.gds.BaseTest; +import org.neo4j.gds.api.DatabaseId; +import org.neo4j.gds.catalog.GraphProjectProc; +import org.neo4j.gds.core.Username; +import org.neo4j.gds.core.loading.GraphStoreCatalog; +import org.neo4j.gds.extension.IdFunction; +import org.neo4j.gds.extension.Inject; +import org.neo4j.gds.extension.Neo4jGraph; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LONG; +import static org.neo4j.gds.compat.GraphDatabaseApiProxy.registerProcedures; + +class ArticulationPointsMutateProcTest extends BaseTest { + + @Neo4jGraph + private static final String DB_CYPHER = + """ + 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 IdFunction idFunction; + + @BeforeEach + void setup() throws Exception { + registerProcedures( + db, + ArticulationPointsMutateProc.class, + GraphProjectProc.class + ); + + runQuery("CALL gds.graph.project('graph', 'Node', {R: {orientation: 'UNDIRECTED'}})"); + } + + @Test + void shouldMutate() { + var expectedArticulationPoints = List.of( + idFunction.of("a3"), + idFunction.of("a7"), + idFunction.of("a10"), + idFunction.of("a11"), + idFunction.of("a14") + ); + + var resultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.mutate('graph', {mutateProperty:'foo'})", + row -> { + var nodeId = row.getNumber("numberOfArticulationPoints"); + assertThat(nodeId) + .asInstanceOf(LONG) + .isEqualTo(5L); + } + ); + + assertThat(resultRowCount) + .isEqualTo(1L); + + var actualGraph = GraphStoreCatalog.get(Username.EMPTY_USERNAME.username(), DatabaseId.of(db.databaseName()), "graph") + .graphStore() + .getUnion(); + + var mutateNodeProperties = actualGraph.nodeProperties("foo"); + assertThat(mutateNodeProperties.nodeCount()).isEqualTo(16); + + expectedArticulationPoints.stream() + .map( actualGraph::toMappedNodeId) + .map( mutateNodeProperties::longValue) + .forEach( v -> { + assertThat(v).isEqualTo(1L); + } ); + + } + +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java new file mode 100644 index 0000000000..1e6ee115c5 --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java @@ -0,0 +1,76 @@ +/* + * 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.applications.algorithms.machinery.AlgorithmProcessingTimings; +import org.neo4j.gds.procedures.algorithms.results.StandardMutateResult; +import org.neo4j.gds.result.AbstractResultBuilder; + +import java.util.Map; + +public class ArticulationPointsMutateResult extends StandardMutateResult { + public final long numberOfArticulationPoints; + public final long nodePropertiesWritten; + + public ArticulationPointsMutateResult( + long mutateMillis, + long nodePropertiesWritten, + long computeMillis, + Map configuration, + long numberOfArticulationPoints + ) { + super(0,computeMillis,0,mutateMillis,configuration); + this.numberOfArticulationPoints = numberOfArticulationPoints; + this.nodePropertiesWritten = nodePropertiesWritten; + } + public static Builder builder() { + return new Builder(); + } + + + public static ArticulationPointsMutateResult emptyFrom(AlgorithmProcessingTimings timings, Map configurationMap) { + return new ArticulationPointsMutateResult( + timings.mutateOrWriteMillis, + 0, + timings.computeMillis, + configurationMap, + 0 + ); + } + + public static class Builder extends AbstractResultBuilder { + private long numberOfArticulationPoints;; + + public ArticulationPointsMutateResult.Builder withNumberOfArticulationPoints(long numberOfArticulationPoints) { + this.numberOfArticulationPoints = numberOfArticulationPoints; + return this; + } + + public ArticulationPointsMutateResult build() { + return new ArticulationPointsMutateResult( + mutateMillis, + nodePropertiesWritten, + computeMillis, + config.toMap(), + numberOfArticulationPoints + ); + } + } +} 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 a2fe161393..478072facc 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 @@ -26,6 +26,7 @@ import org.neo4j.gds.applications.algorithms.centrality.CentralityAlgorithmsStreamModeBusinessFacade; import org.neo4j.gds.applications.algorithms.centrality.CentralityAlgorithmsWriteModeBusinessFacade; import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig; @@ -49,6 +50,7 @@ import org.neo4j.gds.pagerank.PageRankStatsConfig; import org.neo4j.gds.pagerank.PageRankStreamConfig; import org.neo4j.gds.pagerank.PageRankWriteConfig; +import org.neo4j.gds.procedures.algorithms.centrality.stubs.ArticulationPointsMutateStub; import org.neo4j.gds.procedures.algorithms.centrality.stubs.BetaClosenessCentralityMutateStub; import org.neo4j.gds.procedures.algorithms.centrality.stubs.BetweennessCentralityMutateStub; import org.neo4j.gds.procedures.algorithms.centrality.stubs.CelfMutateStub; @@ -67,6 +69,7 @@ public final class CentralityProcedureFacade { private final ProcedureReturnColumns procedureReturnColumns; + private final ArticulationPointsMutateStub articulationPointsMutateStub; private final PageRankMutateStub articleRankMutateStub; private final BetaClosenessCentralityMutateStub betaClosenessCentralityMutateStub; private final BetweennessCentralityMutateStub betweennessCentralityMutateStub; @@ -93,6 +96,7 @@ private CentralityProcedureFacade( DegreeCentralityMutateStub degreeCentralityMutateStub, MutateStub eigenVectorMutateStub, HarmonicCentralityMutateStub harmonicCentralityMutateStub, + ArticulationPointsMutateStub articulationPointsMutateStub, PageRankMutateStub pageRankMutateStub, ApplicationsFacade applicationsFacade, EstimationModeRunner estimationMode, @@ -101,6 +105,7 @@ private CentralityProcedureFacade( ) { this.procedureReturnColumns = procedureReturnColumns; this.articleRankMutateStub = articleRankMutateStub; + this.articulationPointsMutateStub = articulationPointsMutateStub; this.betaClosenessCentralityMutateStub = betaClosenessCentralityMutateStub; this.betweennessCentralityMutateStub = betweennessCentralityMutateStub; this.celfMutateStub = celfMutateStub; @@ -171,6 +176,11 @@ public static CentralityProcedureFacade create( applicationsFacade.centrality().mutate()::pageRank ); + var articulationPointsMutateStub = new ArticulationPointsMutateStub( + genericStub, + applicationsFacade + ); + return new CentralityProcedureFacade( procedureReturnColumns, articleRankMutateStub, @@ -181,6 +191,7 @@ public static CentralityProcedureFacade create( degreeCentralityMutateStub, eigenVectorMutateStub, harmonicCentralityMutateStub, + articulationPointsMutateStub, pageRankMutateStub, applicationsFacade, estimationModeRunner, @@ -440,6 +451,8 @@ public Stream articulationPoints( ); } + public ArticulationPointsMutateStub articulationPointsMutateStub(){return articulationPointsMutateStub;} + public Stream articulationPointsStreamEstimate( Object graphNameOrConfiguration, Map algorithmConfiguration @@ -453,6 +466,22 @@ public Stream articulationPointsStreamEstimate( ) ); + return Stream.of(result); + } + + public Stream articulationPointsMutateEstimate( + Object graphNameOrConfiguration, + Map algorithmConfiguration + ) { + var result = estimationMode.runEstimation( + algorithmConfiguration, + ArticulationPointsMutateConfig::of, + configuration -> estimationMode().articulationPoints( + configuration, + graphNameOrConfiguration + ) + ); + return Stream.of(result); } public Stream bridgesStream( diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsMutateStub.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsMutateStub.java new file mode 100644 index 0000000000..9211165e9a --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsMutateStub.java @@ -0,0 +1,87 @@ +/* + * 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.stubs; + +import org.neo4j.gds.applications.ApplicationsFacade; +import org.neo4j.gds.applications.algorithms.centrality.CentralityAlgorithmsEstimationModeBusinessFacade; +import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; +import org.neo4j.gds.mem.MemoryEstimation; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPointsMutateResult; +import org.neo4j.gds.procedures.algorithms.stubs.GenericStub; +import org.neo4j.gds.procedures.algorithms.stubs.MutateStub; + +import java.util.Map; +import java.util.stream.Stream; + +public class ArticulationPointsMutateStub implements MutateStub { + private final GenericStub genericStub; + private final ApplicationsFacade applicationsFacade; + + public ArticulationPointsMutateStub( + GenericStub genericStub, + ApplicationsFacade applicationsFacade + ) { + this.genericStub = genericStub; + this.applicationsFacade = applicationsFacade; + } + + @Override + public ArticulationPointsMutateConfig parseConfiguration(Map configuration) { + return genericStub.parseConfiguration(ArticulationPointsMutateConfig::of, configuration); + } + + @Override + public MemoryEstimation getMemoryEstimation(String username, Map configuration) { + return genericStub.getMemoryEstimation( + username, + configuration, + ArticulationPointsMutateConfig::of, + (config) -> estimationMode().articulationPoints() + ); + } + + @Override + public Stream estimate(Object graphName, Map configuration) { + return genericStub.estimate( + graphName, + configuration, + ArticulationPointsMutateConfig::of, + (config) -> estimationMode().articulationPoints() + ); + } + + @Override + public Stream execute(String graphNameAsString, Map rawConfiguration) { + var resultBuilder = new ArticulationPointsResultBuilderForMutateMode(); + + return genericStub.execute( + graphNameAsString, + rawConfiguration, + ArticulationPointsMutateConfig::of, + applicationsFacade.centrality().mutate()::articulationPoints, + resultBuilder + ); + } + + private CentralityAlgorithmsEstimationModeBusinessFacade estimationMode() { + return applicationsFacade.centrality().estimate(); + } +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java new file mode 100644 index 0000000000..176958cad7 --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java @@ -0,0 +1,57 @@ +/* + * 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.stubs; + +import com.carrotsearch.hppc.BitSet; +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.applications.algorithms.metadata.NodePropertiesWritten; +import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPointsMutateResult; + +import java.util.Optional; + +public class ArticulationPointsResultBuilderForMutateMode implements ResultBuilder { + + @Override + public ArticulationPointsMutateResult build( + Graph graph, + GraphStore graphStore, + ArticulationPointsMutateConfig configuration, + Optional result, + AlgorithmProcessingTimings timings, + Optional metadata + ) { + if (result.isEmpty()) return ArticulationPointsMutateResult.emptyFrom(timings, configuration.toMap()); + + var articulationPointsResult = result.get(); + + return ArticulationPointsMutateResult.builder() + .withNumberOfArticulationPoints(articulationPointsResult.cardinality()) + .withPreProcessingMillis(timings.preProcessingMillis) + .withComputeMillis(timings.computeMillis) + .withMutateMillis(timings.mutateOrWriteMillis) + .withNodePropertiesWritten(metadata.orElseThrow().value) + .withConfig(configuration) + .build(); + } +} From 627438bcd202fdf28025ac6391c12b9f58052946 Mon Sep 17 00:00:00 2001 From: Veselin Nikolov Date: Thu, 8 Aug 2024 08:55:17 +0100 Subject: [PATCH 4/6] Add write procedure for Articulation points Co-authored-by: Ioannis Panagiotas --- .../ArticulationPointsWriteConfig.java | 37 +++++ ... => ArticulationPointsBaseConfigTest.java} | 28 +++- .../ArticulationPointsWriteStep.java | 77 ++++++++++ ...lityAlgorithmsWriteModeBusinessFacade.java | 20 +++ .../metadata/LabelForProgressTracking.java | 2 +- .../gds/doc/ArticulationPointsDocTest.java | 4 +- .../pages/algorithms/articulation-points.adoc | 125 +++++++++++++-- .../algorithm-references.adoc | 6 +- .../neo4j/gds/OpenGdsProcedureSmokeTest.java | 6 +- .../ArticulationPointsMutateProc.java | 2 +- .../ArticulationPointsStreamProc.java | 2 +- .../ArticulationPointsWriteProc.java | 59 +++++++ .../ArticulationPointsMutateProcTest.java | 2 +- .../ArticulationPointsWriteProcTest.java | 144 ++++++++++++++++++ .../ArticulationPointsMutateResult.java | 14 +- ...lationPointsResultBuilderForWriteMode.java | 60 ++++++++ .../ArticulationPointsWriteResult.java | 33 ++++ .../centrality/CentralityProcedureFacade.java | 37 ++++- ...ationPointsResultBuilderForMutateMode.java | 2 +- 19 files changed, 622 insertions(+), 38 deletions(-) create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteConfig.java rename algo/src/test/java/org/neo4j/gds/articulationpoints/{ArticulationPointsStreamConfigTest.java => ArticulationPointsBaseConfigTest.java} (68%) create mode 100644 applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsWriteStep.java create mode 100644 proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProc.java create mode 100644 proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProcTest.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteConfig.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteConfig.java new file mode 100644 index 0000000000..5bfff7454e --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteConfig.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.articulationpoints; + +import org.neo4j.gds.annotation.Configuration; +import org.neo4j.gds.config.WritePropertyConfig; +import org.neo4j.gds.core.CypherMapWrapper; + +import java.util.Map; + +@Configuration +public interface ArticulationPointsWriteConfig extends ArticulationPointsBaseConfig, WritePropertyConfig { + static ArticulationPointsWriteConfig of(CypherMapWrapper cypherMapWrapper) { + return new ArticulationPointsWriteConfigImpl(cypherMapWrapper); + } + + static ArticulationPointsWriteConfig of(Map rawMapConfiguration) { + return of(CypherMapWrapper.create(rawMapConfiguration)); + } +} diff --git a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfigTest.java similarity index 68% rename from algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java rename to algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfigTest.java index ca449d0d98..ed1a58515f 100644 --- a/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamConfigTest.java +++ b/algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsBaseConfigTest.java @@ -19,21 +19,24 @@ */ package org.neo4j.gds.articulationpoints; -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.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 java.util.Map; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @GdlExtension -class ArticulationPointsStreamConfigTest { +class ArticulationPointsBaseConfigTest { @GdlGraph(orientation = Orientation.NATURAL) private static final String GRAPH = @@ -47,13 +50,15 @@ class ArticulationPointsStreamConfigTest { @Inject private GraphStore graphStore; - @Test - void shouldRaiseAnExceptionIfGraphIsNotUndirected() { - var articulationPointsConfiguration = ArticulationPointsStreamConfig.of(CypherMapWrapper.empty()); - + @ParameterizedTest(name = "{0}") + @MethodSource("baseConfigImplementations") + void shouldRaiseAnExceptionIfGraphIsNotUndirected( + String mode, + ArticulationPointsBaseConfig configuration + ) { assertThatIllegalArgumentException() .isThrownBy(() -> - articulationPointsConfiguration.requireUndirectedGraph( + configuration.requireUndirectedGraph( graphStore, List.of(), List.of(RelationshipType.of("R")) @@ -61,4 +66,11 @@ void shouldRaiseAnExceptionIfGraphIsNotUndirected() { .withMessageContaining("Articulation Points") .withMessageContaining("requires relationship projections to be UNDIRECTED."); } + + private static Stream baseConfigImplementations() { + return Stream.of( + Arguments.of("stream", ArticulationPointsStreamConfig.of(Map.of())), + Arguments.of("write", ArticulationPointsWriteConfig.of(Map.of("writeProperty", "articulationPoint"))) + ); + } } diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsWriteStep.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsWriteStep.java new file mode 100644 index 0000000000..0a8b202fa2 --- /dev/null +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/ArticulationPointsWriteStep.java @@ -0,0 +1,77 @@ +/* + * 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.applications.algorithms.centrality; + +import com.carrotsearch.hppc.BitSet; +import org.neo4j.gds.api.Graph; +import org.neo4j.gds.api.GraphStore; +import org.neo4j.gds.api.ResultStore; +import org.neo4j.gds.api.properties.nodes.LongNodePropertyValues; +import org.neo4j.gds.applications.algorithms.machinery.MutateOrWriteStep; +import org.neo4j.gds.applications.algorithms.machinery.WriteToDatabase; +import org.neo4j.gds.applications.algorithms.metadata.NodePropertiesWritten; +import org.neo4j.gds.articulationpoints.ArticulationPointsWriteConfig; +import org.neo4j.gds.core.utils.progress.JobId; + +import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticulationPoints; + +class ArticulationPointsWriteStep implements MutateOrWriteStep { + private final ArticulationPointsWriteConfig configuration; + private final WriteToDatabase writeToDatabase; + + public ArticulationPointsWriteStep( + ArticulationPointsWriteConfig configuration, WriteToDatabase writeToDatabase + ) { + this.configuration = configuration; + this.writeToDatabase = writeToDatabase; + } + + @Override + public NodePropertiesWritten execute( + Graph graph, + GraphStore graphStore, + ResultStore resultStore, + BitSet bitSet, + JobId jobId + ) { + var nodePropertyValues = new LongNodePropertyValues() { + @Override + public long longValue(long nodeId) { + return bitSet.get(nodeId) ? 1 : 0; + } + + @Override + public long nodeCount() { + return graph.nodeCount(); + } + }; + + return writeToDatabase.perform( + graph, + graphStore, + resultStore, + configuration, + configuration, + ArticulationPoints, + jobId, + nodePropertyValues + ); + } +} diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsWriteModeBusinessFacade.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsWriteModeBusinessFacade.java index 9da6034ba0..fdf4284eea 100644 --- a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsWriteModeBusinessFacade.java +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsWriteModeBusinessFacade.java @@ -19,6 +19,7 @@ */ package org.neo4j.gds.applications.algorithms.centrality; +import com.carrotsearch.hppc.BitSet; import org.neo4j.gds.algorithms.centrality.CentralityAlgorithmResult; import org.neo4j.gds.api.GraphName; import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTemplateConvenience; @@ -27,6 +28,7 @@ import org.neo4j.gds.applications.algorithms.machinery.WriteContext; import org.neo4j.gds.applications.algorithms.machinery.WriteToDatabase; import org.neo4j.gds.applications.algorithms.metadata.NodePropertiesWritten; +import org.neo4j.gds.articulationpoints.ArticulationPointsWriteConfig; import org.neo4j.gds.betweenness.BetweennessCentralityWriteConfig; import org.neo4j.gds.closeness.ClosenessCentralityWriteConfig; import org.neo4j.gds.degree.DegreeCentralityWriteConfig; @@ -39,6 +41,7 @@ import org.neo4j.gds.pagerank.PageRankWriteConfig; import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticleRank; +import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticulationPoints; 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; @@ -119,6 +122,22 @@ public RESULT betweennessCentrality( ); } + public RESULT articulationPoints( + GraphName graphName, + ArticulationPointsWriteConfig configuration, + ResultBuilder resultBuilder + ) { + return algorithmProcessingTemplateConvenience.processRegularAlgorithmInMutateOrWriteMode( + graphName, + configuration, + ArticulationPoints, + estimationFacade::articulationPoints, + (graph, __) -> centralityAlgorithms.articulationPoints(graph, configuration), + new ArticulationPointsWriteStep(configuration, writeToDatabase), + resultBuilder + ); + } + public RESULT celf( GraphName graphName, CONFIGURATION configuration, @@ -226,4 +245,5 @@ public RESULT pageRank( resultBuilder ); } + } 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 e1aa9283b4..61d21c9828 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 @@ -23,7 +23,7 @@ public enum LabelForProgressTracking { AllShortestPaths("All Shortest Paths"), ApproximateMaximumKCut("ApproxMaxKCut"), ArticleRank("ArticleRank"), - ArticulationPoints("ArticulationPoints"), + ArticulationPoints("Articulation Points"), AStar("AStar"), BellmanFord("Bellman-Ford"), BetaClosenessCentrality("Closeness Centrality (beta)"), diff --git a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java index f3ec90e0d4..463b0d5dd0 100644 --- a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java +++ b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java @@ -21,6 +21,7 @@ import org.neo4j.gds.articulationpoints.ArticulationPointsMutateProc; import org.neo4j.gds.articulationpoints.ArticulationPointsStreamProc; +import org.neo4j.gds.articulationpoints.ArticulationPointsWriteProc; import org.neo4j.gds.functions.AsNodeFunc; import java.util.List; @@ -36,7 +37,8 @@ protected List> functions() { protected List> procedures() { return List.of( ArticulationPointsStreamProc.class, - ArticulationPointsMutateProc.class + ArticulationPointsMutateProc.class, + ArticulationPointsWriteProc.class ); } diff --git a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc index b0b3fd308e..c632699f2d 100644 --- a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc +++ b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc @@ -1,5 +1,5 @@ [[algorithms-articulation-points]] -= Bridges += Articulation Points :description: This section describes the Articulation Points algorithm in the Neo4j Graph Data Science library. :entity: node :result: node property @@ -21,7 +21,7 @@ The Neo4j GDS Library provides an efficient linear time sequential algorithm to include::partial$/algorithms/shared/syntax-intro-named-graph.adoc[] -.Bridges syntax per mode +.Articulation Points syntax per mode [.tabbed-example, caption = ] ==== @@ -67,11 +67,10 @@ CALL gds.articulationPoints.mutate( configuration: Map ) YIELD - YIELD mutateMillis: Integer, nodePropertiesWritten: Integer, computeMillis: Integer, - numberOfArticulationPoints: Integer, + articulationPointCount: Integer, configuration: Map ---- @@ -87,12 +86,52 @@ include::partial$/algorithms/common-configuration/common-mutate-configuration-en .Results [opts="header"] |=== -| Name | Type | Description -| mutateMillis | Integer | Milliseconds for adding properties to the projected graph. -| nodePropertiesWritten | Integer | Number of properties added to the projected graph. -| computeMillis | Integer | Milliseconds for running the algorithm. -| numberOfArticulationPoints | Integer | Number of articulation points in the graph. -| configuration | Map | The configuration used for running the algorithm. +| Name | Type | Description +| mutateMillis | Integer | Milliseconds for adding properties to the projected graph. +| nodePropertiesWritten | Integer | Number of properties added to the projected graph. +| computeMillis | Integer | Milliseconds for running the algorithm. +| articulationPointCount | Integer | Count of the articulation points in the graph. +| configuration | Map | The configuration used for running the algorithm. +|=== + +====== + +[.include-with-write] +====== + +.Run Articulation points in write mode on a named graph. +[source, cypher, role=noplay] +---- +CALL gds.articulationPoints.write( + graphName: String, + configuration: Map +) +YIELD + writeMillis: Integer, + nodePropertiesWritten: Integer, + computeMillis: Integer, + articulationPointCount: Integer, + configuration: Map +---- + +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-mutate-configuration-entries.adoc +|=== + +.Results +[opts="header"] +|=== +| Name | Type | Description +| mutateMillis | Integer | Milliseconds for adding properties to the projected graph. +| nodePropertiesWritten | Integer | Number of properties added to the projected graph. +| computeMillis | Integer | Milliseconds for running the algorithm. +| articulationPointCount | Integer | Count of the articulation points in the graph. +| configuration | Map | The configuration used for running the algorithm. |=== ====== @@ -204,7 +243,6 @@ The `mutate` mode updates the named graph with a new {entity} property that deno This is achieved through setting `0,1` values, where `1` denotes that the node is an articulation point. The name of the new property is specified using the mandatory configuration parameter `mutateProperty`. The result is a single summary row, similar to `stats`, but with some additional metrics. -The `mutate` mode is especially useful when multiple algorithms are used in conjunction. [role=query-example] -- @@ -212,14 +250,75 @@ The `mutate` mode is especially useful when multiple algorithms are used in conj [source, cypher, role=noplay] ---- CALL gds.articulationPoints.mutate('myGraph', { mutateProperty: 'articulationPoint'}) -YIELD numberOfArticulationPoints +YIELD articulationPointCount +---- + +.Results +[opts="header"] +|=== +| articulationPointCount +| 2 +|=== +-- + +[[algorithms-articulation-points-examples-write]] +=== Write + +The `write` mode updates the Neo4j graph with a new {entity} property that denotes whether a node is an articulation point or not. +This is achieved through setting `0,1` values, where `1` denotes that the node is an articulation point. +The name of the new property is specified using the mandatory configuration parameter `writeProperty`. +The result is a single summary row, similar to `stats`, but with some additional metrics. +The `mutate` mode is especially useful when multiple algorithms are used in conjunction. + +[role=query-example, group=write] +-- +.The following will run the algorithm in `write` mode: +[source, cypher, role=noplay] +---- +CALL gds.articulationPoints.write('myGraph', { writeProperty: 'articulationPoint'}) +YIELD articulationPointCount ---- .Results [opts="header"] |=== -| numberOfArticulationPoints +| articulationPointCount | 2 |=== -- +[role=query-example, group=write] +-- +Then we can then query Neo4j to see the articulation points: +[source, cypher, role=noplay] +---- +MATCH (n { articulationPoint: 1 }) RETURN n.name AS name ORDER BY name ASC +---- + +.Results +[opts="header"] +|=== +| name +| "Alice" +| "Doug" +|=== +-- + +[role=query-example, group=write] +-- +Or we can query Neo4j to see the nodes that are *not* articulation points: +[source, cypher, role=noplay] +---- +MATCH (n { articulationPoint: 0 }) RETURN n.name AS name ORDER BY name ASC +---- + +.Results +[opts="header"] +|=== +| name +| "Bridget" +| "Charles" +| "Mark" +| "Michael" +|=== +-- diff --git a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc index 1c6cc6938c..2f28bb287a 100644 --- a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc +++ b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc @@ -36,9 +36,13 @@ | `gds.articleRank.stream.estimate` label:procedure[Procedure] | `gds.articleRank.stats` label:procedure[Procedure] | `gds.articleRank.stats.estimate` label:procedure[Procedure] -.2+<.^|xref:algorithms/articulation-points.adoc[Articulation Points] +.6+<.^|xref:algorithms/articulation-points.adoc[Articulation Points] +| `gds.articulationPoints.mutate` label:procedure[Procedure] +| `gds.articulationPoints.mutate.estimate` label:procedure[Procedure] | `gds.articulationPoints.stream` label:procedure[Procedure] | `gds.articulationPoints.stream.estimate` label:procedure[Procedure] +| `gds.articulationPoints.write` label:procedure[Procedure] +| `gds.articulationPoints.write.estimate` label:procedure[Procedure] .8+<.^| xref:algorithms/bellman-ford-single-source.adoc[Bellman-Ford] | `gds.bellmanFord.mutate` label:procedure[Procedure] | `gds.bellmanFord.mutate.estimate` 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 8e858212da..de6154c304 100644 --- a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java +++ b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java @@ -54,8 +54,12 @@ class OpenGdsProcedureSmokeTest extends BaseProcTest { "gds.allShortestPaths.stream", + "gds.articulationPoints.mutate", + "gds.articulationPoints.mutate.estimate", "gds.articulationPoints.stream", "gds.articulationPoints.stream.estimate", + "gds.articulationPoints.write", + "gds.articulationPoints.write.estimate", "gds.bellmanFord.stats", "gds.bellmanFord.stats.estimate", @@ -579,7 +583,7 @@ void countShouldMatch() { ); // If you find yourself updating this count, please also update the count in SmokeTest.kt - int expectedCount = 422; + int expectedCount = 426; assertEquals( expectedCount, returnedRows, diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java index 65bfe08634..03d7d1fc13 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProc.java @@ -48,7 +48,7 @@ public Stream mutate( return facade.algorithms().centrality().articulationPointsMutateStub().execute(graphName, configuration); } - @Procedure(value = "gds.articulationPoints.stream.estimate", mode = READ) + @Procedure(value = "gds.articulationPoints.mutate.estimate", mode = READ) @Description(MEMORY_ESTIMATION_DESCRIPTION) public Stream estimate( @Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration, diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java index cd49bfb8e0..179046ed14 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStreamProc.java @@ -45,7 +45,7 @@ public Stream stream( @Name(value = "graphName") String graphName, @Name(value = "configuration", defaultValue = "{}") Map configuration ) { - return facade.algorithms().centrality().articulationPoints(graphName, configuration); + return facade.algorithms().centrality().articulationPointsStream(graphName, configuration); } @Procedure(value = "gds.articulationPoints.stream.estimate", mode = READ) diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProc.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProc.java new file mode 100644 index 0000000000..96efab6ef4 --- /dev/null +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProc.java @@ -0,0 +1,59 @@ +/* + * 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.articulationpoints; + +import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.procedures.GraphDataScienceProcedures; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPointsWriteResult; +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.articulationpoints.Constants.PROC_DESCRIPTION; +import static org.neo4j.gds.procedures.ProcedureConstants.MEMORY_ESTIMATION_DESCRIPTION; +import static org.neo4j.procedure.Mode.READ; +import static org.neo4j.procedure.Mode.WRITE; + +public class ArticulationPointsWriteProc { + @Context + public GraphDataScienceProcedures facade; + + @Procedure(value = "gds.articulationPoints.write", mode = WRITE) + @Description(PROC_DESCRIPTION) + public Stream write( + @Name(value = "graphName") String graphName, + @Name(value = "configuration", defaultValue = "{}") Map configuration + ) { + return facade.algorithms().centrality().articulationPointsWrite(graphName, configuration); + } + + @Procedure(value = "gds.articulationPoints.write.estimate", mode = READ) + @Description(MEMORY_ESTIMATION_DESCRIPTION) + public Stream estimate( + @Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration, + @Name(value = "algoConfiguration") Map algoConfiguration + ) { + return facade.algorithms().centrality().articulationPointsWriteEstimate(graphNameOrConfiguration, algoConfiguration); + } +} diff --git a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java index c76abfdaa0..1242dfbaf6 100644 --- a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java +++ b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java @@ -102,7 +102,7 @@ void shouldMutate() { var resultRowCount = runQueryWithRowConsumer( "CALL gds.articulationPoints.mutate('graph', {mutateProperty:'foo'})", row -> { - var nodeId = row.getNumber("numberOfArticulationPoints"); + var nodeId = row.getNumber("articulationPointCount"); assertThat(nodeId) .asInstanceOf(LONG) .isEqualTo(5L); diff --git a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProcTest.java new file mode 100644 index 0000000000..0d9e93833d --- /dev/null +++ b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsWriteProcTest.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.articulationpoints; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.neo4j.gds.BaseTest; +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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LONG; +import static org.neo4j.gds.compat.GraphDatabaseApiProxy.registerProcedures; + +class ArticulationPointsWriteProcTest extends BaseTest { + + @Neo4jGraph + private static final String DB_CYPHER = + """ + 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 IdFunction idFunction; + + @BeforeEach + void setup() throws Exception { + registerProcedures( + db, + ArticulationPointsWriteProc.class, + GraphProjectProc.class + ); + + runQuery("CALL gds.graph.project('graph', 'Node', {R: {orientation: 'UNDIRECTED'}})"); + } + + @Test + void shouldWriteBackResults() { + var expectedArticulationPoints = List.of( + idFunction.of("a3"), + idFunction.of("a7"), + idFunction.of("a10"), + idFunction.of("a11"), + idFunction.of("a14") + ); + + var writeResultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.write('graph', { writeProperty: 'point' })", + row -> { + assertThat(row.getNumber("articulationPointCount")) + .asInstanceOf(LONG) + .as("There should be five articulation points reported in the write result.") + .isEqualTo(5L); + } + ); + + assertThat(writeResultRowCount) + .as("There should be one row as a result of write.") + .isEqualTo(1L); + + + var resultRowCount = runQueryWithRowConsumer( + "MATCH (n { point: 1 }) RETURN id(n) AS nodeId", + row -> { + var nodeId = row.getNumber("nodeId"); + assertThat(nodeId) + .asInstanceOf(LONG) + .isIn(expectedArticulationPoints); + } + ); + + assertThat(resultRowCount) + .as("There should be five articulation points.") + .isEqualTo(5L); + } + + @Test + void shouldEstimateWrite() { + var writeResultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.write.estimate('graph', { writeProperty: 'point' })", + row -> { + assertThat(row.get("requiredMemory")).isNotNull(); + assertThat(row.get("treeView")).isNotNull(); + } + ); + + assertThat(writeResultRowCount) + .as("There should be one row as a result of estimating write.") + .isEqualTo(1L); + } + +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java index 1e6ee115c5..c759d98e29 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java @@ -26,7 +26,7 @@ import java.util.Map; public class ArticulationPointsMutateResult extends StandardMutateResult { - public final long numberOfArticulationPoints; + public final long articulationPointCount; public final long nodePropertiesWritten; public ArticulationPointsMutateResult( @@ -34,10 +34,10 @@ public ArticulationPointsMutateResult( long nodePropertiesWritten, long computeMillis, Map configuration, - long numberOfArticulationPoints + long articulationPointCount ) { super(0,computeMillis,0,mutateMillis,configuration); - this.numberOfArticulationPoints = numberOfArticulationPoints; + this.articulationPointCount = articulationPointCount; this.nodePropertiesWritten = nodePropertiesWritten; } public static Builder builder() { @@ -56,10 +56,10 @@ public static ArticulationPointsMutateResult emptyFrom(AlgorithmProcessingTiming } public static class Builder extends AbstractResultBuilder { - private long numberOfArticulationPoints;; + private long articulationPointCount;; - public ArticulationPointsMutateResult.Builder withNumberOfArticulationPoints(long numberOfArticulationPoints) { - this.numberOfArticulationPoints = numberOfArticulationPoints; + public ArticulationPointsMutateResult.Builder withArticulationPointCount(long articulationPointCount) { + this.articulationPointCount = articulationPointCount; return this; } @@ -69,7 +69,7 @@ public ArticulationPointsMutateResult build() { nodePropertiesWritten, computeMillis, config.toMap(), - numberOfArticulationPoints + articulationPointCount ); } } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java new file mode 100644 index 0000000000..3ce061a5b5 --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java @@ -0,0 +1,60 @@ +/* + * 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 com.carrotsearch.hppc.BitSet; +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.applications.algorithms.metadata.NodePropertiesWritten; +import org.neo4j.gds.articulationpoints.ArticulationPointsWriteConfig; + +import java.util.Optional; +import java.util.stream.Stream; + +class ArticulationPointsResultBuilderForWriteMode implements ResultBuilder, NodePropertiesWritten> { + + @Override + public Stream build( + Graph graph, + GraphStore graphStore, + ArticulationPointsWriteConfig configuration, + Optional result, + AlgorithmProcessingTimings timings, + Optional metadata + ) { + if (result.isEmpty()) { + return Stream.of(ArticulationPointsWriteResult.EMPTY); + } + + var bitSet = result.get(); + return Stream.of( + new ArticulationPointsWriteResult( + bitSet.cardinality(), + metadata.map(n -> n.value).orElseThrow(), + timings.mutateOrWriteMillis, + timings.computeMillis, + graph.nodeCount(), + configuration.toMap() + ) + ); + } +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java new file mode 100644 index 0000000000..c06aaa37a6 --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java @@ -0,0 +1,33 @@ +/* + * 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 java.util.Map; + +public record ArticulationPointsWriteResult( + long articulationPointCount, + long nodePropertiesWritten, + long writeMillis, + long computeMillis, + long nodeCount, + Map configuration +) { + public static final ArticulationPointsWriteResult EMPTY = new ArticulationPointsWriteResult(0, 0, 0, 0, 0, Map.of()); +} 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 478072facc..9ffb4dbe07 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 @@ -28,6 +28,7 @@ import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; +import org.neo4j.gds.articulationpoints.ArticulationPointsWriteConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig; import org.neo4j.gds.betweenness.BetweennessCentralityWriteConfig; @@ -436,7 +437,7 @@ public Stream betweennessCentralityWriteEstimate( } - public Stream articulationPoints( + public Stream articulationPointsStream( String graphName, Map configuration ) { @@ -482,7 +483,39 @@ public Stream articulationPointsMutateEstimate( ) ); - return Stream.of(result); } + return Stream.of(result); + } + + + public Stream articulationPointsWrite( + String graphNameAsString, + Map rawConfiguration + ) { + return algorithmExecutionScaffolding.runAlgorithm( + graphNameAsString, + rawConfiguration, + ArticulationPointsWriteConfig::of, + writeMode()::articulationPoints, + new ArticulationPointsResultBuilderForWriteMode() + ); + } + + public Stream articulationPointsWriteEstimate( + Object graphNameOrConfiguration, + Map algorithmConfiguration + ) { + var result = estimationMode.runEstimation( + algorithmConfiguration, + ArticulationPointsWriteConfig::of, + configuration -> estimationMode().articulationPoints( + configuration, + graphNameOrConfiguration + ) + ); + + return Stream.of(result); + } + public Stream bridgesStream( String graphName, diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java index 176958cad7..d82bdb1fb4 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java @@ -46,7 +46,7 @@ public ArticulationPointsMutateResult build( var articulationPointsResult = result.get(); return ArticulationPointsMutateResult.builder() - .withNumberOfArticulationPoints(articulationPointsResult.cardinality()) + .withArticulationPointCount(articulationPointsResult.cardinality()) .withPreProcessingMillis(timings.preProcessingMillis) .withComputeMillis(timings.computeMillis) .withMutateMillis(timings.mutateOrWriteMillis) From 2693ab994394e5b3d2f0bb9f82d5e2827a353970 Mon Sep 17 00:00:00 2001 From: ioannispan Date: Thu, 8 Aug 2024 15:42:41 +0200 Subject: [PATCH 5/6] Add stats procedure for Articulation points Co-authored-by: Veselin Nikolov --- .../ArticulationPointsStatsConfig.java | 36 ++++++ ...lityAlgorithmsStatsModeBusinessFacade.java | 18 +++ .../gds/doc/ArticulationPointsDocTest.java | 2 + .../pages/algorithms/articulation-points.adoc | 66 +++++++++- .../algorithm-references.adoc | 4 +- .../neo4j/gds/OpenGdsProcedureSmokeTest.java | 4 +- .../ArticulationPointsStatsProc.java | 60 +++++++++ .../ArticulationPointsMutateProcTest.java | 13 ++ .../ArticulationPointsStatsProcTest.java | 120 ++++++++++++++++++ ...lationPointsResultBuilderForStatsMode.java | 56 ++++++++ .../ArticulationPointsStatsResult.java | 68 ++++++++++ .../centrality/CentralityProcedureFacade.java | 32 ++++- 12 files changed, 472 insertions(+), 7 deletions(-) create mode 100644 algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsConfig.java create mode 100644 proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProc.java create mode 100644 proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProcTest.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java create mode 100644 procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java diff --git a/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsConfig.java b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsConfig.java new file mode 100644 index 0000000000..1939e41a57 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsConfig.java @@ -0,0 +1,36 @@ +/* + * 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.articulationpoints; + +import org.neo4j.gds.annotation.Configuration; +import org.neo4j.gds.core.CypherMapWrapper; + +import java.util.Map; + +@Configuration +public interface ArticulationPointsStatsConfig extends ArticulationPointsBaseConfig { + static ArticulationPointsStatsConfig of(CypherMapWrapper cypherMapWrapper) { + return new ArticulationPointsStatsConfigImpl(cypherMapWrapper); + } + + static ArticulationPointsStatsConfig of(Map rawMapConfiguration) { + return of(CypherMapWrapper.create(rawMapConfiguration)); + } +} diff --git a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStatsModeBusinessFacade.java b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStatsModeBusinessFacade.java index ca0a9f22bc..b4a6c2c5a0 100644 --- a/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStatsModeBusinessFacade.java +++ b/applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithmsStatsModeBusinessFacade.java @@ -19,10 +19,12 @@ */ package org.neo4j.gds.applications.algorithms.centrality; +import com.carrotsearch.hppc.BitSet; import org.neo4j.gds.algorithms.centrality.CentralityAlgorithmResult; import org.neo4j.gds.api.GraphName; import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTemplateConvenience; import org.neo4j.gds.applications.algorithms.machinery.ResultBuilder; +import org.neo4j.gds.articulationpoints.ArticulationPointsStatsConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; import org.neo4j.gds.closeness.ClosenessCentralityStatsConfig; import org.neo4j.gds.degree.DegreeCentralityStatsConfig; @@ -33,6 +35,7 @@ import org.neo4j.gds.pagerank.PageRankStatsConfig; import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticleRank; +import static org.neo4j.gds.applications.algorithms.metadata.LabelForProgressTracking.ArticulationPoints; 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; @@ -71,6 +74,21 @@ public RESULT articleRank( ); } + public RESULT articulationPoints( + GraphName graphName, + ArticulationPointsStatsConfig configuration, + ResultBuilder resultBuilder + ) { + return algorithmProcessingTemplateConvenience.processRegularAlgorithmInStatsOrStreamMode( + graphName, + configuration, + ArticulationPoints, + estimationFacade::articulationPoints, + (graph, __) -> centralityAlgorithms.articulationPoints(graph, configuration), + resultBuilder + ); + } + public RESULT betweennessCentrality( GraphName graphName, BetweennessCentralityStatsConfig configuration, diff --git a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java index 463b0d5dd0..f96e7ac26b 100644 --- a/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java +++ b/doc-test/src/test/java/org/neo4j/gds/doc/ArticulationPointsDocTest.java @@ -20,6 +20,7 @@ package org.neo4j.gds.doc; import org.neo4j.gds.articulationpoints.ArticulationPointsMutateProc; +import org.neo4j.gds.articulationpoints.ArticulationPointsStatsProc; import org.neo4j.gds.articulationpoints.ArticulationPointsStreamProc; import org.neo4j.gds.articulationpoints.ArticulationPointsWriteProc; import org.neo4j.gds.functions.AsNodeFunc; @@ -37,6 +38,7 @@ protected List> functions() { protected List> procedures() { return List.of( ArticulationPointsStreamProc.class, + ArticulationPointsStatsProc.class, ArticulationPointsMutateProc.class, ArticulationPointsWriteProc.class ); diff --git a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc index c632699f2d..e63328004e 100644 --- a/doc/modules/ROOT/pages/algorithms/articulation-points.adoc +++ b/doc/modules/ROOT/pages/algorithms/articulation-points.adoc @@ -16,7 +16,7 @@ Given a graph, an articulation point is a node whose removal increases the numbe The Neo4j GDS Library provides an efficient linear time sequential algorithm to compute all articulation points in a graph. -[[algorithms-briges-syntax]] +[[algorithms-articulation-points-syntax]] == Syntax include::partial$/algorithms/shared/syntax-intro-named-graph.adoc[] @@ -54,8 +54,43 @@ include::partial$/algorithms/common-configuration/common-stream-stats-configurat | Name | Type | Description | nodeId | Integer | The ID of the node representing an articulation point. |=== +====== +[.include-with-stats] +====== + +.Run Articulation points in stats mode on a named graph. +[source, cypher, role=noplay] +---- +CALL gds.articulationPoints.stats( + graphName: String, + configuration: Map +) +YIELD + nodeId: 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 +| Name | Type | Description +| computeMillis | Integer | Milliseconds for running the algorithm. +| articulationPointCount | Integer | Count of the articulation points in the graph. +| configuration | Map | The configuration used for running the algorithm. +|=== ====== + + [.include-with-mutate] ====== @@ -127,8 +162,8 @@ include::partial$/algorithms/common-configuration/common-mutate-configuration-en [opts="header"] |=== | Name | Type | Description -| mutateMillis | Integer | Milliseconds for adding properties to the projected graph. -| nodePropertiesWritten | Integer | Number of properties added to the projected graph. +| writeMillis | Integer | Milliseconds for adding properties to the neo4j database. +| nodePropertiesWritten | Integer | Number of properties added to the neo4j database.. | computeMillis | Integer | Milliseconds for running the algorithm. | articulationPointCount | Integer | Count of the articulation points in the graph. | configuration | Map | The configuration used for running the algorithm. @@ -236,6 +271,31 @@ ORDER BY name ASC |=== -- + +[[algorithms-articulation-points-examples-stats]] +=== Stats + +include::partial$/algorithms/shared/examples-stats-intro.adoc[] + + +[role=query-example] +-- +.The following will run the algorithm in `stats` mode: +[source, cypher, role=noplay] +---- +CALL gds.articulationPoints.stats('myGraph',{}) +YIELD articulationPointCount +---- + +.Results +[opts="header"] +|=== +| articulationPointCount +| 2 +|=== +-- + + [[algorithms-articulation-points-examples-mutate]] === Mutate diff --git a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc index 2f28bb287a..76778f5193 100644 --- a/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc +++ b/doc/modules/ROOT/pages/operations-reference/algorithm-references.adoc @@ -36,9 +36,11 @@ | `gds.articleRank.stream.estimate` label:procedure[Procedure] | `gds.articleRank.stats` label:procedure[Procedure] | `gds.articleRank.stats.estimate` label:procedure[Procedure] -.6+<.^|xref:algorithms/articulation-points.adoc[Articulation Points] +.8+<.^|xref:algorithms/articulation-points.adoc[Articulation Points] | `gds.articulationPoints.mutate` label:procedure[Procedure] | `gds.articulationPoints.mutate.estimate` label:procedure[Procedure] +| `gds.articulationPoints.stats` label:procedure[Procedure] +| `gds.articulationPoints.stats.estimate` label:procedure[Procedure] | `gds.articulationPoints.stream` label:procedure[Procedure] | `gds.articulationPoints.stream.estimate` label:procedure[Procedure] | `gds.articulationPoints.write` 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 de6154c304..7b12e17086 100644 --- a/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java +++ b/open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java @@ -56,6 +56,8 @@ class OpenGdsProcedureSmokeTest extends BaseProcTest { "gds.articulationPoints.mutate", "gds.articulationPoints.mutate.estimate", + "gds.articulationPoints.stats", + "gds.articulationPoints.stats.estimate", "gds.articulationPoints.stream", "gds.articulationPoints.stream.estimate", "gds.articulationPoints.write", @@ -583,7 +585,7 @@ void countShouldMatch() { ); // If you find yourself updating this count, please also update the count in SmokeTest.kt - int expectedCount = 426; + int expectedCount = 428; assertEquals( expectedCount, returnedRows, diff --git a/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProc.java b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProc.java new file mode 100644 index 0000000000..a78b0d7338 --- /dev/null +++ b/proc/centrality/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProc.java @@ -0,0 +1,60 @@ +/* + * 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.articulationpoints; + +import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; +import org.neo4j.gds.procedures.GraphDataScienceProcedures; +import org.neo4j.gds.procedures.algorithms.centrality.ArticulationPointsStatsResult; +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.articulationpoints.Constants.PROC_DESCRIPTION; +import static org.neo4j.gds.procedures.ProcedureConstants.MEMORY_ESTIMATION_DESCRIPTION; +import static org.neo4j.procedure.Mode.READ; + +public class ArticulationPointsStatsProc { + + @Context + public GraphDataScienceProcedures facade; + + @Procedure(value = "gds.articulationPoints.stats", mode = READ) + @Description(PROC_DESCRIPTION) + public Stream stream( + @Name(value = "graphName") String graphName, + @Name(value = "configuration", defaultValue = "{}") Map configuration + ) { + return facade.algorithms().centrality().articulationPointsStats(graphName, configuration); + } + + @Procedure(value = "gds.articulationPoints.stats.estimate", mode = READ) + @Description(MEMORY_ESTIMATION_DESCRIPTION) + public Stream estimate( + @Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration, + @Name(value = "algoConfiguration") Map algoConfiguration + ) { + return facade.algorithms().centrality().articulationPointsStatsEstimate(graphNameOrConfiguration, algoConfiguration); + } + +} diff --git a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java index 1242dfbaf6..255530aaaf 100644 --- a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java +++ b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMutateProcTest.java @@ -127,5 +127,18 @@ void shouldMutate() { } ); } + @Test + void shouldEstimateMutate() { + var resultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.mutate.estimate('graph', {mutateProperty: 'foo'})", + row -> { + assertThat(row.get("requiredMemory")).isNotNull(); + assertThat(row.get("treeView")).isNotNull(); + } + ); + assertThat(resultRowCount) + .as("There should be one row as a result of estimating write.") + .isEqualTo(1L); + } } diff --git a/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProcTest.java new file mode 100644 index 0000000000..59ce427989 --- /dev/null +++ b/proc/centrality/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsStatsProcTest.java @@ -0,0 +1,120 @@ +/* + * 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.articulationpoints; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.neo4j.gds.BaseTest; +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; +import static org.assertj.core.api.InstanceOfAssertFactories.LONG; +import static org.neo4j.gds.compat.GraphDatabaseApiProxy.registerProcedures; + +class ArticulationPointsStatsProcTest extends BaseTest { + + @Neo4jGraph + private static final String DB_CYPHER = + """ + 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 IdFunction idFunction; + + @BeforeEach + void setup() throws Exception { + registerProcedures( + db, + ArticulationPointsStatsProc.class, + GraphProjectProc.class + ); + + runQuery("CALL gds.graph.project('graph', 'Node', {R: {orientation: 'UNDIRECTED'}})"); + } + + @Test + void shouldComputeStats() { + + var resultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.stats('graph')", + row -> { + var nodeId = row.getNumber("articulationPointCount"); + assertThat(nodeId) + .asInstanceOf(LONG) + .isEqualTo(5L); + } + ); + + assertThat(resultRowCount) + .isEqualTo(1L); + + } + + @Test + void shouldEstimateStats() { + var resultRowCount = runQueryWithRowConsumer( + "CALL gds.articulationPoints.stats.estimate('graph',{})", + row -> { + assertThat(row.get("requiredMemory")).isNotNull(); + assertThat(row.get("treeView")).isNotNull(); + } + ); + + assertThat(resultRowCount) + .as("There should be one row as a result of estimating write.") + .isEqualTo(1L); + } + +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java new file mode 100644 index 0000000000..32e832fd13 --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java @@ -0,0 +1,56 @@ +/* + * 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 com.carrotsearch.hppc.BitSet; +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.articulationpoints.ArticulationPointsStatsConfig; + +import java.util.Optional; +import java.util.stream.Stream; + +class ArticulationPointsResultBuilderForStatsMode implements ResultBuilder, Void> { + + @Override + public Stream build( + Graph graph, + GraphStore graphStore, + ArticulationPointsStatsConfig configuration, + Optional result, + AlgorithmProcessingTimings timings, + Optional metadata + ) { + if (result.isEmpty()) { + return Stream.of(ArticulationPointsStatsResult.emptyFrom(timings,configuration.toMap())); + } + + var bitSet = result.get(); + return Stream.of( + new ArticulationPointsStatsResult( + timings.computeMillis, + configuration.toMap(), + bitSet.cardinality() + ) + ); + } +} diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java new file mode 100644 index 0000000000..98ea0d6e60 --- /dev/null +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java @@ -0,0 +1,68 @@ +/* + * 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.applications.algorithms.machinery.AlgorithmProcessingTimings; +import org.neo4j.gds.procedures.algorithms.results.StandardStatsResult; +import org.neo4j.gds.result.AbstractResultBuilder; + +import java.util.Map; + +public class ArticulationPointsStatsResult extends StandardStatsResult { + public final long articulationPointCount; + + public ArticulationPointsStatsResult( + long computeMillis, + Map configuration, + long articulationPointCount + ) { + super(0,computeMillis,0,configuration); + this.articulationPointCount = articulationPointCount; + } + public static Builder builder() { + return new Builder(); + } + + + public static ArticulationPointsStatsResult emptyFrom(AlgorithmProcessingTimings timings, Map configurationMap) { + return new ArticulationPointsStatsResult( + timings.computeMillis, + configurationMap, + 0 + ); + } + + public static class Builder extends AbstractResultBuilder { + private long articulationPointCount;; + + public ArticulationPointsStatsResult.Builder withArticulationPointCount(long articulationPointCount) { + this.articulationPointCount = articulationPointCount; + return this; + } + + public ArticulationPointsStatsResult build() { + return new ArticulationPointsStatsResult( + computeMillis, + config.toMap(), + articulationPointCount + ); + } + } +} 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 9ffb4dbe07..7d2152f261 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 @@ -27,6 +27,7 @@ import org.neo4j.gds.applications.algorithms.centrality.CentralityAlgorithmsWriteModeBusinessFacade; import org.neo4j.gds.applications.algorithms.machinery.MemoryEstimateResult; import org.neo4j.gds.articulationpoints.ArticulationPointsMutateConfig; +import org.neo4j.gds.articulationpoints.ArticulationPointsStatsConfig; import org.neo4j.gds.articulationpoints.ArticulationPointsStreamConfig; import org.neo4j.gds.articulationpoints.ArticulationPointsWriteConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; @@ -452,8 +453,6 @@ public Stream articulationPointsStream( ); } - public ArticulationPointsMutateStub articulationPointsMutateStub(){return articulationPointsMutateStub;} - public Stream articulationPointsStreamEstimate( Object graphNameOrConfiguration, Map algorithmConfiguration @@ -469,6 +468,7 @@ public Stream articulationPointsStreamEstimate( return Stream.of(result); } + public ArticulationPointsMutateStub articulationPointsMutateStub(){return articulationPointsMutateStub;} public Stream articulationPointsMutateEstimate( Object graphNameOrConfiguration, @@ -486,6 +486,34 @@ public Stream articulationPointsMutateEstimate( return Stream.of(result); } + public Stream articulationPointsStats( + String graphNameAsString, + Map rawConfiguration + ) { + return algorithmExecutionScaffolding.runAlgorithm( + graphNameAsString, + rawConfiguration, + ArticulationPointsStatsConfig::of, + statsMode()::articulationPoints, + new ArticulationPointsResultBuilderForStatsMode() + ); + } + + public Stream articulationPointsStatsEstimate( + Object graphNameOrConfiguration, + Map algorithmConfiguration + ) { + var result = estimationMode.runEstimation( + algorithmConfiguration, + ArticulationPointsStatsConfig::of, + configuration -> estimationMode().articulationPoints( + configuration, + graphNameOrConfiguration + ) + ); + + return Stream.of(result); + } public Stream articulationPointsWrite( String graphNameAsString, From 15c4f323512f0b4955e5d31cff12a46688adfe80 Mon Sep 17 00:00:00 2001 From: ioannispan Date: Thu, 8 Aug 2024 15:52:40 +0200 Subject: [PATCH 6/6] Align procedure results between different modes Co-authored-by: Veselin Nikolov --- .../ArticulationPointsMutateResult.java | 60 +++---------------- ...lationPointsResultBuilderForStatsMode.java | 10 ++-- ...lationPointsResultBuilderForWriteMode.java | 1 - .../ArticulationPointsStatsResult.java | 50 ++-------------- .../ArticulationPointsWriteResult.java | 3 +- ...ationPointsResultBuilderForMutateMode.java | 19 +++--- 6 files changed, 29 insertions(+), 114 deletions(-) diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java index c759d98e29..8de28bc3f9 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsMutateResult.java @@ -19,58 +19,14 @@ */ package org.neo4j.gds.procedures.algorithms.centrality; -import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTimings; -import org.neo4j.gds.procedures.algorithms.results.StandardMutateResult; -import org.neo4j.gds.result.AbstractResultBuilder; - import java.util.Map; -public class ArticulationPointsMutateResult extends StandardMutateResult { - public final long articulationPointCount; - public final long nodePropertiesWritten; - - public ArticulationPointsMutateResult( - long mutateMillis, - long nodePropertiesWritten, - long computeMillis, - Map configuration, - long articulationPointCount - ) { - super(0,computeMillis,0,mutateMillis,configuration); - this.articulationPointCount = articulationPointCount; - this.nodePropertiesWritten = nodePropertiesWritten; - } - public static Builder builder() { - return new Builder(); - } - - - public static ArticulationPointsMutateResult emptyFrom(AlgorithmProcessingTimings timings, Map configurationMap) { - return new ArticulationPointsMutateResult( - timings.mutateOrWriteMillis, - 0, - timings.computeMillis, - configurationMap, - 0 - ); - } - - public static class Builder extends AbstractResultBuilder { - private long articulationPointCount;; - - public ArticulationPointsMutateResult.Builder withArticulationPointCount(long articulationPointCount) { - this.articulationPointCount = articulationPointCount; - return this; - } - - public ArticulationPointsMutateResult build() { - return new ArticulationPointsMutateResult( - mutateMillis, - nodePropertiesWritten, - computeMillis, - config.toMap(), - articulationPointCount - ); - } - } +public record ArticulationPointsMutateResult( + long articulationPointCount, + long nodePropertiesWritten, + long mutateMillis, + long computeMillis, + Map configuration +) { + public static final ArticulationPointsMutateResult EMPTY = new ArticulationPointsMutateResult(0, 0, 0, 0, Map.of()); } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java index 32e832fd13..5ad2851331 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForStatsMode.java @@ -41,16 +41,16 @@ public Stream build( Optional metadata ) { if (result.isEmpty()) { - return Stream.of(ArticulationPointsStatsResult.emptyFrom(timings,configuration.toMap())); + return Stream.of(ArticulationPointsStatsResult.EMPTY); } var bitSet = result.get(); return Stream.of( new ArticulationPointsStatsResult( - timings.computeMillis, - configuration.toMap(), - bitSet.cardinality() - ) + bitSet.cardinality(), + timings.computeMillis, + configuration.toMap() + ) ); } } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java index 3ce061a5b5..98ac3a2c92 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsResultBuilderForWriteMode.java @@ -52,7 +52,6 @@ public Stream build( metadata.map(n -> n.value).orElseThrow(), timings.mutateOrWriteMillis, timings.computeMillis, - graph.nodeCount(), configuration.toMap() ) ); diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java index 98ea0d6e60..5c4259098f 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsStatsResult.java @@ -19,50 +19,12 @@ */ package org.neo4j.gds.procedures.algorithms.centrality; -import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTimings; -import org.neo4j.gds.procedures.algorithms.results.StandardStatsResult; -import org.neo4j.gds.result.AbstractResultBuilder; - import java.util.Map; -public class ArticulationPointsStatsResult extends StandardStatsResult { - public final long articulationPointCount; - - public ArticulationPointsStatsResult( - long computeMillis, - Map configuration, - long articulationPointCount - ) { - super(0,computeMillis,0,configuration); - this.articulationPointCount = articulationPointCount; - } - public static Builder builder() { - return new Builder(); - } - - - public static ArticulationPointsStatsResult emptyFrom(AlgorithmProcessingTimings timings, Map configurationMap) { - return new ArticulationPointsStatsResult( - timings.computeMillis, - configurationMap, - 0 - ); - } - - public static class Builder extends AbstractResultBuilder { - private long articulationPointCount;; - - public ArticulationPointsStatsResult.Builder withArticulationPointCount(long articulationPointCount) { - this.articulationPointCount = articulationPointCount; - return this; - } - - public ArticulationPointsStatsResult build() { - return new ArticulationPointsStatsResult( - computeMillis, - config.toMap(), - articulationPointCount - ); - } - } +public record ArticulationPointsStatsResult( + long articulationPointCount, + long computeMillis, + Map configuration +) { + public static final ArticulationPointsStatsResult EMPTY = new ArticulationPointsStatsResult(0, 0, Map.of()); } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java index c06aaa37a6..f8de3bc3f9 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/ArticulationPointsWriteResult.java @@ -26,8 +26,7 @@ public record ArticulationPointsWriteResult( long nodePropertiesWritten, long writeMillis, long computeMillis, - long nodeCount, Map configuration ) { - public static final ArticulationPointsWriteResult EMPTY = new ArticulationPointsWriteResult(0, 0, 0, 0, 0, Map.of()); + public static final ArticulationPointsWriteResult EMPTY = new ArticulationPointsWriteResult(0, 0, 0, 0, Map.of()); } diff --git a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java index d82bdb1fb4..0b7b2f26d7 100644 --- a/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java +++ b/procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/centrality/stubs/ArticulationPointsResultBuilderForMutateMode.java @@ -41,17 +41,16 @@ public ArticulationPointsMutateResult build( AlgorithmProcessingTimings timings, Optional metadata ) { - if (result.isEmpty()) return ArticulationPointsMutateResult.emptyFrom(timings, configuration.toMap()); + if (result.isEmpty()) return ArticulationPointsMutateResult.EMPTY; - var articulationPointsResult = result.get(); - return ArticulationPointsMutateResult.builder() - .withArticulationPointCount(articulationPointsResult.cardinality()) - .withPreProcessingMillis(timings.preProcessingMillis) - .withComputeMillis(timings.computeMillis) - .withMutateMillis(timings.mutateOrWriteMillis) - .withNodePropertiesWritten(metadata.orElseThrow().value) - .withConfig(configuration) - .build(); + var bitSet = result.get(); + return new ArticulationPointsMutateResult( + bitSet.cardinality(), + metadata.map(n -> n.value).orElseThrow(), + timings.mutateOrWriteMillis, + timings.computeMillis, + configuration.toMap() + ); } }