Skip to content

Commit

Permalink
Fix for Issue jgrapht#1074 - Delta stepping with custom vertices fails (
Browse files Browse the repository at this point in the history
jgrapht#1094)

* Issue jgrapht#1074 - Delta stepping with custom vertices fails

This commit enables using graphs with custom vertices for
DeltaSteppingShortestPath algorithm. Previously this use
case failed because ConcurrentSkipListSet required its
elements to implement Comparable interface.

jgrapht#1074

* Added custom comparator to DeltaSteppingShortestPath algorithm & updated
the tests to reflect the changes

* Fixed getContentAndReplace method
  • Loading branch information
SChudakov authored Aug 17, 2021
1 parent 7d3612c commit 0987480
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;

/**
* Parallel implementation of a single-source shortest path algorithm: the delta-stepping algorithm.
Expand Down Expand Up @@ -120,6 +121,15 @@ public class DeltaSteppingShortestPath<V, E>
* Buckets structure.
*/
private List<Set<V>> bucketStructure;
/**
* Comparator for vertices in the graph which is used to create {@code ConcurrentSkipListSet}
* instances for the {@code bucketStructure}.
*/
private Comparator<V> vertexComparator;
/**
* Supplier of the buckets for the {@code bucketStructure}.
*/
private Supplier<Set<V>> bucketsSupplier;

/**
* Decorator for {@link ThreadPoolExecutor} supplied to this algorithm that enables to keep
Expand Down Expand Up @@ -159,9 +169,40 @@ public DeltaSteppingShortestPath(Graph<V, E> graph, ThreadPoolExecutor executor)
}

/**
* Constructs a new instance of the algorithm for a given graph, delta and {@code executor}. It
* is up to a user of this algorithm to handle the creation and termination of the provided
* {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see
* Constructs a new instance of the algorithm for a given {@code graph}, {@code executor}
* and {@code vertexComparator}. It is up to a user of this algorithm to handle the creation
* and termination of the provided {@code executor}. For utility methods to manage a
* {@code ThreadPoolExecutor} see {@link ConcurrencyUtil}. {@code vertexComparator}
* provided via this constructor is used to create instances of {@code ConcurrentSkipListSet}
* for the individual buckets. This gives a gives a small performance benefit for shortest
* paths computation.
*
* @param graph graph
* @param executor executor which will be used for parallelization
* @param vertexComparator comparator for vertices of the {@code graph}
*/
public DeltaSteppingShortestPath(Graph<V, E> graph, ThreadPoolExecutor executor, Comparator<V> vertexComparator)
{
this(graph, 0.0, executor, vertexComparator);
}

/**
* Constructs a new instance of the algorithm for a given graph, delta.
*
* @param graph the graph
* @param delta bucket width
* @deprecated replaced with {@link #DeltaSteppingShortestPath(Graph, double, ThreadPoolExecutor)}
*/
@Deprecated
public DeltaSteppingShortestPath(Graph<V, E> graph, double delta)
{
this(graph, delta, DEFAULT_PARALLELISM);
}

/**
* Constructs a new instance of the algorithm for a given graph, delta and {@code executor}.
* It is up to a user of this algorithm to handle the creation and termination of the
* provided {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see
* {@link ConcurrencyUtil}.
*
* @param graph the graph
Expand All @@ -171,7 +212,64 @@ public DeltaSteppingShortestPath(Graph<V, E> graph, ThreadPoolExecutor executor)
public DeltaSteppingShortestPath(Graph<V, E> graph, double delta, ThreadPoolExecutor executor)
{
super(graph);
init(graph, delta, executor);
Objects.requireNonNull(executor, "executor must not be null!");
init(graph, delta, executor, null);
}

/**
* Constructs a new instance of the algorithm for a given graph, delta, {@code executor}
* and {@code vertexComparator}. It is up to a user of this algorithm to handle the creation
* and termination of the provided {@code executor}. For utility methods to manage a
* {@code ThreadPoolExecutor} see {@link ConcurrencyUtil}. {@code vertexComparator}
* provided via this constructor is used to create instances of {@code ConcurrentSkipListSet}
* for the individual buckets. This gives a gives a small performance benefit for shortest
* paths computation.
*
* @param graph the graph
* @param delta bucket width
* @param executor executor which will be used for parallelization
* @param vertexComparator comparator for vertices of the {@code graph}
*/
public DeltaSteppingShortestPath(Graph<V, E> graph, double delta, ThreadPoolExecutor executor,
Comparator<V> vertexComparator)
{
super(graph);
Objects.requireNonNull(executor, "executor must not be null!");
Objects.requireNonNull(executor, "vertexComparator must not be null!");
init(graph, delta, executor, vertexComparator);
}

