Skip to content

Commit abfd825

Browse files
Max-flow write facade
Co-authored-by: Ioannis Panagiotas <ioannis.panagiotas@neo4j.com>
1 parent b33af58 commit abfd825

File tree

10 files changed

+430
-0
lines changed

10 files changed

+430
-0
lines changed

applications/algorithms/path-finding/src/main/java/org/neo4j/gds/applications/algorithms/pathfinding/PathFindingAlgorithmsWriteModeBusinessFacade.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
import org.neo4j.gds.config.WriteRelationshipConfig;
3636
import org.neo4j.gds.kspanningtree.KSpanningTreeWriteConfig;
3737
import org.neo4j.gds.logging.Log;
38+
import org.neo4j.gds.maxflow.FlowResult;
39+
import org.neo4j.gds.maxflow.MaxFlowWriteConfig;
3840
import org.neo4j.gds.mem.MemoryEstimation;
3941
import org.neo4j.gds.pathfinding.BellmanFordWriteStep;
4042
import org.neo4j.gds.pathfinding.KSpanningTreeWriteStep;
43+
import org.neo4j.gds.pathfinding.MaxFlowWriteStep;
4144
import org.neo4j.gds.pathfinding.PrizeCollectingSteinerTreeWriteStep;
4245
import org.neo4j.gds.pathfinding.ShortestPathWriteStep;
4346
import org.neo4j.gds.pathfinding.SpanningTreeWriteStep;
@@ -65,6 +68,7 @@
6568
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.DeltaStepping;
6669
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.Dijkstra;
6770
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.KSpanningTree;
71+
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.MaxFlow;
6872
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.PCST;
6973
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.SingleSourceDijkstra;
7074
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.SteinerTree;
@@ -170,6 +174,30 @@ public <RESULT> RESULT kSpanningTree(
170174
);
171175
}
172176

