Skip to content

Commit 9c5c51c

Browse files
authored
Merge pull request #5253 from s1ck/bfs-stats-proc
Add stats mode for BFS
2 parents 54a8cbc + 9b90463 commit 9c5c51c

File tree

8 files changed

+314
-3
lines changed

8 files changed

+314
-3
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gds.paths.traverse;
21+
22+
import org.neo4j.gds.annotation.Configuration;
23+
import org.neo4j.gds.annotation.ValueClass;
24+
import org.neo4j.gds.core.CypherMapWrapper;
25+
26+
@ValueClass
27+
@Configuration
28+
@SuppressWarnings("immutables:subtype")
29+
public interface BfsStatsConfig extends BfsBaseConfig {
30+
static BfsStatsConfig of(CypherMapWrapper userInput) {
31+
return new BfsStatsConfigImpl(userInput);
32+
}
33+
}

doc/asciidoc/algorithms/bfs/bfs.adoc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,46 @@ include::specific-configuration.adoc[]
108108
|===
109109
======
110110
111+
[.include-with-stats]
112+
======
113+
.Run Breadth First Search in stats mode:
114+
[source, cypher, role=noplay]
115+
----
116+
CALL gds.bfs.stats(
117+
graphName: string,
118+
configuration: map
119+
)
120+
YIELD
121+
preProcessingMillis: Integer,
122+
computeMillis: Integer,
123+
postProcessingMillis: Integer,
124+
configuration: Map
125+
----
126+
127+
include::../common-configuration/common-parameters.adoc[]
128+
129+
.General configuration for algorithm execution.
130+
[opts="header",cols="2,1,1m,1,4"]
131+
|===
132+
| Name | Type | Default | Optional | Description
133+
| <<common-configuration-node-labels,nodeLabels>> | List of String | ['*'] | yes | Filter the named graph using the given node labels.
134+
| <<common-configuration-relationship-types,relationshipTypes>> | List of String | ['*'] | yes | Filter the named graph using the given relationship types.
135+
| <<common-configuration-concurrency,concurrency>> | Integer | 4 | yes | The number of concurrent threads used for running the algorithm.
136+
|===
137+
138+
include::specific-configuration.adoc[]
139+
140+
.Results
141+
[opts="header",cols="1,1,6"]
142+
|===
143+
| Name | Type | Description
144+
| preProcessingMillis | Integer | Milliseconds for preprocessing the graph.
145+
| computeMillis | Integer | Milliseconds for running the algorithm.
146+
| postProcessingMillis | Integer | Unused.
147+
| configuration | Map | The configuration used for running the algorithm.
148+
|===
149+
======
150+
111151
====
112152

113153
[[algorithms-bfs-examples]]

doc/asciidoc/operations-reference/appendix-a-graph-algos.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,13 @@ include::../algorithms/algorithm-tiers.adoc[]
174174
| `gds.knn.stream.estimate`
175175
| `gds.knn.write`
176176
| `gds.knn.write.estimate`
177-
.4+<.^|<<algorithms-bfs, BFS>>
177+
.6+<.^|<<algorithms-bfs, BFS>>
178178
| `gds.bfs.mutate`
179179
| `gds.bfs.mutate.estimate`
180180
| `gds.bfs.stream`
181181
| `gds.bfs.stream.estimate`
182+
| `gds.bfs.stats`
183+
| `gds.bfs.stats.estimate`
182184
.4+<.^|<<algorithms-dfs, Depth First Search>>
183185
| `gds.dfs.mutate`
184186
| `gds.dfs.mutate.estimate`

