Skip to content
This repository was archived by the owner on Apr 22, 2020. It is now read-only.

Commit 9a234a6

Browse files
committed
Updated Readme, renamed louvain
1 parent 5be6120 commit 9a234a6

File tree

7 files changed

+148
-66
lines changed

7 files changed

+148
-66
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.neo4j.graphalgo;
2+
3+
import org.neo4j.graphdb.GraphDatabaseService;
4+
import org.neo4j.procedure.Context;
5+
import org.neo4j.procedure.Description;
6+
import org.neo4j.procedure.Name;
7+
import org.neo4j.procedure.Procedure;
8+
9+
import java.util.Map;
10+
import java.util.stream.Stream;
11+
12+
import static java.util.Collections.singletonMap;
13+
14+
public class ListProc {
15+
16+
private static final String QUERY = "CALL dbms.procedures() " +
17+
" YIELD name, signature, description " +
18+
" WHERE name starts with 'algo.' AND name <> 'algo.list' AND ($name IS NULL OR name CONTAINS $name) " +
19+
" RETURN name, signature, description ORDER BY name";
20+
21+
@Context
22+
public GraphDatabaseService db;
23+
24+
@Procedure("algo.list")
25+
@Description("CALL algo.list - lists all algorithm procedures, their description and signature")
26+
public Stream<ListResult> list(@Name(value = "name", defaultValue = "null") String name) {
27+
return db.execute(QUERY, singletonMap("name", name)).stream().map(ListResult::new);
28+
}
29+
30+
public static class ListResult {
31+
public String name;
32+
public String description;
33+
public String signature;
34+
35+
public ListResult(Map<String, Object> row) {
36+
this.name = (String) row.get("name");
37+
this.description = (String) row.get("description");
38+
this.signature = (String) row.get("signature");
39+
}
40+
}
41+
}