/**
* Constructs a new instance of the algorithm for a given graph, parallelism.
*
* @param graph the graph
* @param parallelism maximum number of threads used in the computations
* @deprecated replaced with {@link #DeltaSteppingShortestPath(Graph, ThreadPoolExecutor)}
*/
@Deprecated
public DeltaSteppingShortestPath(Graph<V, E> graph, int parallelism)
{
this(graph, 0.0, parallelism);
}


/**
* Constructs a new instance of the algorithm for a given graph, delta, parallelism. If delta is
* $0.0$ it will be computed during the algorithm execution. In general if the value of
* $\frac{maximum edge weight}{maximum outdegree}$ is known beforehand, it is preferable to
* specify it via this constructor, because processing the whole graph to compute this value may
* significantly slow down the algorithm.
*
* @param graph the graph
* @param delta bucket width
* @param parallelism maximum number of threads used in the computations
* @deprecated replaced with {@link #DeltaSteppingShortestPath(Graph, double, ThreadPoolExecutor)}
*/
@Deprecated
public DeltaSteppingShortestPath(Graph<V, E> graph, double delta, int parallelism)
{
super(graph);
init(graph, delta, ConcurrencyUtil.createThreadPoolExecutor(parallelism), null);
}

/**
Expand All @@ -183,13 +281,13 @@ public DeltaSteppingShortestPath(Graph<V, E> graph, double delta, ThreadPoolExec
* @param delta bucket width
* @param executor executor which will be used for parallelization
*/
private void init(Graph<V, E> graph, double delta, ThreadPoolExecutor executor)
{
private void init(Graph<V, E> graph, double delta, ThreadPoolExecutor executor, Comparator<V> vertexComparator){
if (delta < 0) {
throw new IllegalArgumentException(DELTA_MUST_BE_NON_NEGATIVE);
}
this.delta = delta;
this.parallelism = executor.getMaximumPoolSize();
this.vertexComparator = vertexComparator;
distanceAndPredecessorMap = new ConcurrentHashMap<>(graph.vertexSet().size());
completionService = new ExecutorCompletionService<>(executor);
verticesQueue = new ConcurrentLinkedQueue<>();
Expand Down Expand Up @@ -302,8 +400,9 @@ public SingleSourcePaths<V, E> getPaths(V source)
}
numOfBuckets = (int) (Math.ceil(maxEdgeWeight / delta) + 1);
bucketStructure = new ArrayList<>(numOfBuckets);
bucketsSupplier = getBucketsSupplier(source);
for (int i = 0; i < numOfBuckets; i++) {
bucketStructure.add(new ConcurrentSkipListSet<>());
bucketStructure.add(bucketsSupplier.get());
}
fillDistanceAndPredecessorMap();

Expand All @@ -312,6 +411,23 @@ public SingleSourcePaths<V, E> getPaths(V source)
return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap);
}

