From efb040ba544684911b03f5929de677ecf6b40014 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Fri, 31 Aug 2018 14:59:26 +0200 Subject: [PATCH 01/16] user testcase --- .../core/utils/queue/IntPriorityQueue.java | 13 +- .../impl/KShortestPathsUserTest.java | 149 ++++++++++++++++++ 2 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java diff --git a/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java b/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java index ce95957c4..b7e6ed14d 100644 --- a/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java +++ b/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java @@ -239,9 +239,16 @@ private void downHeap(int i) { private void ensureCapacityForInsert() { if (size >= heap.length) { - heap = Arrays.copyOf( - heap, - ArrayUtil.oversize(size + 1, Integer.BYTES)); + try { + final int oversize = ArrayUtil.oversize(size + 1, Integer.BYTES); + heap = Arrays.copyOf( + heap, + oversize); + + } catch (Exception e) { + e.printStackTrace(); // TODO + throw e; + } } } diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java new file mode 100644 index 000000000..e36fbc70c --- /dev/null +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2017 "Neo4j, Inc." + * + * This file is part of Neo4j Graph Algorithms . + * + * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.graphalgo.impl; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.neo4j.graphalgo.TestProgressLogger; +import org.neo4j.graphalgo.api.Graph; +import org.neo4j.graphalgo.core.GraphLoader; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; +import org.neo4j.graphalgo.core.utils.ProgressLoggerAdapter; +import org.neo4j.graphalgo.impl.yens.WeightedPath; +import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.test.rule.ImpermanentDatabaseRule; + +import java.util.List; +import java.util.function.DoubleConsumer; + +import static org.mockito.Mockito.mock; + +/** + * Graph: + * + * (b) (e) + * 1/ 2\ 1/ 2\ + * >(a) (d) (g) + * 2\ 1/ 2\ 1/ + * (c) (f) + * + * @author mknblch + */ +public class KShortestPathsUserTest { + + public static final double DELTA = 0.001; + + @ClassRule + public static ImpermanentDatabaseRule db = new ImpermanentDatabaseRule(); + + private static Graph graph; + + @BeforeClass + public static void setupGraph() throws KernelException { + + final String load = + "LOAD CSV FROM 'file:///Users/mknobloch/Downloads/airports.txt' as airport\n" + + "CREATE (:Airport {\n" + + "AirportID : airport[0],\n" + + "Name : airport[1],\n" + + "City : airport[2],\n" + + "Country : airport[3],\n" + + "IATA: airport[4],\n" + + "ICAO: airport[5],\n" + + "Latitude: toFloat(airport[6]),\n" + + "Longitude: toFloat(airport[7]),\n" + + "Altitude: toInteger(airport[8]),\n" + + "Timezone: airport[9],\n" + + "DST: airport[10],\n" + + "TZ: airport[11],\n" + + "Type: airport[12],\n" + + "Source: airport[13]\n" + + "});"; + + final String constraint = + "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE;"; + final String routes = + "LOAD CSV FROM 'file:///Users/mknobloch/Downloads/routes.txt' as route\n" + + "MATCH (s:Airport {AirportID: route[3]})\n" + + "MATCH (d:Airport {AirportID: route[5]})\n" + + "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);"; + final String dist = + "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + + "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}));"; + + System.out.println("execute: " + load); + db.execute(load); +// db.execute(constraint); + System.out.println("execute: " + routes); + db.execute(routes); + System.out.println("execute: " + dist); + db.execute(dist); + + System.out.println("loading db.."); + + graph = (HeavyGraph) new GraphLoader(db) + .withAnyRelationshipType() + .withAnyLabel() + .withoutNodeProperties() + .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) + .withDirection(Direction.BOTH) + .load(HeavyGraphFactory.class); + + System.out.println("done"); + } + + @Test + public void test() throws Exception { + + System.out.println("test"); + + final YensKShortestPaths yens = new YensKShortestPaths(graph) + .withProgressLogger(TestProgressLogger.INSTANCE) + .compute( + getNode("KRK").getId(), + getNode("DFW").getId(), + Direction.BOTH, + 4, Integer.MAX_VALUE); + + final List paths = yens.getPaths(); + + final DoubleConsumer mock = mock(DoubleConsumer.class); + + for (int i = 0; i < paths.size(); i++) { + final WeightedPath path = paths.get(i); + mock.accept(path.getCost()); + System.out.println(path + " = " + path.getCost()); + } + } + + private static Node getNode(String id) { + final Node[] node = new Node[1]; + db.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { + node[0] = row.getNode("n"); + return false; + }); + return node[0]; + } +} From 8fcdccf40233858b5f01f3b0624c6df8cea11621 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Mon, 3 Sep 2018 13:36:51 +0200 Subject: [PATCH 02/16] WIP --- .../impl/KShortestPathsUserTest.java | 84 +++++++------------ 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java index e36fbc70c..1ef7f48a1 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java @@ -1,18 +1,18 @@ /** * Copyright (c) 2017 "Neo4j, Inc." - * + *

* This file is part of Neo4j Graph Algorithms . - * + *

* Neo4j Graph Algorithms is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + *

* You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -26,19 +26,25 @@ import org.neo4j.graphalgo.core.GraphLoader; import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; -import org.neo4j.graphalgo.core.utils.ProgressLoggerAdapter; import org.neo4j.graphalgo.impl.yens.WeightedPath; import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.config.Setting; +import org.neo4j.graphdb.factory.GraphDatabaseFactory; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.test.rule.ImpermanentDatabaseRule; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.function.DoubleConsumer; import static org.mockito.Mockito.mock; +import static org.neo4j.kernel.configuration.Settings.BOOLEAN; +import static org.neo4j.kernel.configuration.Settings.setting; /** * Graph: @@ -53,57 +59,31 @@ */ public class KShortestPathsUserTest { + + private static final Setting udc = setting("dbms.udc.enabled", BOOLEAN, "true"); + + public static final double DELTA = 0.001; - @ClassRule - public static ImpermanentDatabaseRule db = new ImpermanentDatabaseRule(); + public static final Path LOCATION = Paths.get("/Users/mknobloch/data/neo4j/airportgraph/graph.db"); + + public static GraphDatabaseService db; private static Graph graph; + private static GraphDatabaseAPI openDb(Path dbLocation) { + return (GraphDatabaseAPI) new GraphDatabaseFactory() + .newEmbeddedDatabaseBuilder(dbLocation.toFile()) + .setConfig(GraphDatabaseSettings.pagecache_memory, "2G") + .setConfig(GraphDatabaseSettings.allow_upgrade, "true") + .setConfig(udc, "false") + .newGraphDatabase(); + } + @BeforeClass public static void setupGraph() throws KernelException { - - final String load = - "LOAD CSV FROM 'file:///Users/mknobloch/Downloads/airports.txt' as airport\n" + - "CREATE (:Airport {\n" + - "AirportID : airport[0],\n" + - "Name : airport[1],\n" + - "City : airport[2],\n" + - "Country : airport[3],\n" + - "IATA: airport[4],\n" + - "ICAO: airport[5],\n" + - "Latitude: toFloat(airport[6]),\n" + - "Longitude: toFloat(airport[7]),\n" + - "Altitude: toInteger(airport[8]),\n" + - "Timezone: airport[9],\n" + - "DST: airport[10],\n" + - "TZ: airport[11],\n" + - "Type: airport[12],\n" + - "Source: airport[13]\n" + - "});"; - - final String constraint = - "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE;"; - final String routes = - "LOAD CSV FROM 'file:///Users/mknobloch/Downloads/routes.txt' as route\n" + - "MATCH (s:Airport {AirportID: route[3]})\n" + - "MATCH (d:Airport {AirportID: route[5]})\n" + - "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);"; - final String dist = - "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + - "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}));"; - - System.out.println("execute: " + load); - db.execute(load); -// db.execute(constraint); - System.out.println("execute: " + routes); - db.execute(routes); - System.out.println("execute: " + dist); - db.execute(dist); - System.out.println("loading db.."); - - graph = (HeavyGraph) new GraphLoader(db) + graph = (HeavyGraph) new GraphLoader(openDb(LOCATION)) .withAnyRelationshipType() .withAnyLabel() .withoutNodeProperties() @@ -125,7 +105,7 @@ public void test() throws Exception { getNode("KRK").getId(), getNode("DFW").getId(), Direction.BOTH, - 4, Integer.MAX_VALUE); + 10, Integer.MAX_VALUE); final List paths = yens.getPaths(); @@ -134,7 +114,7 @@ public void test() throws Exception { for (int i = 0; i < paths.size(); i++) { final WeightedPath path = paths.get(i); mock.accept(path.getCost()); - System.out.println(path + " = " + path.getCost()); + System.out.println(path + " = " + path.getCost()); } } From 734e9d5dfae467267b60ed5ce6ba1451703fc7d6 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Wed, 19 Sep 2018 15:17:04 +0200 Subject: [PATCH 03/16] WIP --- .../AllShortestPathsComparisionBenchmark.java | 11 +- .../impl/KShortestPathsUserTest.java | 112 +++++++++++------- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java b/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java index 748aea41f..996be1228 100644 --- a/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java +++ b/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java @@ -30,10 +30,7 @@ import org.neo4j.graphalgo.impl.AllShortestPaths; import org.neo4j.graphalgo.impl.HugeMSBFSAllShortestPaths; import org.neo4j.graphalgo.impl.MSBFSAllShortestPaths; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Relationship; -import org.neo4j.graphdb.RelationshipType; -import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.*; import org.neo4j.internal.kernel.api.exceptions.KernelException; import org.neo4j.kernel.impl.proc.Procedures; import org.neo4j.kernel.internal.GraphDatabaseAPI; @@ -142,20 +139,20 @@ private static Relationship createRelation(Node from, Node to) { @Benchmark public long _01_benchmark_ASP() { - return new AllShortestPaths(graph, Pools.DEFAULT, 8) + return new AllShortestPaths(graph, Pools.DEFAULT, 8, Direction.OUTGOING) .resultStream() .count(); } @Benchmark public long _02_benchmark_MS_ASP() { - return new MSBFSAllShortestPaths(graph, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT) + return new MSBFSAllShortestPaths(graph, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT, Direction.OUTGOING) .resultStream().count(); } @Benchmark public long _03_benchmark_Huge_MS_ASP() { - return new HugeMSBFSAllShortestPaths((HugeGraph) graph, AllocationTracker.EMPTY, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT) + return new HugeMSBFSAllShortestPaths((HugeGraph) graph, AllocationTracker.EMPTY, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT, Direction.OUTGOING) .resultStream().count(); } } diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java index 1ef7f48a1..1ca337550 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java @@ -24,87 +24,113 @@ import org.neo4j.graphalgo.TestProgressLogger; import org.neo4j.graphalgo.api.Graph; import org.neo4j.graphalgo.core.GraphLoader; +import org.neo4j.graphalgo.core.NullWeightMap; import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; import org.neo4j.graphalgo.impl.yens.WeightedPath; import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; import org.neo4j.graphdb.Direction; -import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.config.Setting; -import org.neo4j.graphdb.factory.GraphDatabaseFactory; -import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.graphdb.Transaction; import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.test.rule.ImpermanentDatabaseRule; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.function.DoubleConsumer; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; -import static org.neo4j.kernel.configuration.Settings.BOOLEAN; -import static org.neo4j.kernel.configuration.Settings.setting; /** - * Graph: - * - * (b) (e) - * 1/ 2\ 1/ 2\ - * >(a) (d) (g) - * 2\ 1/ 2\ 1/ - * (c) (f) - * * @author mknblch */ public class KShortestPathsUserTest { + private static final String PATH = "/Users/mknobloch/Downloads"; - private static final Setting udc = setting("dbms.udc.enabled", BOOLEAN, "true"); + @ClassRule + public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); + private static Graph graph; - public static final double DELTA = 0.001; - - public static final Path LOCATION = Paths.get("/Users/mknobloch/data/neo4j/airportgraph/graph.db"); + @BeforeClass + public static void setupGraph() throws KernelException { - public static GraphDatabaseService db; - private static Graph graph; + String cypher = "" + + "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + + "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + + "MERGE (a:Airport {IATA: airport[4]}) " + + "set " + + "a.AirportID = airport[0], " + + "a.Name = airport[1], " + + "a.City = airport[2], " + + "a.Country = airport[3], " + + "a.ICAO = airport[5], " + + "a.Latitude = toFloat(airport[6]), " + + "a.Longitude = toFloat(airport[7]), " + + "a.Altitude = toInteger(airport[8]), " + + "a.Timezone = airport[9], " + + "a.DST= airport[10], " + + "a.TZ = airport[11], " + + "a.Type = airport[12], " + + "a.Source = airport[13] ;" + + "create index on :Airport(AirportID); " + + "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + + "MATCH (s:Airport {AirportID: route[3]}) " + + "MATCH (d:Airport {AirportID: route[5]}) " + + "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);"; + + + for (String part : cypher.split(";")) { + + System.out.println(part); + try (Transaction transaction = DB.beginTx()) { + DB.execute(part); + transaction.success(); + } + } - private static GraphDatabaseAPI openDb(Path dbLocation) { - return (GraphDatabaseAPI) new GraphDatabaseFactory() - .newEmbeddedDatabaseBuilder(dbLocation.toFile()) - .setConfig(GraphDatabaseSettings.pagecache_memory, "2G") - .setConfig(GraphDatabaseSettings.allow_upgrade, "true") - .setConfig(udc, "false") - .newGraphDatabase(); + loadDB(); } - @BeforeClass - public static void setupGraph() throws KernelException { - System.out.println("loading db.."); - graph = (HeavyGraph) new GraphLoader(openDb(LOCATION)) - .withAnyRelationshipType() - .withAnyLabel() - .withoutNodeProperties() + private static void loadDB() { + System.out.println("loading DB.."); + graph = (HeavyGraph) new GraphLoader(DB) + .withLabel("Airport") + .withRelationshipType("Route") .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) - .withDirection(Direction.BOTH) + .asUndirected(true) .load(HeavyGraphFactory.class); System.out.println("done"); } + /* @Test - public void test() throws Exception { + public void testWeights() throws Exception { + graph.forEachNode(node -> { + graph.forEachOutgoing(node, (s, t, r) -> { + double weight = graph.weightOf(s, t); + System.out.println("weight = " + weight); + return true; + }); + return true; + }); - System.out.println("test"); + } + */ + + @Test + public void test() throws Exception { final YensKShortestPaths yens = new YensKShortestPaths(graph) .withProgressLogger(TestProgressLogger.INSTANCE) .compute( getNode("KRK").getId(), - getNode("DFW").getId(), - Direction.BOTH, + getNode("CFU").getId(), + Direction.OUTGOING, 10, Integer.MAX_VALUE); final List paths = yens.getPaths(); @@ -120,7 +146,7 @@ public void test() throws Exception { private static Node getNode(String id) { final Node[] node = new Node[1]; - db.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { + DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { node[0] = row.getNode("n"); return false; }); From 19d98ce14e91d6256fa27434f3ddda60a78b4450 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Wed, 19 Sep 2018 16:41:33 +0200 Subject: [PATCH 04/16] changed IntPrioQueue in Dijkstra to Javas own PrioQueue --- .../neo4j/graphalgo/impl/yens/Dijkstra.java | 57 +++++++++++++------ .../core/utils/queue/IntPriorityQueue.java | 2 +- .../impl/KShortestPathsUserTest.java | 8 ++- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java b/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java index a044541b5..8ac8e14fc 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java @@ -22,13 +22,11 @@ import org.neo4j.graphalgo.api.Graph; import org.neo4j.graphalgo.api.RelationshipConsumer; import org.neo4j.graphalgo.core.utils.TerminationFlag; -import org.neo4j.graphalgo.core.utils.queue.IntPriorityQueue; -import org.neo4j.graphalgo.core.utils.queue.SharedIntPriorityQueue; -import org.neo4j.graphalgo.core.utils.traverse.SimpleBitSet; import org.neo4j.graphdb.Direction; import java.util.Arrays; import java.util.Optional; +import java.util.PriorityQueue; /** * specialized dijkstra impl. for YensKShortestPath @@ -37,20 +35,39 @@ */ public class Dijkstra { + class T implements Comparable { + + public final Integer key; + public final double value; + + T(Integer key, double value) { + this.key = key; + this.value = value; + } + + @Override + public int compareTo(T o) { + return Double.compare(value, o.value); + } + } + + // initial weighted path capacity + public static final int INITIAL_CAPACITY = 64; private static final int PATH_END = -1; - private final Graph graph; + private final int nodeCount; private TerminationFlag terminationFlag = TerminationFlag.RUNNING_TRUE; - // node to cost map private final IntDoubleMap costs; // next node priority queue - private final IntPriorityQueue queue; + +// private final IntPriorityQueue queue; + private final PriorityQueue queue; // auxiliary path map private final IntIntMap path; // visited set @@ -67,9 +84,10 @@ public Dijkstra(Graph graph) { this.graph = graph; nodeCount = Math.toIntExact(graph.nodeCount()); costs = new IntDoubleScatterMap(nodeCount); - queue = SharedIntPriorityQueue.min(nodeCount, - costs, - Double.MAX_VALUE); +// queue = SharedIntPriorityQueue.min(nodeCount, +// costs, +// Double.MAX_VALUE); + queue = new PriorityQueue<>(nodeCount); path = new IntIntScatterMap(nodeCount); visited = new BitSet(nodeCount); depth = new int[nodeCount]; @@ -156,10 +174,12 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep path.clear(); visited.clear(); costs.put(source, 0.0); - queue.add(source, 0.0); - Arrays.fill(depth, 1); + queue.add(new T(source, 0.0)); + Arrays.fill(depth, 0); + depth[source] = 1; while (!queue.isEmpty() && terminationFlag.running()) { - int node = queue.pop(); + T k = queue.poll(); + int node = k.key; final int d = depth[node]; if (d >= maxDepth) { continue; @@ -178,12 +198,13 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep final double w = graph.weightOf(s, t); final boolean updateCosts = updateCosts(s, t, w + costs); if (!visited.get(t)) { - depth[t] = d + 1; - if (updateCosts) { - queue.update(t); - } else { - queue.add(t, w); - } + depth[t] = depth[s] + 1; +// if (updateCosts) { +// queue.update(t); +// } else { +// queue.add(t, w); +// } + queue.add(new T(t, w)); } return terminationFlag.running(); }); diff --git a/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java b/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java index b7e6ed14d..7dca4f41f 100644 --- a/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java +++ b/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java @@ -240,7 +240,7 @@ private void downHeap(int i) { private void ensureCapacityForInsert() { if (size >= heap.length) { try { - final int oversize = ArrayUtil.oversize(size + 1, Integer.BYTES); + final int oversize = size + 1024; // ArrayUtil.oversize(size + 1, Integer.BYTES); heap = Arrays.copyOf( heap, oversize); diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java index 1ca337550..0df3a7275 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java @@ -80,7 +80,9 @@ public static void setupGraph() throws KernelException { "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + "MATCH (s:Airport {AirportID: route[3]}) " + "MATCH (d:Airport {AirportID: route[5]}) " + - "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);"; + "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + + "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + + "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; for (String part : cypher.split(";")) { @@ -101,7 +103,7 @@ private static void loadDB() { .withLabel("Airport") .withRelationshipType("Route") .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) - .asUndirected(true) + .withDirection(Direction.BOTH) .load(HeavyGraphFactory.class); System.out.println("done"); @@ -131,7 +133,7 @@ public void test() throws Exception { getNode("KRK").getId(), getNode("CFU").getId(), Direction.OUTGOING, - 10, Integer.MAX_VALUE); + 20 ,100); final List paths = yens.getPaths(); From 6aebfc79d17d8340532819b6ed9c19be867eed50 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Thu, 20 Sep 2018 08:54:34 +0200 Subject: [PATCH 05/16] replace SharedIntPrioQueue in Dijkstra (used in YKSP) --- .../neo4j/graphalgo/impl/yens/Dijkstra.java | 67 ++++++++----------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java b/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java index 8ac8e14fc..8c34cfbc4 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java @@ -22,6 +22,7 @@ import org.neo4j.graphalgo.api.Graph; import org.neo4j.graphalgo.api.RelationshipConsumer; import org.neo4j.graphalgo.core.utils.TerminationFlag; +import org.neo4j.graphalgo.core.utils.queue.IntPriorityQueue; import org.neo4j.graphdb.Direction; import java.util.Arrays; @@ -35,23 +36,6 @@ */ public class Dijkstra { - class T implements Comparable { - - public final Integer key; - public final double value; - - T(Integer key, double value) { - this.key = key; - this.value = value; - } - - @Override - public int compareTo(T o) { - return Double.compare(value, o.value); - } - } - - // initial weighted path capacity public static final int INITIAL_CAPACITY = 64; @@ -65,9 +49,7 @@ public int compareTo(T o) { // node to cost map private final IntDoubleMap costs; // next node priority queue - -// private final IntPriorityQueue queue; - private final PriorityQueue queue; + private final PriorityQueue queue; // auxiliary path map private final IntIntMap path; // visited set @@ -84,10 +66,7 @@ public Dijkstra(Graph graph) { this.graph = graph; nodeCount = Math.toIntExact(graph.nodeCount()); costs = new IntDoubleScatterMap(nodeCount); -// queue = SharedIntPriorityQueue.min(nodeCount, -// costs, -// Double.MAX_VALUE); - queue = new PriorityQueue<>(nodeCount); + queue = new PriorityQueue<>(); path = new IntIntScatterMap(nodeCount); visited = new BitSet(nodeCount); depth = new int[nodeCount]; @@ -174,12 +153,12 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep path.clear(); visited.clear(); costs.put(source, 0.0); - queue.add(new T(source, 0.0)); + queue.add(new Element(source, 0.0)); Arrays.fill(depth, 0); depth[source] = 1; - while (!queue.isEmpty() && terminationFlag.running()) { - T k = queue.poll(); - int node = k.key; + while (queue.size() > 0 && terminationFlag.running()) { + final Element k = queue.poll(); + final int node = k.key; final int d = depth[node]; if (d >= maxDepth) { continue; @@ -196,15 +175,10 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep return true; } final double w = graph.weightOf(s, t); - final boolean updateCosts = updateCosts(s, t, w + costs); if (!visited.get(t)) { - depth[t] = depth[s] + 1; -// if (updateCosts) { -// queue.update(t); -// } else { -// queue.add(t, w); -// } - queue.add(new T(t, w)); + depth[t] = depth[node] + 1; + queue.add(new Element(t, w)); + updateCosts(s, t, costs + w); } return terminationFlag.running(); }); @@ -212,17 +186,32 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep return false; } + /** * update cost map */ - private boolean updateCosts(int source, int target, double newCosts) { + private void updateCosts(int source, int target, double newCosts) { double oldCosts = costs.getOrDefault(target, Double.MAX_VALUE); if (newCosts < oldCosts) { costs.put(target, newCosts); path.put(target, source); - return oldCosts < Double.MAX_VALUE; } - return false; + } + + class Element implements Comparable { + + public final Integer key; + public final double value; + + Element(Integer key, double value) { + this.key = key; + this.value = value; + } + + @Override + public int compareTo(Element o) { + return Double.compare(value, o.value); + } } } From a72c592ad9414901c53db952bc0e58a2630e1b3e Mon Sep 17 00:00:00 2001 From: Paul Horn Date: Thu, 20 Sep 2018 09:33:09 +0200 Subject: [PATCH 06/16] Fix updateCosts --- .../neo4j/graphalgo/impl/yens/Dijkstra.java | 70 +++++++++---------- .../core/utils/queue/IntPriorityQueue.java | 14 ++-- 2 files changed, 37 insertions(+), 47 deletions(-) diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java b/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java index 8ac8e14fc..c6aba8b74 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/yens/Dijkstra.java @@ -22,11 +22,12 @@ import org.neo4j.graphalgo.api.Graph; import org.neo4j.graphalgo.api.RelationshipConsumer; import org.neo4j.graphalgo.core.utils.TerminationFlag; +import org.neo4j.graphalgo.core.utils.queue.IntPriorityQueue; +import org.neo4j.graphalgo.core.utils.queue.SharedIntPriorityQueue; import org.neo4j.graphdb.Direction; import java.util.Arrays; import java.util.Optional; -import java.util.PriorityQueue; /** * specialized dijkstra impl. for YensKShortestPath @@ -35,23 +36,6 @@ */ public class Dijkstra { - class T implements Comparable { - - public final Integer key; - public final double value; - - T(Integer key, double value) { - this.key = key; - this.value = value; - } - - @Override - public int compareTo(T o) { - return Double.compare(value, o.value); - } - } - - // initial weighted path capacity public static final int INITIAL_CAPACITY = 64; @@ -66,8 +50,7 @@ public int compareTo(T o) { private final IntDoubleMap costs; // next node priority queue -// private final IntPriorityQueue queue; - private final PriorityQueue queue; + private final IntPriorityQueue queue; // auxiliary path map private final IntIntMap path; // visited set @@ -84,10 +67,9 @@ public Dijkstra(Graph graph) { this.graph = graph; nodeCount = Math.toIntExact(graph.nodeCount()); costs = new IntDoubleScatterMap(nodeCount); -// queue = SharedIntPriorityQueue.min(nodeCount, -// costs, -// Double.MAX_VALUE); - queue = new PriorityQueue<>(nodeCount); + queue = SharedIntPriorityQueue.min(nodeCount, + costs, + Double.MAX_VALUE); path = new IntIntScatterMap(nodeCount); visited = new BitSet(nodeCount); depth = new int[nodeCount]; @@ -174,12 +156,11 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep path.clear(); visited.clear(); costs.put(source, 0.0); - queue.add(new T(source, 0.0)); + queue.add(source, 0.0); Arrays.fill(depth, 0); depth[source] = 1; while (!queue.isEmpty() && terminationFlag.running()) { - T k = queue.poll(); - int node = k.key; + int node = queue.pop(); final int d = depth[node]; if (d >= maxDepth) { continue; @@ -196,15 +177,19 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep return true; } final double w = graph.weightOf(s, t); - final boolean updateCosts = updateCosts(s, t, w + costs); + final UpdateResult updateCosts = updateCosts(s, t, w + costs); if (!visited.get(t)) { + switch (updateCosts) { + case NO_PREVIOUS_COSTS: + queue.add(t, w); + break; + case UPDATED_COST: + queue.update(t); + break; + default: + break; + } depth[t] = depth[s] + 1; -// if (updateCosts) { -// queue.update(t); -// } else { -// queue.add(t, w); -// } - queue.add(new T(t, w)); } return terminationFlag.running(); }); @@ -215,14 +200,25 @@ private boolean dijkstra(int source, int target, Direction direction, int maxDep /** * update cost map */ - private boolean updateCosts(int source, int target, double newCosts) { + private UpdateResult updateCosts(int source, int target, double newCosts) { double oldCosts = costs.getOrDefault(target, Double.MAX_VALUE); + if (oldCosts == Double.MAX_VALUE) { + if (!costs.containsKey(target)) { + costs.put(target, newCosts); + path.put(target, source); + return UpdateResult.NO_PREVIOUS_COSTS; + } + } if (newCosts < oldCosts) { costs.put(target, newCosts); path.put(target, source); - return oldCosts < Double.MAX_VALUE; + return UpdateResult.UPDATED_COST; } - return false; + return UpdateResult.COST_NOT_COMPETITIVE; + } + + private enum UpdateResult { + NO_PREVIOUS_COSTS, UPDATED_COST, COST_NOT_COMPETITIVE; } } diff --git a/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java b/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java index 7dca4f41f..d9aaca23e 100644 --- a/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java +++ b/core/src/main/java/org/neo4j/graphalgo/core/utils/queue/IntPriorityQueue.java @@ -239,16 +239,10 @@ private void downHeap(int i) { private void ensureCapacityForInsert() { if (size >= heap.length) { - try { - final int oversize = size + 1024; // ArrayUtil.oversize(size + 1, Integer.BYTES); - heap = Arrays.copyOf( - heap, - oversize); - - } catch (Exception e) { - e.printStackTrace(); // TODO - throw e; - } + final int oversize = ArrayUtil.oversize(size + 1, Integer.BYTES); + heap = Arrays.copyOf( + heap, + oversize); } } From a131485a24fa7d119eff9647842a176d0b9289e7 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Thu, 20 Sep 2018 09:51:27 +0200 Subject: [PATCH 07/16] ignore testcase --- .../impl/KShortestPathsUserTest.java | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java index 0df3a7275..b04d2190b 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java @@ -20,11 +20,11 @@ import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Test; import org.neo4j.graphalgo.TestProgressLogger; import org.neo4j.graphalgo.api.Graph; import org.neo4j.graphalgo.core.GraphLoader; -import org.neo4j.graphalgo.core.NullWeightMap; import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; import org.neo4j.graphalgo.impl.yens.WeightedPath; @@ -38,13 +38,14 @@ import java.util.List; import java.util.function.DoubleConsumer; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; /** * @author mknblch + * + * TODO rm if verified */ +@Ignore public class KShortestPathsUserTest { private static final String PATH = "/Users/mknobloch/Downloads"; @@ -86,7 +87,6 @@ public static void setupGraph() throws KernelException { for (String part : cypher.split(";")) { - System.out.println(part); try (Transaction transaction = DB.beginTx()) { DB.execute(part); @@ -94,35 +94,14 @@ public static void setupGraph() throws KernelException { } } - loadDB(); - } - - private static void loadDB() { - System.out.println("loading DB.."); graph = (HeavyGraph) new GraphLoader(DB) .withLabel("Airport") .withRelationshipType("Route") .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) .withDirection(Direction.BOTH) .load(HeavyGraphFactory.class); - - System.out.println("done"); } - /* - @Test - public void testWeights() throws Exception { - graph.forEachNode(node -> { - graph.forEachOutgoing(node, (s, t, r) -> { - double weight = graph.weightOf(s, t); - System.out.println("weight = " + weight); - return true; - }); - return true; - }); - - } - */ @Test public void test() throws Exception { @@ -133,7 +112,7 @@ public void test() throws Exception { getNode("KRK").getId(), getNode("CFU").getId(), Direction.OUTGOING, - 20 ,100); + 20, 100); final List paths = yens.getPaths(); @@ -148,7 +127,7 @@ public void test() throws Exception { private static Node getNode(String id) { final Node[] node = new Node[1]; - DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { + DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { node[0] = row.getNode("n"); return false; }); From aa4c653b525c10bb64951aaef6249145f567b606 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Thu, 20 Sep 2018 09:56:44 +0200 Subject: [PATCH 08/16] adapt testcase --- .../java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java index b04d2190b..e44e41683 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java @@ -111,8 +111,8 @@ public void test() throws Exception { .compute( getNode("KRK").getId(), getNode("CFU").getId(), - Direction.OUTGOING, - 20, 100); + Direction.BOTH, + 20, Integer.MAX_VALUE); final List paths = yens.getPaths(); From 648a490d03d6135a7f14c671efcd6b1f3fc65a85 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Thu, 20 Sep 2018 14:04:36 +0200 Subject: [PATCH 09/16] WIP --- .../java/org/neo4j/graphalgo/impl/Prim.java | 9 +++- .../impl/spanningTrees/KSpanningTree.java | 2 - .../AllShortestPathsComparisionBenchmark.java | 11 ++-- .../impl/ShortestPathDijkstraUserTest.java | 52 +++++++++++++++++++ 4 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java b/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java index a9cb3cb0f..225ce2f72 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java @@ -45,6 +45,7 @@ */ public class Prim extends Algorithm { + public static final int UNUSED = 42; private final RelationshipIterator relationshipIterator; private final RelationshipWeights weights; private final int nodeCount; @@ -90,11 +91,15 @@ private SpanningTree prim(int startNode, boolean max) { if (visited.contains(t)) { return true; } - // invert weight to calculate maximum + // inverting the weight calculates maximum- instead of minimum spanning tree final double w = max ? -weights.weightOf(s, t) : weights.weightOf(s, t); + /* + cost is updated once for each node. therefore no + update(..) needed. + */ if (w < cost.getOrDefault(t, Double.MAX_VALUE)) { cost.put(t, w); - queue.add(t, -1.0); + queue.add(t, UNUSED); spanningTree.parent[t] = s; } return true; diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/KSpanningTree.java b/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/KSpanningTree.java index 6d39bb2b5..d1ae9c4eb 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/KSpanningTree.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/KSpanningTree.java @@ -29,8 +29,6 @@ import org.neo4j.graphalgo.results.AbstractResultBuilder; /** - * Sequential Single-Source minimum weight spanning tree algorithm (PRIM). - *

* The algorithm computes the MST by traversing all nodes from a given * startNodeId. It aggregates all transitions into a MinPriorityQueue * and visits each (unvisited) connected node by following only the diff --git a/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java b/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java index 748aea41f..996be1228 100644 --- a/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java +++ b/benchmark/src/main/java/org/neo4j/graphalgo/bench/AllShortestPathsComparisionBenchmark.java @@ -30,10 +30,7 @@ import org.neo4j.graphalgo.impl.AllShortestPaths; import org.neo4j.graphalgo.impl.HugeMSBFSAllShortestPaths; import org.neo4j.graphalgo.impl.MSBFSAllShortestPaths; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Relationship; -import org.neo4j.graphdb.RelationshipType; -import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.*; import org.neo4j.internal.kernel.api.exceptions.KernelException; import org.neo4j.kernel.impl.proc.Procedures; import org.neo4j.kernel.internal.GraphDatabaseAPI; @@ -142,20 +139,20 @@ private static Relationship createRelation(Node from, Node to) { @Benchmark public long _01_benchmark_ASP() { - return new AllShortestPaths(graph, Pools.DEFAULT, 8) + return new AllShortestPaths(graph, Pools.DEFAULT, 8, Direction.OUTGOING) .resultStream() .count(); } @Benchmark public long _02_benchmark_MS_ASP() { - return new MSBFSAllShortestPaths(graph, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT) + return new MSBFSAllShortestPaths(graph, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT, Direction.OUTGOING) .resultStream().count(); } @Benchmark public long _03_benchmark_Huge_MS_ASP() { - return new HugeMSBFSAllShortestPaths((HugeGraph) graph, AllocationTracker.EMPTY, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT) + return new HugeMSBFSAllShortestPaths((HugeGraph) graph, AllocationTracker.EMPTY, Pools.DEFAULT_CONCURRENCY, Pools.DEFAULT, Direction.OUTGOING) .resultStream().count(); } } diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java new file mode 100644 index 000000000..b31555d3b --- /dev/null +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2017 "Neo4j, Inc." + * + * This file is part of Neo4j Graph Algorithms . + * + * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.graphalgo.impl; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.neo4j.graphalgo.api.Graph; +import org.neo4j.graphalgo.api.GraphFactory; +import org.neo4j.graphalgo.core.GraphLoader; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; +import org.neo4j.graphalgo.core.huge.HugeGraphFactory; +import org.neo4j.graphalgo.core.neo4jview.GraphViewFactory; +import org.neo4j.graphdb.*; +import org.neo4j.test.rule.ImpermanentDatabaseRule; + +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Stream; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public final class ShortestPathDijkstraUserTest { + + @Rule + private ImpermanentDatabaseRule db = new ImpermanentDatabaseRule(); + + @BeforeClass + public static void setup() { + + } +} From 8183f9ce815c055468de87b778200acc637d030e Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Thu, 27 Sep 2018 10:06:50 +0200 Subject: [PATCH 10/16] WIP --- .../graphalgo/impl/ShortestPathDijkstra.java | 23 ++---- .../impl/ShortestPathDijkstraUserTest.java | 78 ++++++++++++++++++- 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java b/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java index 90b741de0..ca37909b2 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java @@ -39,6 +39,7 @@ public class ShortestPathDijkstra extends Algorithm { private static final int PATH_END = -1; public static final double NO_PATH_FOUND = -1.0; + public static final int UNUSED = 42; private Graph graph; @@ -148,16 +149,18 @@ private void run(int goal, Direction direction) { } visited.set(node); - double costs = this.costs.getOrDefault(node, Double.MAX_VALUE); + final double costToSource = this.costs.getOrDefault(node, Double.MAX_VALUE); graph.forEachRelationship( node, direction, (source, target, relId, weight) -> { - boolean oldCostChanged = updateCosts(source, target, weight + costs); - if (!visited.get(target)) { - if (oldCostChanged) { + double newCosts = costToSource + weight; + if (newCosts < this.costs.getOrDefault(target, Double.MAX_VALUE)) { + this.costs.put(target, newCosts); + this.path.put(target, source); + if (visited.get(target)) { queue.update(target); } else { - queue.add(target, 0); + queue.add(target, weight); } } return true; @@ -166,16 +169,6 @@ private void run(int goal, Direction direction) { } } - private boolean updateCosts(int source, int target, double newCosts) { - double oldCosts = costs.getOrDefault(target, Double.MAX_VALUE); - if (newCosts < oldCosts) { - costs.put(target, newCosts); - path.put(target, source); - return oldCosts < Double.MAX_VALUE; - } - return false; - } - @Override public ShortestPathDijkstra me() { return this; diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java index b31555d3b..4d27d012c 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java @@ -24,13 +24,18 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.neo4j.graphalgo.TestProgressLogger; import org.neo4j.graphalgo.api.Graph; import org.neo4j.graphalgo.api.GraphFactory; import org.neo4j.graphalgo.core.GraphLoader; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; import org.neo4j.graphalgo.core.huge.HugeGraphFactory; import org.neo4j.graphalgo.core.neo4jview.GraphViewFactory; +import org.neo4j.graphalgo.core.utils.Pointer; +import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; import org.neo4j.graphdb.*; +import org.neo4j.internal.kernel.api.exceptions.KernelException; import org.neo4j.test.rule.ImpermanentDatabaseRule; import java.util.Arrays; @@ -42,11 +47,78 @@ public final class ShortestPathDijkstraUserTest { - @Rule - private ImpermanentDatabaseRule db = new ImpermanentDatabaseRule(); + private static final String PATH = "/Users/mknobloch/Downloads"; + + @ClassRule + public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); + + private static Graph graph; @BeforeClass - public static void setup() { + public static void setupGraph() throws KernelException { + + + String cypher = "" + + "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + + "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + + "MERGE (a:Airport {IATA: airport[4]}) " + + "set " + + "a.AirportID = airport[0], " + + "a.Name = airport[1], " + + "a.City = airport[2], " + + "a.Country = airport[3], " + + "a.ICAO = airport[5], " + + "a.Latitude = toFloat(airport[6]), " + + "a.Longitude = toFloat(airport[7]), " + + "a.Altitude = toInteger(airport[8]), " + + "a.Timezone = airport[9], " + + "a.DST= airport[10], " + + "a.TZ = airport[11], " + + "a.Type = airport[12], " + + "a.Source = airport[13] ;" + + "create index on :Airport(AirportID); " + + "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + + "MATCH (s:Airport {AirportID: route[3]}) " + + "MATCH (d:Airport {AirportID: route[5]}) " + + "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + + "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + + "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; + + + for (String part : cypher.split(";")) { + System.out.println(part); + try (Transaction transaction = DB.beginTx()) { + DB.execute(part); + transaction.success(); + } + } + + graph = (HeavyGraph) new GraphLoader(DB) + .withLabel("Airport") + .withRelationshipType("Route") + .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) + .withDirection(Direction.BOTH) + .load(HeavyGraphFactory.class); + } + + private static Node getNode(String id) { + final Pointer.GenericPointer nodePointer = new Pointer.GenericPointer<>(null); + DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { + nodePointer.v = row.getNode("n"); + return false; + }); + return nodePointer.v; + } + + @Test + public void test() throws Exception { + + System.out.println("test"); + + ShortestPathDijkstra dijkstra = new ShortestPathDijkstra(graph) + .compute(getNode("CFU").getId(), + getNode("KRK").getId()); + System.out.println(dijkstra.getTotalCost()); } } From 646e602a61dcc0477dbe4813c2286ba941c3e46c Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Fri, 28 Sep 2018 15:54:57 +0200 Subject: [PATCH 11/16] WIP --- .../impl => }/ShortestPathDijkstraUserTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) rename tests/src/test/java/{org/neo4j/graphalgo/impl => }/ShortestPathDijkstraUserTest.java (94%) diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java b/tests/src/test/java/ShortestPathDijkstraUserTest.java similarity index 94% rename from tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java rename to tests/src/test/java/ShortestPathDijkstraUserTest.java index 4d27d012c..7c4b527a9 100644 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java +++ b/tests/src/test/java/ShortestPathDijkstraUserTest.java @@ -18,10 +18,8 @@ */ package org.neo4j.graphalgo.impl; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; +import com.carrotsearch.hppc.cursors.IntCursor; +import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.neo4j.graphalgo.TestProgressLogger; @@ -40,11 +38,13 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; import java.util.stream.Stream; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +@Ignore("rm if verified") public final class ShortestPathDijkstraUserTest { private static final String PATH = "/Users/mknobloch/Downloads"; @@ -119,6 +119,11 @@ public void test() throws Exception { .compute(getNode("CFU").getId(), getNode("KRK").getId()); + final Iterator iterator = dijkstra.getFinalPath().iterator(); + + while (iterator.hasNext()) { + System.out.println(iterator.next().value); + } System.out.println(dijkstra.getTotalCost()); } } From d4caf4f890dd891f0babd56a52580b69a0cd2470 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Mon, 1 Oct 2018 10:48:43 +0200 Subject: [PATCH 12/16] Prim test --- .../neo4j/graphalgo/impl/PrimUserTest.java | 120 ++++++++++++++++++ .../impl}/ShortestPathDijkstraUserTest.java | 7 +- 2 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java rename tests/src/test/java/{ => org/neo4j/graphalgo/impl}/ShortestPathDijkstraUserTest.java (97%) diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java new file mode 100644 index 000000000..430057944 --- /dev/null +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2017 "Neo4j, Inc." + * + * This file is part of Neo4j Graph Algorithms . + * + * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.graphalgo.impl; + +import com.carrotsearch.hppc.cursors.IntCursor; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.neo4j.graphalgo.TestProgressLogger; +import org.neo4j.graphalgo.api.Graph; +import org.neo4j.graphalgo.core.GraphLoader; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; +import org.neo4j.graphalgo.core.utils.Pointer; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.test.rule.ImpermanentDatabaseRule; + +import java.util.Iterator; + +@Ignore("rm if verified") +public final class PrimUserTest { + + private static final String PATH = "/Users/mknobloch/Downloads"; + + @ClassRule + public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); + + private static Graph graph; + + @BeforeClass + public static void setupGraph() throws KernelException { + + + String cypher = "" + + "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + + "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + + "MERGE (a:Airport {IATA: airport[4]}) " + + "set " + + "a.AirportID = airport[0], " + + "a.Name = airport[1], " + + "a.City = airport[2], " + + "a.Country = airport[3], " + + "a.ICAO = airport[5], " + + "a.Latitude = toFloat(airport[6]), " + + "a.Longitude = toFloat(airport[7]), " + + "a.Altitude = toInteger(airport[8]), " + + "a.Timezone = airport[9], " + + "a.DST= airport[10], " + + "a.TZ = airport[11], " + + "a.Type = airport[12], " + + "a.Source = airport[13] ;" + + "create index on :Airport(AirportID); " + + "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + + "MATCH (s:Airport {AirportID: route[3]}) " + + "MATCH (d:Airport {AirportID: route[5]}) " + + "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + + "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + + "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; + + + for (String part : cypher.split(";")) { + System.out.println(part); + try (Transaction transaction = DB.beginTx()) { + DB.execute(part); + transaction.success(); + } + } + + graph = (HeavyGraph) new GraphLoader(DB) + .withLabel("Airport") + .withRelationshipType("Route") + .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) + .withDirection(Direction.BOTH) + .load(HeavyGraphFactory.class); + } + + private static Node getNode(String id) { + final Pointer.GenericPointer nodePointer = new Pointer.GenericPointer<>(null); + DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { + nodePointer.v = row.getNode("n"); + return false; + }); + return nodePointer.v; + } + + @Test + public void test() throws Exception { + + System.out.println("== Test =="); + + new Prim(graph, graph, graph) + .withProgressLogger(TestProgressLogger.INSTANCE) + .computeMinimumSpanningTree(3871) + .getSpanningTree() + .forEach((s, t, r) -> { + System.out.println(s + " -> " + t); + return true; + }); + } +} diff --git a/tests/src/test/java/ShortestPathDijkstraUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java similarity index 97% rename from tests/src/test/java/ShortestPathDijkstraUserTest.java rename to tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java index 7c4b527a9..f68bb0f92 100644 --- a/tests/src/test/java/ShortestPathDijkstraUserTest.java +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java @@ -113,17 +113,14 @@ private static Node getNode(String id) { @Test public void test() throws Exception { - System.out.println("test"); - ShortestPathDijkstra dijkstra = new ShortestPathDijkstra(graph) .compute(getNode("CFU").getId(), getNode("KRK").getId()); final Iterator iterator = dijkstra.getFinalPath().iterator(); - while (iterator.hasNext()) { - System.out.println(iterator.next().value); + System.out.print(iterator.next().value + " -> "); } - System.out.println(dijkstra.getTotalCost()); + System.out.println(" = (" + dijkstra.getTotalCost() + ")"); } } From 2794bec0be8516311b91b3d3a74ff02e16c92805 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Mon, 1 Oct 2018 10:57:45 +0200 Subject: [PATCH 13/16] rm example airport-graph tests --- .../impl/KShortestPathsUserTest.java | 136 ------------------ .../neo4j/graphalgo/impl/PrimUserTest.java | 120 ---------------- .../impl/ShortestPathDijkstraUserTest.java | 126 ---------------- 3 files changed, 382 deletions(-) delete mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java delete mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java delete mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java deleted file mode 100644 index e44e41683..000000000 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/KShortestPathsUserTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2017 "Neo4j, Inc." - *

- * This file is part of Neo4j Graph Algorithms . - *

- * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - *

- * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.graphalgo.impl; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; -import org.neo4j.graphalgo.TestProgressLogger; -import org.neo4j.graphalgo.api.Graph; -import org.neo4j.graphalgo.core.GraphLoader; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; -import org.neo4j.graphalgo.impl.yens.WeightedPath; -import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; -import org.neo4j.graphdb.Direction; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Transaction; -import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.test.rule.ImpermanentDatabaseRule; - -import java.util.List; -import java.util.function.DoubleConsumer; - -import static org.mockito.Mockito.mock; - -/** - * @author mknblch - * - * TODO rm if verified - */ -@Ignore -public class KShortestPathsUserTest { - - private static final String PATH = "/Users/mknobloch/Downloads"; - - @ClassRule - public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); - - private static Graph graph; - - @BeforeClass - public static void setupGraph() throws KernelException { - - - String cypher = "" + - "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + - "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + - "MERGE (a:Airport {IATA: airport[4]}) " + - "set " + - "a.AirportID = airport[0], " + - "a.Name = airport[1], " + - "a.City = airport[2], " + - "a.Country = airport[3], " + - "a.ICAO = airport[5], " + - "a.Latitude = toFloat(airport[6]), " + - "a.Longitude = toFloat(airport[7]), " + - "a.Altitude = toInteger(airport[8]), " + - "a.Timezone = airport[9], " + - "a.DST= airport[10], " + - "a.TZ = airport[11], " + - "a.Type = airport[12], " + - "a.Source = airport[13] ;" + - "create index on :Airport(AirportID); " + - "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + - "MATCH (s:Airport {AirportID: route[3]}) " + - "MATCH (d:Airport {AirportID: route[5]}) " + - "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + - "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + - "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; - - - for (String part : cypher.split(";")) { - System.out.println(part); - try (Transaction transaction = DB.beginTx()) { - DB.execute(part); - transaction.success(); - } - } - - graph = (HeavyGraph) new GraphLoader(DB) - .withLabel("Airport") - .withRelationshipType("Route") - .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) - .withDirection(Direction.BOTH) - .load(HeavyGraphFactory.class); - } - - - @Test - public void test() throws Exception { - - final YensKShortestPaths yens = new YensKShortestPaths(graph) - .withProgressLogger(TestProgressLogger.INSTANCE) - .compute( - getNode("KRK").getId(), - getNode("CFU").getId(), - Direction.BOTH, - 20, Integer.MAX_VALUE); - - final List paths = yens.getPaths(); - - final DoubleConsumer mock = mock(DoubleConsumer.class); - - for (int i = 0; i < paths.size(); i++) { - final WeightedPath path = paths.get(i); - mock.accept(path.getCost()); - System.out.println(path + " = " + path.getCost()); - } - } - - private static Node getNode(String id) { - final Node[] node = new Node[1]; - DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { - node[0] = row.getNode("n"); - return false; - }); - return node[0]; - } -} diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java deleted file mode 100644 index 430057944..000000000 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/PrimUserTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2017 "Neo4j, Inc." - * - * This file is part of Neo4j Graph Algorithms . - * - * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.graphalgo.impl; - -import com.carrotsearch.hppc.cursors.IntCursor; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; -import org.neo4j.graphalgo.TestProgressLogger; -import org.neo4j.graphalgo.api.Graph; -import org.neo4j.graphalgo.core.GraphLoader; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; -import org.neo4j.graphalgo.core.utils.Pointer; -import org.neo4j.graphdb.Direction; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Transaction; -import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.test.rule.ImpermanentDatabaseRule; - -import java.util.Iterator; - -@Ignore("rm if verified") -public final class PrimUserTest { - - private static final String PATH = "/Users/mknobloch/Downloads"; - - @ClassRule - public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); - - private static Graph graph; - - @BeforeClass - public static void setupGraph() throws KernelException { - - - String cypher = "" + - "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + - "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + - "MERGE (a:Airport {IATA: airport[4]}) " + - "set " + - "a.AirportID = airport[0], " + - "a.Name = airport[1], " + - "a.City = airport[2], " + - "a.Country = airport[3], " + - "a.ICAO = airport[5], " + - "a.Latitude = toFloat(airport[6]), " + - "a.Longitude = toFloat(airport[7]), " + - "a.Altitude = toInteger(airport[8]), " + - "a.Timezone = airport[9], " + - "a.DST= airport[10], " + - "a.TZ = airport[11], " + - "a.Type = airport[12], " + - "a.Source = airport[13] ;" + - "create index on :Airport(AirportID); " + - "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + - "MATCH (s:Airport {AirportID: route[3]}) " + - "MATCH (d:Airport {AirportID: route[5]}) " + - "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + - "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + - "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; - - - for (String part : cypher.split(";")) { - System.out.println(part); - try (Transaction transaction = DB.beginTx()) { - DB.execute(part); - transaction.success(); - } - } - - graph = (HeavyGraph) new GraphLoader(DB) - .withLabel("Airport") - .withRelationshipType("Route") - .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) - .withDirection(Direction.BOTH) - .load(HeavyGraphFactory.class); - } - - private static Node getNode(String id) { - final Pointer.GenericPointer nodePointer = new Pointer.GenericPointer<>(null); - DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { - nodePointer.v = row.getNode("n"); - return false; - }); - return nodePointer.v; - } - - @Test - public void test() throws Exception { - - System.out.println("== Test =="); - - new Prim(graph, graph, graph) - .withProgressLogger(TestProgressLogger.INSTANCE) - .computeMinimumSpanningTree(3871) - .getSpanningTree() - .forEach((s, t, r) -> { - System.out.println(s + " -> " + t); - return true; - }); - } -} diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java deleted file mode 100644 index f68bb0f92..000000000 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2017 "Neo4j, Inc." - * - * This file is part of Neo4j Graph Algorithms . - * - * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.graphalgo.impl; - -import com.carrotsearch.hppc.cursors.IntCursor; -import org.junit.*; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.neo4j.graphalgo.TestProgressLogger; -import org.neo4j.graphalgo.api.Graph; -import org.neo4j.graphalgo.api.GraphFactory; -import org.neo4j.graphalgo.core.GraphLoader; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; -import org.neo4j.graphalgo.core.huge.HugeGraphFactory; -import org.neo4j.graphalgo.core.neo4jview.GraphViewFactory; -import org.neo4j.graphalgo.core.utils.Pointer; -import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; -import org.neo4j.graphdb.*; -import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.test.rule.ImpermanentDatabaseRule; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.stream.Stream; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -@Ignore("rm if verified") -public final class ShortestPathDijkstraUserTest { - - private static final String PATH = "/Users/mknobloch/Downloads"; - - @ClassRule - public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); - - private static Graph graph; - - @BeforeClass - public static void setupGraph() throws KernelException { - - - String cypher = "" + - "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + - "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + - "MERGE (a:Airport {IATA: airport[4]}) " + - "set " + - "a.AirportID = airport[0], " + - "a.Name = airport[1], " + - "a.City = airport[2], " + - "a.Country = airport[3], " + - "a.ICAO = airport[5], " + - "a.Latitude = toFloat(airport[6]), " + - "a.Longitude = toFloat(airport[7]), " + - "a.Altitude = toInteger(airport[8]), " + - "a.Timezone = airport[9], " + - "a.DST= airport[10], " + - "a.TZ = airport[11], " + - "a.Type = airport[12], " + - "a.Source = airport[13] ;" + - "create index on :Airport(AirportID); " + - "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + - "MATCH (s:Airport {AirportID: route[3]}) " + - "MATCH (d:Airport {AirportID: route[5]}) " + - "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + - "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + - "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; - - - for (String part : cypher.split(";")) { - System.out.println(part); - try (Transaction transaction = DB.beginTx()) { - DB.execute(part); - transaction.success(); - } - } - - graph = (HeavyGraph) new GraphLoader(DB) - .withLabel("Airport") - .withRelationshipType("Route") - .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) - .withDirection(Direction.BOTH) - .load(HeavyGraphFactory.class); - } - - private static Node getNode(String id) { - final Pointer.GenericPointer nodePointer = new Pointer.GenericPointer<>(null); - DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { - nodePointer.v = row.getNode("n"); - return false; - }); - return nodePointer.v; - } - - @Test - public void test() throws Exception { - - ShortestPathDijkstra dijkstra = new ShortestPathDijkstra(graph) - .compute(getNode("CFU").getId(), - getNode("KRK").getId()); - - final Iterator iterator = dijkstra.getFinalPath().iterator(); - while (iterator.hasNext()) { - System.out.print(iterator.next().value + " -> "); - } - System.out.println(" = (" + dijkstra.getTotalCost() + ")"); - } -} From d2ff95cdd817c0eadcaab5f4489e18fb66404d6b Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Tue, 2 Oct 2018 14:20:56 +0200 Subject: [PATCH 14/16] WIP --- .../graphalgo/impl/ShortestPathDijkstra.java | 30 +++-- .../impl/ShortestPathDijkstraUserTest.java | 126 ++++++++++++++++++ 2 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java b/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java index ca37909b2..53b2a461e 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDijkstra.java @@ -149,26 +149,34 @@ private void run(int goal, Direction direction) { } visited.set(node); - final double costToSource = this.costs.getOrDefault(node, Double.MAX_VALUE); + double costs = this.costs.getOrDefault(node, Double.MAX_VALUE); graph.forEachRelationship( node, direction, (source, target, relId, weight) -> { - double newCosts = costToSource + weight; - if (newCosts < this.costs.getOrDefault(target, Double.MAX_VALUE)) { - this.costs.put(target, newCosts); - this.path.put(target, source); - if (visited.get(target)) { - queue.update(target); - } else { - queue.add(target, weight); - } - } + updateCosts(source, target, weight + costs); return true; }); progressLogger.logProgress((double) node / (nodeCount - 1)); } } + private void updateCosts(int source, int target, double newCosts) { + + if (costs.containsKey(target)) { + if (newCosts < costs.getOrDefault(target, Double.MAX_VALUE)) { + costs.put(target, newCosts); + path.put(target, source); + queue.update(target); + } + } else { + if (newCosts < costs.getOrDefault(target, Double.MAX_VALUE)) { + costs.put(target, newCosts); + path.put(target, source); + queue.add(target, newCosts); + } + } + } + @Override public ShortestPathDijkstra me() { return this; diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java new file mode 100644 index 000000000..f68bb0f92 --- /dev/null +++ b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2017 "Neo4j, Inc." + * + * This file is part of Neo4j Graph Algorithms . + * + * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.graphalgo.impl; + +import com.carrotsearch.hppc.cursors.IntCursor; +import org.junit.*; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.neo4j.graphalgo.TestProgressLogger; +import org.neo4j.graphalgo.api.Graph; +import org.neo4j.graphalgo.api.GraphFactory; +import org.neo4j.graphalgo.core.GraphLoader; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; +import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; +import org.neo4j.graphalgo.core.huge.HugeGraphFactory; +import org.neo4j.graphalgo.core.neo4jview.GraphViewFactory; +import org.neo4j.graphalgo.core.utils.Pointer; +import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; +import org.neo4j.graphdb.*; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.test.rule.ImpermanentDatabaseRule; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +@Ignore("rm if verified") +public final class ShortestPathDijkstraUserTest { + + private static final String PATH = "/Users/mknobloch/Downloads"; + + @ClassRule + public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); + + private static Graph graph; + + @BeforeClass + public static void setupGraph() throws KernelException { + + + String cypher = "" + + "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + + "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + + "MERGE (a:Airport {IATA: airport[4]}) " + + "set " + + "a.AirportID = airport[0], " + + "a.Name = airport[1], " + + "a.City = airport[2], " + + "a.Country = airport[3], " + + "a.ICAO = airport[5], " + + "a.Latitude = toFloat(airport[6]), " + + "a.Longitude = toFloat(airport[7]), " + + "a.Altitude = toInteger(airport[8]), " + + "a.Timezone = airport[9], " + + "a.DST= airport[10], " + + "a.TZ = airport[11], " + + "a.Type = airport[12], " + + "a.Source = airport[13] ;" + + "create index on :Airport(AirportID); " + + "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + + "MATCH (s:Airport {AirportID: route[3]}) " + + "MATCH (d:Airport {AirportID: route[5]}) " + + "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + + "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + + "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; + + + for (String part : cypher.split(";")) { + System.out.println(part); + try (Transaction transaction = DB.beginTx()) { + DB.execute(part); + transaction.success(); + } + } + + graph = (HeavyGraph) new GraphLoader(DB) + .withLabel("Airport") + .withRelationshipType("Route") + .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) + .withDirection(Direction.BOTH) + .load(HeavyGraphFactory.class); + } + + private static Node getNode(String id) { + final Pointer.GenericPointer nodePointer = new Pointer.GenericPointer<>(null); + DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { + nodePointer.v = row.getNode("n"); + return false; + }); + return nodePointer.v; + } + + @Test + public void test() throws Exception { + + ShortestPathDijkstra dijkstra = new ShortestPathDijkstra(graph) + .compute(getNode("CFU").getId(), + getNode("KRK").getId()); + + final Iterator iterator = dijkstra.getFinalPath().iterator(); + while (iterator.hasNext()) { + System.out.print(iterator.next().value + " -> "); + } + System.out.println(" = (" + dijkstra.getTotalCost() + ")"); + } +} From 57e98f4908010bbdd23dc19af0e8f10ac1273763 Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Tue, 2 Oct 2018 14:22:50 +0200 Subject: [PATCH 15/16] rm usertest --- .../impl/ShortestPathDijkstraUserTest.java | 126 ------------------ 1 file changed, 126 deletions(-) delete mode 100644 tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java diff --git a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java b/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java deleted file mode 100644 index f68bb0f92..000000000 --- a/tests/src/test/java/org/neo4j/graphalgo/impl/ShortestPathDijkstraUserTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2017 "Neo4j, Inc." - * - * This file is part of Neo4j Graph Algorithms . - * - * Neo4j Graph Algorithms is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.graphalgo.impl; - -import com.carrotsearch.hppc.cursors.IntCursor; -import org.junit.*; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.neo4j.graphalgo.TestProgressLogger; -import org.neo4j.graphalgo.api.Graph; -import org.neo4j.graphalgo.api.GraphFactory; -import org.neo4j.graphalgo.core.GraphLoader; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraph; -import org.neo4j.graphalgo.core.heavyweight.HeavyGraphFactory; -import org.neo4j.graphalgo.core.huge.HugeGraphFactory; -import org.neo4j.graphalgo.core.neo4jview.GraphViewFactory; -import org.neo4j.graphalgo.core.utils.Pointer; -import org.neo4j.graphalgo.impl.yens.YensKShortestPaths; -import org.neo4j.graphdb.*; -import org.neo4j.internal.kernel.api.exceptions.KernelException; -import org.neo4j.test.rule.ImpermanentDatabaseRule; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.stream.Stream; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -@Ignore("rm if verified") -public final class ShortestPathDijkstraUserTest { - - private static final String PATH = "/Users/mknobloch/Downloads"; - - @ClassRule - public static final ImpermanentDatabaseRule DB = new ImpermanentDatabaseRule(); - - private static Graph graph; - - @BeforeClass - public static void setupGraph() throws KernelException { - - - String cypher = "" + - "CREATE CONSTRAINT ON (a:Airport) ASSERT a.IATA IS UNIQUE; " + - "LOAD CSV FROM 'file://" + PATH + "/airports.txt' as airport " + - "MERGE (a:Airport {IATA: airport[4]}) " + - "set " + - "a.AirportID = airport[0], " + - "a.Name = airport[1], " + - "a.City = airport[2], " + - "a.Country = airport[3], " + - "a.ICAO = airport[5], " + - "a.Latitude = toFloat(airport[6]), " + - "a.Longitude = toFloat(airport[7]), " + - "a.Altitude = toInteger(airport[8]), " + - "a.Timezone = airport[9], " + - "a.DST= airport[10], " + - "a.TZ = airport[11], " + - "a.Type = airport[12], " + - "a.Source = airport[13] ;" + - "create index on :Airport(AirportID); " + - "LOAD CSV FROM 'file://" + PATH + "/routes.txt' as route " + - "MATCH (s:Airport {AirportID: route[3]}) " + - "MATCH (d:Airport {AirportID: route[5]}) " + - "CREATE (s)-[:Route {Airline:route[1],Codeshare : [6]}]->(d);" + - "MATCH (a:Airport)-[r:Route]->(b:Airport)\n" + - "SET r.distance=distance(point({longitude: a.Longitude, latitude : a.Latitude}),point({longitude: b.Longitude, latitude : b.Latitude}))"; - - - for (String part : cypher.split(";")) { - System.out.println(part); - try (Transaction transaction = DB.beginTx()) { - DB.execute(part); - transaction.success(); - } - } - - graph = (HeavyGraph) new GraphLoader(DB) - .withLabel("Airport") - .withRelationshipType("Route") - .withRelationshipWeightsFromProperty("distance", Double.MAX_VALUE) - .withDirection(Direction.BOTH) - .load(HeavyGraphFactory.class); - } - - private static Node getNode(String id) { - final Pointer.GenericPointer nodePointer = new Pointer.GenericPointer<>(null); - DB.execute("MATCH (n:Airport) WHERE n.IATA = '" + id + "' RETURN n").accept(row -> { - nodePointer.v = row.getNode("n"); - return false; - }); - return nodePointer.v; - } - - @Test - public void test() throws Exception { - - ShortestPathDijkstra dijkstra = new ShortestPathDijkstra(graph) - .compute(getNode("CFU").getId(), - getNode("KRK").getId()); - - final Iterator iterator = dijkstra.getFinalPath().iterator(); - while (iterator.hasNext()) { - System.out.print(iterator.next().value + " -> "); - } - System.out.println(" = (" + dijkstra.getTotalCost() + ")"); - } -} From ebe34d63875f19589d45d9b3db4a0f3172c0b78e Mon Sep 17 00:00:00 2001 From: Martin Knobloch Date: Tue, 2 Oct 2018 14:50:54 +0200 Subject: [PATCH 16/16] rm prim user test --- .../src/main/java/org/neo4j/graphalgo/impl/Prim.java | 12 +++++++----- .../java/org/neo4j/graphalgo/impl/ShortestPaths.java | 2 +- .../org/neo4j/graphalgo/impl/spanningTrees/Prim.java | 9 +++++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java b/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java index 5f5bc0823..7c218f898 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/Prim.java @@ -93,12 +93,14 @@ private SpanningTree prim(int startNode, boolean max) { } // inverting the weight calculates maximum- instead of minimum spanning tree final double w = max ? -weights.weightOf(s, t) : weights.weightOf(s, t); - /* - cost is updated 1x for each node. - */ if (w < cost.getOrDefault(t, Double.MAX_VALUE)) { - cost.put(t, w); - queue.add(t, UNUSED); + if (cost.containsKey(t)) { + cost.put(t, w); + queue.update(t); + } else { + cost.put(t, w); + queue.add(t, w); + } spanningTree.parent[t] = s; } return true; diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPaths.java b/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPaths.java index 65521b815..bd71c33ec 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPaths.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPaths.java @@ -99,7 +99,7 @@ private void run() { final double targetCosts = this.costs.getOrDefault(target, Double.POSITIVE_INFINITY); if (weight + sourceCosts < targetCosts) { costs.put(target, weight + sourceCosts); - queue.set(target, targetCosts); + queue.set(target, weight + sourceCosts); } return true; }); diff --git a/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/Prim.java b/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/Prim.java index 0967ce4d3..9e57cbde9 100644 --- a/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/Prim.java +++ b/algo/src/main/java/org/neo4j/graphalgo/impl/spanningTrees/Prim.java @@ -86,8 +86,13 @@ private SpanningTree prim(int startNode, boolean max) { // invert weight to calculate maximum final double w = max ? -weights.weightOf(s, t) : weights.weightOf(s, t); if (w < cost.getOrDefault(t, Double.MAX_VALUE)) { - cost.put(t, w); - queue.add(t, -1.0); + if (cost.containsKey(t)) { + cost.put(t, w); + queue.update(t); + } else { + cost.put(t, w); + queue.add(t, -1.0); + } parent[t] = s; } return true;