algo/src/main/java/org/neo4j/graphalgo/LouvainProc.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public class LouvainProc {
5656
@Context
5757
public KernelTransaction transaction;
5858

59-
@Procedure(value = "algo.clustering.louvain", mode = Mode.WRITE)
60-
@Description("CALL algo.clustering.louvain(label:String, relationship:String, " +
59+
@Procedure(value = "algo.louvain", mode = Mode.WRITE)
60+
@Description("CALL algo.louvain(label:String, relationship:String, " +
6161
"{weightProperty:'weight', defaultValue:1.0, write: true, writeProperty:'community', concurrency:4}) " +
6262
"YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis")
6363
public Stream<LouvainResult> louvain(
@@ -102,8 +102,8 @@ public Stream<LouvainResult> louvain(
102102
return Stream.of(builder.build());
103103
}
104104

105-
@Procedure(value = "algo.clustering.louvain.stream")
106-
@Description("CALL algo.clustering.louvain.stream(label:String, relationship:String, " +
105+
@Procedure(value = "algo.louvain.stream")
106+
@Description("CALL algo.louvain.stream(label:String, relationship:String, " +
107107
"{weightProperty:'propertyName', defaultValue:1.0, concurrency:4) " +
108108
"YIELD nodeId, community - yields a setId to each node id")
109109
public Stream<Louvain.Result> louvainStream(

doc/louvain.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ include::scripts/louvain.cypher[tag=write-sample-graph]
125125
.Running algorithm and writing back results
126126
[source,cypher]
127127
----
128-
CALL algo.clustering.louvain(label:String, relationship:String,
128+
CALL algo.louvain(label:String, relationship:String,
129129
{weightProperty:'weight', defaultValue:1.0, write: true, writeProperty:'community', concurrency:4})
130130
YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis
131131
@@ -160,7 +160,7 @@ YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis
160160
.Running algorithm and streaming results
161161
[source,cypher]
162162
----
163-
CALL algo.clustering.louvain.stream(label:String, relationship:String,
163+
CALL algo.louvain.stream(label:String, relationship:String,
164164
{weightProperty:'propertyName', defaultValue:1.0, concurrency:4})
165165
YIELD nodeId, setId - yields a setId to each node id
166166
----

doc/scripts/louvain.cypher

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ CREATE (nAlice)-[:FRIEND]->(nBridget)
1818

1919
// tag::stream-sample-graph[]
2020

21-
CALL algo.clustering.louvain.stream('User', 'FRIEND',
21+
CALL algo.louvain.stream('User', 'FRIEND',
2222
{})
2323
YIELD nodeId, setId
2424
RETURN nodeId, setId LIMIT 20;
@@ -27,15 +27,15 @@ RETURN nodeId, setId LIMIT 20;
2727

2828
// tag::write-sample-graph[]
2929

30-
CALL algo.clustering.louvain('User', 'FRIEND',
30+
CALL algo.louvain('User', 'FRIEND',
3131
{write:true, writeProperty:'community'})
3232
YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis;
3333

3434
// end::write-sample-graph[]
3535

3636
// tag::write-yelp[]
3737

38-
CALL algo.clustering.louvain('Business', 'CO_OCCURENT_REVIEWS',
38+
CALL algo.louvain('Business', 'CO_OCCURENT_REVIEWS',
3939
{write:true, writeProperty:'community'})
4040
YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis;
4141

readme.adoc

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,96 @@
1-
= Efficient Graph Algorithms for Neo4j (pre-Alpha / WIP)
1+
= Efficient Graph Algorithms for Neo4j
22

3-
image:https://travis-ci.org/neo4j-contrib/neo4j-graph-algorithms.svg?branch=3.1["Build Status", link="https://travis-ci.org/neo4j-contrib/neo4j-graph-algorithms"]
3+
image:https://travis-ci.org/neo4j-contrib/neo4j-graph-algorithms.svg?branch=3.3["Build Status", link="https://travis-ci.org/neo4j-contrib/neo4j-graph-algorithms"]
44

55
// tag::readme[]
66

7-
This project aims to develop efficient, well tested graph algorithm implementations for Neo4j 3.1 and 3.2.
7+
The goal of this library is to provide efficiently implemented, parallel versions of common graph algorithms for Neo4j 3.x exposed as Cypher procedures.
88

99
ifndef::env-docs[]
10-
You can find the documentation (WIP) here http://neo4j-contrib.github.io/neo4j-graph-algorithms
10+
You can find the documentation here http://neo4j-contrib.github.io/neo4j-graph-algorithms
1111
endif::env-docs[]
1212

1313
Releases are available here: https://github.com/neo4j-contrib/neo4j-graph-algorithms/releases
1414

15-
The goal is to provide parallel versions of common graph algorithms for Neo4j exposed as Cypher user defined procedures:
15+
16+
== Algorithms
1617

1718
Centralities:
1819

19-
* Page Rank
20-
* Betweenness Centrality
21-
* Closeness Centrality
20+
* Page Rank (`algo.pageRank`)
21+
* Betweenness Centrality (`algo.betweenness`)
22+
* Closeness Centrality (`algo.closeness`)
2223

2324
Community Detection:
2425

25-
* Louvain
26-
* Label Propagation
27-
* (Weakly) Connected Components
28-
* Strongly Connected Components
29-
* Triangle Count / Clustering Coefficient
26+
* Louvain (`algo.louvain`)
27+
* Label Propagation (`algo.labelPropagation`)
28+
* (Weakly) Connected Components (`algo.unionFind`)
29+
* Strongly Connected Components (`algo.scc`)
30+
* Triangle Count / Clustering Coefficient (`algo.triangleCount`)
3031

3132
Path Finding:
3233

33-
* Minimum Weight Spanning Tree
34-
* All Pairs- and Single Source - Shortest Path
34+
* Minimum Weight Spanning Tree (`algo.mst`)
35+
* All Pairs- and Single Source - Shortest Path (`algo.shortestPath`, `algo.allShortestPaths`)
3536

36-
These procedures work on a subgraphm optionally filtered by label and relationship-type.
37-
Future versions will also provide filtering and projection using Cypher queries.
37+
These procedures work either on the whole graph or on a subgraph optionally filtered by label and relationship-type.
38+
You can also use filtering and projection using Cypher queries, see below.
3839

39-
*We'd love your feedback*, so please try out these algorithms and let us know how well they work for your use-case.
40-
Also please note things that you miss from installation instructions, readme, etc.
40+
*We'd love your feedback*, so please try out these algorithms and let us know how well they work for your use-case.
41+
Also please note things that you miss from installation instructions, documentation, etc.
4142

4243
Please raise https://github.com/neo4j-contrib/neo4j-graph-algorithms/issues[GitHub issues] for anything you encounter or join the http://neo4j.com/developer/slack[neo4j-users Slack group] and ask in the `#neo4j-graph-algorithm` channel.
4344

4445
== Installation
4546

46-
Just copy the `graph-algorithms-algo-*.jar` from https://github.com/neo4j-contrib/neo4j-graph-algorithms/releases[the matching release] into your `$NEO4J_HOME/plugins` directory and restart Neo4j.
47-
48-
Then running `call dbms.procedures();` should also list the algorithm procedures.
49-
50-
[source,cypher]
51-
----
52-
CALL dbms.procedures() YIELD name, description, signature
53-
WHERE name STARTS WITH "algo."
54-
RETURN name, description, signature
55-
ORDER BY name
56-
----
47+
Just copy the `graph-algorithms-algo-*.jar` from https://github.com/neo4j-contrib/neo4j-graph-algorithms/releases[the matching release] into your `$NEO4J_HOME/plugins` directory.
5748

58-
[WARNING]
59-
====
60-
For safety reasons, in *Neo4j 3.2.x* you will need to add/enable this line in your `$NEO4J_HOME/conf/neo4j.conf`:
49+
Because the algorithms use the lower level Kernel API to read from and write to Neo4j you also have to enable them in the configuration (for security reasons):
6150

51+
.Add to $NEO4J_HOME/conf/neo4j.conf
6252
----
6353
dbms.security.procedures.unrestricted=algo.*
6454
----
65-
====
6655

56+
Then running `call algo.list();` should list the algorithm procedures.
57+
You can also see the full list in the documentation.
6758

59+
////
6860
== Introduction
6961
70-
Graph theory is the study of graphs, which are mathematical structures used to model pairwise relations between nodes.
71-
A graph is made up of nodes (vertices) which are connected by relationships (edges).
62+
Graph theory is the study of graphs, which are mathematical structures used to model pairwise relations between nodes.
63+
A graph is made up of nodes (vertices) which are connected by relationships (edges).
7264
A graph may be _undirected_, meaning that there is no distinction between the two nodes associated with each relationship, or its relationships may be _directed_ from one node to another.
7365
Relationships are what graph is all about: two nodes are joined by a relationship when they are related in a specified way.
74-
75-
We are tied to our friends.
76-
Cities are connected by roads and airline routes.
77-
Flora and fauna are bound together in a food web.
78-
Countries are involved in trading relationships.
79-
The World Wide Web is a virtual network of information.
80-
8166
82-
* _Note that Neo4j can only save directed relationships, but we can treat them as though they are undirected when we are doing the analysis_
67+
We are tied to our friends.
68+
Cities are connected by roads and airline routes.
69+
Flora and fauna are bound together in a food web.
70+
Countries are involved in trading relationships.
71+
The World Wide Web is a virtual network of information.
8372
73+
* _Note that Neo4j stores directed relationships, we can treat them as though they are undirected when we are doing the analysis_
74+
////
8475

8576
== Usage
8677

8778
These algorithms are exposed as Neo4j procedures.
8879
You can call them directly from Cypher in your Neo4j Browser, from cypher-shell or your client code.
8980

90-
For most algorithms there are two procedures, one that writes results back to the graph as node-properties and another (named `algo.<name>.stream`) that returns a stream of data, e.g. node-ids and computed values.
81+
For most algorithms we provide two procedures, one that writes results back to the graph as node-properties and reports statistics.
82+
And another (named `algo.<name>.stream`) that returns a stream of data, e.g. node-ids and computed values.
83+
84+
For large graphs the streaming procedure might return millions or billions of results, that's why it is often more convenient to store the results of the algorithm and then use them with later queries.
9185

9286
The general call syntax is:
9387

9488
[source,cypher]
9589
----
96-
CALL algo.<name>([label],[relType],{config})
90+
CALL algo.<name>([label],[relationshipType],{config})
9791
----
9892

99-
For example for page rank on dbpedia:
93+
For example for page rank on DBpedia (11M nodes, 116M relationships):
10094

10195
[source,cypher]
10296
----
@@ -105,11 +99,12 @@ CALL algo.pageRank('Page','Link',{iterations:5, dampingFactor:0.85, write: true,
10599
106100
CALL algo.pageRank.stream('Page','Link',{iterations:5, dampingFactor:0.85})
107101
YIELD node, score
108-
RETURN node, score
102+
RETURN node.title, score
109103
ORDER BY score DESC LIMIT 10;
110104
----
111105

112-
=== Cypher Loading
106+
107+
=== Projection via Cypher Queries
113108

114109
If label and relationship-type are not selective enough to describe your subgraph to run the algorithm on, you can use Cypher statements to load or project subsets of your graph.
115110
Then use a node-statement instead of the label parameter and a relationship-statement instead of the relationship-type and use `graph:'cypher'` in the config.
@@ -120,22 +115,23 @@ You can also return a property value or weight (according to your config) in add
120115
----
121116
CALL algo.pageRank(
122117
'MATCH (p:Page) RETURN id(p) as id',
123-
'MATCH (p1:Page)-[:Link]->(p2:Page) RETURN id(p1) as source, id(p2) as target',
118+
'MATCH (p1:Page)-[:Link]->(p2:Page) RETURN id(p1) as source, id(p2) as target, count(*) as weight',
124119
{graph:'cypher', iterations:5, write: true});
125120
----
126121

127122
ifndef::env-docs[]
128-
Details on how to call the individual algorithms can be found in the http://neo4j-contrib.github.io/neo4j-graph-algorithms[project's documentation]
123+
The detailed call syntax and all parameters and possible return values for each algorithm are listed in the http://neo4j-contrib.github.io/neo4j-graph-algorithms[project's documentation]
129124
endif::env-docs[]
130125

131126

132-
== Building
127+
== Building Locally
133128

134-
Currently aiming at Neo4j 3.1 and 3.2 (in the 3.2 branch)
129+
Currently aiming at Neo4j 3.x (with a branch per version)
135130

136131
----
137132
git clone https://github.com/neo4j-contrib/neo4j-graph-algorithms
138133
cd neo4j-graph-algorithms
134+
git checkout 3.3
139135
mvn clean install
140136
cp algo/target/graph-algorithms-*.jar $NEO4J_HOME/plugins/
141137
$NEO4J_HOME/bin/neo4j restart
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.neo4j.graphalgo.algo;
2+
3+
import org.junit.BeforeClass;
4+
import org.junit.ClassRule;
5+
import org.junit.Test;
6+
import org.neo4j.graphalgo.ListProc;
7+
import org.neo4j.graphalgo.PageRankProc;
8+
import org.neo4j.kernel.impl.proc.Procedures;
9+
import org.neo4j.test.rule.ImpermanentDatabaseRule;
10+
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
import static java.util.Arrays.asList;
15+
import static java.util.Collections.*;
16+
import static org.junit.Assert.assertEquals;
17+
18+
/**
19+
* @author mh
20+
* @since 20.10.17
21+
*/
22+
public class ListProcTest {
23+
@ClassRule
24+
public static ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule();
25+
26+
@BeforeClass
27+
public static void setUp() throws Exception {
28+
Procedures procedures = DB.getDependencyResolver().resolveDependency(Procedures.class);
29+
procedures.registerProcedure(ListProc.class);
30+
procedures.registerProcedure(PageRankProc.class);
31+
}
32+
33+
@Test
34+
public void list() throws Exception {
35+
assertEquals(asList("algo.pageRank","algo.pageRank.stream"), listProcs(null));
36+
assertEquals(asList("algo.pageRank","algo.pageRank.stream"), listProcs("page"));
37+
assertEquals(singletonList("algo.pageRank.stream"), listProcs("stream"));
38+
assertEquals(emptyList(), listProcs("foo"));
39+
}
40+
41+
private List<String> listProcs(Object name) {
42+
return DB.execute("CALL algo.list($name)", singletonMap("name", name)).<String>columnAs("name").stream().collect(Collectors.toList());
43+
}
44+
45+
}

tests/src/test/java/org/neo4j/graphalgo/algo/LouvainClusteringIntegrationTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private String getName(long nodeId) {
109109

110110
@Test
111111
public void test() throws Exception {
112-
final String cypher = "CALL algo.clustering.louvain('', '', {write:true, writeProperty:'community', concurrency:2}) " +
112+
final String cypher = "CALL algo.louvain('', '', {write:true, writeProperty:'community', concurrency:2}) " +
113113
"YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis";
114114

115115
db.execute(cypher).accept(row -> {
@@ -134,7 +134,7 @@ public void test() throws Exception {
134134

135135
@Test
136136
public void testStream() throws Exception {
137-
final String cypher = "CALL algo.clustering.louvain.stream('', '', {concurrency:2}) " +
137+
final String cypher = "CALL algo.louvain.stream('', '', {concurrency:2}) " +
138138
"YIELD nodeId, community";
139139
final IntIntScatterMap testMap = new IntIntScatterMap();
140140
db.execute(cypher).accept(row -> {
@@ -146,7 +146,7 @@ public void testStream() throws Exception {
146146

147147
@Test
148148
public void testWithLabelRel() throws Exception {
149-
final String cypher = "CALL algo.clustering.louvain('Node', 'TYPE', {write:true, writeProperty:'community', concurrency:2}) " +
149+
final String cypher = "CALL algo.louvain('Node', 'TYPE', {write:true, writeProperty:'community', concurrency:2}) " +
150150
"YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis";
151151

152152
db.execute(cypher).accept(row -> {
@@ -172,7 +172,7 @@ public void testWithLabelRel() throws Exception {
172172
@Ignore("TODO")
173173
@Test
174174
public void testWithWeight() throws Exception {
175-
final String cypher = "CALL algo.clustering.louvain('Node', 'TYPE', {weightProperty:'w', defaultValue:2.0, " +
175+
final String cypher = "CALL algo.louvain('Node', 'TYPE', {weightProperty:'w', defaultValue:2.0, " +
176176
"write:true, writeProperty:'cluster', concurrency:2}) " +
177177
"YIELD nodes, communityCount, iterations, loadMillis, computeMillis, writeMillis";
178178

0 commit comments

Comments
 (0)