diff --git a/algo-common/src/main/java/org/neo4j/gds/result/CentralityStatistics.java b/algo-common/src/main/java/org/neo4j/gds/result/CentralityStatistics.java index 4ee313035a..c82d1c5236 100644 --- a/algo-common/src/main/java/org/neo4j/gds/result/CentralityStatistics.java +++ b/algo-common/src/main/java/org/neo4j/gds/result/CentralityStatistics.java @@ -20,13 +20,18 @@ package org.neo4j.gds.result; import org.HdrHistogram.DoubleHistogram; +import org.neo4j.gds.annotation.ValueClass; import org.neo4j.gds.core.ProcedureConstants; -import org.neo4j.gds.core.utils.partition.Partition; import org.neo4j.gds.core.concurrency.ParallelUtil; +import org.neo4j.gds.core.utils.ProgressTimer; +import org.neo4j.gds.core.utils.partition.Partition; import org.neo4j.gds.core.utils.partition.PartitionUtils; +import java.util.Collections; +import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.LongToDoubleFunction; public final class CentralityStatistics { @@ -83,4 +88,44 @@ public void run() { }); } } + + public static CentralityStats centralityStatistics( + long nodeCount, + LongToDoubleFunction centralityProvider, + ExecutorService executorService, + int concurrency, + boolean shouldCompute + ) { + Optional maybeHistogram = Optional.empty(); + var computeMilliseconds = new AtomicLong(0); + try (var ignored = ProgressTimer.start(computeMilliseconds::set)) { + if (shouldCompute) { + maybeHistogram = Optional.of(histogram( + nodeCount, + centralityProvider, + executorService, + concurrency + )); + } + } + + return ImmutableCentralityStats.of(maybeHistogram, computeMilliseconds.get()); + } + + public static Map centralitySummary(Optional histogram) { + return histogram + .map(HistogramUtils::centralitySummary) + .orElseGet(Collections::emptyMap); + } + + @ValueClass + @SuppressWarnings("immutables:incompat") + public interface CentralityStats { + + Optional histogram(); + + long computeMilliseconds(); + } + + } diff --git a/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacade.java b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacade.java index 61ee928057..84d28d7703 100644 --- a/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacade.java +++ b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacade.java @@ -19,6 +19,20 @@ */ package org.neo4j.gds.algorithms.centrality; +import org.neo4j.gds.algorithms.AlgorithmComputationResult; +import org.neo4j.gds.algorithms.StatsResult; +import org.neo4j.gds.algorithms.centrality.specificfields.CentralityStatisticsSpecificFields; +import org.neo4j.gds.algorithms.centrality.specificfields.StandardCentralityStatisticsSpecificFields; +import org.neo4j.gds.algorithms.runner.AlgorithmRunner; +import org.neo4j.gds.api.DatabaseId; +import org.neo4j.gds.api.User; +import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; +import org.neo4j.gds.config.AlgoBaseConfig; +import org.neo4j.gds.core.concurrency.DefaultPool; +import org.neo4j.gds.result.CentralityStatistics; + +import java.util.function.Supplier; + public class CentralityAlgorithmsStatsBusinessFacade { private final CentralityAlgorithmsFacade centralityAlgorithmsFacade; @@ -28,4 +42,65 @@ public CentralityAlgorithmsStatsBusinessFacade(CentralityAlgorithmsFacade centra } + public StatsResult betweennessCentrality( + String graphName, + BetweennessCentralityStatsConfig configuration, + User user, + DatabaseId databaseId, + boolean shouldComputeCentralityDistribution + ) { + // 1. Run the algorithm and time the execution + var intermediateResult = AlgorithmRunner.runWithTiming( + () -> centralityAlgorithmsFacade.betweennessCentrality(graphName, configuration, user, databaseId) + ); + var algorithmResult = intermediateResult.algorithmResult; + + return statsResult( + algorithmResult, + configuration, + (result -> result::get), + (result, centralityDistribution) -> { + return new StandardCentralityStatisticsSpecificFields( + centralityDistribution + ); + }, + shouldComputeCentralityDistribution, + intermediateResult.computeMilliseconds, + () -> StandardCentralityStatisticsSpecificFields.EMPTY + ); + } + + StatsResult statsResult( + AlgorithmComputationResult algorithmResult, + CONFIG configuration, + CentralityFunctionSupplier centralityFunctionSupplier, + SpecificFieldsWithCentralityDistributionSupplier specificFieldsSupplier, + boolean shouldComputeCentralityDistribution, + long computeMilliseconds, + Supplier emptyASFSupplier + ) { + + return algorithmResult.result().map(result -> { + + // 2. Compute result statistics + var centralityStatistics = CentralityStatistics.centralityStatistics( + algorithmResult.graph().nodeCount(), + centralityFunctionSupplier.centralityFunction(result), + DefaultPool.INSTANCE, + configuration.concurrency(), + shouldComputeCentralityDistribution + ); + + var communitySummary = CentralityStatistics.centralitySummary(centralityStatistics.histogram()); + + var specificFields = specificFieldsSupplier.specificFields(result, communitySummary); + + return StatsResult.builder() + .computeMillis(computeMilliseconds) + .postProcessingMillis(centralityStatistics.computeMilliseconds()) + .algorithmSpecificFields(specificFields) + .build(); + }).orElseGet(() -> StatsResult.empty(emptyASFSupplier.get())); + + } } diff --git a/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityFunctionSupplier.java b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityFunctionSupplier.java new file mode 100644 index 0000000000..424bd90b10 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/CentralityFunctionSupplier.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.algorithms.centrality; + +import java.util.function.LongToDoubleFunction; + +interface CentralityFunctionSupplier { + LongToDoubleFunction centralityFunction(R result); +} diff --git a/algo/src/main/java/org/neo4j/gds/algorithms/centrality/SpecificFieldsWithCentralityDistributionSupplier.java b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/SpecificFieldsWithCentralityDistributionSupplier.java new file mode 100644 index 0000000000..31b7465bb0 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/SpecificFieldsWithCentralityDistributionSupplier.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.algorithms.centrality; + +import java.util.Map; + +interface SpecificFieldsWithCentralityDistributionSupplier { + ASF specificFields(R result, Map centralityDistribution); +} diff --git a/algo/src/main/java/org/neo4j/gds/algorithms/centrality/specificfields/CentralityStatisticsSpecificFields.java b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/specificfields/CentralityStatisticsSpecificFields.java new file mode 100644 index 0000000000..20340cfe2e --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/specificfields/CentralityStatisticsSpecificFields.java @@ -0,0 +1,28 @@ +/* + * 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.algorithms.centrality.specificfields; + +import java.util.Map; + + +public interface CentralityStatisticsSpecificFields { + + Map centralityDistribution(); +} diff --git a/algo/src/main/java/org/neo4j/gds/algorithms/centrality/specificfields/StandardCentralityStatisticsSpecificFields.java b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/specificfields/StandardCentralityStatisticsSpecificFields.java new file mode 100644 index 0000000000..75d948dff3 --- /dev/null +++ b/algo/src/main/java/org/neo4j/gds/algorithms/centrality/specificfields/StandardCentralityStatisticsSpecificFields.java @@ -0,0 +1,45 @@ +/* + * 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.algorithms.centrality.specificfields; + +import java.util.Map; + + +public class StandardCentralityStatisticsSpecificFields implements CentralityStatisticsSpecificFields { + + public static final StandardCentralityStatisticsSpecificFields EMPTY = new StandardCentralityStatisticsSpecificFields( + Map.of() + ); + + private final Map centralityDistribution; + + public StandardCentralityStatisticsSpecificFields( + Map centralityDistribution + ) { + this.centralityDistribution = centralityDistribution; + } + + + @Override + public Map centralityDistribution(){ + return centralityDistribution; + } + +} diff --git a/algo/src/test/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacadeTest.java b/algo/src/test/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacadeTest.java new file mode 100644 index 0000000000..2c9eaa184e --- /dev/null +++ b/algo/src/test/java/org/neo4j/gds/algorithms/centrality/CentralityAlgorithmsStatsBusinessFacadeTest.java @@ -0,0 +1,152 @@ +/* + * 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.algorithms.centrality; + +import org.assertj.core.data.Offset; +import org.junit.jupiter.api.Test; +import org.neo4j.gds.algorithms.AlgorithmComputationResult; +import org.neo4j.gds.algorithms.centrality.specificfields.StandardCentralityStatisticsSpecificFields; +import org.neo4j.gds.api.Graph; +import org.neo4j.gds.api.GraphStore; +import org.neo4j.gds.collections.ha.HugeDoubleArray; +import org.neo4j.gds.config.AlgoBaseConfig; +import org.neo4j.gds.config.MutateNodePropertyConfig; +import org.neo4j.gds.core.utils.TerminationFlag; +import org.neo4j.gds.extension.GdlExtension; +import org.neo4j.gds.extension.GdlGraph; +import org.neo4j.gds.extension.Inject; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@GdlExtension +class CentralityAlgorithmsStatsBusinessFacadeTest { + + @GdlGraph + private static final String GRAPH = + "CREATE " + + " (:Node), " + + " (:Node), " + + " (:Node), " + + " (:Node), "; + + @Inject + private Graph graph; + + @Inject + private GraphStore graphStore; + + + @Test + void statsWithoutAlgorithmResult() { + + var configurationMock = mock(AlgoBaseConfig.class); + var algorithmResult = AlgorithmComputationResult.withoutAlgorithmResult(graph, graphStore); + + + var businessFacade = new CentralityAlgorithmsStatsBusinessFacade(null); + + var statsResult = businessFacade.statsResult( + algorithmResult, + configurationMock, + null, + null, + false, + 0L, + () -> StandardCentralityStatisticsSpecificFields.EMPTY + ); + + assertThat(statsResult.algorithmSpecificFields().centralityDistribution()) + .as("Incorrect additional algorithm field value") + .isEmpty(); + } + + @Test + void statsWithCommunityStatistics() { + + + var businessFacade = new CentralityAlgorithmsStatsBusinessFacade(null); + + var configMock = mock(MutateNodePropertyConfig.class); + + + var result = HugeDoubleArray.of(0.1, 0.2, 0.3, 0.4); + + var algorithmResultMock = AlgorithmComputationResult.of( + result, + graph, + graphStore, + TerminationFlag.RUNNING_TRUE + ); + + var statsResult = businessFacade.statsResult( + algorithmResultMock, + configMock, + ((r) -> r::get), + (r, cs) -> new StandardCentralityStatisticsSpecificFields(cs), + false, + 50L, + () -> StandardCentralityStatisticsSpecificFields.EMPTY + ); + + + assertThat(statsResult.algorithmSpecificFields().centralityDistribution()).isEmpty(); + assertThat(statsResult.computeMillis()).isEqualTo(50); + assertThat(statsResult.postProcessingMillis()).isGreaterThanOrEqualTo(0L); + + } + + @Test + void statsWithoutCommunityStatistics() { + + var businessFacade = new CentralityAlgorithmsStatsBusinessFacade(null); + + var configMock = mock(MutateNodePropertyConfig.class); + + + var result = HugeDoubleArray.of(0.1, 0.2, 0.3, 0.4); + var algorithmResultMock = AlgorithmComputationResult.of( + result, + graph, + graphStore, + TerminationFlag.RUNNING_TRUE + ); + + var statsResult = businessFacade.statsResult( + algorithmResultMock, + configMock, + ((r) -> r::get), + (r, cs) -> new StandardCentralityStatisticsSpecificFields(cs), + true, + 50L, + () -> StandardCentralityStatisticsSpecificFields.EMPTY + ); + + + assertThat((double)statsResult.algorithmSpecificFields().centralityDistribution().get("mean")).isCloseTo(0.25, + Offset.offset(1e-4)); + + assertThat(statsResult.computeMillis()).isEqualTo(50); + assertThat(statsResult.postProcessingMillis()).isGreaterThanOrEqualTo(0L); + + + } + +} diff --git a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProc.java b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProc.java index c7281d18a0..527f8e9e89 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProc.java +++ b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProc.java @@ -20,9 +20,10 @@ package org.neo4j.gds.betweenness; import org.neo4j.gds.BaseProc; -import org.neo4j.gds.executor.MemoryEstimationExecutor; -import org.neo4j.gds.executor.ProcedureExecutor; +import org.neo4j.gds.procedures.GraphDataScience; +import org.neo4j.gds.procedures.centrality.CentralityStatsResult; import org.neo4j.gds.results.MemoryEstimateResult; +import org.neo4j.procedure.Context; import org.neo4j.procedure.Description; import org.neo4j.procedure.Name; import org.neo4j.procedure.Procedure; @@ -35,16 +36,16 @@ public class BetweennessCentralityStatsProc extends BaseProc { + @Context + public GraphDataScience facade; + @Procedure(value = "gds.betweenness.stats", mode = READ) @Description(BETWEENNESS_DESCRIPTION) - public Stream stats( + public Stream stats( @Name(value = "graphName") String graphName, @Name(value = "configuration", defaultValue = "{}") Map configuration ) { - return new ProcedureExecutor<>( - new BetweennessCentralityStatsSpecification(), - executionContext() - ).compute(graphName, configuration); + return facade.centrality().betweenessCentralityStats(graphName, configuration); } @Procedure(value = "gds.betweenness.stats.estimate", mode = READ) @@ -53,11 +54,7 @@ public Stream estimate( @Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration, @Name(value = "algoConfiguration") Map algoConfiguration ) { - return new MemoryEstimationExecutor<>( - new BetweennessCentralityStatsSpecification(), - executionContext(), - transactionContext() - ).computeEstimate(graphNameOrConfiguration, algoConfiguration); + return facade.centrality().betweenessCentralityStatsEstimate(graphNameOrConfiguration, algoConfiguration); } } diff --git a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsSpecification.java b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsSpecification.java index a10ab5d7af..78e0650841 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsSpecification.java +++ b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsSpecification.java @@ -26,6 +26,7 @@ import org.neo4j.gds.executor.GdsCallable; import org.neo4j.gds.executor.NewConfigFunction; import org.neo4j.gds.executor.validation.ValidationConfiguration; +import org.neo4j.gds.procedures.centrality.CentralityStatsResult; import java.util.stream.Stream; @@ -33,7 +34,7 @@ import static org.neo4j.gds.executor.ExecutionMode.STATS; @GdsCallable(name = "gds.betweenness.stats", description = BETWEENNESS_DESCRIPTION, executionMode = STATS) -public class BetweennessCentralityStatsSpecification implements AlgorithmSpec, BetweennessCentralityFactory> { +public class BetweennessCentralityStatsSpecification implements AlgorithmSpec, BetweennessCentralityFactory> { @Override public String name() { return "BetweennessCentralityStats"; @@ -50,9 +51,9 @@ public NewConfigFunction newConfigFunction() { } @Override - public ComputationResultConsumer> computationResultConsumer() { + public ComputationResultConsumer> computationResultConsumer() { return (computationResult, executionContext) -> { - var builder = new StatsResult.Builder( + var builder = new CentralityStatsResult.Builder( executionContext.returnColumns(), computationResult.config().concurrency() ); diff --git a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/MutateResult.java b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/MutateResult.java index 28aee5f87b..a22c079951 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/MutateResult.java +++ b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/MutateResult.java @@ -21,11 +21,12 @@ import org.jetbrains.annotations.Nullable; import org.neo4j.gds.api.ProcedureReturnColumns; +import org.neo4j.gds.procedures.centrality.CentralityStatsResult; import org.neo4j.gds.result.AbstractCentralityResultBuilder; import java.util.Map; -public final class MutateResult extends StatsResult { +public final class MutateResult extends CentralityStatsResult { public final long nodePropertiesWritten; public final long mutateMillis; diff --git a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/WriteResult.java b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/WriteResult.java index d742f3852a..422fd989b9 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/WriteResult.java +++ b/proc/centrality/src/main/java/org/neo4j/gds/betweenness/WriteResult.java @@ -21,11 +21,12 @@ import org.jetbrains.annotations.Nullable; import org.neo4j.gds.api.ProcedureReturnColumns; +import org.neo4j.gds.procedures.centrality.CentralityStatsResult; import org.neo4j.gds.result.AbstractCentralityResultBuilder; import java.util.Map; -public final class WriteResult extends StatsResult { +public final class WriteResult extends CentralityStatsResult { public final long nodePropertiesWritten; public final long writeMillis; diff --git a/proc/centrality/src/test/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProcTest.java b/proc/centrality/src/test/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProcTest.java index 144c397127..faf122a484 100644 --- a/proc/centrality/src/test/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProcTest.java +++ b/proc/centrality/src/test/java/org/neo4j/gds/betweenness/BetweennessCentralityStatsProcTest.java @@ -28,12 +28,10 @@ import org.neo4j.gds.Orientation; import org.neo4j.gds.catalog.GraphProjectProc; import org.neo4j.gds.extension.Neo4jGraph; -import org.neo4j.graphdb.QueryExecutionException; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.InstanceOfAssertFactories.LONG; class BetweennessCentralityStatsProcTest extends BaseProcTest { @@ -41,15 +39,15 @@ class BetweennessCentralityStatsProcTest extends BaseProcTest { @Neo4jGraph private static final String DB_CYPHER = "CREATE" + - " (a:Node {name: 'a'})" + - ", (b:Node {name: 'b'})" + - ", (c:Node {name: 'c'})" + - ", (d:Node {name: 'd'})" + - ", (e:Node {name: 'e'})" + - ", (a)-[:REL]->(b)" + - ", (b)-[:REL]->(c)" + - ", (c)-[:REL]->(d)" + - ", (d)-[:REL]->(e)"; + " (a:Node {name: 'a'})" + + ", (b:Node {name: 'b'})" + + ", (c:Node {name: 'c'})" + + ", (d:Node {name: 'd'})" + + ", (e:Node {name: 'e'})" + + ", (a)-[:REL]->(b)" + + ", (b)-[:REL]->(c)" + + ", (c)-[:REL]->(d)" + + ", (d)-[:REL]->(e)"; @BeforeEach void setupGraph() throws Exception { @@ -80,12 +78,14 @@ void testStats() { .isInstanceOf(Map.class) .asInstanceOf(InstanceOfAssertFactories.MAP) .containsEntry("min", 0.0) - .hasEntrySatisfying("max", + .hasEntrySatisfying( + "max", value -> assertThat(value) .asInstanceOf(InstanceOfAssertFactories.DOUBLE) .isEqualTo(4.0, Offset.offset(1e-4)) ) - .hasEntrySatisfying("mean", + .hasEntrySatisfying( + "mean", value -> assertThat(value) .asInstanceOf(InstanceOfAssertFactories.DOUBLE) .isEqualTo(2.0, Offset.offset(1e-4)) @@ -109,22 +109,4 @@ void testStats() { .isEqualTo(1); } - @Test - void shouldFailOnMixedProjections() { - runQuery( - "CALL gds.graph.project(" + - " 'mixedGraph', " + - " '*', " + - " {" + - " N: {type: 'REL', orientation: 'NATURAL'}, " + - " U: {type: 'REL', orientation: 'UNDIRECTED'}" + - " }" + - ")" - ); - - assertThatExceptionOfType(QueryExecutionException.class) - .isThrownBy(() -> runQuery("CALL gds.betweenness.stats('mixedGraph', {})")) - .withRootCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("Combining UNDIRECTED orientation with NATURAL or REVERSE is not supported."); - } } diff --git a/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/BetweenessCentralityComputationalResultTransformer.java b/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/BetweenessCentralityComputationalResultTransformer.java index c6ca305e28..619360976f 100644 --- a/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/BetweenessCentralityComputationalResultTransformer.java +++ b/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/BetweenessCentralityComputationalResultTransformer.java @@ -19,9 +19,12 @@ */ package org.neo4j.gds.procedures.centrality; +import org.neo4j.gds.algorithms.StatsResult; import org.neo4j.gds.algorithms.StreamComputationResult; +import org.neo4j.gds.algorithms.centrality.specificfields.StandardCentralityStatisticsSpecificFields; import org.neo4j.gds.api.IdMap; import org.neo4j.gds.api.properties.nodes.NodePropertyValuesAdapter; +import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; import org.neo4j.gds.collections.haa.HugeAtomicDoubleArray; import java.util.stream.LongStream; @@ -48,4 +51,17 @@ static Stream toStreamResult( }).orElseGet(Stream::empty); } + + static CentralityStatsResult toStatsResult( + StatsResult computationResult, + BetweennessCentralityStatsConfig configuration + ) { + return new CentralityStatsResult( + computationResult.algorithmSpecificFields().centralityDistribution(), + computationResult.preProcessingMillis(), + computationResult.computeMillis(), + computationResult.postProcessingMillis(), + configuration.toMap() + ); + } } diff --git a/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityProcedureFacade.java b/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityProcedureFacade.java index 1ab5db2794..813a41876e 100644 --- a/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityProcedureFacade.java +++ b/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityProcedureFacade.java @@ -28,6 +28,7 @@ import org.neo4j.gds.api.DatabaseId; import org.neo4j.gds.api.ProcedureReturnColumns; import org.neo4j.gds.api.User; +import org.neo4j.gds.betweenness.BetweennessCentralityStatsConfig; import org.neo4j.gds.betweenness.BetweennessCentralityStreamConfig; import org.neo4j.gds.config.AlgoBaseConfig; import org.neo4j.gds.core.CypherMapWrapper; @@ -92,6 +93,23 @@ public Stream betweenessCentralityStream( return BetweenessCentralityComputationalResultTransformer.toStreamResult(computationResult); } + public Stream betweenessCentralityStats( + String graphName, + Map configuration + ) { + var config = createStreamConfig(configuration, BetweennessCentralityStatsConfig::of); + + var computationResult = statsBusinessFacade.betweennessCentrality( + graphName, + config, + user, + databaseId, + procedureReturnColumns.contains("centralityDistribution") + ); + + return Stream.of(BetweenessCentralityComputationalResultTransformer.toStatsResult(computationResult, config)); + } + public Stream betweenessCentralityStreamEstimate( Object graphNameOrConfiguration, Map configuration @@ -102,6 +120,16 @@ public Stream betweenessCentralityStreamEstimate( } + public Stream betweenessCentralityStatsEstimate( + Object graphNameOrConfiguration, + Map configuration + ) { + var config = createConfig(configuration, BetweennessCentralityStatsConfig::of); + + return Stream.of(estimateBusinessFacade.betweennessCentrality(graphNameOrConfiguration, config)); + + } + diff --git a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/StatsResult.java b/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityStatsResult.java similarity index 79% rename from proc/centrality/src/main/java/org/neo4j/gds/betweenness/StatsResult.java rename to procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityStatsResult.java index 8f10aab29f..1711916623 100644 --- a/proc/centrality/src/main/java/org/neo4j/gds/betweenness/StatsResult.java +++ b/procedures/facade/src/main/java/org/neo4j/gds/procedures/centrality/CentralityStatsResult.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.gds.betweenness; +package org.neo4j.gds.procedures.centrality; import org.jetbrains.annotations.Nullable; import org.neo4j.gds.api.ProcedureReturnColumns; @@ -26,11 +26,11 @@ import java.util.Map; -public class StatsResult extends StandardStatsResult { +public class CentralityStatsResult extends StandardStatsResult { public final Map centralityDistribution; - StatsResult( + public CentralityStatsResult( @Nullable Map centralityDistribution, long preProcessingMillis, long computeMillis, @@ -41,14 +41,14 @@ public class StatsResult extends StandardStatsResult { this.centralityDistribution = centralityDistribution; } - static final class Builder extends AbstractCentralityResultBuilder { - protected Builder(ProcedureReturnColumns returnColumns, int concurrency) { + public static final class Builder extends AbstractCentralityResultBuilder { + public Builder(ProcedureReturnColumns returnColumns, int concurrency) { super(returnColumns, concurrency); } @Override - public StatsResult buildResult() { - return new StatsResult( + public CentralityStatsResult buildResult() { + return new CentralityStatsResult( centralityHistogram, preProcessingMillis, computeMillis,