Skip to content

Commit

Permalink
IMC Trip Score Estimator (matsim-org#3642)
Browse files Browse the repository at this point in the history
* add interface for trip score estimates

* fix injection

* fix test

* make strategy names final
rakow authored Dec 16, 2024

Verified

This commit was signed with the committer’s verified signature.
mibofra Francesco Bonanno
1 parent 02d6485 commit 426d78c
Showing 7 changed files with 109 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ public class InformedModeChoiceConfigGroup extends ReflectiveConfigGroup {

@Parameter
@Comment("Require that new plan modes are always different from the current one.")
private boolean requireDifferentModes = true;
private boolean requireDifferentModes = false;

@Parameter
@Comment("Defines how constraint violations are handled.")
Original file line number Diff line number Diff line change
@@ -6,27 +6,23 @@
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import org.matsim.core.config.Config;
import org.matsim.core.config.groups.ReplanningConfigGroup;
import org.matsim.core.controler.AbstractModule;
import org.matsim.core.controler.listener.ControlerListener;
import org.matsim.core.router.PlanRouter;
import org.matsim.core.router.TripRouter;
import org.matsim.core.utils.timing.TimeInterpretation;
import org.matsim.facilities.ActivityFacilities;
import org.matsim.modechoice.constraints.TripConstraint;
import org.matsim.modechoice.estimators.ActivityEstimator;
import org.matsim.modechoice.estimators.FixedCostsEstimator;
import org.matsim.modechoice.estimators.LegEstimator;
import org.matsim.modechoice.estimators.TripEstimator;
import org.matsim.modechoice.estimators.*;
import org.matsim.modechoice.pruning.CandidatePruner;
import org.matsim.modechoice.replanning.*;
import org.matsim.modechoice.search.BestChoiceGenerator;
import org.matsim.modechoice.search.SingleTripChoicesGenerator;
import org.matsim.modechoice.search.TopKChoicesGenerator;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;

/**
* The main and only module needed to install to use informed mode choice functionality.
@@ -35,12 +31,12 @@
*/
public final class InformedModeChoiceModule extends AbstractModule {

public static String SELECT_BEST_K_PLAN_MODES_STRATEGY = "SelectBestKPlanModes";
public final static String SELECT_BEST_K_PLAN_MODES_STRATEGY = "SelectBestKPlanModes";

public static String SELECT_SINGLE_TRIP_MODE_STRATEGY = "SelectSingleTripMode";
public final static String SELECT_SINGLE_TRIP_MODE_STRATEGY = "SelectSingleTripMode";

public static String SELECT_SUBTOUR_MODE_STRATEGY = "SelectSubtourMode";
public static String RANDOM_SUBTOUR_MODE_STRATEGY = "RandomSubtourMode";
public final static String SELECT_SUBTOUR_MODE_STRATEGY = "SelectSubtourMode";
public final static String RANDOM_SUBTOUR_MODE_STRATEGY = "RandomSubtourMode";


private final Builder builder;
@@ -49,21 +45,44 @@ private InformedModeChoiceModule(Builder builder) {
this.builder = builder;
}

/**
* Replaces a strategy in the config. Can be used to enable one of the strategies in this module programmatically.
*/
public static void replaceReplanningStrategy(Config config, String subpopulation,
String existing, String replacement) {

// Copy list because it is unmodifiable
List<ReplanningConfigGroup.StrategySettings> strategies = new ArrayList<>(config.replanning().getStrategySettings());
List<ReplanningConfigGroup.StrategySettings> found = strategies.stream()
.filter(s -> s.getSubpopulation().equals(subpopulation))
.filter(s -> s.getStrategyName().equals(existing))
.toList();

if (found.isEmpty())
throw new IllegalArgumentException("No strategy %s found for subpopulation %s".formatted(existing, subpopulation));

if (found.size() > 1)
throw new IllegalArgumentException("Multiple strategies %s found for subpopulation %s".formatted(existing, subpopulation));

ReplanningConfigGroup.StrategySettings old = found.getFirst();
old.setStrategyName(replacement);

// reset und set new strategies
config.replanning().clearStrategySettings();
strategies.forEach(s -> config.replanning().addStrategySettings(s));
}

public static Builder newBuilder() {
return new Builder();
}

@Override
public void install() {

bindAllModes(builder.fixedCosts, new TypeLiteral<>() {
});
bindAllModes(builder.legEstimators, new TypeLiteral<>() {
});
bindAllModes(builder.tripEstimators, new TypeLiteral<>() {
});
bindAllModes(builder.options, new TypeLiteral<>() {
});
bindAllModes(builder.fixedCosts, new TypeLiteral<>() {});
bindAllModes(builder.legEstimators, new TypeLiteral<>() {});
bindAllModes(builder.tripEstimators, new TypeLiteral<>() {});
bindAllModes(builder.options, new TypeLiteral<>() {});

bind(ActivityEstimator.class).to(builder.activityEstimator).in(Singleton.class);

@@ -77,12 +96,16 @@ public void install() {
bind(PlanModelService.class).asEagerSingleton();
addControlerListenerBinding().to(PlanModelService.class).asEagerSingleton();

Multibinder<TripConstraint<?>> tcBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {
});
Multibinder<TripConstraint<?>> tcBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {});
for (Class<? extends TripConstraint<?>> c : builder.constraints) {
tcBinder.addBinding().to(c).in(Singleton.class);
}

Multibinder<TripScoreEstimator> tripScores = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {});
for (Class<? extends TripScoreEstimator> c : builder.tripScoreEstimators) {
tripScores.addBinding().to(c).in(Singleton.class);
}

MapBinder<String, CandidatePruner> pBinder = MapBinder.newMapBinder(binder(), String.class, CandidatePruner.class);
for (Map.Entry<String, CandidatePruner> e : builder.pruner.entrySet()) {
CandidatePruner instance = e.getValue();
@@ -133,8 +156,7 @@ public PlanRouter planRouter(Provider<TripRouter> tripRouter, ActivityFacilities
private <T> void bindAllModes(Map<String, Class<? extends T>> map, TypeLiteral<T> value) {

// Ensure to bind to internal strings
MapBinder<String, T> mapBinder = MapBinder.newMapBinder(binder(), new TypeLiteral<>() {
}, value);
MapBinder<String, T> mapBinder = MapBinder.newMapBinder(binder(), new TypeLiteral<>() {}, value);
for (Map.Entry<String, Class<? extends T>> e : map.entrySet()) {
Class<? extends T> clazz = e.getValue();
mapBinder.addBinding(e.getKey().intern()).to(clazz).in(Singleton.class);
@@ -153,6 +175,7 @@ public static final class Builder {
private final Map<String, Class<? extends ModeOptions>> options = new HashMap<>();

private final Set<Class<? extends TripConstraint<?>>> constraints = new LinkedHashSet<>();
private final Set<Class<? extends TripScoreEstimator>> tripScoreEstimators = new LinkedHashSet<>();

private final Map<String, CandidatePruner> pruner = new HashMap<>();

@@ -230,6 +253,14 @@ public Builder withActivityEstimator(Class<? extends ActivityEstimator> activity
return this;
}

/**
* Add general score estimator that is applied to all trips.
*/
public Builder withTripScoreEstimator(Class<? extends TripScoreEstimator> tripScoreEstimator) {
tripScoreEstimators.add(tripScoreEstimator);
return this;
}

/**
* Builds the module, which can then be used with {@link #install()}
*/
Original file line number Diff line number Diff line change
@@ -12,10 +12,7 @@
import org.matsim.core.router.TripStructureUtils;
import org.matsim.core.utils.timing.TimeInterpretation;
import org.matsim.modechoice.constraints.TripConstraint;
import org.matsim.modechoice.estimators.ActivityEstimator;
import org.matsim.modechoice.estimators.LegEstimator;
import org.matsim.modechoice.estimators.MinMaxEstimate;
import org.matsim.modechoice.estimators.TripEstimator;
import org.matsim.modechoice.estimators.*;

import java.util.*;
import java.util.function.Predicate;
@@ -33,6 +30,9 @@ public final class PlanModelService implements StartupListener {
@Inject
private Map<String, TripEstimator> tripEstimator;

@Inject
private Set<TripScoreEstimator> tripScores;

@Inject
private Set<TripConstraint<?>> constraints;

@@ -223,6 +223,10 @@ public void calculateEstimates(EstimatorContext context, PlanModel planModel) {
// early or late arrival can also have an effect on the activity scores which is potentially considered here
estimate += actEstimator.estimate(context, planModel.getStartTimes()[i] + tt, trip.getDestinationActivity());

for (TripScoreEstimator tripScore : tripScores) {
estimate += tripScore.estimate(context, c.getMode(), trip);
}

values[i] = estimate;
}
}
Original file line number Diff line number Diff line change
@@ -154,8 +154,7 @@ public Module applyModule(Binder binder, Config config, Consumer<InformedModeCho
strategies.forEach(s -> config.replanning().addStrategySettings(s));

if (group.forceInnovation > 0)
binder.bind(new TypeLiteral<StrategyChooser<Plan, Person>>() {
}).toInstance(new ForceInnovationStrategyChooser<>(group.forceInnovation, ForceInnovationStrategyChooser.Permute.yes));
binder.bind(new TypeLiteral<StrategyChooser<Plan, Person>>() {}).toInstance(new ForceInnovationStrategyChooser<>(group.forceInnovation, ForceInnovationStrategyChooser.Permute.yes));


InformedModeChoiceModule.Builder builder = InformedModeChoiceModule.newBuilder();
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.matsim.modechoice.estimators;

import org.matsim.core.router.TripStructureUtils;
import org.matsim.modechoice.EstimatorContext;

/**
* This class can be used to estimate additional scores for a trip.
* These score are added to the existing estimates and might be independent of the mode.
*/
public interface TripScoreEstimator {

/**
* Compute a score estimate for a trip.
*/
double estimate(EstimatorContext context, String mainMode, TripStructureUtils.Trip trip);


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.matsim.modechoice.pruning;

import org.matsim.modechoice.PlanModel;

/**
* Removes plans by a fixed utility threshold.
*/
public class PlanScoreThresholdPruner implements CandidatePruner {

private final double threshold;

/**
* Threshold to be applied on the best known plan estimate. Candidates with a larger difference to the best, than this threshold are discarded.
*/
public PlanScoreThresholdPruner(double threshold) {
this.threshold = threshold;
}

@Override
public double planThreshold(PlanModel planModel) {
return threshold;
}
}
Original file line number Diff line number Diff line change
@@ -217,6 +217,9 @@ else if (invocationOnMock.getArgument(0).equals(TransportMode.walk)) {
Multibinder<TripConstraint<?>> tcBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {
});

Multibinder<TripScoreEstimator> tEstBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {
});

MapBinder<String, FixedCostsEstimator> fcBinder = MapBinder.newMapBinder(binder(), new TypeLiteral<>() {
}, new TypeLiteral<>() {
});

0 comments on commit 426d78c

Please sign in to comment.