From e42801ef639c0714bcc50f1b154c2163646228d8 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 28 Nov 2023 12:21:17 +0100 Subject: [PATCH] Configurable initial DRT estimators (#2968) * add interface and implementation for initial drt estimates * remove left over toods --- .../estimator/DrtInitialEstimator.java | 9 ++ .../{ => impl}/BasicDrtEstimator.java | 30 ++---- .../estimator/impl/ConstantDrtEstimator.java | 50 ++++++++++ .../impl/PessimisticDrtEstimator.java | 40 ++++++++ .../run/DrtEstimatorConfigGroup.java | 13 +++ .../estimator/run/DrtEstimatorModule.java | 45 ++++++++- .../MultiModaFixedDrtLegEstimatorTest.java | 93 +++++++++++++++++++ 7 files changed, 256 insertions(+), 24 deletions(-) create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtInitialEstimator.java rename contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/{ => impl}/BasicDrtEstimator.java (85%) create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/ConstantDrtEstimator.java create mode 100644 contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/PessimisticDrtEstimator.java create mode 100644 contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModaFixedDrtLegEstimatorTest.java diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtInitialEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtInitialEstimator.java new file mode 100644 index 00000000000..a826a936e46 --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtInitialEstimator.java @@ -0,0 +1,9 @@ +package org.matsim.contrib.drt.extension.estimator; + +/** + * This interface is used to provide an initial estimate for the drt service. + * Supposed to be used when no data is available from the simulation yet. + * The interface is exactly the same as {@link DrtEstimator}, but this class won't be called with update events. + */ +public interface DrtInitialEstimator extends DrtEstimator { +} 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/impl/BasicDrtEstimator.java similarity index 85% rename from contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java rename to contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/BasicDrtEstimator.java index d446d5f34bc..f1fd6f04c8f 100644 --- 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/impl/BasicDrtEstimator.java @@ -1,4 +1,4 @@ -package org.matsim.contrib.drt.extension.estimator; +package org.matsim.contrib.drt.extension.estimator.impl; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import org.apache.commons.math3.stat.regression.RegressionResults; @@ -9,8 +9,9 @@ import org.matsim.api.core.v01.events.PersonMoneyEvent; import org.matsim.api.core.v01.population.Person; import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector; +import org.matsim.contrib.drt.extension.estimator.DrtEstimator; +import org.matsim.contrib.drt.extension.estimator.DrtInitialEstimator; 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; @@ -32,6 +33,7 @@ public class BasicDrtEstimator implements DrtEstimator, IterationEndsListener { private final DrtEventSequenceCollector collector; private final DrtEstimatorConfigGroup config; private final DrtConfigGroup drtConfig; + private final DrtInitialEstimator initial; private final SplittableRandom rnd = new SplittableRandom(); /** @@ -40,10 +42,11 @@ public class BasicDrtEstimator implements DrtEstimator, IterationEndsListener { private GlobalEstimate currentEst; private RegressionResults fare; - public BasicDrtEstimator(DrtEventSequenceCollector collector, DrtEstimatorConfigGroup config, - DrtConfigGroup drtConfig) { + public BasicDrtEstimator(DrtEventSequenceCollector collector, DrtInitialEstimator initial, + DrtEstimatorConfigGroup config, DrtConfigGroup drtConfig) { //zones = injector.getModal(DrtZonalSystem.class); this.collector = collector; + this.initial = initial; this.config = config; this.drtConfig = drtConfig; } @@ -115,23 +118,8 @@ public void notifyIterationEnds(IterationEndsEvent event) { 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); + // Same interface, just different binding + return initial.estimate(route, departureTime); } double fare = 0; diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/ConstantDrtEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/ConstantDrtEstimator.java new file mode 100644 index 00000000000..d9a4d4be46f --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/ConstantDrtEstimator.java @@ -0,0 +1,50 @@ +package org.matsim.contrib.drt.extension.estimator.impl; + +import org.matsim.contrib.drt.extension.estimator.DrtInitialEstimator; +import org.matsim.contrib.drt.fare.DrtFareParams; +import org.matsim.contrib.drt.routing.DrtRoute; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.core.utils.misc.OptionalTime; + +/** + * Estimates using a constant detour factor and waiting time. + */ +public class ConstantDrtEstimator implements DrtInitialEstimator { + + private final DrtConfigGroup drtConfig; + + /** + * Detour factor for the estimate. 1.0 means no detour, 2.0 means twice the distance. + */ + private final double detourFactor; + + /** + * Constant waiting time estimate in seconds. + */ + private final double waitingTime; + + public ConstantDrtEstimator(DrtConfigGroup drtConfig, double detourFactor, double waitingTime) { + this.drtConfig = drtConfig; + this.detourFactor = detourFactor; + this.waitingTime = waitingTime; + } + + @Override + public Estimate estimate(DrtRoute route, OptionalTime departureTime) { + + double distance = route.getDistance() * detourFactor; + double travelTime = route.getDirectRideTime() * detourFactor; + + double fare = 0; + if (drtConfig.getDrtFareParams().isPresent()) { + DrtFareParams fareParams = drtConfig.getDrtFareParams().get(); + fare = fareParams.distanceFare_m * distance + + fareParams.timeFare_h * travelTime / 3600.0 + + fareParams.baseFare; + + fare = Math.max(fare, fareParams.minFarePerTrip); + } + + return new Estimate(distance, travelTime, waitingTime, fare, 0); + } +} diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/PessimisticDrtEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/PessimisticDrtEstimator.java new file mode 100644 index 00000000000..d26a318c635 --- /dev/null +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/impl/PessimisticDrtEstimator.java @@ -0,0 +1,40 @@ +package org.matsim.contrib.drt.extension.estimator.impl; + +import org.matsim.contrib.drt.extension.estimator.DrtInitialEstimator; +import org.matsim.contrib.drt.fare.DrtFareParams; +import org.matsim.contrib.drt.routing.DrtRoute; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.core.utils.misc.OptionalTime; + +/** + * Uses the upper bounds from config for the initial estimate. + */ +public class PessimisticDrtEstimator implements DrtInitialEstimator { + private final DrtConfigGroup drtConfig; + + public PessimisticDrtEstimator(DrtConfigGroup drtConfig) { + this.drtConfig = drtConfig; + } + + @Override + public Estimate estimate(DrtRoute route, OptionalTime departureTime) { + // 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); + } + +} 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 index 107aa2eb53a..985435bcfc3 100644 --- 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 @@ -16,6 +16,11 @@ public class DrtEstimatorConfigGroup extends ReflectiveConfigGroupWithConfigurab public enum EstimatorType { BASIC, + /** + * Will use the bound initial estimator, without any updates. + */ + INITIAL, + /** * Custom estimator, that needs to provided via binding. */ @@ -58,4 +63,12 @@ public String getMode() { return mode; } + /** + * Set estimator type and return same instance. + */ + public DrtEstimatorConfigGroup withEstimator(EstimatorType estimator) { + this.estimator = estimator; + return this; + } + } 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 index 14bc2ae330d..c5a8ed00e7c 100644 --- 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 @@ -3,9 +3,11 @@ 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.extension.estimator.DrtInitialEstimator; +import org.matsim.contrib.drt.extension.estimator.impl.BasicDrtEstimator; +import org.matsim.contrib.drt.extension.estimator.impl.PessimisticDrtEstimator; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule; @@ -14,13 +16,22 @@ import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.AbstractModule; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.function.Function; /** * Main module that needs to be installed if any estimator is to be used. */ public class DrtEstimatorModule extends AbstractModule { + + /** + * Map of initial providers. + */ + private final Map> initial = new HashMap<>(); + @Override public void install() { @@ -35,7 +46,24 @@ public void install() { } } - static final class ModeModule extends AbstractDvrpModeModule { + /** + * Configure initial estimators for the given modes. + * + * @param getter modal getter, which can be used to use other modal components via guice + */ + public DrtEstimatorModule withInitialEstimator(Function getter, String... modes) { + + if (modes.length == 0) + throw new IllegalArgumentException("At least one mode needs to be provided."); + + for (String mode : modes) { + initial.put(mode, getter); + } + + return this; + } + + final class ModeModule extends AbstractDvrpModeModule { private final DrtConfigGroup cfg; private final DrtEstimatorConfigGroup group; @@ -52,10 +80,21 @@ 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) + getter -> new BasicDrtEstimator( + getter.getModal(DrtEventSequenceCollector.class), + getter.getModal(DrtInitialEstimator.class), + group, cfg + ) )).in(Singleton.class); + } else if (group.estimator == DrtEstimatorConfigGroup.EstimatorType.INITIAL) { + bindModal(DrtEstimator.class).to(modalKey(DrtInitialEstimator.class)); } + if (initial.containsKey(group.mode)) { + bindModal(DrtInitialEstimator.class).toProvider(() -> initial.get(group.mode).apply(cfg)).in(Singleton.class); + } else + bindModal(DrtInitialEstimator.class).toInstance(new PessimisticDrtEstimator(cfg)); + // DRT Estimators will be available as Map MapBinder.newMapBinder(this.binder(), DvrpMode.class, DrtEstimator.class) .addBinding(DvrpModes.mode(getMode())) diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModaFixedDrtLegEstimatorTest.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModaFixedDrtLegEstimatorTest.java new file mode 100644 index 00000000000..de1c98e62d1 --- /dev/null +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/estimator/MultiModaFixedDrtLegEstimatorTest.java @@ -0,0 +1,93 @@ +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.impl.ConstantDrtEstimator; +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 MultiModaFixedDrtLegEstimatorTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + private Controler controler; + + 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() + .withInitialEstimator(cfg -> new ConstantDrtEstimator(cfg, 1.05, 300), "drt", "av")); + } + + private static void prepare(Config config) { + + MultiModeDrtEstimatorConfigGroup estimators = ConfigUtils.addOrGetModule(config, MultiModeDrtEstimatorConfigGroup.class); + + estimators.addParameterSet(new DrtEstimatorConfigGroup("drt") + .withEstimator(DrtEstimatorConfigGroup.EstimatorType.INITIAL)); + 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)); + + } + + @Before + public void setUp() throws Exception { + + Config config = DrtTestScenario.loadConfig(utils); + + config.controller().setLastIteration(3); + + controler = MATSimApplication.prepare(new DrtTestScenario(MultiModaFixedDrtLegEstimatorTest::prepare, MultiModaFixedDrtLegEstimatorTest::prepare), config); + } + + @Test + public void run() { + + String out = utils.getOutputDirectory(); + + controler.run(); + + assertThat(new File(out, "kelheim-mini-drt.drt_estimates_drt.csv")) + .exists() + .isNotEmpty(); + + + } +}