├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── graphaware │ └── module │ └── noderank │ ├── NodeRankApi.java │ ├── NodeRankContext.java │ ├── NodeRankController.java │ ├── NodeRankModule.java │ ├── NodeRankModuleBootstrapper.java │ ├── NodeRankModuleConfiguration.java │ ├── NodeRankProcedure.java │ └── TopRankedNodes.java └── test ├── java └── com │ └── graphaware │ └── module │ └── noderank │ ├── EmbeddedDatabaseIntegration.java │ ├── EmbeddedDatabaseIntegration2.java │ ├── NodeRankControllerTest.java │ ├── NodeRankModuleTest.java │ ├── NodeRankProcedureTest.java │ ├── NodeRankProcedureTestCausalCluster.java │ ├── NodeRankProcedureTestHighAvailability.java │ ├── PageRankIntegration.java │ ├── TopRankedNodesTest.java │ └── utils │ ├── NetworkMatrix.java │ ├── NetworkMatrixFactory.java │ ├── NetworkMatrixFactoryTest.java │ ├── PageRank.java │ ├── Permutation.java │ ├── PermutationTest.java │ ├── RankNodePair.java │ └── SimilarityComparison.java ├── python ├── README.md └── neorank.py └── resources ├── graph.cyp ├── int-test-neo4j.conf ├── neo4j-2.conf ├── schema.cyp └── test-neo4j.conf /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .project 3 | .settings 4 | target/ 5 | .idea/ 6 | *.class 7 | *.iml 8 | *.iws 9 | *.ipr 10 | *.db 11 | .DS_Store 12 | .classpath 13 | neo4j-home/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | sudo: false 4 | 5 | jdk: 6 | - oraclejdk8 7 | 8 | after_success: 9 | - echo "ossrh\${env.OSSRH_USER}\${env.OSSRH_PASS}" > ~/settings.xml 10 | - mvn deploy --settings ~/settings.xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | GraphAware Neo4j NodeRank - RETIRED 4 | ===================================== 5 | 6 | NodeRank Has Been Retired 7 | --------------------------- 8 | 9 | As of October 17th 2017, this module is retiring. This means it will no longer be maintained and released together with 10 | new versions of the GraphAware Framework and Neo4j. The last compatible Neo4j version is 3.2.5. 11 | 12 | NodeRank is superseded by a much more active Neo4j Graph Algorithms project. 13 | 14 | This repository will remain public. Please get in touch if you've been using NodeRank and would like to migrate to 15 | Neo4j Graph Algorithms. 16 | 17 | ===================================== 18 | 19 | [![Build Status](https://travis-ci.org/graphaware/neo4j-noderank.png)](https://travis-ci.org/graphaware/neo4j-noderank) | Downloads | Javadoc | Latest Release: 3.2.5.51.5 20 | 21 | GraphAware NodeRank is a [GraphAware](https://github.com/graphaware/neo4j-framework) Runtime Module that executes a configurable 22 | Page Rank-like algorithm on the Neo4j graph. It is a reference implementation of a [Timer-Driven GraphAware Runtime Module](https://github.com/graphaware/neo4j-framework/tree/master/runtime#building-a-timer-driven-graphaware-runtime-module). 23 | 24 | The module "crawls" the graph continuously behind the scenes, slowing down as the database gets busier and speeding up 25 | in quiet periods. It starts by selecting a node at random (obeying configured node inclusion policy). At each step of the 26 | algorithm, it follows a random relationship (obeying configured relationship inclusion policy) and increments a property 27 | (with configurable key) of the other node of the relationship. With a probability 1-p, where p is the configurable damping 28 | factor, it selects another random node rather than following a relationship. Also, if there are no suitable relationships 29 | to follow from a node, a jump to another suitable random node is performed. 30 | 31 | Over time, the node ranks written to the nodes in the graph converge to the results of Page Rank, had it been computed 32 | analytically. The amount of time it takes to converge greatly depends on the size of the graph, the load on the database, 33 | and the [Timer-Driven Module Scheduling Configuration](https://github.com/graphaware/neo4j-framework/tree/master/runtime#building-a-timer-driven-graphaware-runtime-module). 34 | With default settings and 100 nodes in the database, the top 10 nodes are identical for NodeRank and Page Rank within a 35 | few seconds of running the module. 36 | 37 | Getting the Software 38 | -------------------- 39 | 40 | ### Server Mode 41 | 42 | When using Neo4j in the standalone server mode, 43 | you will need the GraphAware Neo4j Framework and GraphAware Neo4j NodeRank .jar files (both of which you can download here) dropped 44 | into the `plugins` directory of your Neo4j installation. After a change in neo4.properties (described later) and Neo4j restart, you will be able to use the REST APIs of the NodeRank 45 | and the computation will take place continuously. 46 | 47 | ### Embedded Mode / Java Development 48 | 49 | Java developers that use Neo4j in embedded mode 50 | and those developing Neo4j server plugins, 51 | unmanaged extensions, 52 | GraphAware Runtime Modules, or Spring MVC Controllers can include use the NodeRank as a dependency for their Java project. 53 | 54 | #### Releases 55 | 56 | Releases are synced to Maven Central repository. When using Maven for dependency management, include the following dependency in your pom.xml. 57 | 58 | 59 | ... 60 | 61 | com.graphaware.neo4j 62 | noderank 63 | 3.2.5.51.5 64 | 65 | ... 66 | 67 | 68 | #### Snapshots 69 | 70 | To use the latest development version, just clone this repository, run `mvn clean install` and change the version in the 71 | dependency above to 3.2.5.51.6-SNAPSHOT. 72 | 73 | #### Note on Versioning Scheme 74 | 75 | The version number has two parts. The first four numbers indicate compatibility with Neo4j GraphAware Framework. 76 | The last number is the version of the NodeRank library. For example, version 2.1.4.19.1 is version 1 of the NodeRank 77 | compatible with GraphAware Neo4j Framework 2.1.4.19. 78 | 79 | Setup and Configuration 80 | ======================= 81 | 82 | ### Server Mode 83 | 84 | Edit `conf/neo4j.conf` to register the NodeRank module: 85 | 86 | ``` 87 | com.graphaware.runtime.enabled=true 88 | 89 | #NR becomes the module ID: 90 | com.graphaware.module.NR.1=com.graphaware.module.noderank.NodeRankModuleBootstrapper 91 | 92 | #optional number of top ranked nodes to remember, the default is 10 93 | com.graphaware.module.NR.maxTopRankNodes=10 94 | 95 | #optional daming factor, which is a number p such that a random node will be selected at any step of the algorithm 96 | #with the probability 1-p (as opposed to following a random relationship). The default is 0.85 97 | com.graphaware.module.NR.dampingFactor=0.85 98 | 99 | #optional key of the property that gets written to the ranked nodes, default is "nodeRank" 100 | com.graphaware.module.NR.propertyKey=nodeRank 101 | 102 | #optionally specify nodes to rank using an expression-based node inclusion policy, default is all business (i.e. non-framework-internal) nodes 103 | com.graphaware.module.NR.node=hasLabel('Person') 104 | 105 | #optionally specify relationships to follow using an expression-based relationship inclusion policy, default is all business (i.e. non-framework-internal) relationships 106 | com.graphaware.module.NR.relationship=isType('FRIEND_OF') 107 | ``` 108 | 109 | Note that "NR" becomes the module ID. It is possible to register the NodeRank module multiple times with different 110 | configurations, provided that their IDs are different. This ID is important for querying the top ranked nodes (read on). 111 | 112 | ### Embedded Mode / Java Development 113 | 114 | To use the NodeRank programmatically, register the module like this 115 | 116 | ```java 117 | GraphAwareRuntime runtime = GraphAwareRuntimeFactory.createRuntime(database); //where database is an instance of GraphDatabaseService 118 | NodeRankModule module = new NodeRankModule("NR"); 119 | runtime.registerModule(module); 120 | runtime.start(); 121 | ``` 122 | 123 | Alternatively: 124 | ```java 125 | GraphDatabaseService database = new TestGraphDatabaseFactory().newEmbeddedDatabaseBuilder(pathToDb) 126 | .loadPropertiesFromFile(this.getClass().getClassLoader().getResource("neo4j.properties").getPath()) 127 | .newGraphDatabase(); 128 | 129 | RuntimeRegistry.getStartedRuntime(database); 130 | //make sure neo4j.properties contain the lines mentioned in previous section 131 | ``` 132 | 133 | Using GraphAware NodeRank 134 | ========================= 135 | 136 | ### Cypher 137 | 138 | The `ga.noderank.getTopRanked` procedure call can be used in Cypher to get a list of top ranked nodes, ordered by decreasing rank. 139 | 140 | ``` 141 | CALL ga.noderank.getTopRanked("moduleId", 10) YIELD node RETURN node 142 | ``` 143 | 144 | There are 2 arguments to pass to the call : 145 | 146 | * `moduleId`: a string representing the name of the module id you used to register the module in the configuration 147 | * `limit` : an integer used to determine the size of the returned list of nodes 148 | 149 | ### REST API 150 | 151 | In Server Mode, the NodeRank is accessible via the REST API. 152 | 153 | You can issue GET requests to `http://your-server-address:7474/graphaware/noderank/{moduleId}` to get a list of top ranked 154 | nodes, ordered by decreasing rank. The maximum size of the list is determined by the `maxTopRankNodes` parameter configured 155 | earlier. You can further limit the size of the results by provising a `limit` request parameter, e.g. 156 | `http://your-server-address:7474/graphaware/noderank/{moduleId}?limit=10`. 157 | 158 | The REST API returns a JSON array of nodes, e.g. 159 | 160 | ```json 161 | [ 162 | { 163 | "id": 5, 164 | "labels": ["Person", "Male"], 165 | "properties": 166 | { 167 | "nodeRank": 3022, 168 | "name": "Brandon Morley" 169 | } 170 | }, 171 | { 172 | "id": 9, 173 | "labels": ["Person", "Male"], 174 | "properties": 175 | { 176 | "nodeRank": 2656, 177 | "name": "Liam Rees" 178 | } 179 | }, 180 | { 181 | "id": 8, 182 | "labels": ["Person", "Female"], 183 | "properties": 184 | { 185 | "nodeRank": 2280, 186 | "name": "Amelie Green" 187 | } 188 | } 189 | ] 190 | ``` 191 | 192 | ### Java API 193 | 194 | To use the Java API, obtain the module and ask for top nodes. You can also just interrogate the node properties as usual, 195 | the property that NodeRank writes to the nodes is configured using the `propertyKey` parameter described above. 196 | 197 | ``` 198 | NodeRankModule nodeRankModule = ProductionRuntime.getStartedRuntime(database).getModule("NR", NodeRankModule.class); 199 | List topNodes = nodeRankModule.getTopNodes().getTopNodes(); 200 | ``` 201 | 202 | Please refer to Javadoc for more detail. 203 | 204 | License 205 | ------- 206 | 207 | Copyright (c) 2014-2017 GraphAware 208 | 209 | GraphAware is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 210 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 211 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 212 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 213 | You should have received a copy of the GNU General Public License along with this program. 214 | If not, see . 215 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 4.0.0 19 | 20 | noderank 21 | 3.2.5.51.6-SNAPSHOT 22 | 23 | 24 | com.graphaware.neo4j 25 | module-parent 26 | 3.2.5.51 27 | 28 | 29 | GraphAware Neo4j Node Rank Module 30 | Neo4j Timer-Driven Module that computes a page-rank-like algorithm on a Neo4j graph 31 | http://graphaware.com 32 | 33 | 34 | 35 | GNU General Public License, version 3 36 | http://www.gnu.org/licenses/gpl-3.0.txt 37 | repo 38 | 39 | 40 | 41 | 42 | scm:git:git@github.com:graphaware/neo4j-noderank.git 43 | scm:git:git@github.com:graphaware/neo4j-noderank.git 44 | git@github.com:graphaware/neo4j-noderank.git 45 | HEAD 46 | 47 | 48 | 49 | 50 | bachmanm 51 | Michal Bachman 52 | neo4j-noderank@graphaware.com 53 | 54 | 55 | havlicek 56 | Vojtech Hlavicek 57 | vojta@graphaware.com 58 | 59 | 60 | atg 61 | Adam George 62 | adam@graphaware.com 63 | 64 | 65 | 66 | 67 | https://travis-ci.org/graphaware/neo4j-noderank 68 | Travis CI 69 | 70 | 71 | 2014 72 | 73 | 74 | GitHub 75 | https://github.com/graphaware/neo4j-noderank/issues 76 | 77 | 78 | 79 | Graph Aware Limited 80 | http://graphaware.com 81 | 82 | 83 | 84 | 85 | 86 | org.neo4j 87 | neo4j-kernel 88 | test-jar 89 | 90 | 91 | 92 | com.graphaware.neo4j 93 | api 94 | 95 | 96 | 97 | com.graphaware.neo4j 98 | runtime 99 | 100 | 101 | 102 | org.springframework 103 | spring-webmvc 104 | 105 | 106 | 107 | com.graphaware.neo4j 108 | server 109 | 110 | 111 | 112 | 113 | com.google.guava 114 | guava 115 | test 116 | 117 | 118 | 119 | org.la4j 120 | la4j 121 | 0.4.9 122 | test 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankApi.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.runtime.RuntimeRegistry; 20 | import org.neo4j.graphdb.GraphDatabaseService; 21 | import org.neo4j.graphdb.Node; 22 | import org.neo4j.graphdb.NotFoundException; 23 | import org.neo4j.graphdb.Transaction; 24 | 25 | import java.util.LinkedList; 26 | import java.util.List; 27 | 28 | public class NodeRankApi { 29 | 30 | private final GraphDatabaseService database; 31 | 32 | public NodeRankApi(GraphDatabaseService database) { 33 | this.database = database; 34 | } 35 | 36 | public List getTopRankedNodes(String moduleId, int limit) { 37 | List result = new LinkedList<>(); 38 | NodeRankModule module = RuntimeRegistry.getStartedRuntime(database).getModule(moduleId, NodeRankModule.class); 39 | 40 | try (Transaction tx = database.beginTx()) { 41 | for (Node node : module.getTopNodes().getTopNodes()) { 42 | try { 43 | result.add(node); 44 | 45 | if (result.size() >= limit) { 46 | break; 47 | } 48 | 49 | } catch (NotFoundException e) { 50 | //oh well, deleted in the meantime 51 | } 52 | } 53 | 54 | tx.success(); 55 | } 56 | 57 | return result; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.runtime.metadata.NodeBasedContext; 20 | import org.neo4j.graphdb.Node; 21 | 22 | /** 23 | * Context for the {@link NodeRankModule} that extends {@link NodeBasedContext} and also remembers a 24 | * number of nodes with highest node ranks. 25 | */ 26 | public class NodeRankContext extends NodeBasedContext { 27 | 28 | private Long[] topNodes; 29 | 30 | public NodeRankContext(long nodeId, Long[] topNodes) { 31 | super(nodeId); 32 | this.topNodes = topNodes; 33 | } 34 | 35 | public NodeRankContext(Node node, Long[] topNodes) { 36 | super(node); 37 | this.topNodes = topNodes; 38 | } 39 | 40 | public NodeRankContext(long nodeId, long earliestNextCall, Long[] topNodes) { 41 | super(nodeId, earliestNextCall); 42 | this.topNodes = topNodes; 43 | } 44 | 45 | public NodeRankContext(Node node, long earliestNextCall, Long[] topNodes) { 46 | super(node, earliestNextCall); 47 | this.topNodes = topNodes; 48 | } 49 | 50 | public Long[] getTopNodes() { 51 | return topNodes; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.api.json.JsonNode; 20 | import com.graphaware.api.json.LongIdJsonNode; 21 | import org.neo4j.graphdb.GraphDatabaseService; 22 | import org.neo4j.graphdb.NotFoundException; 23 | import org.neo4j.graphdb.Transaction; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.http.HttpStatus; 26 | import org.springframework.stereotype.Controller; 27 | import org.springframework.web.bind.annotation.*; 28 | 29 | import java.util.LinkedList; 30 | import java.util.List; 31 | 32 | /** 33 | * REST API for {@link NodeRankModule}. 34 | */ 35 | @Controller 36 | @RequestMapping("/noderank") 37 | public class NodeRankController { 38 | 39 | private final GraphDatabaseService database; 40 | 41 | private final NodeRankApi nodeRankApi; 42 | 43 | @Autowired 44 | public NodeRankController(GraphDatabaseService database) { 45 | this.database = database; 46 | this.nodeRankApi = new NodeRankApi(database); 47 | } 48 | 49 | @RequestMapping(value = "/{moduleId}", method = RequestMethod.GET) 50 | @ResponseBody 51 | public List topRankedNodes(@PathVariable String moduleId, @RequestParam(value = "limit", defaultValue = "10") int limit) { 52 | List result = new LinkedList<>(); 53 | try (Transaction tx = database.beginTx()) { 54 | nodeRankApi.getTopRankedNodes(moduleId, limit).stream().forEach((node) -> { 55 | result.add(new LongIdJsonNode(node)); 56 | }); 57 | tx.success(); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | @ExceptionHandler(IllegalArgumentException.class) 64 | @ResponseStatus(HttpStatus.BAD_REQUEST) 65 | public void handleIllegalArguments() { 66 | } 67 | 68 | @ExceptionHandler(NotFoundException.class) 69 | @ResponseStatus(HttpStatus.NOT_FOUND) 70 | public void handleNotFound() { 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.common.log.LoggerFactory; 20 | import com.graphaware.runtime.config.TimerDrivenModuleConfiguration; 21 | import com.graphaware.runtime.config.util.InstanceRoleUtils; 22 | import com.graphaware.runtime.metadata.NodeBasedContext; 23 | import com.graphaware.runtime.module.BaseTimerDrivenModule; 24 | import com.graphaware.runtime.module.TimerDrivenModule; 25 | import com.graphaware.runtime.walk.NodeSelector; 26 | import com.graphaware.runtime.walk.RandomNodeSelector; 27 | import com.graphaware.runtime.walk.RandomRelationshipSelector; 28 | import com.graphaware.runtime.walk.RelationshipSelector; 29 | import org.neo4j.graphdb.*; 30 | import org.neo4j.logging.Log; 31 | 32 | import java.util.Random; 33 | 34 | /** 35 | * A {@link TimerDrivenModule} that perpetually walks the graph by randomly following relationships and increments 36 | * a configured node property called as it goes. 37 | *

