Skip to content

Commit

Permalink
fix #6
Browse files Browse the repository at this point in the history
  • Loading branch information
markusstraub committed Nov 28, 2024
1 parent e96162d commit f2687ff
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 111 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.vscode
.idea

/TODO
/TODO.md
/data/floridsdorf/network.geojsonl
/data/vienna

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,18 @@ List of all parameters:

**Matching Algorithm**

- `cellSize`: The side length of square zones in meters used in zone registers of riders requests.
The default value is good for urban areas. For large areas with sparsely distributed population and low drs share,
you may consider using a bigger cell size. On the other hand, if neighbourhoodSize is very low, a smaller cell size may work better.
- `maxMatchingDistanceMeters`: Maximum euclidean distance between requests to be considered by the matching algorithm,
i.e. if both the distance between two requests' origin and destination location are closer than the given value they are potential matches.
The default value is good for urban areas.
For large areas with sparsely distributed population and low drs share, you may consider using a larger value.
- `maxPossibleCandidates`: Limits the number of possible riders requests considered for a driver during the matching process.
Used to speed up computations, values 20 to 40 make a good trade-off between computational speed and quality of results.
To turn off this feature specify a sufficiently big number (not recommended).
- `minDriverLegMeters`: minimum length of legs (routed with the drsDriver mode) to be considered for the drs driver mode. 0 means no minimum.
- `minRiderLegMeters` minimum length of legs (routed with the drsDriver mode) to be considered for the drs ride mode. 0 means no minimum.
- `timeSegmentLengthSeconds`: The duration of the time segments used in time segment registers of riders requests.
To avoid scenarios where a driver and a rider departure time are close but cross a segment boundary
candidate requests are token not only from the current segment but also from the one before and after.
To avoid scenarios where a driver and a rider departure time are close, but cross a segment boundary,
candidate requests are taken not only from the current segment but also from the one before and after.

**Simulation / Plan Adjustment**

Expand Down
2 changes: 1 addition & 1 deletion data/floridsdorf/config_drs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@

<!-- DRS NOTE: main drs config, leave empty to use defaults -->
<module name="drs">
<param name="cellSize" value="800" />
<param name="maxMatchingDistanceMeters" value="500" />
<param name="driverProfitPerKm" value="1" />
<param name="pickupWaitingSeconds" value="300" />
</module>
Expand Down
29 changes: 23 additions & 6 deletions src/main/java/at/ac/ait/matsim/drs/engine/DrsData.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.network.Network;
import org.matsim.contrib.common.zones.ZoneSystem;
import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
import org.matsim.contrib.common.zones.systems.grid.h3.H3Utils;
import org.matsim.contrib.common.zones.systems.grid.h3.H3ZoneSystem;
import org.matsim.pt2matsim.tools.NetworkTools;

import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.uber.h3core.H3Core;
import com.uber.h3core.LengthUnit;