177+
public <RESULT> RESULT maxFlow(
178+
GraphName graphName,
179+
MaxFlowWriteConfig configuration,
180+
ResultBuilder<MaxFlowWriteConfig, FlowResult, RESULT, RelationshipsWritten> resultBuilder
181+
) {
182+
var writeStep = new MaxFlowWriteStep(
183+
writeRelationshipService,
184+
configuration.writeRelationshipType(),
185+
configuration.writeProperty(),
186+
configuration::resolveResultStore,
187+
configuration.jobId()
188+
);
189+
190+
return runAlgorithmAndWrite(
191+
graphName,
192+
configuration,
193+
MaxFlow,
194+
estimationFacade::maxFlow,
195+
(graph, __) -> pathFindingAlgorithms.maxFlow(graph, configuration),
196+
writeStep,
197+
resultBuilder
198+
);
199+
}
200+
173201
public <RESULT> RESULT pcst(
174202
GraphName graphName,
175203
PCSTWriteConfig configuration,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.maxflow;
21+
22+
import org.neo4j.gds.procedures.GraphDataScienceProcedures;
23+
import org.neo4j.gds.procedures.algorithms.pathfinding.MaxFlowWriteResult;
24+
import org.neo4j.procedure.Context;
25+
import org.neo4j.procedure.Description;
26+
import org.neo4j.procedure.Name;
27+
import org.neo4j.procedure.Procedure;
28+
29+
import java.util.Map;
30+
import java.util.stream.Stream;
31+
32+
import static org.neo4j.gds.paths.maxflow.MaxFlowConstants.MAXFLOW_DESCRIPTION;
33+
import static org.neo4j.procedure.Mode.WRITE;
34+
35+
public class MaxFlowWriteProc {
36+
@Context
37+
public GraphDataScienceProcedures facade;
38+
39+
@Procedure(value = "gds.maxFlow.write", mode = WRITE)
40+
@Description(MAXFLOW_DESCRIPTION)
41+
public Stream<MaxFlowWriteResult> maxFlow(
42+
@Name(value = "graphName") String graphName,
43+
@Name(value = "configuration", defaultValue = "{}") Map<String, Object> configuration
44+
) {
45+
return facade.algorithms().pathFinding().maxFlowWrite(graphName, configuration);
46+
}
47+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.maxflow;
21+
22+
import org.apache.commons.lang3.tuple.Triple;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
import org.neo4j.gds.BaseProcTest;
26+
import org.neo4j.gds.GdsCypher;
27+
import org.neo4j.gds.catalog.GraphProjectProc;
28+
import org.neo4j.gds.extension.IdFunction;
29+
import org.neo4j.gds.extension.Inject;
30+
import org.neo4j.gds.extension.Neo4jGraph;
31+
32+
import java.util.HashSet;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
class MaxFlowWriteProcTest extends BaseProcTest {
37+
38+
@Neo4jGraph(offsetIds = true)
39+
static final String DB_CYPHER = """
40+
CREATE
41+
(a:Node {id: 0}),
42+
(b:Node {id: 1}),
43+
(c:Node {id: 2}),
44+
(d:Node {id: 3}),
45+
(e:Node {id: 4}),
46+
(a)-[:R {w: 4.0}]->(d),
47+
(b)-[:R {w: 3.0}]->(a),
48+
(c)-[:R {w: 2.0}]->(a),
49+
(c)-[:R {w: 0.0}]->(b),
50+
(d)-[:R {w: 5.0}]->(e)
51+
""";
52+
53+
@Inject
54+
private IdFunction idFunction;
55+
56+
@BeforeEach
57+
void setup() throws Exception {
58+
registerProcedures(MaxFlowWriteProc.class, GraphProjectProc.class);
59+
var createQuery = GdsCypher.call(DEFAULT_GRAPH_NAME)
60+
.graphProject()
61+
.withAnyLabel()
62+
.withRelationshipProperty("w")
63+
.yields();
64+
runQuery(createQuery);
65+
}
66+
67+
@Test
68+
void testYields() {
69+
String query = GdsCypher.call(DEFAULT_GRAPH_NAME)
70+
.algo("gds.maxFlow")
71+
.writeMode()
72+
.addParameter("sourceNodes", idFunction.of("a"))
73+
.addParameter("capacityProperty", "w")
74+
.addParameter("targetNodes", idFunction.of("e"))
75+
.addParameter("writeProperty", "flow")
76+
.addParameter("writeRelationshipType", "MAX_FLOW")
77+
.yields(
78+
"preProcessingMillis",
79+
"computeMillis",
80+
"writeMillis",
81+
"relationshipsWritten",
82+
"totalFlow"
83+
);
84+
85+
runQueryWithRowConsumer(
86+
query,
87+
res -> {
88+
assertThat(res.getNumber("preProcessingMillis").longValue()).isGreaterThanOrEqualTo(0L);
89+
assertThat(res.getNumber("computeMillis").longValue()).isGreaterThanOrEqualTo(0L);
90+
assertThat(res.getNumber("writeMillis").longValue()).isGreaterThanOrEqualTo(0L);
91+
assertThat(res.getNumber("relationshipsWritten").longValue()).isEqualTo(2L);
92+
assertThat(res.getNumber("totalFlow").doubleValue()).isEqualTo(4L);
93+
94+
}
95+
);
96+
97+
var set = new HashSet<Triple<Long, Long, Double>>();
98+
runQueryWithRowConsumer("MATCH (a)-[r:MAX_FLOW]->(b) return id(a) AS a, id(b) AS b, r.flow AS flow",
99+
resultRow -> {
100+
set.add(Triple.of(resultRow.getNumber("a").longValue(), resultRow.getNumber("b").longValue(), resultRow.getNumber("flow").doubleValue()));
101+
});
102+
assertThat(set).containsExactlyInAnyOrder(
103+
Triple.of(idFunction.of("a"), idFunction.of("d"), 4D),
104+
Triple.of(idFunction.of("d"), idFunction.of("e"), 4D)
105+
);
106+
assertThat(set.size()).isEqualTo(2);
107+
}
108+
}

procedures/algorithms-facade/src/main/java/org/neo4j/gds/procedures/algorithms/pathfinding/LocalPathFindingProcedureFacade.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.neo4j.gds.kspanningtree.KSpanningTreeWriteConfig;
3737
import org.neo4j.gds.maxflow.MaxFlowStatsConfig;
3838
import org.neo4j.gds.maxflow.MaxFlowStreamConfig;
39+
import org.neo4j.gds.maxflow.MaxFlowWriteConfig;
3940
import org.neo4j.gds.paths.astar.config.ShortestPathAStarStreamConfig;
4041
import org.neo4j.gds.paths.astar.config.ShortestPathAStarWriteConfig;
4142
import org.neo4j.gds.paths.bellmanford.AllShortestPathsBellmanFordStatsConfig;
@@ -647,6 +648,17 @@ public Stream<MaxFlowStreamResult> maxFlowStream(String graphName, Map<String, O
647648
);
648649
}
649650

651+
@Override
652+
public Stream<MaxFlowWriteResult> maxFlowWrite(String graphName, Map<String, Object> configuration) {
653+
return Stream.of(
654+
writeModeBusinessFacade.maxFlow(
655+
GraphName.parse(graphName),
656+
configurationParser.parseConfiguration(configuration, MaxFlowWriteConfig::of),
657+
new MaxFlowResultBuilderForWriteMode()
658+
)
659+
);
660+
}
661+
650662
@Override
651663
public Stream<SpanningTreeStreamResult> prizeCollectingSteinerTreeStream(
652664
String graphName,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.procedures.algorithms.pathfinding;
21+
22+
import org.neo4j.gds.api.Graph;
23+
import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTimings;
24+
import org.neo4j.gds.applications.algorithms.machinery.ResultBuilder;
25+
import org.neo4j.gds.applications.algorithms.metadata.RelationshipsWritten;
26+
import org.neo4j.gds.maxflow.FlowResult;
27+
import org.neo4j.gds.maxflow.MaxFlowWriteConfig;
28+
29+
import java.util.Optional;
30+
31+
class MaxFlowResultBuilderForWriteMode implements ResultBuilder<MaxFlowWriteConfig, FlowResult, MaxFlowWriteResult, RelationshipsWritten> {
32+
@Override
33+
public MaxFlowWriteResult build(
34+
Graph graph,
35+
MaxFlowWriteConfig configuration,
36+
Optional<FlowResult> result,
37+
AlgorithmProcessingTimings timings,
38+
Optional<RelationshipsWritten> metadata
39+
) {
40+
if (result.isEmpty()) {
41+
return MaxFlowWriteResult.emptyFrom(timings, configuration.toMap());
42+
}
43+
44+
var flowResult = result.get();
45+
46+
return new MaxFlowWriteResult(
47+
timings.preProcessingMillis,
48+
timings.computeMillis,
49+
timings.sideEffectMillis,
50+
metadata.get().value(),
51+
flowResult.totalFlow(),
52+
configuration.toMap()
53+
);
54+
}
55+
}
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.maxflow;
21+
22+
import org.neo4j.gds.annotation.Configuration;
23+
import org.neo4j.gds.config.WritePropertyConfig;
24+
import org.neo4j.gds.config.WriteRelationshipConfig;
25+
import org.neo4j.gds.core.CypherMapWrapper;
26+
27+
@Configuration
28+
public interface MaxFlowWriteConfig extends MaxFlowBaseConfig, WritePropertyConfig, WriteRelationshipConfig {
29+
30+
static MaxFlowWriteConfig of(CypherMapWrapper userInput) {
31+
return new MaxFlowWriteConfigImpl(userInput);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.procedures.algorithms.pathfinding;
21+
22+
import org.neo4j.gds.applications.algorithms.machinery.AlgorithmProcessingTimings;
23+
import org.neo4j.gds.procedures.algorithms.results.ModeResult;
24+
25+
import java.util.Map;
26+
27+
public record MaxFlowWriteResult(
28+
long preProcessingMillis,
29+
long computeMillis,
30+
long writeMillis,
31+
long relationshipsWritten,
32+
double totalFlow,
33+
Map<String, Object> configuration
34+
) implements ModeResult {
35+
36+
public static MaxFlowWriteResult emptyFrom(
37+
AlgorithmProcessingTimings timings,
38+
Map<String, Object> configuration)
39+
{
40+
return new MaxFlowWriteResult(
41+
timings.preProcessingMillis,
42+
timings.computeMillis,
43+
0,
44+
0,
45+
0D,
46+
configuration
47+
);
48+
}
49+
50+
}

procedures/facade-api/path-finding-facade-api/src/main/java/org/neo4j/gds/procedures/algorithms/pathfinding/PathFindingProcedureFacade.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ Stream<MemoryEstimateResult> depthFirstSearchStreamEstimate(
150150

151151
Stream<MaxFlowStatsResult> maxFlowStats(String graphName, Map<String, Object> configuration);
152152

153+
Stream<MaxFlowWriteResult> maxFlowWrite(String graphName, Map<String, Object> configuration);
154+
153155
Stream<SpanningTreeStreamResult> prizeCollectingSteinerTreeStream(String graphName, Map<String, Object> configuration);
154156

155157
Stream<PrizeCollectingSteinerTreeMutateResult> prizeCollectingSteinerTreeMutate(String graphName, Map<String, Object> configuration);

0 commit comments

Comments
 (0)