From a9efdbc8e49c98719b4ebc2b0e2d1f17d27ba1df Mon Sep 17 00:00:00 2001 From: rakow Date: Wed, 18 Oct 2023 14:02:44 +0200 Subject: [PATCH] DRT Leg Estimators (#2865) * 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 --- contribs/drt-extensions/pom.xml | 7 + .../estimator/BasicDrtEstimator.java | 172 ++++++++++++++++++ .../estimator/DrtEstimateAnalyzer.java | 126 +++++++++++++ .../drt/extension/estimator/DrtEstimator.java | 35 ++++ .../estimator/MultiModalDrtLegEstimator.java | 58 ++++++ .../run/DrtEstimatorConfigGroup.java | 61 +++++++ .../estimator/run/DrtEstimatorModule.java | 80 ++++++++ .../run/MultiModeDrtEstimatorConfigGroup.java | 103 +++++++++++ .../drt/extension/DrtTestScenario.java | 4 + .../MultiModalDrtLegEstimatorTest.java | 90 +++++++++ .../speedup/DrtTeleportedRouteCalculator.java | 6 + .../modechoice/InformedModeChoiceModule.java | 2 +- .../matsim/modechoice/PlanModelService.java | 3 +- 13 files changed, 745 insertions(+), 2 deletions(-) create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimator.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimator.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorModule.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/MultiModeDrtEstimatorConfigGroup.java create mode 100644 contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimatorTest.java diff --git a/contribs/drt-extensions/pom.xml b/contribs/drt-extensions/pom.xml index b56ab569782..2217f9ebdc7 100644 --- a/contribs/drt-extensions/pom.xml +++ b/contribs/drt-extensions/pom.xml @@ -24,6 +24,12 @@ 16.0-SNAPSHOT + + org.matsim.contrib + informed-mode-choice + 16.0-SNAPSHOT + + org.matsim.contrib simwrapper @@ -33,6 +39,7 @@ org.assertj assertj-core + test diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java new file mode 100644 index 00000000000..3d7555020e1 --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java @@ -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 + + '}'; + } + } +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java new file mode 100644 index 00000000000..2d87f7ea7b8 --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java @@ -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 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) + ); + } + +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimator.java new file mode 100644 index 00000000000..fef209ed211 --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimator.java @@ -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) { + + } + +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimator.java new file mode 100644 index 00000000000..ed1771ae34f --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimator.java @@ -0,0 +1,58 @@ +package org.matsim.contrib.drt.extension.estimator; + +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.contrib.drt.routing.DrtRoute; +import org.matsim.contrib.dvrp.run.DvrpMode; +import org.matsim.core.scoring.functions.ModeUtilityParameters; +import org.matsim.core.utils.misc.OptionalTime; +import org.matsim.modechoice.EstimatorContext; +import org.matsim.modechoice.ModeAvailability; +import org.matsim.modechoice.estimators.LegEstimator; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + + +/** + * Aggregate class for informed-mode-choice that makes sure to invoke the correct estimator for each drt mode. + */ +public class MultiModalDrtLegEstimator implements LegEstimator { + + private static final Logger log = LogManager.getLogger(MultiModalDrtLegEstimator.class); + + protected final Map estimators = new HashMap<>(); + + @Inject + public MultiModalDrtLegEstimator(Map estimators) { + for (Map.Entry e : estimators.entrySet()) { + this.estimators.put(e.getKey().value(), e.getValue()); + } + } + + @Override + public double estimate(EstimatorContext context, String mode, Leg leg, ModeAvailability option) { + + if (!(leg.getRoute() instanceof DrtRoute route)) + throw new IllegalStateException("Drt leg routes must be of type DrtRoute."); + + OptionalTime departureTime = leg.getDepartureTime(); + + DrtEstimator estimator = Objects.requireNonNull(estimators.get(mode), String.format("No drt estimator found for mode %s. Check warnings for errors.", mode)); + + DrtEstimator.Estimate est = estimator.estimate(route, departureTime); + ModeUtilityParameters params = context.scoring.modeParams.get(mode); + + // By default, waiting time is scored as travel time + return params.constant + + params.marginalUtilityOfDistance_m * est.distance() + + params.marginalUtilityOfTraveling_s * est.travelTime() + + params.marginalUtilityOfTraveling_s * est.waitingTime() + + context.scoring.marginalUtilityOfMoney * params.monetaryDistanceCostRate * est.distance() + + context.scoring.marginalUtilityOfMoney * est.fare(); + + } +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java new file mode 100644 index 00000000000..107aa2eb53a --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java @@ -0,0 +1,61 @@ +package org.matsim.contrib.drt.extension.estimator.run; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.contrib.dvrp.run.Modal; +import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets; + +public class DrtEstimatorConfigGroup extends ReflectiveConfigGroupWithConfigurableParameterSets implements Modal { + + /** + * Type of estimator, which will be installed in {@link DrtEstimatorModule}. + */ + public enum EstimatorType { + BASIC, + + /** + * Custom estimator, that needs to provided via binding. + */ + CUSTOM + } + + public static final String GROUP_NAME = "drtEstimator"; + + public DrtEstimatorConfigGroup() { + super(GROUP_NAME); + } + + public DrtEstimatorConfigGroup(String mode) { + super(GROUP_NAME); + this.mode = mode; + } + + @Parameter + @Comment("Mode of the drt service to estimate.") + @NotBlank + public String mode = TransportMode.drt; + + @Parameter + @Comment("Estimator typed to be used. In case of 'CUSTOM', guice bindings needs to be provided.") + @NotNull + public EstimatorType estimator = EstimatorType.BASIC; + + @Parameter + @Comment("Decay of the exponential moving average.") + @Positive + public double decayFactor = 0.7; + + @Parameter + @Comment("Factor multiplied with standard deviation to randomize estimates.") + @PositiveOrZero + public double randomization = 0.1; + + @Override + public String getMode() { + return mode; + } + +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorModule.java new file mode 100644 index 00000000000..14bc2ae330d --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorModule.java @@ -0,0 +1,80 @@ +package org.matsim.contrib.drt.extension.estimator.run; + +import com.google.inject.Singleton; +import com.google.inject.multibindings.MapBinder; +import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector; +import org.matsim.contrib.drt.extension.estimator.BasicDrtEstimator; +import org.matsim.contrib.drt.extension.estimator.DrtEstimateAnalyzer; +import org.matsim.contrib.drt.extension.estimator.DrtEstimator; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule; +import org.matsim.contrib.dvrp.run.DvrpMode; +import org.matsim.contrib.dvrp.run.DvrpModes; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; + +import java.util.Optional; + +/** + * Main module that needs to be installed if any estimator is to be used. + */ +public class DrtEstimatorModule extends AbstractModule { + + @Override + public void install() { + + MultiModeDrtConfigGroup drtConfigs = MultiModeDrtConfigGroup.get(getConfig()); + MultiModeDrtEstimatorConfigGroup configs = ConfigUtils.addOrGetModule(getConfig(), MultiModeDrtEstimatorConfigGroup.class); + + for (DrtConfigGroup cfg : drtConfigs.getModalElements()) { + + Optional estCfg = configs.getModalElement(cfg.mode); + + estCfg.ifPresent(drtEstimatorConfigGroup -> install(new ModeModule(cfg, drtEstimatorConfigGroup))); + } + } + + static final class ModeModule extends AbstractDvrpModeModule { + + private final DrtConfigGroup cfg; + private final DrtEstimatorConfigGroup group; + + public ModeModule(DrtConfigGroup cfg, DrtEstimatorConfigGroup group) { + super(group.mode); + this.cfg = cfg; + this.group = group; + } + + @Override + public void install() { + + // try with default injections and overwrite + if (group.estimator == DrtEstimatorConfigGroup.EstimatorType.BASIC) { + bindModal(DrtEstimator.class).toProvider(modalProvider( + getter -> new BasicDrtEstimator(getter.getModal(DrtEventSequenceCollector.class), group, cfg) + )).in(Singleton.class); + } + + // DRT Estimators will be available as Map + MapBinder.newMapBinder(this.binder(), DvrpMode.class, DrtEstimator.class) + .addBinding(DvrpModes.mode(getMode())) + .to(modalKey(DrtEstimator.class)); + + addControlerListenerBinding().to(modalKey(DrtEstimator.class)); + + bindModal(DrtEstimatorConfigGroup.class).toInstance(group); + + // Needs to run before estimators + bindModal(DrtEstimateAnalyzer.class) + .toProvider( + modalProvider(getter -> new DrtEstimateAnalyzer(getter.getModal(DrtEstimator.class), getter.getModal(DrtEventSequenceCollector.class), group)) + ) + .in(Singleton.class); + + addControlerListenerBinding().to(modalKey(DrtEstimateAnalyzer.class)); + + } + + } +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/MultiModeDrtEstimatorConfigGroup.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/MultiModeDrtEstimatorConfigGroup.java new file mode 100644 index 00000000000..b998bf78648 --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/MultiModeDrtEstimatorConfigGroup.java @@ -0,0 +1,103 @@ +/* + * *********************************************************************** * + * project: org.matsim.* + * *********************************************************************** * + * * + * copyright : (C) 2018 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** * + */ + +package org.matsim.contrib.drt.extension.estimator.run; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.Supplier; + +import org.matsim.contrib.dvrp.run.MultiModal; +import org.matsim.contrib.dvrp.run.MultiModals; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.config.ReflectiveConfigGroup; + +import com.google.common.base.Verify; + +/** + * @author Michal Maciejewski (michalm) + */ +public final class MultiModeDrtEstimatorConfigGroup extends ReflectiveConfigGroup implements MultiModal { + public static final String GROUP_NAME = "drtEstimators"; + + /** + * @param config + * @return MultiModeDrtConfigGroup if exists. Otherwise fails + */ + public static MultiModeDrtEstimatorConfigGroup get(Config config) { + return (MultiModeDrtEstimatorConfigGroup)config.getModule(GROUP_NAME); + } + + private final Supplier drtConfigSupplier; + + public MultiModeDrtEstimatorConfigGroup() { + this(DrtEstimatorConfigGroup::new); + } + + public MultiModeDrtEstimatorConfigGroup(Supplier drtConfigSupplier) { + super(GROUP_NAME); + this.drtConfigSupplier = drtConfigSupplier; + } + + @Override + protected void checkConsistency(Config config) { + super.checkConsistency(config); + Verify.verify(config.getModule(DrtEstimatorConfigGroup.GROUP_NAME) == null, + "In the multi-mode DRT setup, DrtEstimatorConfigGroup must not be defined at the config top level"); + MultiModals.requireAllModesUnique(this); + } + + @Override + public ConfigGroup createParameterSet(String type) { + if (type.equals(DrtEstimatorConfigGroup.GROUP_NAME)) { + return drtConfigSupplier.get(); + } else { + throw new IllegalArgumentException("Unsupported parameter set type: " + type); + } + } + + @Override + public void addParameterSet(ConfigGroup set) { + if (set instanceof DrtEstimatorConfigGroup) { + super.addParameterSet(set); + } else { + throw new IllegalArgumentException("Unsupported parameter set class: " + set); + } + } + + public void addParameterSet(DrtEstimatorConfigGroup set) { + addParameterSet((ConfigGroup) set); + } + + @Override + @SuppressWarnings("unchecked") + public Collection getModalElements() { + return (Collection)getParameterSets(DrtEstimatorConfigGroup.GROUP_NAME); + } + + /** + * Find estimator config for specific mode. + */ + public Optional getModalElement(String mode) { + return getModalElements().stream().filter(m -> m.getMode().equals(mode)).findFirst(); + } + +} diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/DrtTestScenario.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/DrtTestScenario.java index db01301303c..7131c33a368 100644 --- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/DrtTestScenario.java +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/DrtTestScenario.java @@ -24,12 +24,14 @@ import org.matsim.core.utils.io.IOUtils; import org.matsim.examples.ExamplesUtils; import org.matsim.simwrapper.SimWrapperModule; +import org.matsim.modechoice.InformedModeChoiceConfigGroup; import org.matsim.testcases.MatsimTestUtils; import org.matsim.vehicles.VehicleType; import javax.annotation.Nullable; import java.net.URL; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -82,6 +84,8 @@ protected Config prepareConfig(Config config) { config.scoring().addActivityParams(new ScoringConfigGroup.ActivityParams("freight_start").setTypicalDuration(60 * 15)); config.scoring().addActivityParams(new ScoringConfigGroup.ActivityParams("freight_end").setTypicalDuration(60 * 15)); + InformedModeChoiceConfigGroup imc = ConfigUtils.addOrGetModule(config, InformedModeChoiceConfigGroup.class); + imc.setModes(Set.of("drt", "av", "car", "pt", "bike", "walk")); MultiModeDrtConfigGroup multiModeDrtConfig = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class); ConfigUtils.addOrGetModule(config, DvrpConfigGroup.class); diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimatorTest.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimatorTest.java new file mode 100644 index 00000000000..8d61ef68123 --- /dev/null +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModalDrtLegEstimatorTest.java @@ -0,0 +1,90 @@ +package org.matsim.contrib.drt.extension.estimator; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.application.MATSimApplication; +import org.matsim.contrib.drt.extension.DrtTestScenario; +import org.matsim.contrib.drt.extension.estimator.run.DrtEstimatorConfigGroup; +import org.matsim.contrib.drt.extension.estimator.run.DrtEstimatorModule; +import org.matsim.contrib.drt.extension.estimator.run.MultiModeDrtEstimatorConfigGroup; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.ReplanningConfigGroup; +import org.matsim.core.controler.Controler; +import org.matsim.modechoice.InformedModeChoiceModule; +import org.matsim.modechoice.ModeOptions; +import org.matsim.modechoice.estimators.DefaultLegScoreEstimator; +import org.matsim.modechoice.estimators.FixedCostsEstimator; +import org.matsim.testcases.MatsimTestUtils; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MultiModalDrtLegEstimatorTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + private Controler controler; + + @Before + public void setUp() throws Exception { + + Config config = DrtTestScenario.loadConfig(utils); + + config.controller().setLastIteration(3); + + controler = MATSimApplication.prepare(new DrtTestScenario(MultiModalDrtLegEstimatorTest::prepare, MultiModalDrtLegEstimatorTest::prepare), config); + } + + private static void prepare(Controler controler) { + InformedModeChoiceModule.Builder builder = InformedModeChoiceModule.newBuilder() + .withFixedCosts(FixedCostsEstimator.DailyConstant.class, "car") + .withLegEstimator(DefaultLegScoreEstimator.class, ModeOptions.AlwaysAvailable.class, "bike", "walk", "pt") + .withLegEstimator(DefaultLegScoreEstimator.class, ModeOptions.ConsiderYesAndNo.class, "car") + .withLegEstimator(MultiModalDrtLegEstimator.class, ModeOptions.AlwaysAvailable.class, "drt", "av"); + + controler.addOverridingModule(builder.build()); + controler.addOverridingModule(new DrtEstimatorModule()); + } + + private static void prepare(Config config) { + + MultiModeDrtEstimatorConfigGroup estimators = ConfigUtils.addOrGetModule(config, MultiModeDrtEstimatorConfigGroup.class); + + estimators.addParameterSet(new DrtEstimatorConfigGroup("drt")); + estimators.addParameterSet(new DrtEstimatorConfigGroup("av")); + + // Set subtour mode selection as strategy + List strategies = config.replanning().getStrategySettings().stream() + .filter(s -> !s.getStrategyName().toLowerCase().contains("mode") + ).collect(Collectors.toList()); + + strategies.add(new ReplanningConfigGroup.StrategySettings() + .setStrategyName(InformedModeChoiceModule.SELECT_SUBTOUR_MODE_STRATEGY) + .setSubpopulation("person") + .setWeight(0.2)); + + config.replanning().clearStrategySettings(); + strategies.forEach(s -> config.replanning().addStrategySettings(s)); + + } + + @Test + public void run() { + + String out = utils.getOutputDirectory(); + + controler.run(); + + assertThat(new File(out, "kelheim-mini-drt.drt_estimates_drt.csv")) + .exists() + .isNotEmpty(); + + + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtTeleportedRouteCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtTeleportedRouteCalculator.java index 7935a2403e8..eca53f0c0d7 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtTeleportedRouteCalculator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtTeleportedRouteCalculator.java @@ -40,6 +40,12 @@ public class DrtTeleportedRouteCalculator implements TeleportedRouteCalculator { this.averageInVehicleBeelineSpeed = averageInVehicleBeelineSpeed; } + // TODO: from discussion from michal and rakow + // speedup is currently using very simple and not exchangeable estimators + // it could be possible to integrate the drt estimators used by the informed mode-choice + // this router should probably not use the beeline distance but the direct travel route + // speed-up would still be significant (oct'23) + @Override public Route calculateRoute(PassengerRequest request) { Link startLink = request.getFromLink(); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java index d151624afe2..a6229390386 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java @@ -167,7 +167,7 @@ public > Builder withFixedCosts(Class> Builder withLegEstimator(Class> estimator, Class> option, + public > Builder withLegEstimator(Class> estimator, Class> option, String... modes) { for (String mode : modes) { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java index 4930197c262..93c2ca21a0f 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java @@ -157,6 +157,7 @@ public List allowedModes(PlanModel planModel) { /** * Calculate the estimates for all options. Note that plan model has to be routed before computing estimates. */ + @SuppressWarnings("rawtypes") public void calculateEstimates(EstimatorContext context, PlanModel planModel) { for (Map.Entry> e : planModel.getEstimates().entrySet()) { @@ -208,7 +209,7 @@ public void calculateEstimates(EstimatorContext context, PlanModel planModel) { if (tripEst != null && legMode.equals(c.getMode())) continue; - LegEstimator> legEst = (LegEstimator>) legEstimators.get(legMode); + LegEstimator legEst = legEstimators.get(legMode); if (legEst == null) throw new IllegalStateException("No leg estimator defined for mode: " + legMode);