/**
* Creates a supplier of sets for the {@code bucketStructure}.
*
* @param vertex a vertex in the graph
* @return supplier of buckets
*/
private Supplier<Set<V>> getBucketsSupplier(V vertex) {
if(vertexComparator != null){
return () -> new ConcurrentSkipListSet<>(vertexComparator);
} else if (vertex instanceof Comparable) {
return () -> new ConcurrentSkipListSet<>();
} else {
return () -> Collections.newSetFromMap(new ConcurrentHashMap<>());
}
}


/**
* Calculates value of {@link #delta}. The value is calculated as the maximal edge weight
* divided by maximal out-degree in the {@link #graph} or $1.0$ if edge set of the
Expand Down Expand Up @@ -570,17 +686,16 @@ private int bucketIndex(double distance)
}

/**
* Replaces the bucket at the {@code bucketIndex} index with a new instance of the
* {@link ConcurrentSkipListSet}. Return the reference to the set that was previously in the
* bucket.
* Replaces the bucket at the {@code bucketIndex} with a new instance of the concurrent set.
* Return the reference to the set that was previously in the bucket.
*
* @param bucketIndex bucket index
* @return content of the bucket
*/
private Set<V> getContentAndReplace(int bucketIndex)
{
Set<V> result = bucketStructure.get(bucketIndex);
bucketStructure.set(bucketIndex, new ConcurrentSkipListSet<V>());
bucketStructure.set(bucketIndex, bucketsSupplier.get());
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,68 @@ public void testLineGraph()
}
}

@Test
public void testSupplyComparator() {
Graph<String, DefaultWeightedEdge> graph =
new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class);
String v0 = "v0";
String v1 = "v1";
String v2 = "v2";
Graphs.addEdgeWithVertices(graph, v0, v1);
Graphs.addEdgeWithVertices(graph, v1, v2);

DeltaSteppingShortestPath<String, DefaultWeightedEdge> shortestPath
= new DeltaSteppingShortestPath<>(graph, executor);
GraphPath<String, DefaultWeightedEdge> path = shortestPath.getPath(v0, v2);

assertEquals(path.getWeight(), 2.0, 1e-9);
assertEquals(path.getVertexList(), Arrays.asList(v0, v1, v2));
}

@Test
public void testComparableVertices() {
class ComparableVertex implements Comparable<ComparableVertex>{
@Override
public int compareTo(ComparableVertex comparableVertex) {
return 0;
}
}
ComparableVertex v1 = new ComparableVertex();
ComparableVertex v2 = new ComparableVertex();

Graph<ComparableVertex, DefaultWeightedEdge> graph =
new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class);
Graphs.addAllVertices(graph, Arrays.asList(v1, v2));
Graphs.addEdge(graph, v1, v2, 1.0);

DeltaSteppingShortestPath<ComparableVertex, DefaultWeightedEdge> shortestPath
= new DeltaSteppingShortestPath<>(graph, executor);
GraphPath<ComparableVertex, DefaultWeightedEdge> path = shortestPath.getPath(v1, v2);

assertEquals(path.getWeight(), 1.0, 1e-9);
assertEquals(path.getVertexList(), Arrays.asList(v1, v2));
}

@Test
public void testNonComparableVertices() {
class NonComparableVertex {
}
NonComparableVertex v1 = new NonComparableVertex();
NonComparableVertex v2 = new NonComparableVertex();

Graph<NonComparableVertex, DefaultWeightedEdge> graph =
new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class);
Graphs.addAllVertices(graph, Arrays.asList(v1, v2));
Graphs.addEdge(graph, v1, v2, 1.0);

DeltaSteppingShortestPath<NonComparableVertex, DefaultWeightedEdge> shortestPath
= new DeltaSteppingShortestPath<>(graph, executor);
GraphPath<NonComparableVertex, DefaultWeightedEdge> path = shortestPath.getPath(v1, v2);

assertEquals(path.getWeight(), 1.0, 1e-9);
assertEquals(path.getVertexList(), Arrays.asList(v1, v2));
}

@Test
public void testGetPath()
{
Expand Down

0 comments on commit 0987480

Please sign in to comment.