-
Notifications
You must be signed in to change notification settings - Fork 453
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Put changes for DRT estimator into separate PR * add todos from discussion * small improvements with config and API * integrate fare params into initial estimate * revert change to generics * improve how guice bindings are used * update default decay * controler -> controller * update to new config api * small change in initial estimate * simplified injection * Avoid storing speedUpParams * bind estimators as map as well
- Loading branch information
Showing
13 changed files
with
745 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
...xtensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
package org.matsim.contrib.drt.extension.estimator; | ||
|
||
import org.apache.commons.math3.stat.descriptive.SummaryStatistics; | ||
import org.apache.commons.math3.stat.regression.RegressionResults; | ||
import org.apache.commons.math3.stat.regression.SimpleRegression; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.matsim.api.core.v01.events.PersonMoneyEvent; | ||
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector; | ||
import org.matsim.contrib.drt.extension.estimator.run.DrtEstimatorConfigGroup; | ||
import org.matsim.contrib.drt.fare.DrtFareParams; | ||
import org.matsim.contrib.drt.routing.DrtRoute; | ||
import org.matsim.contrib.drt.run.DrtConfigGroup; | ||
import org.matsim.contrib.drt.speedup.DrtSpeedUp; | ||
import org.matsim.core.controler.events.IterationEndsEvent; | ||
import org.matsim.core.controler.listener.IterationEndsListener; | ||
import org.matsim.core.utils.misc.OptionalTime; | ||
|
||
import java.util.SplittableRandom; | ||
|
||
/** | ||
* Estimates drt trips based only daily averages. No spatial or temporal differentiation is taken into account for the estimate. | ||
* This estimator is suited for small scenarios with few vehicles and trips and consequently few data points. | ||
*/ | ||
public class BasicDrtEstimator implements DrtEstimator, IterationEndsListener { | ||
|
||
private static final Logger log = LogManager.getLogger(BasicDrtEstimator.class); | ||
|
||
private final DrtEventSequenceCollector collector; | ||
private final DrtEstimatorConfigGroup config; | ||
private final DrtConfigGroup drtConfig; | ||
|
||
private final SplittableRandom rnd = new SplittableRandom(); | ||
/** | ||
* Currently valid estimates. | ||
*/ | ||
private GlobalEstimate currentEst; | ||
private RegressionResults fare; | ||
|
||
public BasicDrtEstimator(DrtEventSequenceCollector collector, DrtEstimatorConfigGroup config, | ||
DrtConfigGroup drtConfig) { | ||
//zones = injector.getModal(DrtZonalSystem.class); | ||
this.collector = collector; | ||
this.config = config; | ||
this.drtConfig = drtConfig; | ||
} | ||
|
||
@Override | ||
public void notifyIterationEnds(IterationEndsEvent event) { | ||
|
||
// Speed-up iteration need to be ignored for the estimates | ||
if (drtConfig.getDrtSpeedUpParams().isPresent() && | ||
DrtSpeedUp.isTeleportDrtUsers(drtConfig.getDrtSpeedUpParams().get(), | ||
event.getServices().getConfig().controller(), event.getIteration())) { | ||
return; | ||
} | ||
|
||
GlobalEstimate est = new GlobalEstimate(); | ||
|
||
int n = 0; | ||
|
||
int nRejections = collector.getRejectedRequestSequences().size(); | ||
int nSubmitted = collector.getRequestSubmissions().size(); | ||
|
||
for (DrtEventSequenceCollector.EventSequence seq : collector.getPerformedRequestSequences().values()) { | ||
|
||
if (seq.getPickedUp().isPresent() && seq.getDroppedOff().isPresent()) { | ||
|
||
double waitTime = seq.getPickedUp().get().getTime() - seq.getSubmitted().getTime(); | ||
est.waitTime.addValue(waitTime); | ||
|
||
double unsharedTime = seq.getSubmitted().getUnsharedRideTime(); | ||
double travelTime = seq.getDroppedOff().get().getTime() - seq.getPickedUp().get().getTime(); | ||
|
||
est.detour.addValue(travelTime / unsharedTime); | ||
|
||
double fare = seq.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum(); | ||
est.fare.addData(seq.getSubmitted().getUnsharedRideDistance(), fare); | ||
n++; | ||
} | ||
} | ||
|
||
// At least some data points are required | ||
if (n <= 3) | ||
return; | ||
|
||
fare = est.fare.regress(); | ||
|
||
double rejectionRate = (double) nRejections / nSubmitted; | ||
|
||
if (currentEst == null) { | ||
est.meanWait = est.waitTime.getMean(); | ||
est.stdWait = est.waitTime.getStandardDeviation(); | ||
est.meanDetour = est.detour.getMean(); | ||
est.stdDetour = est.detour.getStandardDeviation(); | ||
est.rejectionRate = rejectionRate; | ||
} else { | ||
est.meanWait = config.decayFactor * est.waitTime.getMean() + (1 - config.decayFactor) * currentEst.waitTime.getMean(); | ||
est.stdWait = config.decayFactor * est.waitTime.getStandardDeviation() + (1 - config.decayFactor) * currentEst.waitTime.getStandardDeviation(); | ||
est.meanDetour = config.decayFactor * est.detour.getMean() + (1 - config.decayFactor) * currentEst.detour.getMean(); | ||
est.stdDetour = config.decayFactor * est.detour.getStandardDeviation() + (1 - config.decayFactor) * currentEst.detour.getStandardDeviation(); | ||
est.rejectionRate = config.decayFactor * rejectionRate + (1 - config.decayFactor) * currentEst.rejectionRate; | ||
} | ||
|
||
log.info("Calculated {}", est); | ||
currentEst = est; | ||
} | ||
|
||
@Override | ||
public Estimate estimate(DrtRoute route, OptionalTime departureTime) { | ||
|
||
if (currentEst == null) { | ||
// If not estimates are present, use travel time alpha as detour | ||
// beta is not used, because estimates are supposed to be minimums and not worst cases | ||
double travelTime = Math.min(route.getDirectRideTime() + drtConfig.maxAbsoluteDetour, | ||
route.getDirectRideTime() * drtConfig.maxTravelTimeAlpha); | ||
|
||
double fare = 0; | ||
if (drtConfig.getDrtFareParams().isPresent()) { | ||
DrtFareParams fareParams = drtConfig.getDrtFareParams().get(); | ||
fare = fareParams.distanceFare_m * route.getDistance() | ||
+ fareParams.timeFare_h * route.getDirectRideTime() / 3600.0 | ||
+ fareParams.baseFare; | ||
|
||
fare = Math.max(fare, fareParams.minFarePerTrip); | ||
} | ||
|
||
// for distance, also use the max travel time alpha | ||
return new Estimate(route.getDistance() * drtConfig.maxTravelTimeAlpha, travelTime, drtConfig.maxWaitTime, fare, 0); | ||
} | ||
|
||
double fare = 0; | ||
if (this.fare != null) | ||
fare = this.fare.getParameterEstimate(0) + this.fare.getParameterEstimate(1) * route.getDistance(); | ||
|
||
if (drtConfig.getDrtFareParams().isPresent()) { | ||
fare = Math.max(fare, drtConfig.getDrtFareParams().get().minFarePerTrip); | ||
} | ||
|
||
double detour = Math.max(1, rnd.nextGaussian(currentEst.meanDetour, config.randomization * currentEst.stdDetour)); | ||
double waitTime = Math.max(0, rnd.nextGaussian(currentEst.meanWait, config.randomization * currentEst.stdWait)); | ||
|
||
return new Estimate(route.getDistance() * detour, route.getDirectRideTime() * detour, waitTime, fare, currentEst.rejectionRate); | ||
} | ||
|
||
/** | ||
* Helper class to hold statistics. | ||
*/ | ||
private static final class GlobalEstimate { | ||
|
||
private final SummaryStatistics waitTime = new SummaryStatistics(); | ||
private final SummaryStatistics detour = new SummaryStatistics(); | ||
private final SimpleRegression fare = new SimpleRegression(true); | ||
|
||
private double meanWait; | ||
private double stdWait; | ||
private double meanDetour; | ||
private double stdDetour; | ||
private double rejectionRate; | ||
|
||
@Override | ||
public String toString() { | ||
return "GlobalEstimate{" + | ||
"meanWait=" + meanWait + | ||
", stdWait=" + stdWait + | ||
", meanDetour=" + meanDetour + | ||
", stdDetour=" + stdDetour + | ||
", rejectionRate=" + rejectionRate + | ||
'}'; | ||
} | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
...ensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package org.matsim.contrib.drt.extension.estimator; | ||
|
||
import org.apache.commons.csv.CSVFormat; | ||
import org.apache.commons.csv.CSVPrinter; | ||
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.matsim.api.core.v01.events.PersonMoneyEvent; | ||
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector; | ||
import org.matsim.contrib.drt.extension.estimator.run.DrtEstimatorConfigGroup; | ||
import org.matsim.contrib.drt.routing.DrtRoute; | ||
import org.matsim.core.controler.events.AfterMobsimEvent; | ||
import org.matsim.core.controler.events.ShutdownEvent; | ||
import org.matsim.core.controler.events.StartupEvent; | ||
import org.matsim.core.controler.listener.AfterMobsimListener; | ||
import org.matsim.core.controler.listener.ShutdownListener; | ||
import org.matsim.core.controler.listener.StartupListener; | ||
import org.matsim.core.utils.misc.OptionalTime; | ||
|
||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
|
||
/** | ||
* Analyzes and outputs drt estimates errors metrics based on daily requests. | ||
*/ | ||
public final class DrtEstimateAnalyzer implements StartupListener, ShutdownListener, AfterMobsimListener { | ||
|
||
private static final Logger log = LogManager.getLogger(DrtEstimateAnalyzer.class); | ||
|
||
// Might be useful but not needed currently | ||
//private final DefaultMainLegRouter.RouteCreator creator; | ||
private final DrtEstimator estimator; | ||
private final DrtEventSequenceCollector collector; | ||
private final DrtEstimatorConfigGroup config; | ||
|
||
private CSVPrinter csv; | ||
|
||
public DrtEstimateAnalyzer(DrtEstimator estimator, DrtEventSequenceCollector collector, DrtEstimatorConfigGroup config) { | ||
this.estimator = estimator; | ||
this.collector = collector; | ||
this.config = config; | ||
} | ||
|
||
@Override | ||
public void notifyStartup(StartupEvent event) { | ||
|
||
String filename = event.getServices().getControlerIO().getOutputFilename("drt_estimates_" + config.getMode() + ".csv"); | ||
|
||
try { | ||
csv = new CSVPrinter(Files.newBufferedWriter(Path.of(filename), StandardCharsets.UTF_8), CSVFormat.DEFAULT); | ||
csv.printRecord("iteration", | ||
"wait_time_mae", "wait_time_err_q5", "wait_time_err_q50", "wait_time_err_q95", | ||
"travel_time_mae", "travel_time_err_q5", "travel_time_err_q50", "travel_time_err_q95", | ||
"fare_mae", "fare_err_q5", "fare_err_q50", "fare_err_q95" | ||
); | ||
|
||
} catch (IOException e) { | ||
throw new UncheckedIOException("Could not open output file for estimates.", e); | ||
} | ||
} | ||
|
||
@Override | ||
public void notifyShutdown(ShutdownEvent event) { | ||
try { | ||
csv.close(); | ||
} catch (IOException e) { | ||
log.warn("Could not close drt estimate file", e); | ||
} | ||
} | ||
|
||
/** | ||
* Needs to run before any estimators updates. | ||
*/ | ||
@Override | ||
public void notifyAfterMobsim(AfterMobsimEvent event) { | ||
|
||
try { | ||
csv.printRecord(calcMetrics(event.getIteration())); | ||
csv.flush(); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
/** | ||
* Return row of metrics for the csv file. | ||
*/ | ||
private Iterable<Number> calcMetrics(int iteration) { | ||
|
||
DescriptiveStatistics waitTime = new DescriptiveStatistics(); | ||
DescriptiveStatistics travelTime = new DescriptiveStatistics(); | ||
DescriptiveStatistics fare = new DescriptiveStatistics(); | ||
|
||
for (DrtEventSequenceCollector.EventSequence seq : collector.getPerformedRequestSequences().values()) { | ||
if (seq.getPickedUp().isPresent() && seq.getDroppedOff().isPresent()) { | ||
|
||
// many attributes are not filled, when using the constructor | ||
DrtRoute route = new DrtRoute(seq.getSubmitted().getFromLinkId(), seq.getSubmitted().getToLinkId()); | ||
route.setDirectRideTime(seq.getSubmitted().getUnsharedRideTime()); | ||
route.setDistance(seq.getSubmitted().getUnsharedRideDistance()); | ||
|
||
double valWaitTime = seq.getPickedUp().get().getTime() - seq.getSubmitted().getTime(); | ||
double valTravelTime = seq.getDroppedOff().get().getTime() - seq.getPickedUp().get().getTime(); | ||
double valFare = seq.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum(); | ||
|
||
DrtEstimator.Estimate estimate = estimator.estimate(route, OptionalTime.defined(seq.getSubmitted().getTime())); | ||
|
||
waitTime.addValue(Math.abs(estimate.waitingTime() - valWaitTime)); | ||
travelTime.addValue(Math.abs(estimate.travelTime() - valTravelTime)); | ||
fare.addValue(Math.abs(estimate.fare() - valFare)); | ||
} | ||
} | ||
|
||
return List.of( | ||
iteration, | ||
waitTime.getMean(), waitTime.getPercentile(5), waitTime.getPercentile(50), waitTime.getPercentile(95), | ||
travelTime.getMean(), travelTime.getPercentile(5), travelTime.getPercentile(50), travelTime.getPercentile(95), | ||
fare.getMean(), fare.getPercentile(5), fare.getPercentile(50), fare.getPercentile(95) | ||
); | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
...drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package org.matsim.contrib.drt.extension.estimator; | ||
|
||
import org.matsim.contrib.drt.routing.DrtRoute; | ||
import org.matsim.core.controler.listener.ControlerListener; | ||
import org.matsim.core.utils.misc.OptionalTime; | ||
|
||
/** | ||
* Interface to estimate a DRT service's detour, waiting time and costs. | ||
*/ | ||
public interface DrtEstimator extends ControlerListener { | ||
|
||
/** | ||
* Provide an estimate for a drt route with specific pickup and dropoff point. | ||
* | ||
* @param route drt route | ||
* @param departureTime estimated departure time | ||
* @return An {@link Estimate} instance | ||
*/ | ||
Estimate estimate(DrtRoute route, OptionalTime departureTime); | ||
|
||
|
||
/** | ||
* Estimate for various attributes for a drt trip. | ||
* | ||
* @param distance travel distance in meter | ||
* @param travelTime travel time in seconds | ||
* @param waitingTime waiting time in seconds | ||
* @param fare money, which is negative if the customer needs to pay it | ||
* @param rejectionRate probability of a trip being rejected | ||
*/ | ||
record Estimate(double distance, double travelTime, double waitingTime, double fare, double rejectionRate) { | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.