Skip to content

Commit 64529c3

Browse files
authored
Merge pull request #10329 from neo-technology/maxkcut
Implement Approx Max K-cut in GDS Session Cypher API
2 parents 14c7890 + 1073fa1 commit 64529c3

File tree

13 files changed

+431
-67
lines changed

13 files changed

+431
-67
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.applications.algorithms.community;
21+
22+
import org.neo4j.gds.algorithms.community.CommunityCompanion;
23+
import org.neo4j.gds.api.Graph;
24+
import org.neo4j.gds.api.GraphStore;
25+
import org.neo4j.gds.api.ResultStore;
26+
import org.neo4j.gds.api.properties.nodes.NodePropertyValuesAdapter;
27+
import org.neo4j.gds.applications.algorithms.machinery.WriteStep;
28+
import org.neo4j.gds.applications.algorithms.machinery.WriteToDatabase;
29+
import org.neo4j.gds.applications.algorithms.metadata.NodePropertiesWritten;
30+
import org.neo4j.gds.approxmaxkcut.ApproxMaxKCutResult;
31+
import org.neo4j.gds.approxmaxkcut.config.ApproxMaxKCutWriteConfig;
32+
import org.neo4j.gds.core.utils.progress.JobId;
33+
34+
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.ApproximateMaximumKCut;
35+
36+
class ApproxMaxKCutWriteStep implements WriteStep<ApproxMaxKCutResult, NodePropertiesWritten> {
37+
private final WriteToDatabase writeToDatabase;
38+
private final ApproxMaxKCutWriteConfig configuration;
39+
40+
ApproxMaxKCutWriteStep(WriteToDatabase writeToDatabase, ApproxMaxKCutWriteConfig configuration) {
41+
this.writeToDatabase = writeToDatabase;
42+
this.configuration = configuration;
43+
}
44+
45+
@Override
46+
public NodePropertiesWritten execute(
47+
Graph graph,
48+
GraphStore graphStore,
49+
ResultStore resultStore,
50+
ApproxMaxKCutResult result,
51+
JobId jobId
52+
) {
53+
var longNodePropertyValues = NodePropertyValuesAdapter.adapt(result.candidateSolution());
54+
var nodePropertyValues = CommunityCompanion.nodePropertyValues(false, longNodePropertyValues);
55+
56+
return writeToDatabase.perform(
57+
graph,
58+
graphStore,
59+
resultStore,
60+
configuration,
61+
configuration,
62+
ApproximateMaximumKCut,
63+
jobId,
64+
nodePropertyValues
65+
);
66+
}
67+
}

applications/algorithms/community/src/main/java/org/neo4j/gds/applications/algorithms/community/CommunityAlgorithmsWriteModeBusinessFacade.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.neo4j.gds.applications.algorithms.machinery.WriteContext;
2929
import org.neo4j.gds.applications.algorithms.machinery.WriteToDatabase;
3030
import org.neo4j.gds.applications.algorithms.metadata.NodePropertiesWritten;
31+
import org.neo4j.gds.approxmaxkcut.ApproxMaxKCutResult;
32+
import org.neo4j.gds.approxmaxkcut.config.ApproxMaxKCutWriteConfig;
3133
import org.neo4j.gds.beta.pregel.PregelResult;
3234
import org.neo4j.gds.collections.ha.HugeLongArray;
3335
import org.neo4j.gds.core.utils.paged.dss.DisjointSetStruct;
@@ -57,6 +59,7 @@
5759
import org.neo4j.gds.triangle.TriangleCountWriteConfig;
5860
import org.neo4j.gds.wcc.WccWriteConfig;
5961

62+
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.ApproximateMaximumKCut;
6063
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.K1Coloring;
6164
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.KCore;
6265
import static org.neo4j.gds.applications.algorithms.machinery.AlgorithmLabel.KMeans;
@@ -106,6 +109,24 @@ public static CommunityAlgorithmsWriteModeBusinessFacade create(
106109
);
107110
}
108111

