Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/network-improvments' into networ…
Browse files Browse the repository at this point in the history
…k-improvments
  • Loading branch information
frievoe97 committed May 23, 2024
2 parents 4afa360 + 2e6d861 commit b6422c3
Show file tree
Hide file tree
Showing 13 changed files with 710 additions and 596 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ public Integer call() throws Exception {

List<? extends Node> list = e.getValue();

// for now only consider traffic lights
if (!e.getKey().equals("traffic_light"))
continue;

log.info("Sampling {} out of {} intersections for type {}", sample, list.size(), e.getKey());

for (int i = 0; i < sample && !list.isEmpty(); i++) {
Expand Down Expand Up @@ -193,7 +197,7 @@ public Integer call() throws Exception {

RandomizedTravelTime tt = new RandomizedTravelTime(rnd);

LeastCostPathCalculator router = createRandomizedRouter(network, tt);
LeastCostPathCalculator router = createRandomizedRouter(cityNetwork, tt);

sampleCityRoutes(cityNetwork, router, tt, rnd);

Expand Down Expand Up @@ -223,9 +227,16 @@ private void sampleCityRoutes(Network network, LeastCostPathCalculator router, R

Link to = NetworkUtils.getNearestLink(network, dest);

// Links could be on the very edge so that nodes are outside the network
if (to == null || !network.getNodes().containsKey(link.getFromNode().getId()) ||
!network.getNodes().containsKey(to.getToNode().getId())) {
i--;
continue;
}

LeastCostPathCalculator.Path path = router.calcLeastCostPath(link.getFromNode(), to.getToNode(), 0, null, null);

if (path.nodes.size() < 2) {
if (path == null || path.nodes.size() < 2) {
i--;
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Id;
Expand All @@ -21,6 +20,9 @@
import picocli.CommandLine;

import java.io.BufferedReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -54,15 +56,19 @@ public class ApplyNetworkParams implements MATSimAppCommand {
@CommandLine.Option(names = "--factor-bounds", split = ",", description = "Speed factor limits (lower,upper bound)", defaultValue = NetworkParamsOpt.DEFAULT_FACTOR_BOUNDS)
private double[] speedFactorBounds;

@CommandLine.Option(names = "--capacity-bounds", split = ",", description = "Relative capacity bounds against theoretical max (lower,upper bound)", defaultValue = "0.3,1.0")
private double[] capacityBounds;
@CommandLine.Option(names = "--capacity-bounds", split = ",", defaultValue = "0.4,0.6,0.8",
description = "Minimum relative capacity against theoretical max (traffic light,right before left, priority)")
private List<Double> capacityBounds;

@CommandLine.Option(names = "--road-types", split = ",", description = "Road types to apply changes to")
private Set<String> roadTypes;

@CommandLine.Option(names = "--junction-types", split = ",", description = "Junction types to apply changes to")
private Set<String> junctionTypes;

@CommandLine.Option(names = "--decrease-only", description = "Only set values if the are lower than the current value", defaultValue = "false")
private boolean decrease;

private NetworkModel model;
private NetworkParams paramsOpt;

Expand All @@ -73,7 +79,7 @@ public static void main(String[] args) {
}

/**
* Theoretical capacity.
* Theoretical capacity assuming fixed car length and headway. This should usually not be exceeded.
*/
public static double capacityEstimate(double v) {

Expand Down Expand Up @@ -118,7 +124,7 @@ public Integer call() throws Exception {

try {
applyChanges(link, ft);
} catch (IllegalArgumentException e) {
} catch (IllegalArgumentException e) {
warn++;
log.warn("Error processing link {}", link.getId(), e);
}
Expand All @@ -139,73 +145,121 @@ private void applyChanges(Link link, Feature ft) {
boolean modified = false;

if (params.contains(NetworkAttribute.capacity)) {
modified = applyCapacity(link, ft);
}

Predictor capacity = model.capacity(ft.junctionType(), ft.highwayType());
// No operation performed if not supported
if (capacity == null) {
return;
}
if (params.contains(NetworkAttribute.freespeed)) {
modified |= applyFreeSpeed(link, ft);
}

double perLane = capacity.predict(ft.features(), ft.categories());
if (Double.isNaN(perLane)) {
return;
}
if (modified)
warn++;
}

double cap = capacityEstimate(ft.features().getDouble("speed"));
private boolean applyCapacity(Link link, Feature ft) {

if (perLane < cap * capacityBounds[0]) {
log.warn("Increasing capacity per lane on {} ({}, {}) from {} to {}",
link.getId(), ft.highwayType(), ft.junctionType(), perLane, cap * capacityBounds[0]);
perLane = cap * capacityBounds[0];
modified = true;
}
Predictor capacity = model.capacity(ft.junctionType(), ft.highwayType());
// No operation performed if not supported
if (capacity == null) {
return false;
}

if (perLane > cap * capacityBounds[1]) {
log.warn("Reducing capacity per lane on {} ({}, {}) from {} to {}",
link.getId(), ft.highwayType(), ft.junctionType(), perLane, cap * capacityBounds[1]);
perLane = cap * capacityBounds[1];
modified = true;
}
double perLane = capacity.predict(ft.features(), ft.categories());
if (Double.isNaN(perLane)) {
return true;
}

double cap = capacityEstimate(ft.features().getDouble("speed"));

link.setCapacity(link.getNumberOfLanes() * perLane);
if (capacityBounds.isEmpty())
capacityBounds.add(0.0);

// Fill up to 3 elements if not provided
if (capacityBounds.size() < 3) {
capacityBounds.add(capacityBounds.get(0));
capacityBounds.add(capacityBounds.get(1));
}

// Minimum thresholds
double threshold = switch (ft.junctionType()) {
case "traffic_light" -> capacityBounds.get(0);
case "right_before_left" -> capacityBounds.get(1);
case "priority" -> capacityBounds.get(2);
default -> 0;
};

if (params.contains(NetworkAttribute.freespeed)) {
boolean modified = false;

Predictor speedModel = model.speedFactor(ft.junctionType(), ft.highwayType());
if (perLane < cap * threshold) {
log.warn("Increasing capacity per lane on {} ({}, {}) from {} to {}",
link.getId(), ft.highwayType(), ft.junctionType(), perLane, cap * threshold);
perLane = cap * threshold;
modified = true;
}

// No operation performed if not supported
if (speedModel == null) {
return;
}
if (perLane > cap) {
log.warn("Reducing capacity per lane on {} ({}, {}) from {} to {}",
link.getId(), ft.highwayType(), ft.junctionType(), perLane, cap);
perLane = cap;
modified = true;
}

double speedFactor = paramsOpt != null ?
speedModel.predict(ft.features(), ft.categories(), paramsOpt.getParams(ft.junctionType())) :
speedModel.predict(ft.features(), ft.categories());
if (ft.features().getOrDefault("num_lanes", link.getNumberOfLanes()) != link.getNumberOfLanes())
log.warn("Number of lanes for link {} does not match the feature file", link.getId());

if (Double.isNaN(speedFactor)) {
return;
}
int totalCap = BigDecimal.valueOf(link.getNumberOfLanes() * perLane).setScale(0, RoundingMode.HALF_UP).intValue();

if (speedFactor > speedFactorBounds[1]) {
log.warn("Reducing speed factor on {} from {} to {}", link.getId(), speedFactor, speedFactorBounds[1]);
speedFactor = speedFactorBounds[1];
modified = true;
}
if (decrease && totalCap > link.getCapacity())
return false;

// Threshold for very low speed factors
if (speedFactor < speedFactorBounds[0]) {
log.warn("Increasing speed factor on {} from {} to {}", link, speedFactor, speedFactorBounds[0]);
speedFactor = speedFactorBounds[0];
modified = true;
}
link.setCapacity(totalCap);

return modified;
}

link.setFreespeed((double) link.getAttributes().getAttribute("allowed_speed") * speedFactor);
link.getAttributes().putAttribute("speed_factor", speedFactor);
private boolean applyFreeSpeed(Link link, Feature ft) {

Predictor speedModel = model.speedFactor(ft.junctionType(), ft.highwayType());

// No operation performed if not supported
if (speedModel == null) {
return false;
}

if (modified)
warn++;
double speedFactor = paramsOpt != null ?
speedModel.predict(ft.features(), ft.categories(), paramsOpt.getParams(ft.junctionType())) :
speedModel.predict(ft.features(), ft.categories());

if (Double.isNaN(speedFactor)) {
return false;
}

boolean modified = false;

if (speedFactor > speedFactorBounds[1]) {
log.warn("Reducing speed factor on {} from {} to {}", link.getId(), speedFactor, speedFactorBounds[1]);
speedFactor = speedFactorBounds[1];
modified = true;
}

// Threshold for very low speed factors
if (speedFactor < speedFactorBounds[0]) {
log.warn("Increasing speed factor on {} from {} to {}", link, speedFactor, speedFactorBounds[0]);
speedFactor = speedFactorBounds[0];
modified = true;
}

double freeSpeed = (double) link.getAttributes().getAttribute("allowed_speed") * speedFactor;

freeSpeed = BigDecimal.valueOf(freeSpeed).setScale(3, RoundingMode.HALF_EVEN).doubleValue();

if (decrease && freeSpeed > link.getFreespeed())
return false;

link.setFreespeed(freeSpeed);
link.getAttributes().putAttribute("speed_factor", freeSpeed);

return modified;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ static NetworkModel load(Class<? extends NetworkModel> modelClazz) {
*/
static Map<Id<Link>, Feature> readFeatures(String input, Map<Id<Link>, ? extends Link> links) throws IOException {

// TODO: read features from link attributes as well, if not present as input

Map<Id<Link>, Feature> features = new IdMap<>(Link.class, links.size());

// Create features from link attributes
for (Link link : links.values()) {
features.put(link.getId(), createDefaultFeature(link));
}

try (CSVParser reader = new CSVParser(IOUtils.getBufferedReader(input),
CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build())) {

Expand All @@ -73,32 +76,64 @@ static Map<Id<Link>, Feature> readFeatures(String input, Map<Id<Link>, ? extends

Id<Link> id = Id.createLinkId(row.get("linkId"));
Link link = links.get(id);

Object2DoubleOpenHashMap<String> ft = new Object2DoubleOpenHashMap<>();
ft.defaultReturnValue(Double.NaN);
Object2ObjectMap<String, String> categories = new Object2ObjectOpenHashMap<>();
Feature ft = features.computeIfAbsent(id, (k) -> createDefaultFeature(link));

for (String column : header) {
String v = row.get(column);
try {
ft.put(column, Double.parseDouble(v));
ft.features.put(column, Double.parseDouble(v));
} catch (NumberFormatException e) {
// every not equal to True will be false
ft.put(column, Boolean.parseBoolean(v) ? 1 : 0);
categories.put(column, v);
ft.features.put(column, Boolean.parseBoolean(v) ? 1 : 0);
ft.categories.put(column, v);
}
}

String highwayType = header.contains(NetworkUtils.TYPE) ? row.get(NetworkUtils.TYPE) :
(link != null ? NetworkUtils.getHighwayType(link) : null);

features.put(id, new Feature(row.get("junction_type").intern(), highwayType, ft, categories));
features.put(id, new Feature(row.get("junction_type").intern(), highwayType, ft.features, ft.categories));
}
}

return features;
}

/**
* Create default feature based on link attributes.
*/
private static Feature createDefaultFeature(Link link) {
Object2DoubleOpenHashMap<String> ft = new Object2DoubleOpenHashMap<>();
ft.defaultReturnValue(Double.NaN);
Object2ObjectMap<String, String> categories = new Object2ObjectOpenHashMap<>();

// Link might not be present in the network
if (link == null)
return new Feature("", "", ft, categories);

String highwayType = NetworkUtils.getHighwayType(link);
categories.put("highway_type", highwayType);
ft.put("speed", NetworkUtils.getAllowedSpeed(link));
ft.put("num_lanes", link.getNumberOfLanes());
ft.put("length", link.getLength());
ft.put("capacity", link.getCapacity());
ft.put("freespeed", link.getFreespeed());

for (Map.Entry<String, Object> e : link.getAttributes().getAsMap().entrySet()) {
String key = e.getKey();
Object value = e.getValue();
if (value instanceof Number) {
ft.put(key, ((Number) value).doubleValue());
} else if (value instanceof Boolean) {
ft.put(key, (Boolean) value ? 1 : 0);
} else {
categories.put(key, value.toString());
}
}

return new Feature("", highwayType, ft, categories);
}

/**
* Read validation files and calc target speed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public double predict(Object2DoubleMap<String> features, Object2ObjectMap<String

// speed in km/h
int speed = (int) Math.round(features.getDouble("speed") * 3.6);
int lanes = (int) features.getOrDefault("lanes", 1);
int lanes = (int) features.getOrDefault("num_lanes", 1);

// Capacity for 1 lane motorways is not defined in HBS
double capacity = 2000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public double predict(Object2DoubleMap<String> features, Object2ObjectMap<String

// Speed in km/h
int speed = (int) Math.round(features.getDouble("speed") * 3.6);
int lanes = (int) features.getOrDefault("lanes", 1);
int lanes = (int) features.getOrDefault("num_lanes", 1);
String type = categories.get("highway_type");

// Primary and trunk roads are often Bundesstraßen,
Expand All @@ -98,7 +98,7 @@ public double predict(Object2DoubleMap<String> features, Object2ObjectMap<String
}

// Capacity for city roads
if (speed >= 40 || lanes >= 2) {
if (speed >= 40 || lanes >= 2 || features.getDouble("is_secondary_or_higher") == 1) {
return switch (lanes) {
case 1 -> 1139.0625;
case 2 -> 2263.438914027149 / 2;
Expand Down
Loading

0 comments on commit b6422c3

Please sign in to comment.