open-packaging/src/test/java/org/neo4j/gds/OpenGdsProcedureSmokeTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ class OpenGdsProcedureSmokeTest extends BaseProcTest {
235235
"gds.bfs.mutate.estimate",
236236
"gds.bfs.stream",
237237
"gds.bfs.stream.estimate",
238+
"gds.bfs.stats",
239+
"gds.bfs.stats.estimate",
238240

239241
"gds.debug.sysInfo",
240242

@@ -442,7 +444,7 @@ void countShouldMatch() {
442444
);
443445

444446
// If you find yourself updating this count, please also update the count in SmokeTest.kt
445-
int expectedCount = 302;
447+
int expectedCount = 304;
446448
assertEquals(
447449
expectedCount,
448450
registeredProcedures.size(),

proc/path-finding/src/main/java/org/neo4j/gds/paths/traverse/BfsMutateSpec.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
public class BfsMutateSpec implements AlgorithmSpec<BFS, HugeLongArray, BfsMutateConfig, Stream<MutateResult>, BfsAlgorithmFactory<BfsMutateConfig>> {
3535
@Override
3636
public String name() {
37-
return "gds.dfs.mutate";
37+
return "gds.bfs.mutate";
3838
}
3939

4040
@Override
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gds.paths.traverse;
21+
22+
import org.neo4j.gds.BaseProc;
23+
import org.neo4j.gds.executor.MemoryEstimationExecutor;
24+
import org.neo4j.gds.executor.ProcedureExecutor;
25+
import org.neo4j.gds.results.MemoryEstimateResult;
26+
import org.neo4j.gds.results.StandardStatsResult;
27+
import org.neo4j.procedure.Description;
28+
import org.neo4j.procedure.Name;
29+
import org.neo4j.procedure.Procedure;
30+
31+
import java.util.Map;
32+
import java.util.stream.Stream;
33+
34+
import static org.neo4j.procedure.Mode.READ;
35+
36+
public class BfsStatsProc extends BaseProc {
37+
38+
@Procedure(name = "gds.bfs.stats", mode = READ)
39+
@Description(BfsStreamProc.DESCRIPTION)
40+
public Stream<StandardStatsResult> stats(
41+
@Name(value = "graphName") String graphName,
42+
@Name(value = "configuration", defaultValue = "{}") Map<String, Object> configuration
43+
) {
44+
var statsSpec = new BfsStatsSpec();
45+
46+
return new ProcedureExecutor<>(
47+
statsSpec,
48+
executionContext()
49+
).compute(graphName, configuration, false, false);
50+
}
51+
52+
@Procedure(name = "gds.bfs.stats.estimate", mode = READ)
53+
@Description(ESTIMATE_DESCRIPTION)
54+
public Stream<MemoryEstimateResult> estimate(
55+
@Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration,
56+
@Name(value = "algoConfiguration") Map<String, Object> algoConfiguration
57+
) {
58+
var statsSpec = new BfsStatsSpec();
59+
60+
return new MemoryEstimationExecutor<>(
61+
statsSpec,
62+
executionContext()
63+
).computeEstimate(graphNameOrConfiguration, algoConfiguration);
64+
}
65+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gds.paths.traverse;
21+
22+
import org.neo4j.gds.core.utils.paged.HugeLongArray;
23+
import org.neo4j.gds.executor.AlgorithmSpec;
24+
import org.neo4j.gds.executor.ComputationResultConsumer;
25+
import org.neo4j.gds.executor.GdsCallable;
26+
import org.neo4j.gds.executor.NewConfigFunction;
27+
import org.neo4j.gds.results.StandardStatsResult;
28+
29+
import java.util.stream.Stream;
30+
31+
import static org.neo4j.gds.executor.ExecutionMode.MUTATE_RELATIONSHIP;
32+
33+
@GdsCallable(name = "gds.bfs.stats", description = BfsStreamProc.DESCRIPTION, executionMode = MUTATE_RELATIONSHIP)
34+
public class BfsStatsSpec implements AlgorithmSpec<BFS, HugeLongArray, BfsStatsConfig, Stream<StandardStatsResult>, BfsAlgorithmFactory<BfsStatsConfig>> {
35+
@Override
36+
public String name() {
37+
return "gds.bfs.stats";
38+
}
39+
40+
@Override
41+
public BfsAlgorithmFactory<BfsStatsConfig> algorithmFactory() {
42+
return new BfsAlgorithmFactory<>();
43+
}
44+
45+
@Override
46+
public NewConfigFunction<BfsStatsConfig> newConfigFunction() {
47+
return (__, config) -> BfsStatsConfig.of(config);
48+
}
49+
50+
@Override
51+
public ComputationResultConsumer<BFS, HugeLongArray, BfsStatsConfig, Stream<StandardStatsResult>> computationResultConsumer() {
52+
return (computationResult, executionContext) -> Stream.of(new StandardStatsResult(
53+
computationResult.preProcessingMillis(),
54+
computationResult.computeMillis(),
55+
0,
56+
computationResult.config().toMap()
57+
));
58+
}
59+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gds.paths.traverse;
21+
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.neo4j.gds.BaseProcTest;
25+
import org.neo4j.gds.GdsCypher;
26+
import org.neo4j.gds.catalog.GraphProjectProc;
27+
import org.neo4j.gds.extension.Neo4jGraph;
28+
29+
import java.util.List;
30+
import java.util.Map;
31+
32+
import static org.hamcrest.Matchers.greaterThan;
33+
import static org.hamcrest.Matchers.isA;
34+
35+
class BfsStatsProcTest extends BaseProcTest {
36+
37+
@Neo4jGraph
38+
private static final String DB_CYPHER =
39+
"CREATE" +
40+
" (a:Node)" +
41+
", (b:Node)" +
42+
", (c:Node)" +
43+
", (d:Node)" +
44+
", (e:Node)" +
45+
", (f:Node)" +
46+
", (g:Node)" +
47+
", (a)-[:TYPE]->(b)" +
48+
", (a)-[:TYPE]->(c)" +
49+
", (b)-[:TYPE]->(d)" +
50+
", (c)-[:TYPE]->(d)" +
51+
", (d)-[:TYPE]->(e)" +
52+
", (d)-[:TYPE]->(f)" +
53+
", (e)-[:TYPE]->(g)" +
54+
", (f)-[:TYPE]->(g)";
55+
56+
@BeforeEach
57+
void setupGraph() throws Exception {
58+
registerProcedures(BfsStatsProc.class, GraphProjectProc.class);
59+
}
60+
61+
@Test
62+
void testCompute() {
63+
var createQuery = GdsCypher.call(DEFAULT_GRAPH_NAME)
64+
.graphProject()
65+
.withNodeLabel("Node")
66+
.withRelationshipType("TYPE")
67+
.yields();
68+
runQuery(createQuery);
69+
70+
long id = idFunction.of("a");
71+
String query = GdsCypher.call(DEFAULT_GRAPH_NAME)
72+
.algo("bfs")
73+
.statsMode()
74+
.addParameter("sourceNode", id)
75+
.addParameter("targetNodes", List.of(idFunction.of("e"), idFunction.of("f")))
76+
.yields();
77+
78+
assertCypherResult(query, List.of(Map.of(
79+
"preProcessingMillis", greaterThan(-1L),
80+
"computeMillis", greaterThan(-1L),
81+
"postProcessingMillis", greaterThan(-1L),
82+
"configuration", isA(Map.class)
83+
)));
84+
}
85+
86+
@Test
87+
void testEstimate() {
88+
var createQuery = GdsCypher.call(DEFAULT_GRAPH_NAME)
89+
.graphProject()
90+
.withNodeLabel("Node")
91+
.withRelationshipType("TYPE")
92+
.yields();
93+
runQuery(createQuery);
94+
95+
long id = idFunction.of("a");
96+
String query = GdsCypher.call(DEFAULT_GRAPH_NAME)
97+
.algo("bfs")
98+
.statsEstimation()
99+
.addParameter("sourceNode", id)
100+
.addParameter("targetNodes", List.of(idFunction.of("e"), idFunction.of("f")))
101+
.yields("bytesMin", "bytesMax", "nodeCount", "relationshipCount");
102+
103+
assertCypherResult(query, List.of(Map.of(
104+
"bytesMin", greaterThan(0L),
105+
"bytesMax", greaterThan(0L),
106+
"nodeCount", 7L,
107+
"relationshipCount", 8L
108+
)));
109+
}
110+
}

0 commit comments

Comments
 (0)