Skip to content

Commit

Permalink
Configurable initial DRT estimators (#2968)
Browse files Browse the repository at this point in the history
* add interface and implementation for initial drt estimates

* remove left over toods
  • Loading branch information
rakow authored Nov 28, 2023
1 parent de7b2a7 commit e42801e
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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();
/**
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String, Function<DrtConfigGroup, DrtInitialEstimator>> initial = new HashMap<>();

@Override
public void install() {

Expand All @@ -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<DrtConfigGroup, DrtInitialEstimator> 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;
Expand All @@ -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<DvrpMode, DrtEstimator>
MapBinder.newMapBinder(this.binder(), DvrpMode.class, DrtEstimator.class)
.addBinding(DvrpModes.mode(getMode()))
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ReplanningConfigGroup.StrategySettings> 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();


}
}

0 comments on commit e42801e

Please sign in to comment.