import at.ac.ait.matsim.drs.run.Drs;
import at.ac.ait.matsim.drs.run.DrsConfigGroup;
Expand All @@ -20,9 +23,10 @@
public class DrsData {

private static final Logger LOGGER = LogManager.getLogger();
private static final int H3_MAX_LEVEL = 15;

private Network drsNetwork;
private ZoneSystem zoneSystem;
private H3ZoneSystem zoneSystem;

@Inject
public DrsData(Scenario scenario, DrsConfigGroup drsConfig) {
Expand All @@ -31,8 +35,21 @@ public DrsData(Scenario scenario, DrsConfigGroup drsConfig) {
LOGGER.info("Filtered {} drs driver links from network with {} links", drsNetwork.getLinks().size(),
scenario.getNetwork().getLinks().size());

this.zoneSystem = new SquareGridZoneSystem(scenario.getNetwork(), drsConfig.getCellSize());
LOGGER.info("Initialized zone system.");
int resolution = findH3ResolutionForDistance(drsConfig.getMaxMatchingDistanceMeters());
this.zoneSystem = new H3ZoneSystem(scenario.getConfig().global().getCoordinateSystem(), resolution,
scenario.getNetwork(), Predicates.alwaysTrue());
LOGGER.info("Initialized H3 zone system with resolution {}.", resolution);
}

public static int findH3ResolutionForDistance(int meters) {
H3Core h3core = H3Utils.getInstance();
for (int res = H3_MAX_LEVEL; res >= 0; res--) {
double edgeLength = h3core.getHexagonEdgeLengthAvg(res, LengthUnit.m);
if (edgeLength > meters) {
return res;
}
}
return 0;
}

public static Logger getLogger() {
Expand All @@ -43,7 +60,7 @@ public Network getDrsNetwork() {
return drsNetwork;
}

public ZoneSystem getZoneSystem() {
public H3ZoneSystem getH3ZoneSystem() {
return zoneSystem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public DrsOptimizer(DrsData drsData,

public MatchingResult optimize() {
LOGGER.info("Matching process started!");
RequestZoneRegistry originZoneRegistry = RequestZoneRegistry.forOrigins(drsData.getZoneSystem());
RequestZoneRegistry originZoneRegistry = RequestZoneRegistry.forOrigins(drsData.getH3ZoneSystem());
RequestZoneRegistry destinationZoneRegistry = RequestZoneRegistry
.forDestinations(drsData.getZoneSystem());
.forDestinations(drsData.getH3ZoneSystem());
RequestTimeSegmentRegistry timeSegmentRegistry = new RequestTimeSegmentRegistry(drsConfig);
RequestsCollector requestsCollector = new RequestsCollector(drsConfig, population,
drsData.getDrsNetwork(), driverRouter);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/at/ac/ait/matsim/drs/optimizer/DrsRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
import org.matsim.api.core.v01.population.Activity;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Person;
Expand Down Expand Up @@ -171,6 +172,14 @@ public Link getToLink() {
return toLink;
}

public Node getFromNode() {
return fromLink.getFromNode();
}

public Node getToNode() {
return toLink.getToNode();
}

@Override
public double getSubmissionTime() {
return 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void match() {
for (Iterator<DrsRequest> iterator = driverRequests.iterator(); iterator.hasNext();) {
DrsRequest driverRequest = iterator.next();
List<DrsRequest> potentialRequests = potentialRequestsFinder.findRegistryIntersections(
driverRequest.getFromLink().getFromNode(), driverRequest.getToLink().getFromNode(),
driverRequest.getFromNode(), driverRequest.getToNode(),
driverRequest.getDepartureTime());
List<DrsMatch> filteredMatches = requestsFilter.filterRequests(driverRequest, potentialRequests);
DrsMatch bestMatch = bestRequestFinder.findBestRequest(filteredMatches);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ public PotentialRequestsFinder(DrsConfigGroup drsConfig, RequestsRegister reques
}

public List<DrsRequest> findRegistryIntersections(Node origin, Node destination, double departureTime) {
return getIntersection(drsConfig, requestsRegister.getOriginZoneRegistry().findNearestRequests(origin),
requestsRegister.getDestinationZoneRegistry().findNearestRequests(destination),
return getIntersection(drsConfig,
requestsRegister.getOriginZoneRegistry().findRequestsWithinDistance(origin,
drsConfig.getMaxMatchingDistanceMeters()),
requestsRegister.getDestinationZoneRegistry().findRequestsWithinDistance(destination,
drsConfig.getMaxMatchingDistanceMeters()),
requestsRegister.getTimeSegmentRegistry().findNearestRequests(departureTime));
}

Expand All @@ -30,6 +33,7 @@ static List<DrsRequest> getIntersection(DrsConfigGroup drsConfig,
Stream<DrsRequest> zoneRegistryIntersection = originNearRequests
.filter(destinationNearRequests.collect(Collectors.toList())::contains);
return zoneRegistryIntersection.filter(temporalNearRequests.collect(Collectors.toList())::contains)
.limit(drsConfig.getMaxPossibleCandidates()).collect(Collectors.toCollection(ArrayList::new));
.limit(drsConfig.getMaxPossibleCandidates())
.collect(Collectors.toCollection(ArrayList::new));
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
package at.ac.ait.matsim.drs.optimizer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Node;
import org.matsim.contrib.common.zones.Zone;
import org.matsim.contrib.common.zones.ZoneSystem;
import org.matsim.contrib.common.zones.systems.grid.h3.H3Utils;
import org.matsim.contrib.common.zones.systems.grid.h3.H3ZoneSystem;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.core.network.NetworkUtils;

import com.uber.h3core.H3Core;

/**
* Heavily inspired by
* org.matsim.contrib.taxi.optimizer.rules.UnplannedRequestZonalRegistry
* Geographically matches each request to a H3 cell (zone)
*/
public class RequestZoneRegistry {
private final ZoneSystem zoneSystem;
private final H3ZoneSystem zoneSystem;
private final boolean isOriginZoneRegistry;
private final Map<Id<Zone>, Map<Id<Request>, DrsRequest>> requestsInZones;
private final H3Core h3;

private RequestZoneRegistry(ZoneSystem zoneSystem, boolean isOriginZoneRegistry) {
private RequestZoneRegistry(H3ZoneSystem zoneSystem, boolean isOriginZoneRegistry) {
this.zoneSystem = zoneSystem;
this.isOriginZoneRegistry = isOriginZoneRegistry;
requestsInZones = new HashMap<>(zoneSystem.getZones().size());
for (Id<Zone> id : zoneSystem.getZones().keySet()) {
requestsInZones.put(id, new LinkedHashMap<>());
}
h3 = H3Utils.getInstance();
}

public static RequestZoneRegistry forOrigins(ZoneSystem zoneSystem) {
public static RequestZoneRegistry forOrigins(H3ZoneSystem zoneSystem) {
return new RequestZoneRegistry(zoneSystem, true);
}

public static RequestZoneRegistry forDestinations(ZoneSystem zoneSystem) {
public static RequestZoneRegistry forDestinations(H3ZoneSystem zoneSystem) {
return new RequestZoneRegistry(zoneSystem, false);
}

Expand All @@ -52,20 +60,45 @@ public void removeRequest(DrsRequest request) {
}
}

public Stream<DrsRequest> findNearestRequests(Node node) {
return requestsInZones.get(getZoneId(node)).values().stream();
/**
* Uses neighbouring H3 cells to guarantee finding all requests within the given
* max distance (this guarantee only holds if the given distance does not exceed
* the value used for calculating the H3 resolution)
*/
public Stream<DrsRequest> findRequestsWithinDistance(Node node, int maxEuclideanDistance) {
Id<Zone> centerZoneId = getZoneId(node);
Long cellId = Long.parseLong(centerZoneId.toString());

List<Long> disk = h3.gridDisk(cellId, 1);
List<Id<Zone>> diskZones = disk.stream().map(id -> Id.create(id, Zone.class)).collect(Collectors.toList());

List<DrsRequest> potentialRequests = new ArrayList<>();
for (var zoneId : diskZones) {
potentialRequests.addAll(requestsInZones.getOrDefault(zoneId, Map.of()).values());
}

return potentialRequests.stream()
.filter(r -> NetworkUtils.getEuclideanDistance(node.getCoord(),
getRequestNode(r).getCoord()) < maxEuclideanDistance);
}

public Map<Id<Zone>, Map<Id<Request>, DrsRequest>> getRequestsInZones() {
return requestsInZones;
}

public Id<Zone> getZoneId(DrsRequest request) {
Node node = isOriginZoneRegistry ? request.getFromLink().getFromNode() : request.getToLink().getToNode();
return getZoneId(node);
public long requestCount() {
return requestsInZones.values().stream().flatMap(m -> m.values().stream()).count();
}

Node getRequestNode(DrsRequest request) {
return isOriginZoneRegistry ? request.getFromNode() : request.getToNode();
}

Id<Zone> getZoneId(DrsRequest request) {
return getZoneId(getRequestNode(request));
}

public Id<Zone> getZoneId(Node node) {
Id<Zone> getZoneId(Node node) {
Optional<Zone> zone = zoneSystem.getZoneForNodeId(node.getId());
if (!zone.isPresent()) {
throw new IllegalArgumentException("no zone found for node " + node.getId());
Expand Down
40 changes: 26 additions & 14 deletions src/main/java/at/ac/ait/matsim/drs/run/DrsConfigGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public class DrsConfigGroup extends ReflectiveConfigGroupWithConfigurableParamet

public static final String GROUP_NAME = "drs";

public static final String CELL_SIZE = "cellSize";
private int cellSize = 4000;
public static final String MAX_MATCHING_DISTANCE_METERS = "maxMatchingDistanceMeters";
private int maxMatchingDistanceMeters = 2000;

public static final String TIME_SEGMENT_LENGTH_SECONDS = "timeSegmentLengthSeconds";
private int timeSegmentLengthSeconds = 2 * 60 * 60;
Expand Down Expand Up @@ -54,20 +54,32 @@ public DrsConfigGroup() {
@Override
public Map<String, String> getComments() {
Map<String, String> map = super.getComments();
map.put(CELL_SIZE,
"The side length of square zones in meters used in zone registers of riders requests. The default value is good for urban areas. For large areas with sparsely distributed population and low drs share, you may consider using a bigger cell size. On the other hand, if neighbourhoodSize is very low, a smaller cell size may work better. (inspired by taxi contrib)");
map.put(MAX_MATCHING_DISTANCE_METERS,
"Maximum euclidean distance between requests to be considered by the matching algorithm, "
+ "i.e. if both the distance between two requests' origin and destination location are closer than the given value they are potential matches. "
+ "The default value is good for urban areas. "
+ "For large areas with sparsely distributed population and low drs share, you may consider using a larger value.");
map.put(MAX_POSSIBLE_CANDIDATES,
"Limits the number of possible riders requests considered for a driver during the matching process. Used to speed up computations, values 20 to 40 make a good trade-off between computational speed and quality of results. To turn off this feature specify a sufficiently big number (not recommended). (inspired by taxi contrib)");
"Limits the number of possible riders requests considered for a driver during the matching process. "
+ "Used to speed up computations, values 20 to 40 make a good trade-off between computational speed and quality of results. "
+ "To turn off this feature specify a sufficiently big number (not recommended). (inspired by taxi contrib)");
map.put(RIDER_DEPARTURE_TIME_ADJUSTMENT_SECONDS,
"The amount of time the riders are willing to adjust their departure times. During the matching process, the arrival of driver to pick-up point is checked whether it is within the rider departure time +- the riderDepartureTimeAdjustment.");
"The amount of time the riders are willing to adjust their departure times. "
+ "During the matching process, the arrival of driver to pick-up point is checked whether it is within "
+ "the rider departure time +- the riderDepartureTimeAdjustment.");
map.put(TIME_SEGMENT_LENGTH_SECONDS,
"The duration of the time segments used in time segment registers of riders requests. To avoid scenarios where a driver and a rider departure time are close but cross a segment boundary candidate requests are token not only from the current segment but also from the one before and after.");
"The duration of the time segments used in time segment registers of riders requests. Must be larger than "
+ RIDER_DEPARTURE_TIME_ADJUSTMENT_SECONDS
+ ". To avoid scenarios where a driver and a rider departure time are close, but cross a segment boundary, "
+ "candidate requests are taken not only from the current segment but also from the one before and after.");
map.put(PICKUP_WAITING_SECONDS,
"The amount of time the driver is expected to wait until the rider enters the vehicle.");
map.put(DRIVER_PROFIT_PER_KM,
"The amount of money per kilometre the driver gains for a rider (typically positive)");
map.put(CAR_AND_DRS_DAILY_MONETARY_CONSTANT,
"Daily price for car usage including when using the private car as drsDriver. If specified here do not additionaly specify it in planCalcScore.scoringParameters.modeParams.dailyMonetaryConstant - otherwise it will be counted twice (typically negative)");
"Daily price for car usage including when using the private car as drsDriver. "
+ "If specified here do not additionaly specify it in planCalcScore.scoringParameters.modeParams.dailyMonetaryConstant - "
+ "otherwise it will be counted twice (typically negative)");
map.put(SUBTOUR_MODE_CHOICE_MODES,
"Defines all modes available for the '" + SubtourModeChoiceForDrs.STRATEGY_NAME
+ "' strategy, including chain-based modes, separated by commas");
Expand All @@ -81,14 +93,14 @@ public Map<String, String> getComments() {
return map;
}

@StringGetter(CELL_SIZE)
public int getCellSize() {
return cellSize;
@StringGetter(MAX_MATCHING_DISTANCE_METERS)
public int getMaxMatchingDistanceMeters() {
return maxMatchingDistanceMeters;
}

@StringSetter(CELL_SIZE)
public void setCellSize(int cellSize) {
this.cellSize = cellSize;
@StringSetter(MAX_MATCHING_DISTANCE_METERS)
public void setMaxMatchingDistanceMeters(int maxMatchingDistanceMeters) {
this.maxMatchingDistanceMeters = maxMatchingDistanceMeters;
}

@StringGetter(MAX_POSSIBLE_CANDIDATES)
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/at/ac/ait/matsim/drs/run/H3Experiments.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
public class H3Experiments {
public static void main(String[] args) {
playground();
// writeCellsForTestNetwork();
writeCellsForTestNetwork();
}

public static void playground() {
Expand Down Expand Up @@ -59,10 +59,11 @@ public static void writeCellsForTestNetwork() {
Collection<SimpleFeature> features = new ArrayList<>();

for (Entry<Id<Zone>, Zone> entry : h3.getZones().entrySet()) {
String id = entry.getKey().toString();
long id = Long.parseLong(entry.getKey().toString());
String address = Long.toHexString(id);
features.add(
factory.createPolygon(entry.getValue().getPreparedGeometry().getGeometry().getCoordinates(),
new Object[] { id }, id));
new Object[] { address }, address));
}
GeoFileWriter.writeGeometries(features, "/tmp/floridsdorf-h3grid.gpkg");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void adjustConfig(Config config) {
config.global().setNumberOfThreads(10);

DrsConfigGroup drs = (DrsConfigGroup) config.getModules().get("drs");
drs.setCellSize(500);
drs.setMaxMatchingDistanceMeters(500);
}

}
Loading

0 comments on commit f2687ff

Please sign in to comment.