diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b91a3192327ae476c221054c6888267516fe19..1c4c17bdeae18a7ac08447e75f4a6f3a25bd4b95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Upcoming version: ----------------- + - implemented various different types for node queue (Nikolaus Krismer) - externalizing dependency versions (Nikolaus Krismer) - added configuration for vienna (Nikolaus Krismer) - upgrading gradle plugin shadowjar (Nikolaus Krismer) diff --git a/src/main/java/it/unibz/inf/isochrone/algorithm/AbstractAlgorithm.java b/src/main/java/it/unibz/inf/isochrone/algorithm/AbstractAlgorithm.java index 6e1c2f75703f98c3e8bab6421ec0be49226b0769..c32adca1984532f39084197cd539c4fd95fea759 100644 --- a/src/main/java/it/unibz/inf/isochrone/algorithm/AbstractAlgorithm.java +++ b/src/main/java/it/unibz/inf/isochrone/algorithm/AbstractAlgorithm.java @@ -7,7 +7,6 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; @@ -20,6 +19,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import it.unibz.inf.isochrone.algorithm.AlgorithmCache.Mode; +import it.unibz.inf.isochrone.algorithm.queue.INodeQueue; +import it.unibz.inf.isochrone.algorithm.queue.NodeQueueFactory; import it.unibz.inf.isochrone.algorithm.speed.ISpeedEstimation; import it.unibz.inf.isochrone.algorithm.speed.SpeedEstimationFactory; import it.unibz.inf.isochrone.config.ConfigAlgorithm; @@ -36,7 +37,6 @@ import it.unibz.inf.isochrone.network.NodeConnection; import it.unibz.inf.isochrone.network.ServiceCalendar; import it.unibz.inf.isochrone.output.AbstractOutput; import it.unibz.inf.isochrone.output.MemoryOutput; -import it.unibz.inf.isochrone.util.FibonacciHeap; /** * The isochrone algorithm. As the different algorithms all have @@ -67,8 +67,8 @@ public abstract class AbstractAlgorithm { private final AlgorithmCacheHelper cache; private final ConfigAlgorithm config; - private final FibonacciHeap<INode> nodeQueue; - private final Map<INode, FibonacciHeap.Entry<INode>> nodeQueueEntries; + private final INodeQueue<INode> nodeQueue; + private final Predicate<IEdge> predicateContinuous; private final AtomicBoolean running = new AtomicBoolean(false); private final DaySpanContainer<String> routeIds; @@ -162,11 +162,7 @@ public abstract class AbstractAlgorithm { } } - // FibonacciHeap serves as a PriorityQueue here (without the need to remove entries before adding them) - // To find HeapEntries we use a HashMap... although we need two structures this is still way faster than using a PriorityQueue alone - this.nodeQueue = new FibonacciHeap<>(); - this.nodeQueueEntries = new HashMap<>(); - + this.nodeQueue = NodeQueueFactory.getInstance(config.getQueueType()); if (cIsMultimodal) { predicateContinuous = l -> l.getMode() == Edge.Mode.STREET || l.getMode() == Edge.Mode.LINK; } else { @@ -421,7 +417,7 @@ public abstract class AbstractAlgorithm { if (isWithinDuration(newDuration)) { targetNode.setDuration(newDuration); - updateNodeQueue(targetNode); + nodeQueue.update(targetNode); } } } @@ -547,8 +543,7 @@ public abstract class AbstractAlgorithm { running.set(true); while (running.get() && hasExpandableNodes() && (!limitNodeCount || expandedNodes < cLimit)) { - final INode n = nodeQueue.dequeueMin().getValue(); - nodeQueueEntries.remove(n); + final INode n = nodeQueue.dequeueMin(); expandNode(n); ++expandedNodes; @@ -563,20 +558,7 @@ public abstract class AbstractAlgorithm { * @param node The node that was changed */ protected void updateNodeQueue(final INode node) { - if (node == null) { - return; - } - - final float newPriority = node.getDuration(); - FibonacciHeap.Entry<INode> e = nodeQueueEntries.get(node); - if (e == null) { - e = nodeQueue.enqueue(node, newPriority); - nodeQueueEntries.put(node, e); - } else if (newPriority < e.getPriority()) { - // this check for decreasing priority is only needed when using incremental cache calculation. - nodeQueue.decreaseKey(e, newPriority); - e.setValue(node); - } + nodeQueue.update(node); } // Package private methods diff --git a/src/main/java/it/unibz/inf/isochrone/algorithm/queue/FibonacciHeap.java b/src/main/java/it/unibz/inf/isochrone/algorithm/queue/FibonacciHeap.java index 267bfc660a17be5d79e2d5487eed2279c08f553f..0e2b52dfc65b338378aac80ff791f019fc53c8fa 100644 --- a/src/main/java/it/unibz/inf/isochrone/algorithm/queue/FibonacciHeap.java +++ b/src/main/java/it/unibz/inf/isochrone/algorithm/queue/FibonacciHeap.java @@ -51,7 +51,7 @@ * dequeueMin to extract it. */ -package it.unibz.inf.isochrone.util; +package it.unibz.inf.isochrone.algorithm.queue; import java.util.ArrayList; import java.util.List; @@ -63,7 +63,7 @@ import java.util.NoSuchElementException; * @param T The type of elements to store in the heap. * @author Keith Schwarz (htiek@cs.stanford.edu) */ -public final class FibonacciHeap<T> { +final class FibonacciHeap<T> { /** * In order for all of the Fibonacci heap operations to complete in O(1), * clients need to have O(1) access to any element in the heap. We make diff --git a/src/main/java/it/unibz/inf/isochrone/algorithm/queue/NodeQueueFactory.java b/src/main/java/it/unibz/inf/isochrone/algorithm/queue/NodeQueueFactory.java index 5c18fa865839ce39f33d750f33fec048afa7da42..f9d6d25b86ea303747c91bce25ea525ff11134ec 100644 --- a/src/main/java/it/unibz/inf/isochrone/algorithm/queue/NodeQueueFactory.java +++ b/src/main/java/it/unibz/inf/isochrone/algorithm/queue/NodeQueueFactory.java @@ -1,6 +1,5 @@ package it.unibz.inf.isochrone.algorithm.queue; -import it.unibz.inf.isochrone.config.ConfigAlgorithm.QueueType; import it.unibz.inf.isochrone.network.INode; public final class NodeQueueFactory { diff --git a/src/main/java/it/unibz/inf/isochrone/config/ConfigAlgorithm.java b/src/main/java/it/unibz/inf/isochrone/config/ConfigAlgorithm.java index 55cbcba1a9fcd1c14bc28212f268fc88e84d898d..b46323e0b89d4dba84c10c9668866947fc86e29b 100644 --- a/src/main/java/it/unibz/inf/isochrone/config/ConfigAlgorithm.java +++ b/src/main/java/it/unibz/inf/isochrone/config/ConfigAlgorithm.java @@ -13,6 +13,8 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.Immutable; +import it.unibz.inf.isochrone.algorithm.queue.QueueType; + /** * A query is used to compute different isochrones. It is fed to the * Isochrone algorithm which will use the data to figure out what type @@ -63,6 +65,7 @@ public final class ConfigAlgorithm implements Serializable { private final boolean outputOnlyExpandedEdges; private final Collection<Point2D.Double> queryPoints; private final Integer queryPointSRID; + private final QueueType queueType; private final SpeedEstimation speedEstimation; private final boolean useCache; private final boolean useIncrementalCache; @@ -84,6 +87,7 @@ public final class ConfigAlgorithm implements Serializable { private Mode mode = Mode.ISOCHRONE_MULTIMODAL; private boolean outputOnlyExpandedEdges = false; private Integer queryPointSRID = null; + private QueueType queueType = QueueType.FIBONACCI_HEAP; private SpeedEstimation speedEstimation = SpeedEstimation.VALUE; private boolean useCache = false; private boolean useIncrementalCache = false; @@ -156,6 +160,10 @@ public final class ConfigAlgorithm implements Serializable { } public ConfigAlgorithmBuilder mode(final Mode mode) { + if (mode == null) { + throw new NullPointerException("Can not set mode to null!"); + } + this.mode = mode; return this; } @@ -179,7 +187,20 @@ public final class ConfigAlgorithm implements Serializable { return this; } + public ConfigAlgorithmBuilder queueType(final QueueType queueType) { + if (queueType == null) { + throw new NullPointerException("Can not set queue type to null!"); + } + + this.queueType = queueType; + return this; + } + public ConfigAlgorithmBuilder speedEstimation(final SpeedEstimation speedEstimation) { + if (speedEstimation == null) { + throw new NullPointerException("Can not set speed estimation to null!"); + } + this.speedEstimation = speedEstimation; if (speedEstimation != SpeedEstimation.USER_PROFILE) { this.userProfile = null; @@ -226,6 +247,7 @@ public final class ConfigAlgorithm implements Serializable { this.useCache = builder.useCache; this.useIncrementalCache = builder.useIncrementalCache; this.queryPoints = builder.queryPoints; + this.queueType = builder.queueType; this.speedEstimation = builder.speedEstimation; this.userProfile = builder.userProfile; @@ -296,6 +318,10 @@ public final class ConfigAlgorithm implements Serializable { return queryPointSRID; } + public QueueType getQueueType() { + return queueType; + } + public SpeedEstimation getSpeedEstimation() { return speedEstimation; } @@ -353,6 +379,7 @@ public final class ConfigAlgorithm implements Serializable { if (getClass() != obj.getClass()) { return false; } + final ConfigAlgorithm other = (ConfigAlgorithm) obj; if (activateStatistics != other.activateStatistics) { return false; @@ -400,6 +427,12 @@ public final class ConfigAlgorithm implements Serializable { } else if (!queryPoints.equals(other.queryPoints)) { return false; } + if (queueType != other.queueType) { + return false; + } + if (speedEstimation != other.speedEstimation) { + return false; + } if (toTime == null) { if (other.toTime != null) { return false; @@ -407,17 +440,24 @@ public final class ConfigAlgorithm implements Serializable { } else if (!toTime.equals(other.toTime)) { return false; } + if (travelSpeed == null) { + if (other.travelSpeed != null) { + return false; + } + } else if (!travelSpeed.equals(other.travelSpeed)) { + return false; + } if (useCache != other.useCache) { return false; } if (useIncrementalCache != other.useIncrementalCache) { return false; } - if (travelSpeed == null) { - if (other.travelSpeed != null) { + if (userProfile == null) { + if (other.userProfile != null) { return false; } - } else if (!travelSpeed.equals(other.travelSpeed)) { + } else if (!userProfile.equals(other.userProfile)) { return false; } @@ -439,10 +479,12 @@ public final class ConfigAlgorithm implements Serializable { result = prime * result + (outputOnlyExpandedEdges ? 1231 : 1237); result = prime * result + ((queryPointSRID == null) ? 0 : queryPointSRID.hashCode()); result = prime * result + ((queryPoints == null) ? 0 : queryPoints.hashCode()); + result = prime * result + ((queueType == null) ? 0 : queueType.hashCode()); + result = prime * result + ((speedEstimation == null) ? 0 : speedEstimation.hashCode()); result = prime * result + ((toTime == null) ? 0 : toTime.hashCode()); + result = prime * result + ((travelSpeed == null) ? 0 : travelSpeed.hashCode()); result = prime * result + (useCache ? 1231 : 1237); result = prime * result + (useIncrementalCache ? 1231 : 1237); - result = prime * result + ((travelSpeed == null) ? 0 : travelSpeed.hashCode()); // CHECKSTYLE:ON MagicNumber return result; @@ -456,8 +498,11 @@ public final class ConfigAlgorithm implements Serializable { + ", limit=" + limit + ", limitType=" + limitType + ", mode=" + mode + + ", speedEstimation=" + speedEstimation + ", travelSpeed=" + travelSpeed + + ", userProfile=" + userProfile + ", queryPoints=[" + ((queryPoints == null) ? "null" : queryPoints.stream().map(p -> "[" + p.getX() + "," + p.getY() + "]").collect(Collectors.joining(", "))) + "]" + + ", queueType=" + queueType + "]"; } diff --git a/src/main/java/it/unibz/inf/isochrone/config/ConfigDatasetFinder.java b/src/main/java/it/unibz/inf/isochrone/config/ConfigDatasetFinder.java index a35aa9e2177f859389762c758b03bd3f3fc7ed43..ef7447b281b189e4358abccf152d3d4e7d24d8d0 100644 --- a/src/main/java/it/unibz/inf/isochrone/config/ConfigDatasetFinder.java +++ b/src/main/java/it/unibz/inf/isochrone/config/ConfigDatasetFinder.java @@ -6,10 +6,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.ListIterator; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Matcher; @@ -29,6 +27,7 @@ final class ConfigDatasetFinder { static final String DS_ARCHIVE_PATTERN = "^(.)*isochrone([\\w-.])*\\.(\\w)*$"; static final String DS_CONFIG_PATTERN = "^(.)*config\\_(\\w*)\\." + DS_CONFIG_EXTENSION + "$"; + static final String PROPERTY_FORCE_DATASETS = "forceDatasets"; static final String PROPERTY_SKIP_DATASETS = "skipDatasets"; // Constructor @@ -54,11 +53,12 @@ final class ConfigDatasetFinder { throw new NullPointerException("AllTableNames must not be null!"); } + final Collection<String> forceNames = getDatasetsToForce(); final Collection<String> skipNames = getDatasetsToSkip(); final Collection<String> configNames = getDatasetsFromConfig(); final List<ConfigDataset> dsConfigs = new ArrayList<>(configNames.size()); for (final String dsName : configNames) { - if (!skipNames.contains(dsName)) { + if (!skipNames.contains(dsName) && (forceNames == null || forceNames.contains(dsName))) { LOGGER.debug(" adding dataset configuration: {}", dsName); dsConfigs.add(ConfigDataset.createInstance(dsName)); } @@ -67,7 +67,14 @@ final class ConfigDatasetFinder { if (filterUninitialized) { LOGGER.info("Skipping search for unconfigured datasets."); } else { - final Collection<String> unconfiguredDatasets = getUnconfiguredDatasets(dsConfigs, skipNames, allTableNames); + final List<String> unconfiguredDatasets = new ArrayList<>(getUnconfiguredDatasets(dsConfigs, allTableNames)); + for (final ListIterator<String> i = unconfiguredDatasets.listIterator(); i.hasNext();) { + final String dsName = i.next(); + if (skipNames.contains(dsName) || (forceNames != null && !forceNames.contains(dsName))) { + i.remove(); + } + } + if (unconfiguredDatasets.size() <= 0) { LOGGER.info("There is a configuration file for every dataset in the database."); } else { @@ -92,7 +99,7 @@ final class ConfigDatasetFinder { return dsConfigs.stream().filter(ds -> isValidDataset(ds, filterInvalid, filterSynthetic)).collect(Collectors.toList()); } - private static Collection<String> getUnconfiguredDatasets(final Collection<ConfigDataset> dsConfigs, final Collection<String> skipNames, final Collection<String> tableNames) { + private static Collection<String> getUnconfiguredDatasets(final Collection<ConfigDataset> dsConfigs, final Collection<String> tableNames) { final List<String> unconfiguredTableNames = new ArrayList<>(tableNames); // remove all configured tables @@ -112,17 +119,12 @@ final class ConfigDatasetFinder { ); } - final Set<String> unconfiguredDatasets = new HashSet<>(); + final Collection<String> unconfiguredDatasets = new TreeSet<>(); for (final ListIterator<String> i = unconfiguredTableNames.listIterator(); i.hasNext();) { final String tableName = i.next(); final int i0 = tableName.indexOf('_'); if (i0 > 0) { - final String dsName = tableName.substring(0, i0); - if (skipNames.contains(dsName)) { - i.remove(); - } else { - unconfiguredDatasets.add(dsName); - } + unconfiguredDatasets.add(tableName.substring(0, i0)); } } @@ -178,6 +180,26 @@ final class ConfigDatasetFinder { return datasets; } + private static Collection<String> getDatasetsToForce() { + final Collection<String> forceNames; + + final String strForced = SystemHelper.getProperty(PROPERTY_FORCE_DATASETS); + if (strForced == null) { + LOGGER.debug("No datasets will be forced (non were specified using the system property \"" + PROPERTY_FORCE_DATASETS + "\")"); + forceNames = null; + } else { + LOGGER.info("Some datasets will be forced (specified by user using system property \"" + PROPERTY_FORCE_DATASETS + "\")"); + final String[] dsSkipped = strForced.split(","); + forceNames = new ArrayList<>(dsSkipped.length); + for (final String dsName : dsSkipped) { + LOGGER.debug(" dataset \"{}\" will not be used since it was marked to be skipped!", dsName); + forceNames.add(dsName); + } + } + + return forceNames; + } + private static Collection<String> getDatasetsToSkip() { final Collection<String> skipNames; diff --git a/src/performance/java/it/unibz/inf/isochrone/SampleHelper.java b/src/performance/java/it/unibz/inf/isochrone/SampleHelper.java index d83d3a7a854de6c17bd9d59a7ab6156ca1c7c6f4..025f89082cabf20c93b30dd3201721ce838f39a2 100644 --- a/src/performance/java/it/unibz/inf/isochrone/SampleHelper.java +++ b/src/performance/java/it/unibz/inf/isochrone/SampleHelper.java @@ -17,9 +17,6 @@ public final class SampleHelper { return new SampleRunnerBuilder[] { new SampleRunnerBuilder() .addDatasets(ConfigAlgorithmHelper.getAllDatasetsRW()) -// .addDatasets("berlin", "bolzano", "ulm") -// .addDatasets("berlin") -// .addDatasets("bolzano") .addMaxDuration(TestConfiguration.DEFAULT_MAX_DURATION) .addMaxNodes(TestConfiguration.DEFAULT_MAX_NODES) }; diff --git a/src/test/java/it/unibz/inf/isochrone/ConfigAlgorithmHelper.java b/src/test/java/it/unibz/inf/isochrone/ConfigAlgorithmHelper.java index 251a1475ebbdcdd2c16bab961dd85dbacd80da21..b9e5c2e850e63f63ddeec3698e93822690816578 100644 --- a/src/test/java/it/unibz/inf/isochrone/ConfigAlgorithmHelper.java +++ b/src/test/java/it/unibz/inf/isochrone/ConfigAlgorithmHelper.java @@ -3,6 +3,8 @@ package it.unibz.inf.isochrone; import java.awt.geom.Point2D; import java.lang.reflect.Array; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.TreeMap; @@ -18,6 +20,7 @@ import it.unibz.inf.isochrone.util.SystemHelper; public final class ConfigAlgorithmHelper { private static final int DEFAULT_SYNTHETIC_LENGTH = 60; + private static final String PROPERTY_FORCE_DATASETS = "forceDatasets"; private static final String PROPERTY_SKIP_DATASETS = "skipDatasets"; private static final int SRID_SYNTHETIC = 3857; @@ -149,6 +152,16 @@ public final class ConfigAlgorithmHelper { } } + final String strForced = SystemHelper.getProperty(PROPERTY_FORCE_DATASETS); + final String[] dsForced = (strForced == null) ? null : strForced.split(","); + if (dsForced != null && dsForced.length > 0) { + final Collection<String> toRemove = new ArrayList<>(builderMap.keySet()); + toRemove.removeAll(Arrays.asList(dsForced)); + for (final String dsName : toRemove) { + builderMap.remove(dsName); + } + } + return builderMap; }