Skip to content

Commit

Permalink
allow enabling and recording new results
Browse files Browse the repository at this point in the history
- Enable opportunity temporal density, nearest n opportunities,
  and dual accessibility in AnalysisRequest and AnalysisWorkerTask
- CSVResultWriter recording opportunity density and dual accessibility
  • Loading branch information
abyrd committed Aug 8, 2023
1 parent 655d0e2 commit 6a03c8a
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 13 deletions.
13 changes: 13 additions & 0 deletions src/main/java/com/conveyal/analysis/models/AnalysisRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ public class AnalysisRequest {
*/
public ChaosParameters injectFault;

/**
* Whether to include the number of opportunities reached during each minute of travel in results sent back
* to the broker. Requires both an origin and destination pointset to be specified, and in the case of regional
* analyses the origins must be non-gridded. (Should be possible to make a grid as well.)
*/
public boolean opportunityTemporalDensity = false;

public int dualAccessibilityOpportunityThreshold = 0;


/**
* Create the R5 `Scenario` from this request.
*/
Expand Down Expand Up @@ -265,6 +275,9 @@ public void populateTask (AnalysisWorkerTask task, UserPermissions userPermissio
throw new IllegalArgumentException("Must be admin user to inject faults.");
}
}

task.opportunityTemporalDensity = opportunityTemporalDensity;
task.dualAccessibilityOpportunityThreshold = dualAccessibilityOpportunityThreshold;
}

