From f6056351e5ff789c894f33e426b5f09d6d8feeba Mon Sep 17 00:00:00 2001 From: Thibault Debatty Date: Wed, 15 Feb 2017 13:09:03 +0100 Subject: [PATCH] Neighborlist is now a synchronized queue, to avoid multithreading errors --- .../java/info/debatty/java/graphs/Graph.java | 5 +- .../debatty/java/graphs/NeighborList.java | 4 +- .../java/util/BoundedPriorityQueue.java | 6 +- .../SynchronizedBoundedPriorityQueue.java | 176 ++++++++++++++++++ .../info/debatty/java/graphs/GraphTest.java | 4 +- .../graphs/build/ThreadedNNDescentTest.java | 43 ++++- 6 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 src/main/java/info/debatty/java/util/SynchronizedBoundedPriorityQueue.java diff --git a/src/main/java/info/debatty/java/graphs/Graph.java b/src/main/java/info/debatty/java/graphs/Graph.java index dd9aa40..d6570ad 100644 --- a/src/main/java/info/debatty/java/graphs/Graph.java +++ b/src/main/java/info/debatty/java/graphs/Graph.java @@ -1143,7 +1143,7 @@ public int hashCode() { } @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { if (obj == null) { return false; } @@ -1151,11 +1151,12 @@ public boolean equals(Object obj) { return false; } final Graph other = (Graph) obj; + System.out.println("Delegating to map..."); if (this.map != other.map && (this.map == null || !this.map.equals(other.map))) { return false; } return true; } - + } diff --git a/src/main/java/info/debatty/java/graphs/NeighborList.java b/src/main/java/info/debatty/java/graphs/NeighborList.java index 3e68595..eeaa27d 100644 --- a/src/main/java/info/debatty/java/graphs/NeighborList.java +++ b/src/main/java/info/debatty/java/graphs/NeighborList.java @@ -1,6 +1,6 @@ package info.debatty.java.graphs; -import info.debatty.java.util.BoundedPriorityQueue; +import info.debatty.java.util.SynchronizedBoundedPriorityQueue; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; @@ -12,7 +12,7 @@ * * @author Thibault Debatty */ -public class NeighborList extends BoundedPriorityQueue +public class NeighborList extends SynchronizedBoundedPriorityQueue implements Serializable { public static ArrayList diff --git a/src/main/java/info/debatty/java/util/BoundedPriorityQueue.java b/src/main/java/info/debatty/java/util/BoundedPriorityQueue.java index e2dc93f..66f6d7d 100644 --- a/src/main/java/info/debatty/java/util/BoundedPriorityQueue.java +++ b/src/main/java/info/debatty/java/util/BoundedPriorityQueue.java @@ -1,7 +1,7 @@ package info.debatty.java.util; import java.io.Serializable; -import java.util.concurrent.PriorityBlockingQueue; +import java.util.PriorityQueue; /** * This class implements a bounded priority queue A structure that always keeps @@ -10,7 +10,8 @@ * @author Thibault Debatty * @param */ -public class BoundedPriorityQueue extends PriorityBlockingQueue implements Serializable { +public class BoundedPriorityQueue extends PriorityQueue + implements Serializable { private final int capacity; @@ -84,6 +85,7 @@ public final int getCapacity() { * @param other * @return */ + @Override public boolean equals(final Object other) { if (other == null) { return false; diff --git a/src/main/java/info/debatty/java/util/SynchronizedBoundedPriorityQueue.java b/src/main/java/info/debatty/java/util/SynchronizedBoundedPriorityQueue.java new file mode 100644 index 0000000..0a70a5c --- /dev/null +++ b/src/main/java/info/debatty/java/util/SynchronizedBoundedPriorityQueue.java @@ -0,0 +1,176 @@ +/* + * The MIT License + * + * Copyright 2017 Thibault Debatty. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package info.debatty.java.util; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Queue; + +/** + * + * @author Thibault Debatty + * @param + */ +public class SynchronizedBoundedPriorityQueue + implements Serializable, Iterable, Collection, Queue { + + private final BoundedPriorityQueue queue; + + public SynchronizedBoundedPriorityQueue(final int capacity) { + queue = new BoundedPriorityQueue(capacity); + } + + @Override + public Iterator iterator() { + return queue.iterator(); + } + + @Override + public int size() { + synchronized (this) { + return queue.size(); + } + } + + public boolean offer(E e) { + synchronized (this) { + return queue.offer(e); + } + } + + public E poll() { + synchronized (this) { + return queue.poll(); + } + } + + public E peek() { + synchronized (this) { + return queue.peek(); + } + } + + public boolean isEmpty() { + synchronized (this) { + return queue.isEmpty(); + } + } + + public boolean contains(Object o) { + synchronized (this) { + return queue.contains(o); + } + } + + public Object[] toArray() { + synchronized (this) { + return queue.toArray(); + } + } + + public T[] toArray(T[] a) { + synchronized (this) { + return queue.toArray(a); + } + } + + public boolean add(E e) { + synchronized (this) { + return queue.add(e); + } + } + + public boolean remove(Object o) { + synchronized (this) { + return queue.remove(o); + } + } + + public boolean containsAll(Collection c) { + synchronized (this) { + return queue.containsAll(c); + } + } + + public boolean addAll(Collection c) { + synchronized (this) { + return queue.addAll(c); + } + } + + public boolean removeAll(Collection c) { + synchronized (this) { + return queue.removeAll(c); + } + } + + public boolean retainAll(Collection c) { + synchronized (this) { + return queue.retainAll(c); + } + } + + public void clear() { + synchronized (this) { + queue.clear(); + } + } + + public E remove() { + synchronized (this) { + return queue.remove(); + } + } + + public E element() { + synchronized (this) { + return queue.element(); + } + } + + public boolean equals(Object other) { + if (other == null) { + return false; + } + + if (!other.getClass().isInstance(this)) { + return false; + } + + SynchronizedBoundedPriorityQueue other_synced = + (SynchronizedBoundedPriorityQueue) other; + + synchronized (this) { + return queue.equals(other_synced.queue); + } + } + + public String toString() { + synchronized (this) { + return queue.toString(); + } + } +} diff --git a/src/test/java/info/debatty/java/graphs/GraphTest.java b/src/test/java/info/debatty/java/graphs/GraphTest.java index 9a051fa..38286a2 100644 --- a/src/test/java/info/debatty/java/graphs/GraphTest.java +++ b/src/test/java/info/debatty/java/graphs/GraphTest.java @@ -115,8 +115,8 @@ public void testStronglyConnectedComponents() { * Test of stronglyConnectedComponents method, of class Graph. */ public void testStronglyConnectedComponents2() { - System.out.println("Strongly connected components"); - System.out.println("============================="); + System.out.println("Strongly connected components 2"); + System.out.println("==============================="); // Generate two series of nodes ArrayList nodes = new ArrayList(); diff --git a/src/test/java/info/debatty/java/graphs/build/ThreadedNNDescentTest.java b/src/test/java/info/debatty/java/graphs/build/ThreadedNNDescentTest.java index 4b92d43..06939f4 100644 --- a/src/test/java/info/debatty/java/graphs/build/ThreadedNNDescentTest.java +++ b/src/test/java/info/debatty/java/graphs/build/ThreadedNNDescentTest.java @@ -75,7 +75,7 @@ public double similarity(Integer value1, Integer value2) { Integer first_node = graph.first(); System.out.println(threaded_graph.getNeighbors(first_node)); - assertEquals(graph.getNeighbors(first_node).countCommons(threaded_graph.getNeighbors(first_node)), k); + assertEquals(k, graph.getNeighbors(first_node).countCommons(threaded_graph.getNeighbors(first_node))); int correct_edges = threaded_graph.compare(graph); double correct_ratio = 1.0 * correct_edges / (count * k); @@ -87,4 +87,45 @@ public double similarity(Integer value1, Integer value2) { assertTrue("Too many wrong edges!", correct_ratio >= MIN_CORRECT_RATIO); } + /** + * Test that the threaded nndescent algorithms produces neighborlists of + * the correct size (might fail because of missing synchronization). + */ + public void testK() { + System.out.println("test K"); + + int count = 10; + int k = 5; + int trials = 10; + + // Generate some nodes + Random r = new Random(); + LinkedList nodes = new LinkedList(); + for (int i = 0; i < count; i++) { + nodes.add(r.nextInt()); + } + + // Define the similarity + SimilarityInterface similarity = + new SimilarityInterface() { + + public double similarity(final Integer value1, final Integer value2) { + return 1.0 / (1.0 + Math.abs(value1 - value2)); + } + }; + + for (int i = 0; i < trials; i++) { + System.out.println("NNdescent threaded graph builder..."); + ThreadedNNDescent threaded_builder = + new ThreadedNNDescent(); + threaded_builder.setK(k); + threaded_builder.setSimilarity(similarity); + Graph threaded_graph = threaded_builder.computeGraph(nodes); + + for (Integer value : threaded_graph.getNodes()) { + assertEquals(k, threaded_graph.getNeighbors(value).size()); + } + } + } + }