Skip to content

Commit 1610cc6

Browse files
committed
more detail in select link path reporting
report route name in totals row report proportions out of total iterations reaching destination provide constituent rows below summary rows
1 parent e5ae1c5 commit 1610cc6

File tree

2 files changed

+42
-18
lines changed

2 files changed

+42
-18
lines changed

src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java

+39-17
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.conveyal.r5.analyst.StreetTimesAndModes;
44
import com.conveyal.r5.transit.TransitLayer;
5-
import com.conveyal.r5.transit.TripPattern;
65
import com.conveyal.r5.transit.path.Path;
76
import com.conveyal.r5.transit.path.PatternSequence;
87
import com.conveyal.r5.transit.path.RouteSequence;
@@ -16,7 +15,6 @@
1615
import org.slf4j.Logger;
1716
import org.slf4j.LoggerFactory;
1817

19-
import java.awt.*;
2018
import java.lang.invoke.MethodHandles;
2119
import java.util.ArrayList;
2220
import java.util.Arrays;
@@ -59,6 +57,13 @@ public class PathResult {
5957
*/
6058
public final Multimap<RouteSequence, Iteration>[] iterationsForPathTemplates;
6159

60+
/**
61+
* The total number of iterations that reached each destination can be derived from iterationsForPathTemplates
62+
* as long as every path is being retained. When filtering down to a subset of paths, such as only those passing
63+
* through a selected link, we need this additional array to retain the information.
64+
*/
65+
private final int[] nUnfilteredIterationsReachingDestination;
66+
6267
private final TransitLayer transitLayer;
6368

6469
public static final String[] DATA_COLUMNS = new String[]{
@@ -86,7 +91,9 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
8691
throw new UnsupportedOperationException("Number of detailed path destinations exceeds limit of " + MAX_PATH_DESTINATIONS);
8792
}
8893
}
94+
// FIXME should we be allocating these large arrays when not recording paths?
8995
iterationsForPathTemplates = new Multimap[nDestinations];
96+
nUnfilteredIterationsReachingDestination = new int[nDestinations];
9097
this.transitLayer = transitLayer;
9198
}
9299

@@ -95,10 +102,13 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
95102
* pattern-based keys
96103
*/
97104
public void setTarget(int targetIndex, Multimap<PatternSequence, Iteration> patterns) {
105+
// The size of a multimap is the number of mappings (number of values), not number of unique keys.
106+
// This size method appears to be O(1), see: com.google.common.collect.AbstractMapBasedMultimap.size
107+
nUnfilteredIterationsReachingDestination[targetIndex] = patterns.size();
98108

99109
// When selected link analysis is enabled, filter down the PatternSequence-Iteration Multimap to retain only
100110
// those keys passing through the selected links.
101-
// TODO Maybe selectedLink should be on TransitLayer, and somehow indicate the number of removed iterations.
111+
// Maybe selectedLink instance should be on TransitLayer not TransportNetwork.
102112
if (transitLayer.parentNetwork.selectedLink != null) {
103113
patterns = transitLayer.parentNetwork.selectedLink.filterPatterns(patterns);
104114
}
@@ -125,6 +135,7 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
125135
Multimap<RouteSequence, Iteration> iterationMap = iterationsForPathTemplates[d];
126136
if (iterationMap != null) {
127137
// SelectedLink case: collapse all RouteSequences and Iterations for this OD pair into one to simplify.
138+
// iterationMap is empty (not null) for destinations that were reached without using the selected link.
128139
// This could also be done by merging all Iterations under a single RouteSequence with all route IDs.
129140
if (transitLayer.parentNetwork.selectedLink != null) {
130141
int nIterations = 0;
@@ -137,20 +148,27 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
137148
summedTotalTime += iterations.stream().mapToInt(i -> i.totalTime).sum();
138149
}
139150
// Many destinations will have no iterations at all passing through the SelectedLink area.
140-
// Skip those to keep the CSV output short.
141-
if (nIterations > 0) {
142-
String[] row = new String[DATA_COLUMNS.length];
143-
Arrays.fill(row, "ALL");
144-
String allRouteIdsPipeSeparated = Arrays.stream(allRouteIds.toArray())
145-
.mapToObj(transitLayer.routes::get)
146-
.map(routeInfo -> routeInfo.route_id)
147-
.collect(Collectors.joining("|"));
148-
row[0] = allRouteIdsPipeSeparated;
149-
row[row.length - 1] = Integer.toString(nIterations);
150-
row[row.length - 2] = String.format("%.1f", summedTotalTime / nIterations / 60d); // Average total time
151-
summary[d].add(row);
151+
// Skip those to keep the CSV output short (and to avoid division by zero below).
152+
if (nIterations == 0) {
153+
continue;
152154
}
153-
continue;
155+
String[] row = new String[DATA_COLUMNS.length];
156+
Arrays.fill(row, "ALL");
157+
transitLayer.routeString(1, true);
158+
String allRouteIdsPipeSeparated = Arrays.stream(allRouteIds.toArray())
159+
// If includeName is set to false we record only the ID without the name.
160+
// Name works better than ID for routes added by modifications, which have random IDs.
161+
.mapToObj(ri -> transitLayer.routeString(ri, true))
162+
.collect(Collectors.joining("|"));
163+
String iterationProportion = "%.3f".formatted(
164+
nIterations / (double)(nUnfilteredIterationsReachingDestination[d]));
165+
row[0] = allRouteIdsPipeSeparated;
166+
row[row.length - 1] = iterationProportion;
167+
// Report average of total time over all retained iterations, different than mean/min approach below.
168+
row[row.length - 2] = String.format("%.1f", summedTotalTime / nIterations / 60d);
169+
summary[d].add(row);
170+
// Fall through to the standard case below, so the summary row is followed by its component parts.
171+
// We could optionally continue to the next loop iteration here, to return only the summary row.
154172
}
155173
// Standard (non SelectedLink) case.
156174
for (RouteSequence routeSequence: iterationMap.keySet()) {
@@ -185,7 +203,11 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
185203
score = thisScore;
186204
}
187205
}
188-
String[] row = ArrayUtils.addAll(path, transfer, waits, totalTime, String.valueOf(nIterations));
206+
// Check above guarantees that nIterations is nonzero, so total iterations must be nonzero,
207+
// avoiding divide by zero.
208+
String iterationProportion = "%.3f".formatted(
209+
nIterations / (double)(nUnfilteredIterationsReachingDestination[d]));
210+
String[] row = ArrayUtils.addAll(path, transfer, waits, totalTime, iterationProportion);
189211
checkState(row.length == DATA_COLUMNS.length);
190212
summary[d].add(row);
191213
}

src/main/java/com/conveyal/r5/analyst/scenario/SelectLink.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ public boolean apply(TransportNetwork network) {
140140
.map(s -> tripPattern.stops[s])
141141
.mapToObj(network.transitLayer.stopNames::get)
142142
.collect(Collectors.joining(", "));
143-
String message = String.format("Route %s direction %s after stop %s", routeInfo.getName(), tripPattern.directionId, stopNames);
143+
// Report route name here rather than ID, as the name is better defined on routes created by modifications.
144+
String message = String.format("Route %s direction %s after stop %s",
145+
routeInfo.getName(), tripPattern.directionId, stopNames);
144146
addInfo(message);
145147
LOG.info(message);
146148
return true;

0 commit comments

Comments
 (0)