From 426d78c7644c6610dfcd7b2ced981851f2e7576f Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 16 Dec 2024 14:37:21 +0100 Subject: [PATCH] IMC Trip Score Estimator (#3642) * add interface for trip score estimates * fix injection * fix test * make strategy names final --- .../InformedModeChoiceConfigGroup.java | 2 +- .../modechoice/InformedModeChoiceModule.java | 79 +++++++++++++------ .../matsim/modechoice/PlanModelService.java | 12 ++- .../modechoice/commands/StrategyOptions.java | 3 +- .../estimators/TripScoreEstimator.java | 18 +++++ .../pruning/PlanScoreThresholdPruner.java | 23 ++++++ .../modechoice/search/TopKMinMaxTest.java | 3 + 7 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripScoreEstimator.java create mode 100644 contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/pruning/PlanScoreThresholdPruner.java diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java index 709de1e45ba..76c9e429e0a 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java @@ -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.") 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 e681025ef92..6c422644a14 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 @@ -6,6 +6,8 @@ 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; @@ -13,20 +15,14 @@ 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,6 +45,33 @@ 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 strategies = new ArrayList<>(config.replanning().getStrategySettings()); + List 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(); } @@ -56,14 +79,10 @@ public static Builder newBuilder() { @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> tcBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() { - }); + Multibinder> tcBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {}); for (Class> c : builder.constraints) { tcBinder.addBinding().to(c).in(Singleton.class); } + Multibinder tripScores = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {}); + for (Class c : builder.tripScoreEstimators) { + tripScores.addBinding().to(c).in(Singleton.class); + } + MapBinder pBinder = MapBinder.newMapBinder(binder(), String.class, CandidatePruner.class); for (Map.Entry e : builder.pruner.entrySet()) { CandidatePruner instance = e.getValue(); @@ -133,8 +156,7 @@ public PlanRouter planRouter(Provider tripRouter, ActivityFacilities private void bindAllModes(Map> map, TypeLiteral value) { // Ensure to bind to internal strings - MapBinder mapBinder = MapBinder.newMapBinder(binder(), new TypeLiteral<>() { - }, value); + MapBinder mapBinder = MapBinder.newMapBinder(binder(), new TypeLiteral<>() {}, value); for (Map.Entry> e : map.entrySet()) { Class 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> options = new HashMap<>(); private final Set>> constraints = new LinkedHashSet<>(); + private final Set> tripScoreEstimators = new LinkedHashSet<>(); private final Map pruner = new HashMap<>(); @@ -230,6 +253,14 @@ public Builder withActivityEstimator(Class activity return this; } + /** + * Add general score estimator that is applied to all trips. + */ + public Builder withTripScoreEstimator(Class tripScoreEstimator) { + tripScoreEstimators.add(tripScoreEstimator); + return this; + } + /** * Builds the module, which can then be used with {@link #install()} */ 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 85f80664279..93e0fb86c67 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 @@ -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 tripEstimator; + @Inject + private Set tripScores; + @Inject private Set> 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; } } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/commands/StrategyOptions.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/commands/StrategyOptions.java index 532c0995236..0b10695df86 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/commands/StrategyOptions.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/commands/StrategyOptions.java @@ -154,8 +154,7 @@ public Module applyModule(Binder binder, Config config, Consumer config.replanning().addStrategySettings(s)); if (group.forceInnovation > 0) - binder.bind(new TypeLiteral>() { - }).toInstance(new ForceInnovationStrategyChooser<>(group.forceInnovation, ForceInnovationStrategyChooser.Permute.yes)); + binder.bind(new TypeLiteral>() {}).toInstance(new ForceInnovationStrategyChooser<>(group.forceInnovation, ForceInnovationStrategyChooser.Permute.yes)); InformedModeChoiceModule.Builder builder = InformedModeChoiceModule.newBuilder(); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripScoreEstimator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripScoreEstimator.java new file mode 100644 index 00000000000..d3105ce4514 --- /dev/null +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripScoreEstimator.java @@ -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); + + +} diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/pruning/PlanScoreThresholdPruner.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/pruning/PlanScoreThresholdPruner.java new file mode 100644 index 00000000000..e3446686715 --- /dev/null +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/pruning/PlanScoreThresholdPruner.java @@ -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; + } +} diff --git a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java index 04cfe9860e2..5490b076de8 100644 --- a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java +++ b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java @@ -217,6 +217,9 @@ else if (invocationOnMock.getArgument(0).equals(TransportMode.walk)) { Multibinder> tcBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() { }); + Multibinder tEstBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() { + }); + MapBinder fcBinder = MapBinder.newMapBinder(binder(), new TypeLiteral<>() { }, new TypeLiteral<>() { });