38 | * Sooner or later, depending on the size and shape of the network, it will converge to values that would be computed 39 | * by PageRank algorithm (not normalised). 40 | */ 41 | public class NodeRankModule extends BaseTimerDrivenModule implements TimerDrivenModule { 42 | 43 | private static final Log LOG = LoggerFactory.getLogger(NodeRankModule.class); 44 | 45 | private final NodeRankModuleConfiguration config; 46 | private final NodeSelector nodeSelector; 47 | private final RelationshipSelector relationshipSelector; 48 | private final TopRankedNodes topNodes = new TopRankedNodes(); 49 | private final Random random = new Random(); 50 | 51 | /** 52 | * Constructs a new {@link NodeRankModule} with the given ID using the default module configuration. 53 | * 54 | * @param moduleId The unique identifier for this module instance in the {@link com.graphaware.runtime.GraphAwareRuntime}. 55 | */ 56 | public NodeRankModule(String moduleId) { 57 | this(moduleId, NodeRankModuleConfiguration.defaultConfiguration()); 58 | } 59 | 60 | /** 61 | * Constructs a new {@link NodeRankModule} with the given ID and configuration settings. 62 | * 63 | * @param moduleId The unique identifier for this module instance in the {@link com.graphaware.runtime.GraphAwareRuntime}. 64 | * @param config The {@link NodeRankModuleConfiguration} to use. 65 | */ 66 | public NodeRankModule(String moduleId, NodeRankModuleConfiguration config) { 67 | super(moduleId); 68 | this.config = config; 69 | this.nodeSelector = new RandomNodeSelector(config.getNodeInclusionPolicy()); 70 | this.relationshipSelector = new RandomRelationshipSelector(config.getRelationshipInclusionPolicy()); 71 | } 72 | 73 | /** 74 | * {@inheritDoc} 75 | */ 76 | @Override 77 | public TimerDrivenModuleConfiguration getConfiguration() { 78 | return config; 79 | } 80 | 81 | /** 82 | * {@inheritDoc} 83 | */ 84 | @Override 85 | public NodeRankContext createInitialContext(GraphDatabaseService database) { 86 | Node node; 87 | 88 | try (Transaction tx = database.beginTx()) { 89 | node = nodeSelector.selectNode(database); 90 | tx.success(); 91 | } 92 | 93 | if (node == null) { 94 | LOG.debug("NodeRank did not find a node to start with. There are no nodes matching the configuration."); 95 | return null; 96 | } 97 | 98 | LOG.info("Starting node rank graph walker from random start node..."); 99 | return new NodeRankContext(node.getId(), new Long[0]); 100 | } 101 | 102 | /** 103 | * {@inheritDoc} 104 | */ 105 | @Override 106 | public NodeRankContext doSomeWork(NodeRankContext lastContext, GraphDatabaseService database) { 107 | topNodes.initializeIfNeeded(lastContext, database, config); 108 | 109 | Node lastNode = determineLastNode(lastContext, database); 110 | Node nextNode = determineNextNode(lastNode, database); 111 | 112 | if (nextNode == null) { 113 | LOG.debug("NodeRank did not find a node to continue with. There are no nodes matching the configuration."); 114 | return lastContext; 115 | } 116 | 117 | int rankValue = (int) nextNode.getProperty(config.getRankPropertyKey(), 0) + 1; 118 | nextNode.setProperty(config.getRankPropertyKey(), rankValue); 119 | topNodes.addNode(nextNode, rankValue); 120 | 121 | return new NodeRankContext(nextNode, topNodes.getTopNodeIds()); 122 | } 123 | 124 | private Node determineLastNode(NodeBasedContext lastContext, GraphDatabaseService database) { 125 | if (lastContext == null) { 126 | LOG.debug("No context found. Will start from a random node."); 127 | return null; 128 | } 129 | 130 | try { 131 | return lastContext.find(database); 132 | } catch (NotFoundException e) { 133 | LOG.debug("Node referenced in last context with ID %s was not found in the database. Will start from a random node.", lastContext); 134 | return null; 135 | } 136 | } 137 | 138 | private Node determineNextNode(Node currentNode, GraphDatabaseService database) { 139 | if (currentNode == null) { 140 | return nodeSelector.selectNode(database); 141 | } 142 | 143 | //hyperjump 144 | if (random.nextDouble() > config.getDampingFactor()) { 145 | LOG.debug("Performing hyperjump"); 146 | return nodeSelector.selectNode(database); 147 | } 148 | 149 | Relationship randomRelationship = relationshipSelector.selectRelationship(currentNode); 150 | if (randomRelationship == null) { 151 | LOG.debug("Dead end at %s, selecting a new random node", currentNode); 152 | return nodeSelector.selectNode(database); 153 | } 154 | 155 | Node result = randomRelationship.getOtherNode(currentNode); 156 | 157 | if (!config.getNodeInclusionPolicy().include(result)) { 158 | LOG.debug("Relationship Inclusion Policy allows for a relationship, which leads to a node that " + 159 | "is not included by the Node Inclusion Policy. This is likely a mis-configuration"); 160 | } 161 | 162 | return result; 163 | } 164 | 165 | public TopRankedNodes getTopNodes() { 166 | return topNodes; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankModuleBootstrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import java.util.Map; 20 | 21 | import org.neo4j.graphdb.GraphDatabaseService; 22 | import org.neo4j.logging.Log; 23 | 24 | import com.graphaware.common.log.LoggerFactory; 25 | import com.graphaware.common.policy.inclusion.NodeInclusionPolicy; 26 | import com.graphaware.common.policy.inclusion.RelationshipInclusionPolicy; 27 | import com.graphaware.runtime.config.function.StringToNodeInclusionPolicy; 28 | import com.graphaware.runtime.config.function.StringToRelationshipInclusionPolicy; 29 | import com.graphaware.runtime.module.RuntimeModuleBootstrapper; 30 | 31 | /** 32 | * {@link RuntimeModuleBootstrapper} for {@link NodeRankModule}. 33 | */ 34 | public class NodeRankModuleBootstrapper implements RuntimeModuleBootstrapper { 35 | 36 | private static final Log LOG = LoggerFactory.getLogger(NodeRankModuleBootstrapper.class); 37 | 38 | private static final String MAX_TOP_RANK_NODES = "maxTopRankNodes"; 39 | private static final String DAMPING = "dampingFactor"; 40 | private static final String PROPERTY_KEY = "propertyKey"; 41 | private static final String NODE = "node"; 42 | private static final String RELATIONSHIP = "relationship"; 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | @Override 48 | public NodeRankModule bootstrapModule(String moduleId, Map config, GraphDatabaseService database) { 49 | LOG.info("Constructing new module with ID: %s", moduleId); 50 | LOG.debug("Configuration parameter map is: %s", config); 51 | 52 | NodeRankModuleConfiguration configuration = NodeRankModuleConfiguration.defaultConfiguration(); 53 | 54 | if (config.get(PROPERTY_KEY) != null) { 55 | LOG.info("Property key set to %s", config.get(PROPERTY_KEY)); 56 | configuration = configuration.withRankPropertyKey(config.get(PROPERTY_KEY)); 57 | } 58 | 59 | if (config.get(MAX_TOP_RANK_NODES) != null) { 60 | LOG.info("Max top rank nodes set to %s", config.get(MAX_TOP_RANK_NODES)); 61 | configuration = configuration.withMaxTopRankNodes(Integer.valueOf(config.get(MAX_TOP_RANK_NODES))); 62 | } 63 | 64 | if (config.get(DAMPING) != null) { 65 | LOG.info("Damping factor set to %s", config.get(DAMPING)); 66 | configuration = configuration.withDampingFactor(Double.valueOf(config.get(DAMPING))); 67 | } 68 | 69 | if (config.get(NODE) != null) { 70 | NodeInclusionPolicy policy = StringToNodeInclusionPolicy.getInstance().apply(config.get(NODE)); 71 | LOG.info("Node Inclusion Policy set to %s", policy); 72 | configuration = configuration.with(policy); 73 | } 74 | 75 | if (config.get(RELATIONSHIP) != null) { 76 | RelationshipInclusionPolicy policy = StringToRelationshipInclusionPolicy.getInstance().apply(config.get(RELATIONSHIP)); 77 | LOG.info("Relationship Inclusion Policy set to %s", policy); 78 | configuration = configuration.with(policy); 79 | } 80 | 81 | return new NodeRankModule(moduleId, configuration); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankModuleConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.common.policy.inclusion.NodeInclusionPolicy; 20 | import com.graphaware.common.policy.inclusion.RelationshipInclusionPolicy; 21 | import com.graphaware.common.policy.role.AnyRole; 22 | import com.graphaware.common.policy.role.InstanceRolePolicy; 23 | import com.graphaware.common.policy.role.WritableRole; 24 | import com.graphaware.runtime.config.BaseTimerDrivenModuleConfiguration; 25 | import com.graphaware.runtime.policy.all.IncludeAllBusinessNodes; 26 | import com.graphaware.runtime.policy.all.IncludeAllBusinessRelationships; 27 | 28 | /** 29 | * Configuration settings for the {@link NodeRankModule} with fluent interface. 30 | */ 31 | public class NodeRankModuleConfiguration extends BaseTimerDrivenModuleConfiguration { 32 | 33 | private final String rankPropertyKey; 34 | private final NodeInclusionPolicy nodeInclusionPolicy; 35 | private final RelationshipInclusionPolicy relationshipInclusionPolicy; 36 | private final int maxTopRankNodes; 37 | private final double dampingFactor; 38 | 39 | /** 40 | * Retrieves the default {@link NodeRankModuleConfiguration}, which includes all (non-internal) nodes and relationships. 41 | * 42 | * @return The default {@link NodeRankModuleConfiguration} 43 | */ 44 | public static NodeRankModuleConfiguration defaultConfiguration() { 45 | return new NodeRankModuleConfiguration(WritableRole.getInstance(), "nodeRank", IncludeAllBusinessNodes.getInstance(), IncludeAllBusinessRelationships.getInstance(), 10, 0.85); 46 | } 47 | 48 | /** 49 | * Construct a new configuration with the given rank property key. 50 | * 51 | * @param rankPropertyKey key of the property written to the ranked nodes. 52 | * @return new config. 53 | */ 54 | public NodeRankModuleConfiguration withRankPropertyKey(String rankPropertyKey) { 55 | return new NodeRankModuleConfiguration(getInstanceRolePolicy(), rankPropertyKey, getNodeInclusionPolicy(), getRelationshipInclusionPolicy(), getMaxTopRankNodes(), getDampingFactor()); 56 | } 57 | 58 | /** 59 | * Construct a new configuration with the given node inclusion policy. 60 | * 61 | * @param nodeInclusionPolicy The {@link NodeInclusionPolicy} to use for selecting nodes to include in the rank algorithm. 62 | * @return new config. 63 | */ 64 | public NodeRankModuleConfiguration with(NodeInclusionPolicy nodeInclusionPolicy) { 65 | return new NodeRankModuleConfiguration(getInstanceRolePolicy(), getRankPropertyKey(), nodeInclusionPolicy, getRelationshipInclusionPolicy(), getMaxTopRankNodes(), getDampingFactor()); 66 | } 67 | 68 | /** 69 | * Construct a new configuration with the given node inclusion policy. 70 | * 71 | * @param relationshipInclusionPolicy The {@link RelationshipInclusionPolicy} for selecting which relationships to follow when crawling the graph. 72 | * @return new config. 73 | */ 74 | public NodeRankModuleConfiguration with(RelationshipInclusionPolicy relationshipInclusionPolicy) { 75 | return new NodeRankModuleConfiguration(getInstanceRolePolicy(), getRankPropertyKey(), getNodeInclusionPolicy(), relationshipInclusionPolicy, getMaxTopRankNodes(), getDampingFactor()); 76 | } 77 | 78 | /** 79 | * Construct a new configuration with the given maximum number of top ranked nodes to remember. 80 | * 81 | * @param maxTopRankNodes to remember. 82 | * @return new config. 83 | */ 84 | public NodeRankModuleConfiguration withMaxTopRankNodes(int maxTopRankNodes) { 85 | return new NodeRankModuleConfiguration(getInstanceRolePolicy(), getRankPropertyKey(), getNodeInclusionPolicy(), getRelationshipInclusionPolicy(), maxTopRankNodes, getDampingFactor()); 86 | } 87 | 88 | /** 89 | * Construct a new configuration with the given damping factor. 90 | * 91 | * @param dampingFactor new damping factor. 92 | * @return new config. 93 | */ 94 | public NodeRankModuleConfiguration withDampingFactor(double dampingFactor) { 95 | return new NodeRankModuleConfiguration(getInstanceRolePolicy(), getRankPropertyKey(), getNodeInclusionPolicy(), getRelationshipInclusionPolicy(), getMaxTopRankNodes(), dampingFactor); 96 | } 97 | 98 | /** 99 | * Constructs a new {@link NodeRankModuleConfiguration} based on the given configuration details. 100 | * 101 | * @param instanceRolePolicy specifies which role a machine must have in order to run the module with this configuration. Must not be null. 102 | * @param rankPropertyKey name of the property written to the ranked nodes. 103 | * @param nodeInclusionPolicy The {@link NodeInclusionPolicy} to use for selecting nodes to include in the rank algorithm. 104 | * @param relationshipInclusionPolicy The {@link RelationshipInclusionPolicy} for selecting which relationships to follow when crawling the graph. 105 | * @param maxTopRankNodes maximum number of top ranked nodes to remember. 106 | */ 107 | private NodeRankModuleConfiguration(InstanceRolePolicy instanceRolePolicy, String rankPropertyKey, NodeInclusionPolicy nodeInclusionPolicy, RelationshipInclusionPolicy relationshipInclusionPolicy, int maxTopRankNodes, double dampingFactor) { 108 | super(instanceRolePolicy); 109 | 110 | if (maxTopRankNodes < 0) { 111 | throw new IllegalArgumentException("Max top ranked nodes must be > 0"); 112 | } 113 | 114 | if (dampingFactor < 0 || dampingFactor > 1.0) { 115 | throw new IllegalArgumentException("Damping factor must be between 0.0 and 1.0"); 116 | } 117 | 118 | this.rankPropertyKey = rankPropertyKey; 119 | this.nodeInclusionPolicy = nodeInclusionPolicy; 120 | this.relationshipInclusionPolicy = relationshipInclusionPolicy; 121 | this.maxTopRankNodes = maxTopRankNodes; 122 | this.dampingFactor = dampingFactor; 123 | } 124 | 125 | /** 126 | * {@inheritDoc} 127 | */ 128 | @Override 129 | protected NodeRankModuleConfiguration newInstance(InstanceRolePolicy instanceRolePolicy) { 130 | return new NodeRankModuleConfiguration(instanceRolePolicy, getRankPropertyKey(), getNodeInclusionPolicy(), getRelationshipInclusionPolicy(), getMaxTopRankNodes(), getDampingFactor()); 131 | } 132 | 133 | public String getRankPropertyKey() { 134 | return rankPropertyKey; 135 | } 136 | 137 | public NodeInclusionPolicy getNodeInclusionPolicy() { 138 | return nodeInclusionPolicy; 139 | } 140 | 141 | public RelationshipInclusionPolicy getRelationshipInclusionPolicy() { 142 | return relationshipInclusionPolicy; 143 | } 144 | 145 | public int getMaxTopRankNodes() { 146 | return maxTopRankNodes; 147 | } 148 | 149 | public double getDampingFactor() { 150 | return dampingFactor; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/NodeRankProcedure.java: -------------------------------------------------------------------------------- 1 | package com.graphaware.module.noderank; 2 | 3 | import org.neo4j.graphdb.GraphDatabaseService; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.procedure.Context; 6 | import org.neo4j.procedure.Name; 7 | import org.neo4j.procedure.Procedure; 8 | 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.stream.Stream; 12 | 13 | public class NodeRankProcedure { 14 | 15 | @Context 16 | public GraphDatabaseService database; 17 | 18 | @Procedure("ga.noderank.getTopRanked") 19 | public Stream getTopRankedNodes(@Name("moduleId") String moduleId, @Name("limit") Number limit) { 20 | List result = new LinkedList<>(); 21 | new NodeRankApi(database).getTopRankedNodes(moduleId, limit.intValue()).stream().forEach((node) -> { 22 | result.add(new NodeResult(node)); 23 | }); 24 | 25 | return result.stream(); 26 | } 27 | 28 | public class NodeResult { 29 | 30 | public final Node node; 31 | 32 | public NodeResult(Node node) { 33 | this.node = node; 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/graphaware/module/noderank/TopRankedNodes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.common.log.LoggerFactory; 20 | import com.graphaware.common.util.BoundedSortedList; 21 | import org.neo4j.graphdb.GraphDatabaseService; 22 | import org.neo4j.graphdb.Node; 23 | import org.neo4j.logging.Log; 24 | 25 | import java.util.Collections; 26 | import java.util.LinkedList; 27 | import java.util.List; 28 | 29 | /** 30 | * A container for top ranked nodes. 31 | */ 32 | public class TopRankedNodes { 33 | 34 | private static final Log LOG = LoggerFactory.getLogger(TopRankedNodes.class); 35 | 36 | private BoundedSortedList topNodes; 37 | 38 | public List getTopNodes() { 39 | if (topNodes == null) { 40 | return Collections.emptyList(); 41 | } 42 | 43 | return topNodes.getItems(); 44 | } 45 | 46 | public void addNode(Node node, int rank) { 47 | if (topNodes == null) { 48 | throw new IllegalStateException("Please initialize top ranked nodes first"); 49 | } 50 | 51 | topNodes.add(node, rank); 52 | } 53 | 54 | public void initializeIfNeeded(NodeRankContext context, GraphDatabaseService database, NodeRankModuleConfiguration config) { 55 | if (topNodes != null) { 56 | return; 57 | } 58 | 59 | topNodes = new BoundedSortedList<>(config.getMaxTopRankNodes(), Collections.reverseOrder()); 60 | 61 | if (context == null) { 62 | return; 63 | } 64 | 65 | for (long nodeId : context.getTopNodes()) { 66 | try { 67 | topNodes.add(database.getNodeById(nodeId), (int) database.getNodeById(nodeId).getProperty(config.getRankPropertyKey(), 0)); 68 | } catch (Exception e) { 69 | LOG.warn("Exception while adding ranked node " + nodeId + " to the collection of top ranked nodes. Will ignore...", e); 70 | } 71 | } 72 | } 73 | 74 | public Long[] getTopNodeIds() { 75 | List result = new LinkedList<>(); 76 | 77 | for (Node node : getTopNodes()) { 78 | result.add(node.getId()); 79 | } 80 | 81 | return result.toArray(new Long[result.size()]); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/EmbeddedDatabaseIntegration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.runtime.RuntimeRegistry; 20 | import org.junit.Test; 21 | import org.neo4j.graphdb.GraphDatabaseService; 22 | import org.neo4j.graphdb.Result; 23 | import org.neo4j.test.TestGraphDatabaseFactory; 24 | 25 | import static org.junit.Assert.assertTrue; 26 | 27 | public class EmbeddedDatabaseIntegration { 28 | 29 | @Test 30 | public void shouldSuccessfullyInitialiseAndRunModuleWhenDatabaseIsStarted() throws InterruptedException { 31 | GraphDatabaseService database = new TestGraphDatabaseFactory().newImpermanentDatabaseBuilder() 32 | .loadPropertiesFromFile("src/test/resources/test-neo4j.conf") 33 | .newGraphDatabase(); 34 | 35 | RuntimeRegistry.getStartedRuntime(database); 36 | 37 | populateDatabase(database); 38 | 39 | Thread.sleep(2000); 40 | 41 | Result executionResult = database.execute("MATCH (p:Person) WHERE p.nodeRank > 0 RETURN p"); 42 | 43 | assertTrue("The page rank module didn't run on startup", executionResult.hasNext()); 44 | 45 | database.shutdown(); 46 | } 47 | 48 | private void populateDatabase(GraphDatabaseService database) { 49 | database.execute( "CREATE " + 50 | " (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'}),"+ 51 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'}),"+ 52 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'}),"+ 53 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'}),"+ 54 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'}),"+ 55 | " (vi)-[:FRIEND_OF]->(a),"+ 56 | " (d)-[:FRIEND_OF]->(a),"+ 57 | " (d)-[:FRIEND_OF]->(vi),"+ 58 | " (v)-[:FRIEND_OF]->(a)"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/EmbeddedDatabaseIntegration2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.runtime.GraphAwareRuntime; 20 | import com.graphaware.runtime.GraphAwareRuntimeFactory; 21 | import com.graphaware.runtime.RuntimeRegistry; 22 | import org.junit.Test; 23 | import org.neo4j.graphdb.GraphDatabaseService; 24 | import org.neo4j.graphdb.Node; 25 | import org.neo4j.graphdb.Result; 26 | import org.neo4j.test.TestGraphDatabaseFactory; 27 | 28 | import java.util.List; 29 | 30 | import static org.junit.Assert.assertTrue; 31 | 32 | public class EmbeddedDatabaseIntegration2 { 33 | 34 | @Test 35 | public void shouldSuccessfullyInitialiseAndRunModuleWhenDatabaseIsStarted() throws InterruptedException { 36 | GraphDatabaseService database = new TestGraphDatabaseFactory().newImpermanentDatabase(); 37 | 38 | GraphAwareRuntime runtime = GraphAwareRuntimeFactory.createRuntime(database); //where database is an instance of GraphDatabaseService 39 | NodeRankModule module = new NodeRankModule("NR"); 40 | runtime.registerModule(module); 41 | runtime.start(); 42 | 43 | populateDatabase(database); 44 | 45 | Thread.sleep(2000); 46 | 47 | Result executionResult = database.execute("MATCH (p:Person) WHERE p.nodeRank > 0 RETURN p"); 48 | 49 | assertTrue("The page rank module didn't run on startup", executionResult.hasNext()); 50 | 51 | NodeRankModule nodeRankModule = RuntimeRegistry.getStartedRuntime(database).getModule("NR", NodeRankModule.class); 52 | List topNodes = nodeRankModule.getTopNodes().getTopNodes(); 53 | assertTrue(topNodes.size() > 0); 54 | 55 | database.shutdown(); 56 | } 57 | 58 | @Test 59 | public void shouldSuccessfullyStartWhenMoreThanOneModuleDefinitionIsConfigured() { 60 | GraphDatabaseService database = new TestGraphDatabaseFactory().newImpermanentDatabaseBuilder() 61 | .loadPropertiesFromFile("src/test/resources/neo4j-2.conf") 62 | .newGraphDatabase(); 63 | 64 | RuntimeRegistry.getStartedRuntime(database); 65 | assertTrue(true); 66 | database.shutdown(); 67 | } 68 | 69 | private void populateDatabase(GraphDatabaseService database) { 70 | database.execute("CREATE " + 71 | " (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'})," + 72 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'})," + 73 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'})," + 74 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'})," + 75 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'})," + 76 | " (vi)-[:FRIEND_OF]->(a)," + 77 | " (d)-[:FRIEND_OF]->(a)," + 78 | " (d)-[:FRIEND_OF]->(vi)," + 79 | " (v)-[:FRIEND_OF]->(a)"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/NodeRankControllerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.graphaware.test.integration.GraphAwareIntegrationTest; 21 | import org.junit.Test; 22 | import org.springframework.http.HttpStatus; 23 | 24 | import java.io.IOException; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | 30 | 31 | public class NodeRankControllerTest extends GraphAwareIntegrationTest { 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | protected String configFile() { 38 | return "int-test-neo4j.conf"; 39 | } 40 | 41 | @Test 42 | public void shouldRetrieveTopNodes() throws InterruptedException, IOException { 43 | httpClient.executeCypher(baseNeoUrl(), "CREATE (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'})," + 44 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'})," + 45 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'})," + 46 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'})," + 47 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'})," + 48 | " (vi)-[:FRIEND_OF]->(a)," + 49 | " (d)-[:FRIEND_OF]->(a)," + 50 | " (d)-[:FRIEND_OF]->(vi)," + 51 | " (v)-[:FRIEND_OF]->(a)"); 52 | 53 | Thread.sleep(30000); 54 | 55 | String s = httpClient.get(baseUrl() + "/noderank/noderank/", HttpStatus.OK.value()); 56 | 57 | Map first = (Map) new ObjectMapper().readValue(s, List.class).get(0); 58 | 59 | assertEquals(0, first.get("id")); 60 | assertEquals("Michal", ((Map) first.get("properties")).get("name")); 61 | } 62 | 63 | @Test 64 | public void requestToUnknownModuleShouldProduce404() throws InterruptedException { 65 | httpClient.get(baseUrl() + "/noderank/unknown/", HttpStatus.NOT_FOUND.value()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/NodeRankModuleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | 22 | import java.util.Map; 23 | 24 | import org.junit.Test; 25 | import org.neo4j.graphdb.Label; 26 | import org.neo4j.graphdb.Node; 27 | import org.neo4j.graphdb.Relationship; 28 | import org.neo4j.graphdb.RelationshipType; 29 | import org.neo4j.graphdb.Result; 30 | import org.neo4j.graphdb.Transaction; 31 | 32 | import com.graphaware.common.policy.inclusion.BaseRelationshipInclusionPolicy; 33 | import com.graphaware.common.policy.inclusion.fluent.IncludeNodes; 34 | import com.graphaware.runtime.metadata.NodeBasedContext; 35 | import com.graphaware.test.integration.EmbeddedDatabaseIntegrationTest; 36 | 37 | public class NodeRankModuleTest extends EmbeddedDatabaseIntegrationTest { 38 | 39 | private NodeRankModule module; 40 | 41 | public void setUp() throws Exception { 42 | super.setUp(); 43 | this.module = new NodeRankModule("TEST"); 44 | } 45 | 46 | @Test 47 | public void shouldTolerateEmptyContextGivenIfNoPreviousStepsHaveBeenMade() { 48 | getDatabase().execute("CREATE (arbitraryNode)-[:RELATES_TO]->(otherNode);"); 49 | 50 | try (Transaction tx = getDatabase().beginTx()) { 51 | module.doSomeWork(module.createInitialContext(getDatabase()), getDatabase()); 52 | } 53 | } 54 | 55 | @Test 56 | public void shouldExecuteSingleStepTowardsConvergenceAndUpdateNodePropertiesAccordingly() { 57 | Result executionResult = getDatabase().execute( 58 | "CREATE (p:Person{name:'Gary'})-[:KNOWS]->(q:Person{name:'Sheila'}) RETURN p, q"); 59 | 60 | Map insertionResults = executionResult.next(); 61 | 62 | try (Transaction tx = getDatabase().beginTx()) { 63 | Node startNode = (Node) insertionResults.get("p"); 64 | NodeRankContext lastContext = new NodeRankContext(startNode, new Long[0]); 65 | 66 | Node expectedNextNode = (Node) insertionResults.get("q"); 67 | 68 | NodeBasedContext newContext = module.doSomeWork(lastContext, getDatabase()); 69 | assertNotNull("The new context shouldn't be null", newContext); 70 | Node nextNode = newContext.find(getDatabase()); 71 | assertNotNull("The next node in the new context shouldn't be null", nextNode); 72 | assertEquals("The next node wasn't selected as expected", expectedNextNode, nextNode); 73 | assertEquals("The expected page rank property wasn't updated", 1, nextNode.getProperty("nodeRank")); 74 | } 75 | } 76 | 77 | @Test 78 | public void shouldHonourInclusionStrategiesForNodesAndRelationships() { 79 | 80 | module = new NodeRankModule("TEST2", NodeRankModuleConfiguration 81 | .defaultConfiguration() 82 | .with(IncludeNodes.all().with("Car")) 83 | .with(new BaseRelationshipInclusionPolicy() { 84 | @Override 85 | public boolean include(Relationship relationship) { 86 | return relationship.isType(RelationshipType.withName("OWNS")); 87 | } 88 | 89 | @Override 90 | public boolean include(Relationship relationship, Node pointOfView) { 91 | return include(relationship) && relationship.getOtherNode(pointOfView).hasLabel(Label.label("Car")); 92 | } 93 | })); 94 | 95 | // set up test data and run test 96 | Result executionResult = getDatabase().execute( 97 | "CREATE (p:Person{name:'Sanjiv'})-[:KNOWS]->(:Person{name:'Lakshmipathy'})," 98 | + " (p)-[:KNOWS]->(:Person{name:'Rajani'}), " 99 | + " (p)-[:OWNS]->(:Laptop{manufacturer:'Dell'}), " 100 | + " (p)-[:OWNS]->(:MobilePhone{manufacturer:'Nokia'}), " 101 | + " (p)-[:OWNS]->(:Car{manufacturer:'Vauxhall'}), " 102 | + " (p)-[:OWNS]->(:Tablet{manufacturer:'Samsung'}) " 103 | + "RETURN p"); 104 | 105 | Map insertionResults = executionResult.next(); 106 | 107 | try (Transaction tx = getDatabase().beginTx()) { 108 | Node person = (Node) insertionResults.get("p"); 109 | 110 | NodeRankContext newContext = module.doSomeWork(new NodeRankContext(person, new Long[0]), getDatabase()); 111 | assertNotNull("The new context shouldn't be null", newContext); 112 | Node nextNode = newContext.find(getDatabase()); 113 | assertNotNull("The next node in the new context shouldn't be null", nextNode); 114 | assertEquals("The wrong next node was selected", "Car", nextNode.getLabels().iterator().next().name()); 115 | } 116 | } 117 | 118 | @Test 119 | public void shouldChooseLegitimateRandomStartNodeInAccordanceWithInclusionStrategy() { 120 | module = new NodeRankModule("TEST3", NodeRankModuleConfiguration.defaultConfiguration().with(IncludeNodes.all().with("Vegan"))); 121 | 122 | getDatabase().execute("CREATE (:Meat{name:'Chicken'}), (:Meat{name:'Mutton'}), (:Vegan{name:'Potato'}), " 123 | + "(:Vegetarian{name:'Milk'}), (:Vegetarian{name:'Cheese'}), (:Meat{name:'Pork'})"); 124 | 125 | try (Transaction tx = getDatabase().beginTx()) { 126 | NodeBasedContext initialContext = module.createInitialContext(getDatabase()); 127 | assertNotNull("The initial context shouldn't be null", initialContext); 128 | Node startNode = initialContext.find(getDatabase()); 129 | assertEquals("The wrong start node was selected", "Potato", startNode.getProperty("name")); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/NodeRankProcedureTest.java: -------------------------------------------------------------------------------- 1 | package com.graphaware.module.noderank; 2 | 3 | import com.graphaware.test.integration.GraphAwareIntegrationTest; 4 | import org.junit.Test; 5 | import org.neo4j.graphdb.Node; 6 | import org.neo4j.graphdb.Result; 7 | import org.neo4j.graphdb.Transaction; 8 | import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; 9 | import org.neo4j.kernel.impl.proc.Procedures; 10 | 11 | import java.io.IOException; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | public class NodeRankProcedureTest extends GraphAwareIntegrationTest { 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | protected String configFile() { 25 | return "int-test-neo4j.conf"; 26 | } 27 | 28 | @Test 29 | public void testProcedureCall() throws InterruptedException, IOException { 30 | try (Transaction tx = getDatabase().beginTx()) { 31 | getDatabase().execute("CREATE (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'})," + 32 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'})," + 33 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'})," + 34 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'})," + 35 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'})," + 36 | " (vi)-[:FRIEND_OF]->(a)," + 37 | " (d)-[:FRIEND_OF]->(a)," + 38 | " (d)-[:FRIEND_OF]->(vi)," + 39 | " (v)-[:FRIEND_OF]->(a)"); 40 | 41 | tx.success(); 42 | } 43 | 44 | Thread.sleep(30000); 45 | 46 | try (Transaction tx = getDatabase().beginTx()) { 47 | Result result = getDatabase().execute("CALL ga.noderank.getTopRanked('noderank', 10) YIELD node RETURN node"); 48 | List ranked = new LinkedList<>(); 49 | while (result.hasNext()) { 50 | Map record = result.next(); 51 | ranked.add((Node) record.get("node")); 52 | } 53 | assertEquals(0, ranked.get(0).getId()); 54 | assertEquals("Michal", ranked.get(0).getProperty("name")); 55 | tx.success(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/NodeRankProcedureTestCausalCluster.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | package com.graphaware.module.noderank; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | 21 | import java.io.IOException; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import org.junit.Test; 28 | import org.neo4j.graphdb.GraphDatabaseService; 29 | import org.neo4j.graphdb.Node; 30 | import org.neo4j.graphdb.Result; 31 | import org.neo4j.graphdb.Transaction; 32 | import org.neo4j.kernel.impl.proc.Procedures; 33 | 34 | import com.graphaware.common.policy.role.WritableRole; 35 | import com.graphaware.runtime.GraphAwareRuntime; 36 | import com.graphaware.runtime.GraphAwareRuntimeFactory; 37 | import com.graphaware.test.data.DatabasePopulator; 38 | import com.graphaware.test.integration.cluster.CausalClusterDatabasesintegrationTest; 39 | 40 | /** 41 | * 42 | */ 43 | public class NodeRankProcedureTestCausalCluster extends CausalClusterDatabasesintegrationTest { 44 | 45 | private static boolean initialized = false; 46 | 47 | @Override 48 | protected boolean shouldRegisterModules() { 49 | return true; 50 | } 51 | 52 | @Override 53 | protected boolean shouldRegisterProceduresAndFunctions() { 54 | return true; 55 | } 56 | 57 | @Override 58 | protected void registerModules(GraphDatabaseService db) throws Exception { 59 | GraphAwareRuntime runtime = GraphAwareRuntimeFactory.createRuntime(db); 60 | NodeRankModuleConfiguration config = NodeRankModuleConfiguration.defaultConfiguration() 61 | .with(WritableRole.getInstance()) 62 | .withMaxTopRankNodes(3); 63 | runtime.registerModule(new NodeRankModule("noderank", config)); 64 | runtime.start(); 65 | } 66 | 67 | @Override 68 | protected DatabasePopulator databasePopulator() { 69 | return database -> { 70 | try (Transaction tx = database.beginTx()) { 71 | database.execute("CREATE (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'})," + 72 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'})," + 73 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'})," + 74 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'})," + 75 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'})," + 76 | " (vi)-[:FRIEND_OF]->(a)," + 77 | " (d)-[:FRIEND_OF]->(a)," + 78 | " (d)-[:FRIEND_OF]->(vi)," + 79 | " (v)-[:FRIEND_OF]->(a)"); 80 | tx.success(); 81 | } 82 | 83 | }; 84 | } 85 | 86 | @Override 87 | public void setUp() throws Exception { 88 | super.setUp(); 89 | if (!initialized) { 90 | 91 | GraphDatabaseService database = getOneReplicaDatabase(); 92 | //wait for ranking synch to replica 93 | int count = 0; 94 | do { 95 | count = 0; 96 | try (Transaction tx = database.beginTx()) { 97 | Result result = database.execute("MATCH (node:Person) RETURN node"); 98 | // System.out.println("======================"); 99 | while (result.hasNext()) { 100 | Node node = (Node) result.next().get("node"); 101 | if (node.hasProperty("nodeRank")) { 102 | Integer rank = (Integer) node.getProperty("nodeRank"); 103 | // System.out.println(node.getProperty("name")+": "+node.getProperty("nodeRank")); 104 | if (rank > 2) { 105 | count++; 106 | } 107 | } 108 | } 109 | tx.failure(); 110 | } catch (Exception e) { 111 | e.printStackTrace(); 112 | } 113 | 114 | try { 115 | TimeUnit.SECONDS.sleep(1); 116 | } catch (InterruptedException e) { 117 | e.printStackTrace(); 118 | } 119 | } while (count < 6); 120 | 121 | initialized = true; 122 | } 123 | } 124 | 125 | @Test 126 | public void testProcedureCall_LEADER() throws InterruptedException, IOException { 127 | GraphDatabaseService database = getLeaderDatabase(); 128 | try (Transaction tx = database.beginTx()) { 129 | Result result = database.execute("CALL ga.noderank.getTopRanked('noderank', 10) YIELD node RETURN node"); 130 | List ranked = new LinkedList<>(); 131 | while (result.hasNext()) { 132 | Map record = result.next(); 133 | ranked.add((Node) record.get("node")); 134 | } 135 | assertFalse("No ranked nodes found", ranked.isEmpty()); 136 | assertEquals("Michal", ranked.get(0).getProperty("name")); 137 | tx.failure(); 138 | } 139 | } 140 | 141 | @Test 142 | public void testProcedureCall_FOLLOWER() throws InterruptedException, IOException { 143 | GraphDatabaseService database = getOneFollowerDatabase(); 144 | try (Transaction tx = database.beginTx()) { 145 | Result result = database.execute("CALL ga.noderank.getTopRanked('noderank', 10) YIELD node RETURN node"); 146 | List ranked = new LinkedList<>(); 147 | while (result.hasNext()) { 148 | Map record = result.next(); 149 | ranked.add((Node) record.get("node")); 150 | } 151 | assertFalse("No ranked nodes found", ranked.isEmpty()); 152 | assertEquals("Michal", ranked.get(0).getProperty("name")); 153 | tx.failure(); 154 | } 155 | } 156 | 157 | 158 | @Test 159 | public void testProcedureCall_REPLICA() throws InterruptedException, IOException { 160 | GraphDatabaseService database = getOneReplicaDatabase(); 161 | try (Transaction tx = database.beginTx()) { 162 | Result result = database.execute("CALL ga.noderank.getTopRanked('noderank', 10) YIELD node RETURN node"); 163 | List ranked = new LinkedList<>(); 164 | while (result.hasNext()) { 165 | Map record = result.next(); 166 | ranked.add((Node) record.get("node")); 167 | } 168 | assertFalse("No ranked nodes found", ranked.isEmpty()); 169 | assertEquals("Michal", ranked.get(0).getProperty("name")); 170 | tx.failure(); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/NodeRankProcedureTestHighAvailability.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | package com.graphaware.module.noderank; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | 21 | import java.io.IOException; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import org.junit.Test; 28 | import org.neo4j.graphdb.GraphDatabaseService; 29 | import org.neo4j.graphdb.Node; 30 | import org.neo4j.graphdb.Result; 31 | import org.neo4j.graphdb.Transaction; 32 | import org.neo4j.kernel.impl.proc.Procedures; 33 | 34 | import com.graphaware.common.policy.role.MasterOnly; 35 | import com.graphaware.runtime.GraphAwareRuntime; 36 | import com.graphaware.runtime.GraphAwareRuntimeFactory; 37 | import com.graphaware.test.data.DatabasePopulator; 38 | import com.graphaware.test.integration.cluster.HighAvailabilityClusterDatabasesIntegrationTest; 39 | 40 | /** 41 | * 42 | */ 43 | public class NodeRankProcedureTestHighAvailability extends HighAvailabilityClusterDatabasesIntegrationTest { 44 | 45 | @Override 46 | protected boolean shouldRegisterModules() { 47 | return true; 48 | } 49 | 50 | @Override 51 | protected boolean shouldRegisterProceduresAndFunctions() { 52 | return true; 53 | } 54 | 55 | @Override 56 | protected void registerModules(GraphDatabaseService db) throws Exception { 57 | GraphAwareRuntime runtime = GraphAwareRuntimeFactory.createRuntime(db); 58 | NodeRankModuleConfiguration config = NodeRankModuleConfiguration.defaultConfiguration() 59 | .with(MasterOnly.getInstance()) 60 | .withMaxTopRankNodes(3); 61 | runtime.registerModule(new NodeRankModule("noderank",config)); 62 | runtime.start(); 63 | } 64 | 65 | @Override 66 | protected DatabasePopulator databasePopulator() { 67 | return new DatabasePopulator() { 68 | 69 | @Override 70 | public void populate(GraphDatabaseService database) { 71 | try(Transaction tx = database.beginTx();){ 72 | database.execute("CREATE (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'})," + 73 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'})," + 74 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'})," + 75 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'})," + 76 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'})," + 77 | " (vi)-[:FRIEND_OF]->(a)," + 78 | " (d)-[:FRIEND_OF]->(a)," + 79 | " (d)-[:FRIEND_OF]->(vi)," + 80 | " (v)-[:FRIEND_OF]->(a)"); 81 | tx.success(); 82 | } 83 | 84 | //wait for ranking 85 | int count = 0; 86 | do{ 87 | count = 0; 88 | try (Transaction tx = database.beginTx()) { 89 | Result result = database.execute("MATCH (node:Person) RETURN node"); 90 | // System.out.println("======================"); 91 | while(result.hasNext()){ 92 | Node node = (Node) result.next().get("node"); 93 | if(node.hasProperty("nodeRank")){ 94 | Integer rank = (Integer) node.getProperty("nodeRank"); 95 | // System.out.println(node.getProperty("name")+": "+node.getProperty("nodeRank")); 96 | if(rank > 10){ 97 | count++; 98 | } 99 | } 100 | } 101 | 102 | }catch(Exception e){e.printStackTrace();} 103 | 104 | try { 105 | TimeUnit.SECONDS.sleep(1); 106 | } catch (InterruptedException e) { 107 | e.printStackTrace(); 108 | } 109 | }while( count < 6 ); 110 | } 111 | }; 112 | } 113 | 114 | @Test 115 | public void testProcedureCall_MASTER() throws InterruptedException, IOException { 116 | GraphDatabaseService database = getMasterDatabase(); 117 | try (Transaction tx = database.beginTx()) { 118 | Result result = database.execute("CALL ga.noderank.getTopRanked('noderank', 10) YIELD node RETURN node"); 119 | List ranked = new LinkedList<>(); 120 | while (result.hasNext()) { 121 | Map record = result.next(); 122 | ranked.add((Node) record.get("node")); 123 | } 124 | assertFalse(ranked.isEmpty()); 125 | assertEquals("Michal", ranked.get(0).getProperty("name")); 126 | tx.failure(); 127 | } 128 | } 129 | 130 | @Test 131 | public void testProcedureCall_SLAVE() throws InterruptedException, IOException { 132 | GraphDatabaseService database = getOneSlaveDatabase(); 133 | try (Transaction tx = database.beginTx()) { 134 | Result result = database.execute("CALL ga.noderank.getTopRanked('noderank', 10) YIELD node RETURN node"); 135 | List ranked = new LinkedList<>(); 136 | while (result.hasNext()) { 137 | Map record = result.next(); 138 | ranked.add((Node) record.get("node")); 139 | } 140 | assertFalse("No ranked nodes found",ranked.isEmpty()); 141 | assertEquals("Michal", ranked.get(0).getProperty("name")); 142 | tx.failure(); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/PageRankIntegration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.common.log.LoggerFactory; 20 | import com.graphaware.module.noderank.utils.*; 21 | import com.graphaware.runtime.GraphAwareRuntime; 22 | import com.graphaware.runtime.GraphAwareRuntimeFactory; 23 | import com.graphaware.runtime.config.FluentRuntimeConfiguration; 24 | import com.graphaware.runtime.policy.all.IncludeAllBusinessNodes; 25 | import com.graphaware.runtime.schedule.FixedDelayTimingStrategy; 26 | import com.graphaware.runtime.schedule.TimingStrategy; 27 | import com.graphaware.test.data.CypherFilesPopulator; 28 | import com.graphaware.test.data.DatabasePopulator; 29 | import com.graphaware.test.integration.EmbeddedDatabaseIntegrationTest; 30 | import org.junit.Test; 31 | import org.neo4j.graphdb.Node; 32 | import org.neo4j.graphdb.Transaction; 33 | import org.neo4j.logging.Log; 34 | import org.springframework.core.io.ClassPathResource; 35 | 36 | import java.io.IOException; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import java.util.concurrent.TimeUnit; 40 | 41 | import static java.util.Collections.sort; 42 | import static org.junit.Assert.assertTrue; 43 | 44 | /** 45 | * Integration tests for page rank module. 46 | */ 47 | public class PageRankIntegration extends EmbeddedDatabaseIntegrationTest { 48 | 49 | private static final Log LOG = LoggerFactory.getLogger(PageRankIntegration.class); 50 | 51 | private NodeRankModule nodeRankModule; 52 | 53 | public void setUp() throws Exception { 54 | super.setUp(); 55 | this.nodeRankModule = new NodeRankModule("TEST"); 56 | } 57 | 58 | @Override 59 | protected DatabasePopulator databasePopulator() { 60 | return new CypherFilesPopulator() { 61 | @Override 62 | protected String[] files() throws IOException { 63 | return new String[]{ 64 | new ClassPathResource("schema.cyp").getFile().getAbsolutePath(), 65 | new ClassPathResource("graph.cyp").getFile().getAbsolutePath()}; 66 | } 67 | }; 68 | } 69 | 70 | @Test 71 | public void verifyRandomWalkerModuleGeneratesReasonablePageRank() { 72 | List pageRank = computePageRank(new NetworkMatrixFactory(getDatabase())); 73 | 74 | List nodeRank = computeNodeRank(); 75 | 76 | analyseResults(pageRank, nodeRank); 77 | } 78 | 79 | private List computePageRank(NetworkMatrixFactory networkMatrixFactory) { 80 | LOG.info("Computing page rank based on adjacency matrix..."); 81 | 82 | List pageRankResult; 83 | PageRank pageRank = new PageRank(); 84 | 85 | try (Transaction tx = getDatabase().beginTx()) { 86 | NetworkMatrix transitionMatrix = networkMatrixFactory.getTransitionMatrix(); 87 | pageRankResult = pageRank.getPageRankPairs(transitionMatrix, 0.85); // Sergei's & Larry's suggestion is to use .85 to become rich;) 88 | LOG.info("The highest PageRank in the network is: " + getDatabase().getNodeById(pageRankResult.get(0).node()).getProperty("name").toString()); 89 | 90 | tx.success(); 91 | } 92 | 93 | return pageRankResult; 94 | } 95 | 96 | private ArrayList computeNodeRank() { 97 | letCrawlerDoItsJob(); 98 | 99 | ArrayList nodeRank = new ArrayList<>(); 100 | 101 | try (Transaction tx = getDatabase().beginTx()) { 102 | for (Node node : getDatabase().getAllNodes()) { 103 | if (IncludeAllBusinessNodes.getInstance().include(node)) { 104 | nodeRank.add(new RankNodePair((int) node.getProperty("nodeRank", 0), node.getId())); 105 | } 106 | } 107 | 108 | sort(nodeRank); 109 | LOG.info("The highest NeoRank in the network is: " + getDatabase().getNodeById(nodeRank.get(0).node()).getProperty("name").toString()); 110 | 111 | tx.success(); 112 | } 113 | 114 | return nodeRank; 115 | } 116 | 117 | private void letCrawlerDoItsJob() { 118 | LOG.info("Applying random graph walker module to the graph"); 119 | 120 | TimingStrategy timingStrategy = FixedDelayTimingStrategy.getInstance() 121 | .withInitialDelay(50) 122 | .withDelay(2); 123 | 124 | GraphAwareRuntime runtime = GraphAwareRuntimeFactory.createRuntime(getDatabase(), 125 | FluentRuntimeConfiguration 126 | .defaultConfiguration(getDatabase()) 127 | .withTimingStrategy(timingStrategy)); 128 | runtime.registerModule(nodeRankModule); 129 | runtime.start(); 130 | 131 | LOG.info("Waiting for module walker to do its work"); 132 | 133 | try { 134 | TimeUnit.SECONDS.sleep(30); 135 | } catch (InterruptedException e) { 136 | throw new RuntimeException(e); 137 | } 138 | } 139 | 140 | /** 141 | * Analyses and compares 142 | * the results of PageRank and NeoRank 143 | *

144 | * The input lists have to be 145 | * in descending order and have the same length 146 | */ 147 | private void analyseResults(List pageRankPairs, List nodeRankPairs) { 148 | LOG.info("Analysing results:"); 149 | 150 | List pageRank = RankNodePair.convertToRankedNodeList(pageRankPairs); 151 | List nodeRank = RankNodePair.convertToRankedNodeList(nodeRankPairs); 152 | 153 | SimilarityComparison similarityComparison = new SimilarityComparison(); 154 | LOG.info("Similarity of all entries: " + similarityComparison.getHammingDistanceMeasure(pageRank, nodeRank)); 155 | 156 | List pageRank20 = pageRank.subList(0, (int) (pageRank.size() * .2)); 157 | List nodeRank20 = nodeRank.subList(0, (int) (nodeRank.size() * .2)); 158 | LOG.info("Similarity of top 20% entries: " + similarityComparison.getHammingDistanceMeasure(pageRank20, nodeRank20)); 159 | 160 | List pageRank5 = pageRank.subList(0, 5); 161 | List nodeRank5 = nodeRank.subList(0, 5); 162 | LOG.info("Unordered similarity of the top 5 entries: " + 100 * similarityComparison.unorderedComparisonOfEqualLengthLists(pageRank5, nodeRank5) + "%"); 163 | 164 | 165 | /** 166 | * Measures the "Lehmer ratio" of the resulting list. The ratio is a percentage to which the new list is 167 | * completely permuted. The Lehmer code for nodeRank results, given pageRank results is calculated and 168 | * converted to decimal representation. This is the order-number of a permutation of nodeRank result, 169 | * given the pageRank result as a start. The Lehmer code is the normalised by maximum allowed LC (size!). 170 | * 171 | * 1.0 corresponds to a perfect match of the two algorithms. 172 | * 173 | * The log Lehmer ratio is a ratio of logarithms of the two numbers. 174 | */ 175 | Permutation pageRankToNodeRankPermutation = new Permutation<>(pageRankPairs, nodeRankPairs); 176 | LOG.info("The un-normed Lehmer distance of pageRank to nodeRank is: " + pageRankToNodeRankPermutation.getPermutationIndex().toString()); 177 | LOG.info("Lehmer distance ratio: %s ", pageRankToNodeRankPermutation.getNormedPermutationIndex()); 178 | LOG.info("Lehmer log-distance ratio: %s ", pageRankToNodeRankPermutation.getLogNormedPermutationIndex()); 179 | 180 | assertTrue(pageRankToNodeRankPermutation.getNormedPermutationIndex() * 100 > 90); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/TopRankedNodesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank; 18 | 19 | import com.graphaware.test.integration.DatabaseIntegrationTest; 20 | import com.graphaware.test.integration.EmbeddedDatabaseIntegrationTest; 21 | import org.junit.Test; 22 | import org.neo4j.graphdb.Node; 23 | import org.neo4j.graphdb.Transaction; 24 | 25 | import java.util.List; 26 | 27 | import static org.junit.Assert.*; 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.when; 30 | 31 | /** 32 | * Unit test for {@link NodeRankContext}. 33 | */ 34 | public class TopRankedNodesTest extends EmbeddedDatabaseIntegrationTest { 35 | 36 | @Test 37 | public void emptyTopNodesShouldProduceEmptyList() { 38 | TopRankedNodes topNodes = new TopRankedNodes(); 39 | topNodes.initializeIfNeeded(null, getDatabase(), NodeRankModuleConfiguration.defaultConfiguration().withMaxTopRankNodes(3)); 40 | assertTrue(topNodes.getTopNodes().isEmpty()); 41 | } 42 | 43 | @Test 44 | public void nodeRanksShouldBeCorrectlySorted() { 45 | Node node1 = mock(Node.class); 46 | Node node2 = mock(Node.class); 47 | Node node3 = mock(Node.class); 48 | Node node4 = mock(Node.class); 49 | Node node5 = mock(Node.class); 50 | when(node1.getId()).thenReturn(1L); 51 | when(node2.getId()).thenReturn(2L); 52 | when(node3.getId()).thenReturn(3L); 53 | when(node4.getId()).thenReturn(4L); 54 | when(node5.getId()).thenReturn(5L); 55 | 56 | TopRankedNodes topNodes = new TopRankedNodes(); 57 | topNodes.initializeIfNeeded(null, getDatabase(), NodeRankModuleConfiguration.defaultConfiguration().withMaxTopRankNodes(3)); 58 | 59 | topNodes.addNode(node1, 10); 60 | topNodes.addNode(node2, 1); 61 | topNodes.addNode(node3, 2); 62 | topNodes.addNode(node4, 4); 63 | 64 | List result = topNodes.getTopNodes(); 65 | assertEquals(3, result.size()); 66 | 67 | assertEquals(1L, result.get(0).getId()); 68 | assertEquals(4L, result.get(1).getId()); 69 | assertEquals(3L, result.get(2).getId()); 70 | assertArrayEquals(new Long[]{1L, 4L, 3L}, topNodes.getTopNodeIds()); 71 | 72 | topNodes.addNode(node5, 1); 73 | topNodes.addNode(node2, 3); 74 | topNodes.addNode(node3, 5); 75 | topNodes.addNode(node2, 6); 76 | topNodes.addNode(node2, 7); 77 | 78 | result = topNodes.getTopNodes(); 79 | assertEquals(3, result.size()); 80 | 81 | assertEquals(1L, result.get(0).getId()); 82 | assertEquals(2L, result.get(1).getId()); 83 | assertEquals(3L, result.get(2).getId()); 84 | assertArrayEquals(new Long[]{1L, 2L, 3L}, topNodes.getTopNodeIds()); 85 | } 86 | 87 | @Test 88 | public void nodeRanksShouldBeCorrectlyInitialized() { 89 | try (Transaction tx = getDatabase().beginTx()) { 90 | Node node1 = getDatabase().createNode(); 91 | node1.setProperty("nodeRank", 10); 92 | Node node2 = getDatabase().createNode(); 93 | node2.setProperty("nodeRank", 5); 94 | Node node3 = getDatabase().createNode(); 95 | node3.setProperty("nodeRank", 3); 96 | 97 | tx.success(); 98 | } 99 | 100 | TopRankedNodes topNodes = new TopRankedNodes(); 101 | try (Transaction tx = getDatabase().beginTx()) { 102 | //10L doesn't exist and should be ignored: 103 | topNodes.initializeIfNeeded(new NodeRankContext(0L, new Long[]{0L, 10L, 1L, 2L}), getDatabase(), NodeRankModuleConfiguration.defaultConfiguration().withMaxTopRankNodes(3)); 104 | tx.success(); 105 | } 106 | 107 | List result = topNodes.getTopNodes(); 108 | assertEquals(3, result.size()); 109 | 110 | try (Transaction tx = getDatabase().beginTx()) { 111 | assertEquals(0L, result.get(0).getId()); 112 | assertEquals(10, result.get(0).getProperty("nodeRank")); 113 | assertEquals(1L, result.get(1).getId()); 114 | assertEquals(5, result.get(1).getProperty("nodeRank")); 115 | assertEquals(2L, result.get(2).getId()); 116 | assertEquals(3, result.get(2).getProperty("nodeRank")); 117 | assertArrayEquals(new Long[]{0L, 1L, 2L}, topNodes.getTopNodeIds()); 118 | tx.success(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/NetworkMatrix.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | 20 | import org.la4j.matrix.Matrix; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * An object returned by {@link NetworkMatrixFactory} matrix methods. Contains list of the node IDs and an adjacency matrix. 26 | *

27 | * The list of nodes is an ordered array of node IDs with indices corresponding to rows/columns in the adjacency matrix (and derivates). 28 | */ 29 | public class NetworkMatrix { 30 | 31 | private final List nodeList; 32 | private final Matrix matrix; 33 | 34 | public NetworkMatrix(Matrix matrix, List nodeList) { 35 | this.matrix = matrix; 36 | this.nodeList = nodeList; 37 | } 38 | 39 | /** 40 | * Returns an ordered array of node IDs. Node indices correspond to rows/columns of the adjacency matrix. 41 | * 42 | * @return ordered list of node IDs. 43 | */ 44 | public List getNodeList() { 45 | return nodeList; 46 | } 47 | 48 | /** 49 | * Returns the stored matrix. 50 | * 51 | * @return matrix corresponding to the network. 52 | */ 53 | public Matrix getMatrix() { 54 | return matrix; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/NetworkMatrixFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import org.la4j.factory.CRSFactory; 20 | import org.la4j.factory.Factory; 21 | import org.la4j.matrix.Matrix; 22 | import org.la4j.matrix.sparse.CRSMatrix; 23 | import org.neo4j.graphdb.GraphDatabaseService; 24 | import org.neo4j.graphdb.Node; 25 | import org.neo4j.graphdb.Relationship; 26 | 27 | import java.util.LinkedHashMap; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | import static com.graphaware.common.util.IterableUtils.count; 33 | 34 | /** 35 | * Exports the entire graph as adjacency matrix. 36 | *

37 | * WARNING: This is for testing purposes only. 38 | */ 39 | public class NetworkMatrixFactory { 40 | 41 | private final GraphDatabaseService database; 42 | 43 | public NetworkMatrixFactory(GraphDatabaseService database) { 44 | this.database = database; 45 | } 46 | 47 | /** 48 | * Returns an adjacency matrix in a raw List> form. 49 | * 50 | * @return la4j sparse matrix. 51 | */ 52 | public NetworkMatrix getAdjacencyMatrix() { 53 | return getMatrix(new MatrixPopulator() { 54 | @Override 55 | public void populate(CRSMatrix matrix, Map indices, Node origin, Node target) { 56 | matrix.set(indices.get(origin.getId()), indices.get(target.getId()), 1); 57 | matrix.set(indices.get(target.getId()), indices.get(origin.getId()), 1); 58 | } 59 | }); 60 | } 61 | 62 | /** 63 | * Returns a Markov transition matrix (all entries are weighted by their out degree). 64 | * The matrix is in format (i <- j), the sum of any column is 1. 65 | */ 66 | public NetworkMatrix getTransitionMatrix() { 67 | return getMatrix(new MatrixPopulator() { 68 | @Override 69 | public void populate(CRSMatrix matrix, Map indices, Node origin, Node target) { 70 | matrix.set(indices.get(origin.getId()), indices.get(target.getId()), 1.0 / ((float) target.getDegree())); 71 | matrix.set(indices.get(target.getId()), indices.get(origin.getId()), 1.0 / ((float) origin.getDegree())); 72 | } 73 | }); 74 | } 75 | 76 | /** 77 | * Produce matrix by looking at all relationships. 78 | * 79 | * @param populator that populates the matrix for each relationship. 80 | * @return matrix. 81 | */ 82 | private NetworkMatrix getMatrix(MatrixPopulator populator) { 83 | int length = countNodes(); 84 | CRSMatrix adjacency = new CRSMatrix(length, length); 85 | 86 | Map indices = matrixNodeIndices(database); 87 | 88 | for (Relationship r : database.getAllRelationships()) { 89 | populator.populate(adjacency, indices, r.getStartNode(), r.getEndNode()); 90 | } 91 | 92 | return new NetworkMatrix(adjacency, new LinkedList<>(indices.keySet())); 93 | } 94 | 95 | /** 96 | * Get a mapping of node IDs to indices in the matrix. 97 | * 98 | * @param database in which to find mappings. 99 | * @return mapping. 100 | */ 101 | private Map matrixNodeIndices(GraphDatabaseService database) { 102 | int count = 0; 103 | Map result = new LinkedHashMap<>(); 104 | for (Node node : database.getAllNodes()) { 105 | result.put(node.getId(), count++); 106 | } 107 | return result; 108 | } 109 | 110 | interface MatrixPopulator { 111 | void populate(CRSMatrix matrix, Map indices, Node origin, Node target); 112 | } 113 | 114 | /** 115 | * Returns a google matrix given the specified damping constant. 116 | * The Google matrix is an iterative mtx for the pageRank algorithm. 117 | *

118 | * See.: The Anatomy of a Large-Scale Hypertextual Web Search Engine by Brin & Page 119 | * 120 | * @return Google matrix of the database, given the damping 121 | */ 122 | public NetworkMatrix getGoogleMatrix(double damping) { 123 | Factory matrixFactory = new CRSFactory(); 124 | 125 | NetworkMatrix transitionMatrixData = getTransitionMatrix(); 126 | List nodeList = transitionMatrixData.getNodeList(); 127 | Matrix transitionMatrix = getTransitionMatrix().getMatrix(); 128 | 129 | int size = transitionMatrix.rows(); 130 | Matrix identityMatrix = matrixFactory.createIdentityMatrix(size); 131 | Matrix googleMatrix = identityMatrix.multiply((1 - damping) / ((float) size)).add(transitionMatrix.multiply(damping)); 132 | 133 | return new NetworkMatrix(googleMatrix, nodeList); 134 | 135 | } 136 | 137 | private int countNodes() { 138 | long length = count(database.getAllNodes()); 139 | 140 | if (length > Integer.MAX_VALUE) { 141 | throw new IllegalStateException("Too many nodes in the database"); 142 | } 143 | 144 | return Long.valueOf(length).intValue(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/NetworkMatrixFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import com.graphaware.test.integration.DatabaseIntegrationTest; 20 | import com.graphaware.test.integration.EmbeddedDatabaseIntegrationTest; 21 | import org.junit.Test; 22 | import org.neo4j.graphdb.GraphDatabaseService; 23 | import org.neo4j.graphdb.Transaction; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | 27 | 28 | public class NetworkMatrixFactoryTest extends EmbeddedDatabaseIntegrationTest { 29 | 30 | @Override 31 | public void populateDatabase(GraphDatabaseService database) { 32 | database.execute( "CREATE " + 33 | " (m:Person {name:'Michal'})-[:FRIEND_OF]->(d:Person {name:'Daniela'}),"+ 34 | " (m)-[:FRIEND_OF]->(v:Person {name:'Vojta'}),"+ 35 | " (m)-[:FRIEND_OF]->(a:Person {name:'Adam'}),"+ 36 | " (m)-[:FRIEND_OF]->(vi:Person {name:'Vince'}),"+ 37 | " (m)-[:FRIEND_OF]->(:Person {name:'Luanne'}),"+ 38 | " (vi)-[:FRIEND_OF]->(a),"+ 39 | " (d)-[:FRIEND_OF]->(a),"+ 40 | " (d)-[:FRIEND_OF]->(vi),"+ 41 | " (v)-[:FRIEND_OF]->(a)"); 42 | } 43 | 44 | @Test 45 | public void shouldCalculateCorrectPageRank() { 46 | try (Transaction tx = getDatabase().beginTx()) { 47 | NetworkMatrixFactory networkMatrixFactory = new NetworkMatrixFactory(getDatabase()); 48 | PageRank pageRank = new PageRank(); 49 | 50 | NetworkMatrix adjacencyMatrix = networkMatrixFactory.getAdjacencyMatrix(); 51 | NetworkMatrix transitionMatrix = networkMatrixFactory.getTransitionMatrix(); 52 | 53 | System.out.println(adjacencyMatrix.getMatrix().toString()); 54 | System.out.println(transitionMatrix.getMatrix().toString()); 55 | 56 | System.out.println(pageRank.getPageRankVector(transitionMatrix, 0.85)); 57 | 58 | Object name = getDatabase().getNodeById(pageRank.getPageRank(transitionMatrix, 0.85).get(0)).getProperty("name"); 59 | System.out.println("The highest PageRank in the network is: " + name); 60 | 61 | assertEquals("Michal", name); 62 | 63 | tx.success(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/PageRank.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import org.la4j.LinearAlgebra; 20 | import org.la4j.factory.Basic1DFactory; 21 | import org.la4j.factory.CRSFactory; 22 | import org.la4j.factory.Factory; 23 | import org.la4j.matrix.Matrix; 24 | import org.la4j.vector.Vector; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | import static java.util.Collections.sort; 30 | 31 | /** 32 | * Testing implementation of PageRank. As PR is a global operation which requires a full adjacency matrix, this class 33 | * is meant to be used for TESTING PURPOSES only and mainly as a benchmark against the WEAKER NeoRank. 34 | */ 35 | public class PageRank { 36 | 37 | /** 38 | * Returns PageRank of the nodes from the network. 39 | *

40 | * WARNING: The stored graph must be single-component (the 41 | * matrix must be irreducible for the algorithm to 42 | * succeed) 43 | * 44 | * @param transitionMatrix transition mtx of the system 45 | * @return pageRank vector 46 | */ 47 | public Vector getPageRankVector(NetworkMatrix transitionMatrix, double damping) { 48 | validateArguments(transitionMatrix, damping); 49 | 50 | Factory vectorFactory = new Basic1DFactory(); 51 | Factory matrixFactory = new CRSFactory(); 52 | 53 | int size; 54 | 55 | // Calculates the pageRank. The convergence to PageRank is guaranteed 56 | // by picking the vector which converges to Perron Vector by 57 | // Perron-Frobenius theorem 58 | 59 | size = transitionMatrix.getMatrix().rows(); 60 | 61 | Matrix identityMatrix = matrixFactory.createIdentityMatrix(size); 62 | Vector testVector = vectorFactory.createConstantVector(size, 1); 63 | Matrix inverse = identityMatrix.add(transitionMatrix.getMatrix().multiply(-damping)).withInverter(LinearAlgebra.InverterFactory.SMART).inverse(); 64 | Matrix pageRankOperator = identityMatrix.multiply(1 - damping).multiply(inverse); 65 | Vector pageRank = pageRankOperator.multiply(testVector); 66 | 67 | return pageRank; 68 | } 69 | 70 | /** 71 | * Returns a pageRanked array list of nodes contained in the network. 72 | * 73 | * @return returns a list of nodes 74 | */ 75 | public List getPageRank(NetworkMatrix transitionMatrix, double damping) { 76 | return RankNodePair.convertToRankedNodeList(getPageRankPairs(transitionMatrix, damping)); 77 | } 78 | 79 | /** 80 | * Returns (rank, node) pairs sorted in descending order by pageRank. 81 | * 82 | * @param transitionMatrix tr. matrix 83 | * @param damping damping factor 84 | * @return rankNodePair list 85 | */ 86 | public List getPageRankPairs(NetworkMatrix transitionMatrix, double damping) { 87 | Vector pageRankVector = getPageRankVector(transitionMatrix, damping); 88 | List nodeList = transitionMatrix.getNodeList(); 89 | List rankNodePairs = new ArrayList<>(pageRankVector.length()); 90 | 91 | for (int i = 0; i < pageRankVector.length(); ++i) { 92 | rankNodePairs.add(new RankNodePair(pageRankVector.get(i), nodeList.get(i))); 93 | } 94 | 95 | sort(rankNodePairs); 96 | 97 | return rankNodePairs; 98 | } 99 | 100 | /** 101 | * Throws an exception if the argument set is invalid. 102 | * 103 | * @param transitionMatrix tr. matrix 104 | * @param damping damping factor 105 | */ 106 | private void validateArguments(NetworkMatrix transitionMatrix, double damping) { 107 | if (damping > 1.0 || damping < 0 || transitionMatrix == null) { 108 | throw new IllegalArgumentException("Wrong arguments passed on input"); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/Permutation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import com.google.common.math.BigIntegerMath; 20 | 21 | import java.math.BigDecimal; 22 | import java.math.BigInteger; 23 | import java.math.RoundingMode; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | /** 27 | * Generator of permutations in lexicographic ordering 28 | */ 29 | public class Permutation> { 30 | private final List from; 31 | private final List to; 32 | 33 | public Permutation(List init) { 34 | this.to = init; 35 | this.from = init; 36 | } 37 | 38 | /** 39 | * Returns a new permutation from -> to 40 | * For example [1,2,3,4] -> [1,2,4,3] 41 | * is a permutation with Lehmer distance 1 42 | * 43 | * @param from initial (ordered) item list 44 | * @param to final (permuted) item list 45 | */ 46 | public Permutation(List from, List to) { 47 | this.to = to; 48 | this.from = from; 49 | 50 | for (T first : from) 51 | if (!to.contains(first)) 52 | throw new RuntimeException("Invalid arguments passed to permutation"); 53 | } 54 | 55 | /** 56 | * Returns next permutation of the nodes 57 | * 58 | * @return nextPermutation in the sequence 59 | */ 60 | public Permutation nextPermutation() { 61 | List shallowCopy = new ArrayList<>(to); 62 | 63 | if (isReverseOrdered(shallowCopy)) 64 | return new Permutation<>(from, shallowCopy); 65 | 66 | int i, j; 67 | for (i = shallowCopy.size() - 1; i > 1; --i) 68 | if (shallowCopy.get(i).compareTo(shallowCopy.get(i - 1)) == 1) // stop at ordered seq. i-1 < i 69 | break; 70 | 71 | 72 | // i index of the largest descending pair is found 73 | for (j = shallowCopy.size() - 1; j > i; --j) { 74 | if (shallowCopy.get(j).compareTo(shallowCopy.get(i - 1)) == 1) // i-1 < j 75 | break; 76 | } 77 | 78 | // swap index is found 79 | T temp = shallowCopy.get(j); 80 | shallowCopy.set(j, shallowCopy.get(i - 1)); 81 | shallowCopy.set(i - 1, temp); 82 | 83 | reverseSublist(shallowCopy, i, shallowCopy.size() - 1); 84 | 85 | return new Permutation<>(from, shallowCopy); 86 | } 87 | 88 | /** 89 | * Returns a Lehmer Code of the permutation 90 | * 91 | * @return integer lehmer code 92 | */ 93 | public List getLehmerCode() { 94 | List shallowFrom = new ArrayList<>(from); 95 | List shallowTo = new ArrayList<>(to); 96 | List lehmerCode = new ArrayList<>(); 97 | 98 | for (T decElem : shallowTo) { 99 | int factoradicDigit = shallowFrom.indexOf(decElem); 100 | shallowFrom.remove(factoradicDigit); 101 | lehmerCode.add(factoradicDigit); 102 | } 103 | return lehmerCode; 104 | } 105 | 106 | /** 107 | * Returns a natural log of a number 108 | * This particularly elegant solution is by leonbloy @ StackExchange 109 | * @param bigInteger bigInteger to be logarithmed 110 | * @return logarithm of the bigInteger 111 | */ 112 | private double getBigIntegerLog(BigInteger bigInteger) { 113 | BigInteger val = bigInteger; 114 | int blex = val.bitLength() - 1022; // any value in 60..1023 works 115 | if (blex > 0) 116 | val = val.shiftRight(blex); 117 | double res = Math.log(val.doubleValue()); 118 | return blex > 0 ? res + blex * Math.log(2.0) : res; 119 | } 120 | 121 | /** 122 | * Returns a lexicographic index corresponding to the permutation 123 | * 124 | * @return permutation index 125 | */ 126 | public BigInteger getPermutationIndex() { 127 | BigInteger index = BigInteger.valueOf(0); 128 | List lehmerCode = getLehmerCode(); 129 | int size = lehmerCode.size(); 130 | 131 | for (int j = size - 1; j >= 0; j--) { 132 | index = index.add(BigInteger.valueOf(lehmerCode.get(j)).multiply(BigIntegerMath.factorial(size - j - 1))); 133 | } 134 | 135 | return index; 136 | } 137 | 138 | 139 | /** 140 | * Returns a percentage to which the permutation is reversely permuted 141 | * - i.e. how much is the ordering of the permutation distant from the 142 | * initial state. 143 | * 144 | * @return double in interal [0.0, 1.0], 1.0 corresponds to unpermuted list 145 | */ 146 | public double getNormedPermutationIndex() { 147 | BigDecimal factorial = new BigDecimal(com.google.common.math.BigIntegerMath.factorial(size()).add(BigInteger.valueOf(-1))); 148 | BigDecimal numerator = new BigDecimal(getPermutationIndex()); 149 | 150 | return 1.0 - numerator.divide(factorial, 8, RoundingMode.FLOOR).doubleValue(); 151 | } 152 | 153 | /** 154 | * Returns a percentage from the correct solution in logarithmic distance 155 | * @return double in interal [0.0, 1.0], 1.0 corresponds to unpermuted list 156 | */ 157 | public double getLogNormedPermutationIndex() { 158 | double factorial = getBigIntegerLog(com.google.common.math.BigIntegerMath.factorial(size()).add(BigInteger.ONE)); 159 | double numerator = getBigIntegerLog(getPermutationIndex().add(BigInteger.ONE)); 160 | 161 | return 1.0 - numerator/factorial; 162 | } 163 | 164 | /** 165 | * Reverses a sublist of a list 166 | * 167 | * @param list to reverse 168 | * @param from index 169 | * @param to index 170 | */ 171 | public void reverseSublist(List list, int from, int to) { 172 | 173 | // reverse suffix starting at i ? 174 | int length = to - from; 175 | for (int k = 0; k <= length / 2; ++k) { 176 | T left = list.get(from + k); 177 | T right = list.get(to - k); 178 | 179 | list.set(from + k, right); 180 | list.set(to - k, left); 181 | } 182 | } 183 | 184 | /** 185 | * Checks if the list is reverse ordered. 186 | * 187 | * @param list list to check 188 | * @return true if reverse ordered 189 | */ 190 | public boolean isReverseOrdered(List list) { 191 | for (int i = 0; i < list.size() - 1; ++i) 192 | if ((list.get(i).compareTo(list.get(i + 1))) == -1) 193 | return false; 194 | 195 | 196 | return true; 197 | } 198 | 199 | 200 | /** 201 | * Checks if the list is ordered 202 | * 203 | * @param list list to check 204 | * @return true if ordered 205 | */ 206 | public boolean isOrdered(List list) { 207 | for (int i = 0; i < list.size() - 1; ++i) 208 | if (list.get(i).compareTo(list.get(i + 1)) == 1) 209 | return false; 210 | 211 | 212 | return true; 213 | } 214 | 215 | /** 216 | * Returns a string representation of the permutation 217 | * TODO: use disjoint set representation instead? 218 | */ 219 | @Override 220 | public String toString() { 221 | String strFrom = from.toString(); 222 | String strTo = to.toString(); 223 | 224 | return strFrom + " -> " + strTo; 225 | } 226 | 227 | /** 228 | * Returns size of the permutation 229 | * 230 | * @return size of the permutation 231 | */ 232 | public int size() { 233 | return to.size(); 234 | } 235 | 236 | /** 237 | * Tests if the two permutations are equal 238 | * They are equal if the have the same 239 | * Lehmer code 240 | * 241 | * @param other object to be equal to the permutation 242 | * @return true if the two elements are equal 243 | */ 244 | @Override 245 | public boolean equals(Object other) { 246 | if (other == null) return false; 247 | if (other == this) return true; 248 | if (!(other instanceof Permutation)) return false; 249 | Permutation otherPermutation = (Permutation) other; 250 | 251 | if (otherPermutation.size() != size()) 252 | return false; 253 | 254 | return otherPermutation.hashCode() == hashCode(); 255 | 256 | } 257 | 258 | /** 259 | * Returns a hashCode of the permutation 260 | * (Lehmer code based) 261 | * 262 | * @return lehmer code based hash code 263 | *

264 | * TODO: Is this a valid approach? 265 | */ 266 | @Override 267 | public int hashCode() { 268 | return getPermutationIndex().hashCode(); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/PermutationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import org.junit.Test; 20 | 21 | import java.util.Arrays; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertNotEquals; 25 | 26 | public class PermutationTest { 27 | 28 | @Test 29 | public void testPermutationGeneration() { 30 | /** 31 | * Construct init object, log the permutation into the console 32 | */ 33 | Permutation permutation = new Permutation<>(Arrays.asList(1, 2, 3, 4)); 34 | 35 | /** 36 | * Check that the permutation index mapping is correct: 37 | */ 38 | for (int i = 0; i < 24; ++i) { 39 | assertEquals(permutation.getPermutationIndex().intValue(), i); 40 | permutation = permutation.nextPermutation(); 41 | } 42 | 43 | /** 44 | * Check equality between unequal-type permutations corresponding to the same permutation 45 | * mathematically. 46 | */ 47 | Permutation permutationS = new Permutation<>(Arrays.asList('A', 'B', 'C', 'D')); 48 | permutationS = permutationS.nextPermutation(); 49 | Permutation permutationT = new Permutation<>(Arrays.asList(1, 2, 3, 4), Arrays.asList(1, 2, 4, 3)); 50 | assertEquals(permutationT, permutationS); 51 | 52 | /** 53 | * Check that different-sizes permutations are interpreted as unequal, although they have 54 | * the same permutation index. 55 | */ 56 | Permutation permutationTN = new Permutation<>(Arrays.asList(1, 2, 3, 4, 5), Arrays.asList(1, 2, 3, 5, 4)); 57 | assertNotEquals(permutationT, permutationTN); 58 | 59 | 60 | /** 61 | * Check that the range of normed permutation index is covered 62 | */ 63 | Permutation completePermutation = new Permutation<>(Arrays.asList(1,2,3,4), Arrays.asList(4,3,2,1)); 64 | System.out.format("Permutation index for complete permutation: %f\n", completePermutation.getNormedPermutationIndex()); 65 | assertEquals(completePermutation.getNormedPermutationIndex(), 0, 10e-7); 66 | 67 | Permutation zeroPermutation = new Permutation<>(Arrays.asList('a','b','c'), Arrays.asList('a','b', 'c')); 68 | System.out.format("Permutation index for zero permutation: %f\n", zeroPermutation.getLogNormedPermutationIndex()); 69 | assertEquals(zeroPermutation.getNormedPermutationIndex(), 1.0, 10e-7); 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/RankNodePair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import com.google.common.base.Objects; 20 | import com.graphaware.common.util.Pair; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * IndexNode pair, used for sorting the results of Page Rank algorithm 27 | *

28 | * The two RNP are equal if their names are equal. The comparable is 29 | * implemented with respect to the rank algorithm result value however! 30 | */ 31 | public class RankNodePair extends Pair implements Comparable { 32 | 33 | /** 34 | * Construct a new pair. 35 | * 36 | * @param rank rank of the node 37 | * @param node node data 38 | */ 39 | public RankNodePair(double rank, Long node) { 40 | super(rank, node); 41 | } 42 | 43 | /** 44 | * Return rank stored in the INP 45 | * 46 | * @return returns rank of the node 47 | */ 48 | public double rank() { 49 | return first(); 50 | } 51 | 52 | /** 53 | * Returns node stored in the INP 54 | */ 55 | public Long node() { 56 | return second(); 57 | } 58 | 59 | /** 60 | * Converts RankNodePairs to ArrayList of nodes 61 | * 62 | * @param rankNodePairs list 63 | * @return converted list of nodes 64 | */ 65 | public static List convertToRankedNodeList(List rankNodePairs) { 66 | List toReturn = new ArrayList<>(); 67 | 68 | // I am sure there is a plenty of room for improvement here ;) 69 | for (RankNodePair indexNodePair : rankNodePairs) { 70 | toReturn.add(indexNodePair.node()); 71 | } 72 | 73 | return toReturn; 74 | } 75 | 76 | /** 77 | * Compares two ranks in descending order 78 | * 79 | * @param o RankNodePair to be compared to 80 | * @return -1 if this > o.rank(), 1 if < and 0 if = 81 | */ 82 | @Override 83 | public int compareTo(RankNodePair o) { 84 | if (rank() > o.rank()) { 85 | return -1; 86 | } 87 | if (rank() < o.rank()) { 88 | return 1; 89 | } 90 | return 0; 91 | } 92 | 93 | /** 94 | * Two rank node pairs are equal iff their name is equal. 95 | */ 96 | @Override 97 | public boolean equals(Object other) { 98 | if (other == null) return false; 99 | if (other == this) return true; 100 | if (!(other instanceof RankNodePair)) return false; 101 | RankNodePair otherRNP = (RankNodePair) other; 102 | return otherRNP.second().equals(second()); 103 | } 104 | 105 | 106 | @Override 107 | public int hashCode() { 108 | return Objects.hashCode(first(), second()); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/com/graphaware/module/noderank/utils/SimilarityComparison.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 GraphAware 3 | * 4 | * This file is part of the GraphAware Framework. 5 | * 6 | * GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of 7 | * the GNU General Public License as published by the Free Software Foundation, either 8 | * version 3 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 11 | * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | * See the GNU General Public License for more details. You should have received a copy of 13 | * the GNU General Public License along with this program. If not, see 14 | * . 15 | */ 16 | 17 | package com.graphaware.module.noderank.utils; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * Compares the result lists of two objects 23 | * 24 | */ 25 | public class SimilarityComparison { 26 | 27 | /** 28 | * Returns the most basic similarity 29 | * measure of the two lists of THE SAME LENGTH! 30 | * 31 | * This is just a normalized Hamming distance of 32 | * the two strings. 33 | * 34 | * @param a first list 35 | * @param b second lisr 36 | * @return normalized hamming distance 37 | */ 38 | public double getHammingDistanceMeasure(List a, List b) { 39 | if(a.size() != b.size()) 40 | throw new RuntimeException("Two lists of unequal length were tested for similarity!"); 41 | 42 | int length = a.size(); 43 | int numerator = 0; 44 | 45 | for(int i = 0; i < length; ++i) { 46 | if(a.get(i).equals(b.get(i))) 47 | numerator ++; 48 | } 49 | 50 | return (double) numerator / (double) length; 51 | } 52 | 53 | /** 54 | * Returns an unordered similarity of 55 | * the two lists of THE SAME LENGTH! 56 | * 57 | * @param a first list 58 | * @param b second list 59 | * @return normalized number of equal elements 60 | */ 61 | public double unorderedComparisonOfEqualLengthLists(List a, List b) { 62 | if(a.size() != b.size()) 63 | throw new RuntimeException("Two lists of unequal length were tested for similarity!"); 64 | 65 | int length = a.size(); 66 | int numerator = 0; 67 | 68 | for(int i = 0; i < length; ++i) { 69 | if(a.contains(b.get(i))) 70 | numerator ++; 71 | } 72 | 73 | return (double) numerator / (double) length; 74 | } 75 | 76 | /** 77 | * TODO: weight success by lexicographic permutation distance? 78 | * 79 | * Weight results by their lexicographic distance 80 | */ 81 | } 82 | -------------------------------------------------------------------------------- /src/test/python/README.md: -------------------------------------------------------------------------------- 1 | Neorank test in Python 2 | ====================== 3 | 4 | This is the most basic test comparing the classic 5 | pagerank with its variant using a discrete random 6 | walker on the network. 7 | 8 | Please make sure networkx and pylab are installed 9 | on your system to play around with the parameter 10 | set. 11 | 12 | To install, please use PIP (can be installed using 13 | homebrew on Mac or apt-get (Ubuntu) /yum (Fedora) 14 | or similar package manager on Linux) 15 | 16 | 17 | ## Notes on Running the Script 18 | 19 | The script is written for **Python 2** and is totally incompatible with Python 3. As stated above, you need to have networkx and pylab installed. If pip does the business for you then that's great, but, if not, then the following might be of use. 20 | 21 | ``` 22 | sudo zypper install python-numpy 23 | sudo zypper install python-scipy python-matplotlib 24 | sudo zypper install python-matplotlib-tk 25 | ``` 26 | 27 | Of course, `zypper` is for OpenSuSE so choose the package manager that is appropriate for your distribution. -------------------------------------------------------------------------------- /src/test/python/neorank.py: -------------------------------------------------------------------------------- 1 | # Simple test of NeoRank Algorithm implemented in 2 | # Neo4j graph database 3 | import operator 4 | import networkx as nx 5 | import random as rnd 6 | import pylab 7 | 8 | # Allow for adaptive damping? 9 | # TODO: prepare a rigorous test of the algorithm 10 | 11 | # Picks an object from list at random 12 | # non-universal damping in pagerank? 13 | def pickRandom(lst): 14 | return rnd.choice(lst); 15 | 16 | N = 100 17 | hyperjump = 0.85 18 | ba = nx.barabasi_albert_graph(N, 2) # nx.erdos_renyi_graph(N, 0.8)# nx.fast_gnp_random_graph(N, p) 19 | 20 | steps = 1000 21 | nodes = ba.nodes() # List of nodes in the graph 22 | 23 | # Init neoranks for testing purposes 24 | for index in ba.nodes_iter(): 25 | node = ba.node[index] 26 | node['neorank'] = 0 27 | 28 | current = pickRandom(nodes); 29 | normalization = 0; 30 | 31 | 32 | # Performs a step of a random walker on the graph 33 | def step(): 34 | global current, normalization 35 | 36 | if rnd.random() < hyperjump: 37 | current = pickRandom(ba.neighbors(current)) 38 | else: 39 | current = pickRandom(ba.nodes()) 40 | 41 | # Increment the rank of the visited vertex 42 | node = ba.node[current] 43 | if 'neorank' in node: 44 | node['neorank'] += 1 45 | else : 46 | node['neorank'] = 0; 47 | 48 | # Increment normalization factor to present 49 | # the data consistently 50 | normalization += 1; 51 | 52 | for _ in range(steps): 53 | step() 54 | 55 | 56 | pagerank = list(reversed(sorted(nx.pagerank(ba, alpha = 1.0).iteritems(), key = operator.itemgetter(1)))) 57 | 58 | print "highest pagerank: " + str( pagerank[0:3]) 59 | # Perform analysis of NeoRank distribution 60 | neorank = list(reversed(sorted(ba.nodes(data = True), key = lambda (a, dct): dct['neorank']))) 61 | 62 | print "highest neoranks: " + str( neorank[0:3]) 63 | 64 | labels = dict((n,str(n) + ", " + str(d['neorank'])) for n,d in ba.nodes(data = True)) 65 | layout = nx.spring_layout(ba) 66 | 67 | # TODO: change node size according to its neoRank 68 | nx.draw(ba, layout, labels = labels, node_size = 1000) 69 | 70 | pylab.draw() 71 | pylab.show() 72 | 73 | -------------------------------------------------------------------------------- /src/test/resources/int-test-neo4j.conf: -------------------------------------------------------------------------------- 1 | ################################################################ 2 | # GraphAware configuration 3 | ################################################################ 4 | 5 | dbms.unmanaged_extension_classes=com.graphaware.server=/graphaware 6 | 7 | com.graphaware.runtime.enabled=true 8 | com.graphaware.module.noderank.1=com.graphaware.module.noderank.NodeRankModuleBootstrapper 9 | com.graphaware.module.noderank.maxTopRankNodes=3 10 | 11 | #***************************************************************** 12 | # Neo4j configuration 13 | #***************************************************************** 14 | 15 | # The name of the database to mount 16 | #dbms.active_database=graph.db 17 | 18 | # Paths of directories in the installation. 19 | #dbms.directories.data=data 20 | #dbms.directories.plugins=plugins 21 | #dbms.directories.certificates=certificates 22 | 23 | # This setting constrains all `LOAD CSV` import files to be under the `import` directory. Remove or uncomment it to 24 | # allow files to be loaded from anywhere in filesystem; this introduces possible security problems. See the `LOAD CSV` 25 | # section of the manual for details. 26 | dbms.directories.import=import 27 | 28 | # Whether requests to Neo4j are authenticated. 29 | # To disable authentication, uncomment this line 30 | dbms.security.auth_enabled=false 31 | 32 | # Enable this to be able to upgrade a store from an older version. 33 | #dbms.allow_format_migration=true 34 | 35 | # The amount of memory to use for mapping the store files, in bytes (or 36 | # kilobytes with the 'k' suffix, megabytes with 'm' and gigabytes with 'g'). 37 | # If Neo4j is running on a dedicated server, then it is generally recommended 38 | # to leave about 2-4 gigabytes for the operating system, give the JVM enough 39 | # heap to hold all your transaction state and query context, and then leave the 40 | # rest for the page cache. 41 | # The default page cache memory assumes the machine is dedicated to running 42 | # Neo4j, and is heuristically set to 50% of RAM minus the max Java heap size. 43 | #dbms.memory.pagecache.size=10g 44 | 45 | # Enable online backups to be taken from this database. 46 | dbms.backup.enabled=false 47 | 48 | # To allow remote backups, uncomment this line: 49 | #dbms.backup.address=0.0.0.0:6362 50 | 51 | #***************************************************************** 52 | # Network connector configuration 53 | #***************************************************************** 54 | 55 | # Bolt connector 56 | dbms.connector.bolt.type=BOLT 57 | dbms.connector.bolt.enabled=true 58 | dbms.connector.bolt.tls_level=OPTIONAL 59 | # To have Bolt accept non-local connections, uncomment this line 60 | # dbms.connector.bolt.address=0.0.0.0:7687 61 | 62 | # HTTP Connector 63 | dbms.connector.http.type=HTTP 64 | dbms.connector.http.enabled=true 65 | #dbms.connector.http.encryption=NONE 66 | # To have HTTP accept non-local connections, uncomment this line 67 | #dbms.connector.http.address=0.0.0.0:7474 68 | 69 | # HTTPS Connector 70 | dbms.connector.https.type=HTTP 71 | dbms.connector.https.enabled=true 72 | dbms.connector.https.encryption=TLS 73 | dbms.connector.https.address=localhost:7573 74 | 75 | # Number of Neo4j worker threads. 76 | #dbms.threads.worker_count= 77 | 78 | #***************************************************************** 79 | # Logging configuration 80 | #***************************************************************** 81 | 82 | # To enable HTTP logging, uncomment this line 83 | #dbms.logs.http.enabled=true 84 | 85 | # To enable GC Logging, uncomment this line 86 | #dbms.logs.gc.enabled=true 87 | 88 | # GC Logging Options 89 | # see http://docs.oracle.com/cd/E19957-01/819-0084-10/pt_tuningjava.html#wp57013 for more information. 90 | #dbms.logs.gc.options=-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintPromotionFailure -XX:+PrintTenuringDistribution 91 | 92 | # Number of GC logs to keep. 93 | #dbms.logs.gc.rotation.keep_number=5 94 | 95 | # Size of each GC log that is kept. 96 | #dbms.logs.gc.rotation.size=20m 97 | 98 | # Size threshold for rotation of the debug log. If set to zero then no rotation will occur. Accepts a binary suffix "k", 99 | # "m" or "g". 100 | #dbms.logs.debug.rotation.size=20m 101 | 102 | # Maximum number of history files for the internal log. 103 | #dbms.logs.debug.rotation.keep_number=7 104 | 105 | # Log executed queries that takes longer than the configured threshold. Enable by uncommenting this line. 106 | #dbms.logs.query.enabled=true 107 | 108 | # If the execution of query takes more time than this threshold, the query is logged. If set to zero then all queries 109 | # are logged. 110 | #dbms.logs.query.threshold=0 111 | 112 | # The file size in bytes at which the query log will auto-rotate. If set to zero then no rotation will occur. Accepts a 113 | # binary suffix "k", "m" or "g". 114 | #dbms.logs.query.rotation.size=20m 115 | 116 | # Maximum number of history files for the query log. 117 | #dbms.logs.query.rotation.keep_number=7 118 | 119 | #***************************************************************** 120 | # HA configuration 121 | #***************************************************************** 122 | 123 | # Uncomment and specify these lines for running Neo4j in High Availability mode. 124 | # See the High availability setup tutorial for more details on these settings 125 | # http://neo4j.com/docs/3.0.1/ha-setup-tutorial.html 126 | 127 | # Database mode 128 | # Allowed values: 129 | # HA - High Availability 130 | # SINGLE - Single mode, default. 131 | # To run in High Availability mode uncomment this line: 132 | #dbms.mode=HA 133 | 134 | # ha.server_id is the number of each instance in the HA cluster. It should be 135 | # an integer (e.g. 1), and should be unique for each cluster instance. 136 | #ha.server_id= 137 | 138 | # ha.initial_hosts is a comma-separated list (without spaces) of the host:port 139 | # where the ha.host.coordination of all instances will be listening. Typically 140 | # this will be the same for all cluster instances. 141 | #ha.initial_hosts=127.0.0.1:5001,127.0.0.1:5002,127.0.0.1:5003 142 | 143 | # IP and port for this instance to listen on, for communicating cluster status 144 | # information iwth other instances (also see ha.initial_hosts). The IP 145 | # must be the configured IP address for one of the local interfaces. 146 | #ha.host.coordination=127.0.0.1:5001 147 | 148 | # IP and port for this instance to listen on, for communicating transaction 149 | # data with other instances (also see ha.initial_hosts). The IP 150 | # must be the configured IP address for one of the local interfaces. 151 | #ha.host.data=127.0.0.1:6001 152 | 153 | # The interval at which slaves will pull updates from the master. Comment out 154 | # the option to disable periodic pulling of updates. Unit is seconds. 155 | ha.pull_interval=10 156 | 157 | # Amount of slaves the master will try to push a transaction to upon commit 158 | # (default is 1). The master will optimistically continue and not fail the 159 | # transaction even if it fails to reach the push factor. Setting this to 0 will 160 | # increase write performance when writing through master but could potentially 161 | # lead to branched data (or loss of transaction) if the master goes down. 162 | #ha.tx_push_factor=1 163 | 164 | # Strategy the master will use when pushing data to slaves (if the push factor 165 | # is greater than 0). There are three options available "fixed_ascending" (default), 166 | # "fixed_descending" or "round_robin". Fixed strategies will start by pushing to 167 | # slaves ordered by server id (accordingly with qualifier) and are useful when 168 | # planning for a stable fail-over based on ids. 169 | #ha.tx_push_strategy=fixed_ascending 170 | 171 | # Policy for how to handle branched data. 172 | #ha.branched_data_policy=keep_all 173 | 174 | # How often heartbeat messages should be sent. Defaults to ha.default_timeout. 175 | #ha.heartbeat_interval=5s 176 | 177 | # Timeout for heartbeats between cluster members. Should be at least twice that of ha.heartbeat_interval. 178 | #ha.heartbeat_timeout=11s 179 | 180 | # If you are using a load-balancer that doesn't support HTTP Auth, you may need to turn off authentication for the 181 | # HA HTTP status endpoint by uncommenting the following line. 182 | #dbms.security.ha_status_auth_enabled=false 183 | 184 | # Whether this instance should only participate as slave in cluster. If set to 185 | # true, it will never be elected as master. 186 | #ha.slave_only=false 187 | 188 | #***************************************************************** 189 | # Miscellaneous configuration 190 | #***************************************************************** 191 | 192 | # Enable this to specify a parser other than the default one. 193 | #cypher.default_language_version=3.0 194 | 195 | # Determines if Cypher will allow using file URLs when loading data using 196 | # `LOAD CSV`. Setting this value to `false` will cause Neo4j to fail `LOAD CSV` 197 | # clauses that load data from the file system. 198 | #dbms.security.allow_csv_import_from_file_urls=true 199 | 200 | # Retention policy for transaction logs needed to perform recovery and backups. 201 | #dbms.tx_log.rotation.retention_policy=7 days 202 | 203 | # Limit the number of IOs the background checkpoint process will consume per second. 204 | # This setting is advisory, is ignored in Neo4j Community Edition, and is followed to 205 | # best effort in Enterprise Edition. 206 | # An IO is in this case a 8 KiB (mostly sequential) write. Limiting the write IO in 207 | # this way will leave more bandwidth in the IO subsystem to service random-read IOs, 208 | # which is important for the response time of queries when the database cannot fit 209 | # entirely in memory. The only drawback of this setting is that longer checkpoint times 210 | # may lead to slightly longer recovery times in case of a database or system crash. 211 | # A lower number means lower IO pressure, and consequently longer checkpoint times. 212 | # The configuration can also be commented out to remove the limitation entirely, and 213 | # let the checkpointer flush data as fast as the hardware will go. 214 | # Set this to -1 to disable the IOPS limit. 215 | # dbms.checkpoint.iops.limit=1000 216 | 217 | # Enable a remote shell server which Neo4j Shell clients can log in to. 218 | #dbms.shell.enabled=true 219 | # The network interface IP the shell will listen on (use 0.0.0.0 for all interfaces). 220 | #dbms.shell.host=127.0.0.1 221 | # The port the shell will listen on, default is 1337. 222 | #dbms.shell.port=1337 223 | 224 | # Only allow read operations from this Neo4j instance. This mode still requires 225 | # write access to the directory for lock purposes. 226 | #dbms.read_only=false 227 | 228 | # Comma separated list of JAX-RS packages containing JAX-RS resources, one 229 | # package name for each mountpoint. The listed package names will be loaded 230 | # under the mountpoints specified. Uncomment this line to mount the 231 | # org.neo4j.examples.server.unmanaged.HelloWorldResource.java from 232 | # neo4j-server-examples under /examples/unmanaged, resulting in a final URL of 233 | # http://localhost:7474/examples/unmanaged/helloworld/{nodeId} 234 | #dbms.unmanaged_extension_classes=org.neo4j.examples.server.unmanaged=/examples/unmanaged 235 | -------------------------------------------------------------------------------- /src/test/resources/neo4j-2.conf: -------------------------------------------------------------------------------- 1 | #***************************************************************** 2 | # Neo4j configuration 3 | #***************************************************************** 4 | 5 | # The name of the database to mount 6 | #dbms.active_database=graph.db 7 | 8 | # Paths of directories in the installation. 9 | #dbms.directories.data=data 10 | #dbms.directories.plugins=plugins 11 | #dbms.directories.certificates=certificates 12 | 13 | # This setting constrains all `LOAD CSV` import files to be under the `import` directory. Remove or uncomment it to 14 | # allow files to be loaded from anywhere in filesystem; this introduces possible security problems. See the `LOAD CSV` 15 | # section of the manual for details. 16 | dbms.directories.import=import 17 | 18 | # Whether requests to Neo4j are authenticated. 19 | # To disable authentication, uncomment this line 20 | dbms.security.auth_enabled=false 21 | 22 | # Enable this to be able to upgrade a store from an older version. 23 | #dbms.allow_format_migration=true 24 | 25 | # The amount of memory to use for mapping the store files, in bytes (or 26 | # kilobytes with the 'k' suffix, megabytes with 'm' and gigabytes with 'g'). 27 | # If Neo4j is running on a dedicated server, then it is generally recommended 28 | # to leave about 2-4 gigabytes for the operating system, give the JVM enough 29 | # heap to hold all your transaction state and query context, and then leave the 30 | # rest for the page cache. 31 | # The default page cache memory assumes the machine is dedicated to running 32 | # Neo4j, and is heuristically set to 50% of RAM minus the max Java heap size. 33 | #dbms.memory.pagecache.size=10g 34 | 35 | # Enable online backups to be taken from this database. 36 | #dbms.backup.enabled=true 37 | 38 | # To allow remote backups, uncomment this line: 39 | #dbms.backup.address=0.0.0.0:6362 40 | 41 | #***************************************************************** 42 | # Network connector configuration 43 | #***************************************************************** 44 | 45 | # Bolt connector 46 | dbms.connector.bolt.type=BOLT 47 | dbms.connector.bolt.enabled=true 48 | dbms.connector.bolt.tls_level=OPTIONAL 49 | # To have Bolt accept non-local connections, uncomment this line 50 | # dbms.connector.bolt.address=0.0.0.0:7687 51 | 52 | # HTTP Connector 53 | dbms.connector.http.type=HTTP 54 | dbms.connector.http.enabled=true 55 | #dbms.connector.http.encryption=NONE 56 | # To have HTTP accept non-local connections, uncomment this line 57 | #dbms.connector.http.address=0.0.0.0:7474 58 | 59 | # HTTPS Connector 60 | dbms.connector.https.type=HTTP 61 | dbms.connector.https.enabled=true 62 | dbms.connector.https.encryption=TLS 63 | dbms.connector.https.address=localhost:7473 64 | 65 | # Number of Neo4j worker threads. 66 | #dbms.threads.worker_count= 67 | 68 | #***************************************************************** 69 | # Logging configuration 70 | #***************************************************************** 71 | 72 | # To enable HTTP logging, uncomment this line 73 | #dbms.logs.http.enabled=true 74 | 75 | # To enable GC Logging, uncomment this line 76 | #dbms.logs.gc.enabled=true 77 | 78 | # GC Logging Options 79 | # see http://docs.oracle.com/cd/E19957-01/819-0084-10/pt_tuningjava.html#wp57013 for more information. 80 | #dbms.logs.gc.options=-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintPromotionFailure -XX:+PrintTenuringDistribution 81 | 82 | # Number of GC logs to keep. 83 | #dbms.logs.gc.rotation.keep_number=5 84 | 85 | # Size of each GC log that is kept. 86 | #dbms.logs.gc.rotation.size=20m 87 | 88 | # Size threshold for rotation of the debug log. If set to zero then no rotation will occur. Accepts a binary suffix "k", 89 | # "m" or "g". 90 | #dbms.logs.debug.rotation.size=20m 91 | 92 | # Maximum number of history files for the internal log. 93 | #dbms.logs.debug.rotation.keep_number=7 94 | 95 | # Log executed queries that takes longer than the configured threshold. Enable by uncommenting this line. 96 | #dbms.logs.query.enabled=true 97 | 98 | # If the execution of query takes more time than this threshold, the query is logged. If set to zero then all queries 99 | # are logged. 100 | #dbms.logs.query.threshold=0 101 | 102 | # The file size in bytes at which the query log will auto-rotate. If set to zero then no rotation will occur. Accepts a 103 | # binary suffix "k", "m" or "g". 104 | #dbms.logs.query.rotation.size=20m 105 | 106 | # Maximum number of history files for the query log. 107 | #dbms.logs.query.rotation.keep_number=7 108 | 109 | #***************************************************************** 110 | # HA configuration 111 | #***************************************************************** 112 | 113 | # Uncomment and specify these lines for running Neo4j in High Availability mode. 114 | # See the High availability setup tutorial for more details on these settings 115 | # http://neo4j.com/docs/operations-manual/current/#tutorials 116 | 117 | # Database mode 118 | # Allowed values: 119 | # HA - High Availability 120 | # SINGLE - Single mode, default. 121 | # To run in High Availability mode uncomment this line: 122 | #dbms.mode=HA 123 | 124 | # ha.server_id is the number of each instance in the HA cluster. It should be 125 | # an integer (e.g. 1), and should be unique for each cluster instance. 126 | #ha.server_id= 127 | 128 | # ha.initial_hosts is a comma-separated list (without spaces) of the host:port 129 | # where the ha.host.coordination of all instances will be listening. Typically 130 | # this will be the same for all cluster instances. 131 | #ha.initial_hosts=127.0.0.1:5001,127.0.0.1:5002,127.0.0.1:5003 132 | 133 | # IP and port for this instance to listen on, for communicating cluster status 134 | # information iwth other instances (also see ha.initial_hosts). The IP 135 | # must be the configured IP address for one of the local interfaces. 136 | #ha.host.coordination=127.0.0.1:5001 137 | 138 | # IP and port for this instance to listen on, for communicating transaction 139 | # data with other instances (also see ha.initial_hosts). The IP 140 | # must be the configured IP address for one of the local interfaces. 141 | #ha.host.data=127.0.0.1:6001 142 | 143 | # The interval at which slaves will pull updates from the master. Comment out 144 | # the option to disable periodic pulling of updates. Unit is seconds. 145 | ha.pull_interval=10 146 | 147 | # Amount of slaves the master will try to push a transaction to upon commit 148 | # (default is 1). The master will optimistically continue and not fail the 149 | # transaction even if it fails to reach the push factor. Setting this to 0 will 150 | # increase write performance when writing through master but could potentially 151 | # lead to branched data (or loss of transaction) if the master goes down. 152 | #ha.tx_push_factor=1 153 | 154 | # Strategy the master will use when pushing data to slaves (if the push factor 155 | # is greater than 0). There are three options available "fixed_ascending" (default), 156 | # "fixed_descending" or "round_robin". Fixed strategies will start by pushing to 157 | # slaves ordered by server id (accordingly with qualifier) and are useful when 158 | # planning for a stable fail-over based on ids. 159 | #ha.tx_push_strategy=fixed_ascending 160 | 161 | # Policy for how to handle branched data. 162 | #ha.branched_data_policy=keep_all 163 | 164 | # How often heartbeat messages should be sent. Defaults to ha.default_timeout. 165 | #ha.heartbeat_interval=5s 166 | 167 | # Timeout for heartbeats between cluster members. Should be at least twice that of ha.heartbeat_interval. 168 | #ha.heartbeat_timeout=11s 169 | 170 | # If you are using a load-balancer that doesn't support HTTP Auth, you may need to turn off authentication for the 171 | # HA HTTP status endpoint by uncommenting the following line. 172 | #dbms.security.ha_status_auth_enabled=false 173 | 174 | # Whether this instance should only participate as slave in cluster. If set to 175 | # true, it will never be elected as master. 176 | #ha.slave_only=false 177 | 178 | #***************************************************************** 179 | # Miscellaneous configuration 180 | #***************************************************************** 181 | 182 | # Enable this to specify a parser other than the default one. 183 | #cypher.default_language_version=3.0 184 | 185 | # Determines if Cypher will allow using file URLs when loading data using 186 | # `LOAD CSV`. Setting this value to `false` will cause Neo4j to fail `LOAD CSV` 187 | # clauses that load data from the file system. 188 | #dbms.security.allow_csv_import_from_file_urls=true 189 | 190 | # Retention policy for transaction logs needed to perform recovery and backups. 191 | #dbms.tx_log.rotation.retention_policy=7 days 192 | 193 | # Limit the number of IOs the background checkpoint process will consume per second. 194 | # This setting is advisory, is ignored in Neo4j Community Edition, and is followed to 195 | # best effort in Enterprise Edition. 196 | # An IO is in this case a 8 KiB (mostly sequential) write. Limiting the write IO in 197 | # this way will leave more bandwidth in the IO subsystem to service random-read IOs, 198 | # which is important for the response time of queries when the database cannot fit 199 | # entirely in memory. The only drawback of this setting is that longer checkpoint times 200 | # may lead to slightly longer recovery times in case of a database or system crash. 201 | # A lower number means lower IO pressure, and consequently longer checkpoint times. 202 | # The configuration can also be commented out to remove the limitation entirely, and 203 | # let the checkpointer flush data as fast as the hardware will go. 204 | # Set this to -1 to disable the IOPS limit. 205 | # dbms.checkpoint.iops.limit=1000 206 | 207 | # Enable a remote shell server which Neo4j Shell clients can log in to. 208 | #dbms.shell.enabled=true 209 | # The network interface IP the shell will listen on (use 0.0.0.0 for all interfaces). 210 | #dbms.shell.host=127.0.0.1 211 | # The port the shell will listen on, default is 1337. 212 | #dbms.shell.port=1337 213 | 214 | # Only allow read operations from this Neo4j instance. This mode still requires 215 | # write access to the directory for lock purposes. 216 | #dbms.read_only=false 217 | 218 | # Comma separated list of JAX-RS packages containing JAX-RS resources, one 219 | # package name for each mountpoint. The listed package names will be loaded 220 | # under the mountpoints specified. Uncomment this line to mount the 221 | # org.neo4j.examples.server.unmanaged.HelloWorldResource.java from 222 | # neo4j-server-examples under /examples/unmanaged, resulting in a final URL of 223 | # http://localhost:7474/examples/unmanaged/helloworld/{nodeId} 224 | #dbms.unmanaged_extension_classes=org.neo4j.examples.server.unmanaged=/examples/unmanaged 225 | 226 | #For the framework to work at all, you need this 227 | dbms.unmanaged_extension_classes=com.graphaware.server=/graphaware 228 | 229 | # Runtime must be enabled like this 230 | com.graphaware.runtime.enabled=true 231 | 232 | #NR becomes the module ID: 233 | com.graphaware.module.NR.1=com.graphaware.module.noderank.NodeRankModuleBootstrapper 234 | 235 | #optional number of top ranked nodes to remember, the default is 10 236 | com.graphaware.module.NR.maxTopRankNodes=10 237 | 238 | #optional daming factor, which is a number p such that a random node will be selected at any step of the algorithm 239 | #with the probability 1-p (as opposed to following a random relationship). The default is 0.85 240 | com.graphaware.module.NR.dampingFactor=0.85 241 | 242 | #optional key of the property that gets written to the ranked nodes, default is "nodeRank" 243 | com.graphaware.module.NR.propertyKey=nodeRank 244 | 245 | #optionally specify nodes to rank using an expression-based node inclusion policy, default is all business (i.e. non-framework-internal) nodes 246 | com.graphaware.module.NR.node=hasLabel('Person') 247 | 248 | #optionally specify relationships to follow using an expression-based relationship inclusion policy, default is all business (i.e. non-framework-internal) relationships 249 | com.graphaware.module.NR.relationship=isType('FRIEND_OF') 250 | 251 | 252 | #NX becomes the module ID: 253 | com.graphaware.module.NX.2=com.graphaware.module.noderank.NodeRankModuleBootstrapper 254 | 255 | #optional number of top ranked nodes to remember, the default is 10 256 | com.graphaware.module.NX.maxTopRankNodes=10 257 | 258 | #optional daming factor, which is a number p such that a random node will be selected at any step of the algorithm 259 | #with the probability 1-p (as opposed to following a random relationship). The default is 0.85 260 | com.graphaware.module.NX.dampingFactor=0.85 261 | 262 | #optional key of the property that gets written to the ranked nodes, default is "nodeRank" 263 | com.graphaware.module.NX.propertyKey=nodeRank 264 | 265 | #optionally specify nodes to rank using an expression-based node inclusion policy, default is all business (i.e. non-framework-internal) nodes 266 | com.graphaware.module.NX.node=hasLabel('PersonX') 267 | 268 | #optionally specify relationships to follow using an expression-based relationship inclusion policy, default is all business (i.e. non-framework-internal) relationships 269 | com.graphaware.module.NX.relationship=isType('FRIEND_OF') 270 | -------------------------------------------------------------------------------- /src/test/resources/schema.cyp: -------------------------------------------------------------------------------- 1 | CREATE INDEX ON :`PhoneNumber`(`id`); 2 | CREATE INDEX ON :`Email`(`subject`); 3 | CREATE CONSTRAINT ON (node:`User`) ASSERT node.`id` IS UNIQUE; 4 | CREATE CONSTRAINT ON (node:`Repository`) ASSERT node.`id` IS UNIQUE; 5 | CREATE CONSTRAINT ON (node:`File`) ASSERT node.`path` IS UNIQUE; 6 | CREATE CONSTRAINT ON (node:`PullRequest`) ASSERT node.`id` IS UNIQUE; 7 | CREATE CONSTRAINT ON (node:`EmailAddress`) ASSERT node.`_id` IS UNIQUE; 8 | CREATE CONSTRAINT ON (node:`UNIQUE IMPORT LABEL`) ASSERT node.`UNIQUE IMPORT ID` IS UNIQUE; -------------------------------------------------------------------------------- /src/test/resources/test-neo4j.conf: -------------------------------------------------------------------------------- 1 | ################################################################ 2 | # GraphAware configuration 3 | ################################################################ 4 | 5 | dbms.unmanaged_extension_classes=com.graphaware.server=/graphaware 6 | 7 | com.graphaware.runtime.enabled=true 8 | com.graphaware.module.noderank.1=com.graphaware.module.noderank.NodeRankModuleBootstrapper 9 | 10 | #***************************************************************** 11 | # Neo4j configuration 12 | #***************************************************************** 13 | 14 | # The name of the database to mount 15 | #dbms.active_database=graph.db 16 | 17 | # Paths of directories in the installation. 18 | #dbms.directories.data=data 19 | #dbms.directories.plugins=plugins 20 | #dbms.directories.certificates=certificates 21 | 22 | # This setting constrains all `LOAD CSV` import files to be under the `import` directory. Remove or uncomment it to 23 | # allow files to be loaded from anywhere in filesystem; this introduces possible security problems. See the `LOAD CSV` 24 | # section of the manual for details. 25 | dbms.directories.import=import 26 | 27 | # Whether requests to Neo4j are authenticated. 28 | # To disable authentication, uncomment this line 29 | dbms.security.auth_enabled=false 30 | 31 | # Enable this to be able to upgrade a store from an older version. 32 | #dbms.allow_format_migration=true 33 | 34 | # The amount of memory to use for mapping the store files, in bytes (or 35 | # kilobytes with the 'k' suffix, megabytes with 'm' and gigabytes with 'g'). 36 | # If Neo4j is running on a dedicated server, then it is generally recommended 37 | # to leave about 2-4 gigabytes for the operating system, give the JVM enough 38 | # heap to hold all your transaction state and query context, and then leave the 39 | # rest for the page cache. 40 | # The default page cache memory assumes the machine is dedicated to running 41 | # Neo4j, and is heuristically set to 50% of RAM minus the max Java heap size. 42 | #dbms.memory.pagecache.size=10g 43 | 44 | # Enable online backups to be taken from this database. 45 | dbms.backup.enabled=false 46 | 47 | # To allow remote backups, uncomment this line: 48 | #dbms.backup.address=0.0.0.0:6362 49 | 50 | #***************************************************************** 51 | # Network connector configuration 52 | #***************************************************************** 53 | 54 | # Bolt connector 55 | dbms.connector.bolt.type=BOLT 56 | dbms.connector.bolt.enabled=true 57 | dbms.connector.bolt.tls_level=OPTIONAL 58 | # To have Bolt accept non-local connections, uncomment this line 59 | # dbms.connector.bolt.address=0.0.0.0:7687 60 | 61 | # HTTP Connector 62 | dbms.connector.http.type=HTTP 63 | dbms.connector.http.enabled=true 64 | #dbms.connector.http.encryption=NONE 65 | # To have HTTP accept non-local connections, uncomment this line 66 | #dbms.connector.http.address=0.0.0.0:7474 67 | 68 | # HTTPS Connector 69 | dbms.connector.https.type=HTTP 70 | dbms.connector.https.enabled=true 71 | dbms.connector.https.encryption=TLS 72 | dbms.connector.https.address=localhost:7573 73 | 74 | # Number of Neo4j worker threads. 75 | #dbms.threads.worker_count= 76 | 77 | #***************************************************************** 78 | # Logging configuration 79 | #***************************************************************** 80 | 81 | # To enable HTTP logging, uncomment this line 82 | #dbms.logs.http.enabled=true 83 | 84 | # To enable GC Logging, uncomment this line 85 | #dbms.logs.gc.enabled=true 86 | 87 | # GC Logging Options 88 | # see http://docs.oracle.com/cd/E19957-01/819-0084-10/pt_tuningjava.html#wp57013 for more information. 89 | #dbms.logs.gc.options=-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintPromotionFailure -XX:+PrintTenuringDistribution 90 | 91 | # Number of GC logs to keep. 92 | #dbms.logs.gc.rotation.keep_number=5 93 | 94 | # Size of each GC log that is kept. 95 | #dbms.logs.gc.rotation.size=20m 96 | 97 | # Size threshold for rotation of the debug log. If set to zero then no rotation will occur. Accepts a binary suffix "k", 98 | # "m" or "g". 99 | #dbms.logs.debug.rotation.size=20m 100 | 101 | # Maximum number of history files for the internal log. 102 | #dbms.logs.debug.rotation.keep_number=7 103 | 104 | # Log executed queries that takes longer than the configured threshold. Enable by uncommenting this line. 105 | #dbms.logs.query.enabled=true 106 | 107 | # If the execution of query takes more time than this threshold, the query is logged. If set to zero then all queries 108 | # are logged. 109 | #dbms.logs.query.threshold=0 110 | 111 | # The file size in bytes at which the query log will auto-rotate. If set to zero then no rotation will occur. Accepts a 112 | # binary suffix "k", "m" or "g". 113 | #dbms.logs.query.rotation.size=20m 114 | 115 | # Maximum number of history files for the query log. 116 | #dbms.logs.query.rotation.keep_number=7 117 | 118 | #***************************************************************** 119 | # HA configuration 120 | #***************************************************************** 121 | 122 | # Uncomment and specify these lines for running Neo4j in High Availability mode. 123 | # See the High availability setup tutorial for more details on these settings 124 | # http://neo4j.com/docs/3.0.1/ha-setup-tutorial.html 125 | 126 | # Database mode 127 | # Allowed values: 128 | # HA - High Availability 129 | # SINGLE - Single mode, default. 130 | # To run in High Availability mode uncomment this line: 131 | #dbms.mode=HA 132 | 133 | # ha.server_id is the number of each instance in the HA cluster. It should be 134 | # an integer (e.g. 1), and should be unique for each cluster instance. 135 | #ha.server_id= 136 | 137 | # ha.initial_hosts is a comma-separated list (without spaces) of the host:port 138 | # where the ha.host.coordination of all instances will be listening. Typically 139 | # this will be the same for all cluster instances. 140 | #ha.initial_hosts=127.0.0.1:5001,127.0.0.1:5002,127.0.0.1:5003 141 | 142 | # IP and port for this instance to listen on, for communicating cluster status 143 | # information iwth other instances (also see ha.initial_hosts). The IP 144 | # must be the configured IP address for one of the local interfaces. 145 | #ha.host.coordination=127.0.0.1:5001 146 | 147 | # IP and port for this instance to listen on, for communicating transaction 148 | # data with other instances (also see ha.initial_hosts). The IP 149 | # must be the configured IP address for one of the local interfaces. 150 | #ha.host.data=127.0.0.1:6001 151 | 152 | # The interval at which slaves will pull updates from the master. Comment out 153 | # the option to disable periodic pulling of updates. Unit is seconds. 154 | ha.pull_interval=10 155 | 156 | # Amount of slaves the master will try to push a transaction to upon commit 157 | # (default is 1). The master will optimistically continue and not fail the 158 | # transaction even if it fails to reach the push factor. Setting this to 0 will 159 | # increase write performance when writing through master but could potentially 160 | # lead to branched data (or loss of transaction) if the master goes down. 161 | #ha.tx_push_factor=1 162 | 163 | # Strategy the master will use when pushing data to slaves (if the push factor 164 | # is greater than 0). There are three options available "fixed_ascending" (default), 165 | # "fixed_descending" or "round_robin". Fixed strategies will start by pushing to 166 | # slaves ordered by server id (accordingly with qualifier) and are useful when 167 | # planning for a stable fail-over based on ids. 168 | #ha.tx_push_strategy=fixed_ascending 169 | 170 | # Policy for how to handle branched data. 171 | #ha.branched_data_policy=keep_all 172 | 173 | # How often heartbeat messages should be sent. Defaults to ha.default_timeout. 174 | #ha.heartbeat_interval=5s 175 | 176 | # Timeout for heartbeats between cluster members. Should be at least twice that of ha.heartbeat_interval. 177 | #ha.heartbeat_timeout=11s 178 | 179 | # If you are using a load-balancer that doesn't support HTTP Auth, you may need to turn off authentication for the 180 | # HA HTTP status endpoint by uncommenting the following line. 181 | #dbms.security.ha_status_auth_enabled=false 182 | 183 | # Whether this instance should only participate as slave in cluster. If set to 184 | # true, it will never be elected as master. 185 | #ha.slave_only=false 186 | 187 | #***************************************************************** 188 | # Miscellaneous configuration 189 | #***************************************************************** 190 | 191 | # Enable this to specify a parser other than the default one. 192 | #cypher.default_language_version=3.0 193 | 194 | # Determines if Cypher will allow using file URLs when loading data using 195 | # `LOAD CSV`. Setting this value to `false` will cause Neo4j to fail `LOAD CSV` 196 | # clauses that load data from the file system. 197 | #dbms.security.allow_csv_import_from_file_urls=true 198 | 199 | # Retention policy for transaction logs needed to perform recovery and backups. 200 | #dbms.tx_log.rotation.retention_policy=7 days 201 | 202 | # Limit the number of IOs the background checkpoint process will consume per second. 203 | # This setting is advisory, is ignored in Neo4j Community Edition, and is followed to 204 | # best effort in Enterprise Edition. 205 | # An IO is in this case a 8 KiB (mostly sequential) write. Limiting the write IO in 206 | # this way will leave more bandwidth in the IO subsystem to service random-read IOs, 207 | # which is important for the response time of queries when the database cannot fit 208 | # entirely in memory. The only drawback of this setting is that longer checkpoint times 209 | # may lead to slightly longer recovery times in case of a database or system crash. 210 | # A lower number means lower IO pressure, and consequently longer checkpoint times. 211 | # The configuration can also be commented out to remove the limitation entirely, and 212 | # let the checkpointer flush data as fast as the hardware will go. 213 | # Set this to -1 to disable the IOPS limit. 214 | # dbms.checkpoint.iops.limit=1000 215 | 216 | # Enable a remote shell server which Neo4j Shell clients can log in to. 217 | #dbms.shell.enabled=true 218 | # The network interface IP the shell will listen on (use 0.0.0.0 for all interfaces). 219 | #dbms.shell.host=127.0.0.1 220 | # The port the shell will listen on, default is 1337. 221 | #dbms.shell.port=1337 222 | 223 | # Only allow read operations from this Neo4j instance. This mode still requires 224 | # write access to the directory for lock purposes. 225 | #dbms.read_only=false 226 | 227 | # Comma separated list of JAX-RS packages containing JAX-RS resources, one 228 | # package name for each mountpoint. The listed package names will be loaded 229 | # under the mountpoints specified. Uncomment this line to mount the 230 | # org.neo4j.examples.server.unmanaged.HelloWorldResource.java from 231 | # neo4j-server-examples under /examples/unmanaged, resulting in a final URL of 232 | # http://localhost:7474/examples/unmanaged/helloworld/{nodeId} 233 | #dbms.unmanaged_extension_classes=org.neo4j.examples.server.unmanaged=/examples/unmanaged 234 | --------------------------------------------------------------------------------