112+
public <RESULT> RESULT approxMaxKCut(
113+
GraphName graphName,
114+
ApproxMaxKCutWriteConfig configuration,
115+
ResultBuilder<ApproxMaxKCutWriteConfig, ApproxMaxKCutResult, RESULT, NodePropertiesWritten> resultBuilder
116+
) {
117+
var writeStep = new ApproxMaxKCutWriteStep(writeToDatabase, configuration);
118+
119+
return algorithmProcessingTemplateConvenience.processRegularAlgorithmInWriteMode(
120+
graphName,
121+
configuration,
122+
ApproximateMaximumKCut,
123+
() -> estimationFacade.approximateMaximumKCut(configuration),
124+
(graph, __) -> algorithms.approximateMaximumKCut(graph, configuration),
125+
writeStep,
126+
resultBuilder
127+
);
128+
}
129+
109130
public <RESULT> RESULT k1Coloring(
110131
GraphName graphName,
111132
K1ColoringWriteConfig configuration,

pipeline/src/main/java/org/neo4j/gds/ml/pipeline/stubs/ApproximateMaximumKCutStub.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@
2626

2727
public class ApproximateMaximumKCutStub extends AbstractStub<ApproxMaxKCutMutateConfig, ApproxMaxKCutMutateResult> {
2828
protected MutateStub<ApproxMaxKCutMutateConfig, ApproxMaxKCutMutateResult> stub(AlgorithmsProcedureFacade facade) {
29-
return facade.community().approximateMaximumKCutMutateStub();
29+
return facade.community().approxMaxKCutMutateStub();
3030
}
3131
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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.approxmaxkcut;
21+
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
import org.neo4j.gds.BaseProc;
26+
import org.neo4j.gds.BaseProcTest;
27+
import org.neo4j.gds.GdsCypher;
28+
import org.neo4j.gds.catalog.GraphProjectProc;
29+
import org.neo4j.gds.compat.TestLog;
30+
import org.neo4j.gds.core.loading.GraphStoreCatalog;
31+
import org.neo4j.gds.extension.Neo4jGraph;
32+
import org.neo4j.gds.procedures.GraphDataScienceProcedures;
33+
import org.neo4j.gds.procedures.algorithms.community.ApproxMaxKCutWriteResult;
34+
import org.neo4j.procedure.Context;
35+
import org.neo4j.procedure.Name;
36+
import org.neo4j.procedure.Procedure;
37+
38+
import java.util.Map;
39+
import java.util.stream.Stream;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.assertj.core.api.InstanceOfAssertFactories.DOUBLE;
43+
import static org.assertj.core.api.InstanceOfAssertFactories.LONG;
44+
import static org.neo4j.procedure.Mode.WRITE;
45+
46+
class ApproxMaxKCutWriteProcTest extends BaseProcTest {
47+
48+
public static class ApproxMaxKCutWriteProc extends BaseProc {
49+
50+
@Context
51+
public GraphDataScienceProcedures facade;
52+
53+
@Procedure(name = "gds.maxkcut.write", mode = WRITE)
54+
public Stream<ApproxMaxKCutWriteResult> foo(
55+
@Name(value = "graphName") String graphName,
56+
@Name(value = "configuration", defaultValue = "{}") Map<String, Object> configuration
57+
) {
58+
return facade.algorithms().community().approxMaxKCutWrite(graphName, configuration);
59+
}
60+
}
61+
62+
@Neo4jGraph
63+
private static final
64+
String DB_CYPHER =
65+
"CREATE" +
66+
" (a:Label1)" +
67+
", (b:Label1)" +
68+
", (c:Label1)" +
69+
", (d:Label1)" +
70+
", (e:Label1)" +
71+
", (f:Label1)" +
72+
", (g:Label1)" +
73+
74+
", (a)-[:TYPE1 {weight: 81.0}]->(b)" +
75+
", (a)-[:TYPE1 {weight: 7.0}]->(d)" +
76+
", (b)-[:TYPE1 {weight: 1.0}]->(d)" +
77+
", (b)-[:TYPE1 {weight: 1.0}]->(e)" +
78+
", (b)-[:TYPE1 {weight: 1.0}]->(f)" +
79+
", (b)-[:TYPE1 {weight: 1.0}]->(g)" +
80+
", (c)-[:TYPE1 {weight: 45.0}]->(b)" +
81+
", (c)-[:TYPE1 {weight: 3.0}]->(e)" +
82+
", (d)-[:TYPE1 {weight: 3.0}]->(c)" +
83+
", (d)-[:TYPE1 {weight: 1.0}]->(b)" +
84+
", (e)-[:TYPE1 {weight: 1.0}]->(b)" +
85+
", (f)-[:TYPE1 {weight: 3.0}]->(a)" +
86+
", (f)-[:TYPE1 {weight: 1.0}]->(b)" +
87+
", (g)-[:TYPE1 {weight: 1.0}]->(b)" +
88+
", (g)-[:TYPE1 {weight: 4.0}]->(c)";
89+
90+
static final String GRAPH_NAME = "myGraph";
91+
92+
@BeforeEach
93+
void setupGraph() throws Exception {
94+
registerProcedures(
95+
ApproxMaxKCutWriteProc.class,
96+
GraphProjectProc.class
97+
);
98+
99+
String createQuery = GdsCypher.call(GRAPH_NAME)
100+
.graphProject()
101+
.loadEverything()
102+
.yields();
103+
104+
runQuery(createQuery);
105+
}
106+
107+
@Test
108+
void write() {
109+
String query = GdsCypher
110+
.call(GRAPH_NAME)
111+
.algo("maxkcut")
112+
.writeMode()
113+
.addParameter("k", 2)
114+
.addParameter("iterations", 8)
115+
.addParameter("vnsMaxNeighborhoodOrder", 0)
116+
.addParameter("concurrency", 1)
117+
.addParameter("randomSeed", 1337L)
118+
.addParameter("writeProperty", "community").yields();
119+
120+
var rowCount = runQueryWithRowConsumer(query, row -> {
121+
122+
assertThat(row.getNumber("cutCost"))
123+
.asInstanceOf(DOUBLE)
124+
.isEqualTo(13.0);
125+
126+
assertThat(row.getNumber("preProcessingMillis"))
127+
.asInstanceOf(LONG)
128+
.isGreaterThan(-1L);
129+
130+
assertThat(row.getNumber("computeMillis"))
131+
.asInstanceOf(LONG)
132+
.isGreaterThan(-1L);
133+
134+
assertThat(row.getNumber("postProcessingMillis"))
135+
.asInstanceOf(LONG)
136+
.isGreaterThan(-1L);
137+
138+
assertThat(row.getNumber("writeMillis"))
139+
.asInstanceOf(LONG)
140+
.isGreaterThan(-1L);
141+
142+
assertThat(row.getNumber("nodePropertiesWritten"))
143+
.asInstanceOf(LONG)
144+
.isEqualTo(7L);
145+
});
146+
147+
assertThat(rowCount).isEqualTo(1L);
148+
149+
var sideEffectCheckingQuery = "MATCH (n) RETURN n.community AS prop";
150+
var postWriteRowCount = runQueryWithRowConsumer(sideEffectCheckingQuery, row -> {
151+
assertThat(row.getNumber("prop")).asInstanceOf(LONG).isNotNegative();
152+
});
153+
154+
assertThat(postWriteRowCount).isEqualTo(7);
155+
156+
testLog.assertContainsMessage(TestLog.INFO, "ApproxMaxKCut :: Finished");
157+
}
158+
159+
160+
@AfterEach
161+
void clearStore() {
162+
GraphStoreCatalog.removeAllLoadedGraphs();
163+
}
164+
}

proc/community/src/main/java/org/neo4j/gds/approxmaxkcut/ApproxMaxKCutMutateProc.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public Stream<ApproxMaxKCutMutateResult> mutate(
4545
@Name(value = "graphName") String graphName,
4646
@Name(value = "configuration", defaultValue = "{}") Map<String, Object> configuration
4747
) {
48-
return facade.algorithms().community().approximateMaximumKCutMutate(graphName, configuration);
48+
return facade.algorithms().community().approxMaxKCutMutate(graphName, configuration);
4949
}
5050

5151
@Procedure(value = "gds.maxkcut.mutate.estimate", mode = READ)
@@ -54,7 +54,7 @@ public Stream<MemoryEstimateResult> estimate(
5454
@Name(value = "graphNameOrConfiguration") Object graphNameOrConfiguration,
5555
@Name(value = "algoConfiguration") Map<String, Object> algoConfiguration
5656
) {
57-
return facade.algorithms().community().approximateMaximumKCutMutateEstimate(graphNameOrConfiguration, algoConfiguration);
57+
return facade.algorithms().community().approxMaxKCutMutateEstimate(graphNameOrConfiguration, algoConfiguration);
5858
}
5959

6060
@Deprecated
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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.community;
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.NodePropertiesWritten;
26+
import org.neo4j.gds.approxmaxkcut.ApproxMaxKCutResult;
27+
import org.neo4j.gds.approxmaxkcut.config.ApproxMaxKCutWriteConfig;
28+
29+
import java.util.Optional;
30+
import java.util.stream.Stream;
31+
32+
class ApproxMaxKCutResultBuilderForWriteMode implements ResultBuilder<ApproxMaxKCutWriteConfig, ApproxMaxKCutResult, Stream<ApproxMaxKCutWriteResult>, NodePropertiesWritten> {
33+
34+
@Override
35+
public Stream<ApproxMaxKCutWriteResult> build(
36+
Graph graph,
37+
ApproxMaxKCutWriteConfig configuration,
38+
Optional<ApproxMaxKCutResult> result,
39+
AlgorithmProcessingTimings timings,
40+
Optional<NodePropertiesWritten> nodePropertiesWritten
41+
) {
42+
if (result.isEmpty()) return Stream.of(ApproxMaxKCutWriteResult.emptyFrom(timings, configuration.toMap()));
43+
44+
var approxMaxKCutResult = result.get();
45+
46+
var writeResult = new ApproxMaxKCutWriteResult(
47+
approxMaxKCutResult.cutCost(),
48+
timings.preProcessingMillis,
49+
timings.computeMillis,
50+
0,
51+
timings.sideEffectMillis,
52+
nodePropertiesWritten.orElseThrow().value(),
53+
configuration.toMap()
54+
);
55+
56+
return Stream.of(writeResult);
57+
}
58+
}

0 commit comments

Comments
 (0)