private EnumSet<LegMode> getEnumSetFromString (String s) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
* do serve to enumerate the acceptable parameters coming over the HTTP API.
*/
public enum CsvResultType {
ACCESS, TIMES, PATHS
ACCESS, TIMES, PATHS, OPPORTUNITIES
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ public MultiOriginAssembler (RegionalAnalysis regionalAnalysis, Job job, FileSto
resultWriters.add(new PathCsvResultWriter(job.templateTask, fileStorage));
}

if (job.templateTask.opportunityTemporalDensity) {
resultWriters.add(new OpportunityCsvResultWriter(job.templateTask, fileStorage));
}

checkArgument(job.templateTask.makeTauiSite || notNullOrEmpty(resultWriters),
"A non-Taui regional analysis should always create at least one grid or CSV file.");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.conveyal.analysis.results;

import com.conveyal.file.FileStorage;
import com.conveyal.r5.analyst.cluster.RegionalTask;
import com.conveyal.r5.analyst.cluster.RegionalWorkResult;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* This handles collating regional results into CSV files containing temporal opportunity density
* (number of opportunities reached in each one-minute interval, the derivative of step-function accessibility)
* as well as "dual" accessibility (the amount of time needed to reach n opportunities).
* And maybe the N closest opportunities to each origin?
*/
public class OpportunityCsvResultWriter extends CsvResultWriter {

private final int dualOpportunityCount;

public OpportunityCsvResultWriter(RegionalTask task, FileStorage fileStorage) throws IOException {
super(task, fileStorage);
dualOpportunityCount = task.dualAccessibilityOpportunityThreshold;
}

@Override
public CsvResultType resultType () {
return CsvResultType.OPPORTUNITIES;
}

@Override
public String[] columnHeaders () {
List<String> headers = new ArrayList<>();
// The ids of the freeform origin point and destination set
headers.add("originId");
headers.add("destId");
headers.add("percentile");
for (int m = 0; m < 120; m += 1) {
// The opportunity density over travel minute m
headers.add(Integer.toString(m));
}
// The number of minutes needed to reach d destination opportunities
headers.add("D" + dualOpportunityCount);
return headers.toArray(new String[0]);
}

@Override
protected void checkDimension (RegionalWorkResult workResult) {
checkDimension(workResult, "destination pointsets", workResult.opportunitiesPerMinute.length, task.destinationPointSetKeys.length);
for (double[][] percentilesForPointset : workResult.opportunitiesPerMinute) {
checkDimension(workResult, "percentiles", percentilesForPointset.length, task.percentiles.length);
for (double[] minutesForPercentile : percentilesForPointset) {
checkDimension(workResult, "minutes", minutesForPercentile.length, 120);
}
}
}

@Override
public Iterable<String[]> rowValues (RegionalWorkResult workResult) {
List<String> row = new ArrayList<>(125);
String originId = task.originPointSet.getId(workResult.taskId);
for (int d = 0; d < task.destinationPointSetKeys.length; d++) {
int[][] percentilesForDestPointset = workResult.accessibilityValues[d];
for (int p = 0; p < task.percentiles.length; p++) {
row.add(originId);
row.add(task.destinationPointSets[d].name);
row.add(Integer.toString(p));
int[] densitiesPerMinute = percentilesForDestPointset[p];
for (int m = 0; m < 120; m++) {
row.add(Double.toString(densitiesPerMinute[m]));
}
// Dual accessibility
{
int m = 0;
double sum = 0;
while (sum < dualOpportunityCount) {
sum += densitiesPerMinute[m];
m += 1;
}
row.add(Integer.toString(m >= 120 ? -1 : m));
}
}
}
// List.of() or Arrays.asList() don't work without explicitly specifying the generic type because
// they interpret the String[] as varargs in the method signature.
return List.<String[]>of(row.toArray(new String[0]));
}

}
18 changes: 12 additions & 6 deletions src/main/java/com/conveyal/r5/analyst/NearestNResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.conveyal.r5.analyst.cluster.AnalysisWorkerTask;
import com.google.common.base.Preconditions;

import static com.conveyal.r5.common.Util.newObjectArray;
import static com.conveyal.r5.common.Util.notNullOrEmpty;
import static com.conveyal.r5.profile.FastRaptorWorker.UNREACHED;

/**
* An instance of this is included in a OneOriginResult for reporting the nearest N destinations. If we use more than
Expand All @@ -15,6 +15,9 @@
* function, how many opportunities are encountered during each minute of travel, whose integral is the cumulative
* accessibility curve); and the nearest one or more opportunities to a given origin.
* (expand from comments on https://github.com/conveyal/r5/pull/884)
*
* Corresponds to OpportunityCsvResultWriter when collating regional results, and
* AnalysisWorkerTask#opportunityTemporalDensity to enable, so maybe the names should be made more coherent.
*/
public class NearestNResult {

Expand Down Expand Up @@ -66,14 +69,17 @@ public NearestNResult (AnalysisWorkerTask task) {

private int listLength = 0; // increment as lists grow in length; use as initial insert position

public void record (int target, int[] travelTimePercentilesSeconds) {
public void recordOneTarget (int target, int[] travelTimePercentilesSeconds) {
// Increment histogram bin for the number of minutes of travel by the number of opportunities at the target.
for (int d = 0; d < destinationPointSets.length; d++) {
PointSet dps = destinationPointSets[d];
for (int p = 0; p < nPercentiles; p++) {
int i = travelTimePercentilesSeconds[p] / 60;
if (i <= 120) {
opportunitiesPerMinute[d][p][i] += dps.getOpportunityCount(target);
if (travelTimePercentilesSeconds[p] == UNREACHED) {
break; // If any percentile is unreached, all higher ones are also unreached.
}
int m = travelTimePercentilesSeconds[p] / 60;
if (m <= 120) {
opportunitiesPerMinute[d][p][m] += dps.getOpportunityCount(target);
}
}
}
Expand Down Expand Up @@ -113,7 +119,7 @@ public void record (int target, int[] travelTimePercentilesSeconds) {
* @param n the threshold quantity of opportunities
* @return the number of minutes it takes to reach n opportunities, for each destination set and percentile of travel time.
*/
public int[][] minutesToReachOpporunities (int n) {
public int[][] minutesToReachOpportunities(int n) {
int[][] result = new int[destinationPointSets.length][nPercentiles];
for (int d = 0; d < destinationPointSets.length; d++) {
for (int p = 0; p < nPercentiles; p++) {
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/conveyal/r5/analyst/TravelTimeReducer.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,16 @@ public TravelTimeReducer (AnalysisWorkerTask task, TransportNetwork network) {
// These are conditionally instantiated because they can consume a lot of memory.
if (calculateAccessibility) {
accessibilityResult = new AccessibilityResult(task);
// TODO create this more selectively, choose histograms and nearest n independently
nearestNResult = new NearestNResult(task);
}
if (calculateTravelTimes) {
travelTimeResult = new TravelTimeResult(task);
}
if (task.includePathResults) {
pathResult = new PathResult(task, network.transitLayer);
}
if (task.opportunityTemporalDensity) {
nearestNResult = new NearestNResult(task);
}

// Validate and copy the travel time cutoffs, converting them to seconds to avoid repeated multiplication
// in tight loops. Also find the points where the decay function reaches zero for these cutoffs.
Expand Down Expand Up @@ -279,7 +280,7 @@ private void recordTravelTimePercentilesForTarget (int target, int[] travelTimeP
}
}
if (nearestNResult != null) {
nearestNResult.record(target, travelTimePercentilesSeconds);
nearestNResult.recordOneTarget(target, travelTimePercentilesSeconds);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,6 @@ public static void addJsonToGrid (
if (oneOriginResult.nearest != null) {
jsonBlock.nearby = oneOriginResult.nearest.nearby;
jsonBlock.opportunitiesPerMinute = oneOriginResult.nearest.opportunitiesPerMinute;
LOG.info("Dual accessibility: {}", oneOriginResult.nearest.minutesToReachOpporunities(272_500));
LOG.info("Opportunities per minute: {}", oneOriginResult.nearest.opportunitiesPerMinute);
LOG.info("Opportunities nearby: {}", oneOriginResult.nearest.nearby);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ public abstract class AnalysisWorkerTask extends ProfileRequest {
*/
public boolean includePathResults = false;

/**
* Whether to include the number of opportunities reached during each minute of travel in results sent back
* to the broker. Requires both an origin and destination pointset to be specified, and in the case of regional
* analyses the origins must be non-gridded. (Should be possible to make a grid as well.)
*/
public boolean opportunityTemporalDensity = false;

public int dualAccessibilityOpportunityThreshold = 0;

/** Whether to build a histogram of travel times to each destination, generally used in testing and debugging. */
public boolean recordTravelTimeHistograms = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ public class RegionalWorkResult {
public NearbyOpportunity[][] nearby;

/**
* The nearest n destinations for each percentile of travel time.
* Each item contains a travel time, target index, and ID.
* The temporal density of opportunities - how many are reached during each minute of travel.
*/
public double[][][] opportunitiesPerMinute;

Expand Down

0 comments on commit 6a03c8a

Please sign in to comment.