From 544999918b1d4023cd857e91ca7af95b81109ea7 Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Wed, 4 Sep 2024 17:24:34 +0200 Subject: [PATCH 1/3] feat: DiscreteModeChoiceModule write utilities as an output. --- .../modules/DiscreteModeChoiceModule.java | 1 + .../modules/UtilitiesWriterHandler.java | 52 ++++++ .../config/DiscreteModeChoiceConfigGroup.java | 25 ++- .../DiscreteModeChoiceAlgorithm.java | 152 +++++++++--------- 4 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/UtilitiesWriterHandler.java diff --git a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/DiscreteModeChoiceModule.java b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/DiscreteModeChoiceModule.java index abf34cb42f6..c669cc0d237 100644 --- a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/DiscreteModeChoiceModule.java +++ b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/DiscreteModeChoiceModule.java @@ -23,6 +23,7 @@ public class DiscreteModeChoiceModule extends AbstractModule { @Override public void install() { addPlanStrategyBinding(STRATEGY_NAME).toProvider(DiscreteModeChoiceStrategyProvider.class); + addControlerListenerBinding().to(UtilitiesWriterHandler.class); if (getConfig().replanning().getPlanSelectorForRemoval().equals(NonSelectedPlanSelector.NAME)) { bindPlanSelectorForRemoval().to(NonSelectedPlanSelector.class); diff --git a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/UtilitiesWriterHandler.java b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/UtilitiesWriterHandler.java new file mode 100644 index 00000000000..ff89cfcdc35 --- /dev/null +++ b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/UtilitiesWriterHandler.java @@ -0,0 +1,52 @@ +package org.matsim.contribs.discrete_mode_choice.modules; + +import com.google.inject.Inject; +import org.matsim.api.core.v01.population.Population; +import org.matsim.contribs.discrete_mode_choice.modules.config.DiscreteModeChoiceConfigGroup; +import org.matsim.contribs.discrete_mode_choice.modules.utils.ExtractPlanUtilities; +import org.matsim.core.config.groups.ControllerConfigGroup; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationStartsEvent; +import org.matsim.core.controler.events.ShutdownEvent; +import org.matsim.core.controler.listener.IterationStartsListener; +import org.matsim.core.controler.listener.ShutdownListener; + +import java.io.IOException; + +public class UtilitiesWriterHandler implements ShutdownListener, IterationStartsListener { + private final OutputDirectoryHierarchy outputDirectoryHierarchy; + private final ControllerConfigGroup controllerConfigGroup; + private final Population population; + private final DiscreteModeChoiceConfigGroup discreteModeChoiceConfigGroup; + + @Inject + public UtilitiesWriterHandler(ControllerConfigGroup controllerConfigGroup, DiscreteModeChoiceConfigGroup discreteModeChoiceConfigGroup, Population population, OutputDirectoryHierarchy outputDirectoryHierarchy) { + this.controllerConfigGroup = controllerConfigGroup; + this.discreteModeChoiceConfigGroup = discreteModeChoiceConfigGroup; + this.population = population; + this.outputDirectoryHierarchy = outputDirectoryHierarchy; + } + + @Override + public void notifyShutdown(ShutdownEvent event) { + String filePath = this.outputDirectoryHierarchy.getOutputFilename("dmc_utilities.csv"); + try { + ExtractPlanUtilities.writePlanUtilities(population, filePath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void notifyIterationStarts(IterationStartsEvent event) { + if(event.getIteration() == controllerConfigGroup.getFirstIteration() || this.discreteModeChoiceConfigGroup.getWriteUtilitiesInterval() % event.getIteration() != 0) { + return; + } + String filePath = this.outputDirectoryHierarchy.getIterationFilename(event.getIteration(), "dmc_utilities.csv"); + try { + ExtractPlanUtilities.writePlanUtilities(population, filePath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java index c692de9d40b..f85779aed35 100644 --- a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java +++ b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.stream.Collectors; +import jakarta.validation.constraints.Positive; import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceModel; import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceModel.FallbackBehaviour; import org.matsim.contribs.discrete_mode_choice.modules.ConstraintModule; @@ -27,7 +28,7 @@ /** * Main config group for the DiscreteModeChoice extension. - * + * * @author sebhoerl */ public class DiscreteModeChoiceConfigGroup extends ReflectiveConfigGroup { @@ -53,7 +54,8 @@ public class DiscreteModeChoiceConfigGroup extends ReflectiveConfigGroup { private Collection tripFilters = new HashSet<>(); private Collection cachedModes = new HashSet<>(); - + @Positive + private int writeUtilitiesInterval = 0; public static final String GROUP_NAME = "DiscreteModeChoice"; public static final String PERFORM_REROUTE = "performReroute"; @@ -110,6 +112,9 @@ public class DiscreteModeChoiceConfigGroup extends ReflectiveConfigGroup { public static final String CACHED_MODES = "cachedModes"; public static final String CACHED_MODES_CMT = "Trips tested with the modes listed here will be cached for each combination of trip and agent during one replanning pass."; + public static final String WRITE_UTILITIES_INTERVAL = "writeUtilitiesInterval"; + public static final String WRITE_UTILITIES_INTERVAL_CMT = "Specifies the interval, in iterations, at which the dmc_utilities.csv file is written. If set to 0, the file is written only at the end of the simulation"; + public DiscreteModeChoiceConfigGroup() { super(GROUP_NAME); } @@ -436,6 +441,22 @@ public String getCachedModesAsString() { return String.join(", ", cachedModes); } + /** + * @param writeUtilitiesInterval -- {@value #WRITE_UTILITIES_INTERVAL_CMT} + */ + @StringSetter(WRITE_UTILITIES_INTERVAL) + public void setWriteUtilitiesInterval(int writeUtilitiesInterval) { + this.writeUtilitiesInterval = writeUtilitiesInterval; + } + + /** + * @return -- {@value #WRITE_UTILITIES_INTERVAL_CMT} + */ + @StringGetter(WRITE_UTILITIES_INTERVAL) + public int getWriteUtilitiesInterval() { + return this.writeUtilitiesInterval; + } + // --- Component configuration --- private final Map, ConfigGroup> componentRegistry = createComponentRegistry( diff --git a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/replanning/DiscreteModeChoiceAlgorithm.java b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/replanning/DiscreteModeChoiceAlgorithm.java index 655f3cd94a6..a4c10c02d7a 100644 --- a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/replanning/DiscreteModeChoiceAlgorithm.java +++ b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/replanning/DiscreteModeChoiceAlgorithm.java @@ -1,75 +1,77 @@ -package org.matsim.contribs.discrete_mode_choice.replanning; - -import java.util.Collections; -import java.util.List; -import java.util.Random; - -import org.matsim.api.core.v01.population.Leg; -import org.matsim.api.core.v01.population.Plan; -import org.matsim.api.core.v01.population.PlanElement; -import org.matsim.api.core.v01.population.PopulationFactory; -import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceModel; -import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; -import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceModel.NoFeasibleChoiceException; -import org.matsim.contribs.discrete_mode_choice.model.trip_based.candidates.RoutedTripCandidate; -import org.matsim.contribs.discrete_mode_choice.model.trip_based.candidates.TripCandidate; -import org.matsim.core.population.algorithms.PlanAlgorithm; -import org.matsim.core.router.TripRouter; - -/** - * This replanning algorithm uses a predefined discrete mode choice model to - * perform mode decisions for a given plan. - * - * @author sebhoerl - */ -public class DiscreteModeChoiceAlgorithm implements PlanAlgorithm { - private final Random random; - private final DiscreteModeChoiceModel modeChoiceModel; - private final TripListConverter tripListConverter; - - private final PopulationFactory populationFactory; - - public DiscreteModeChoiceAlgorithm(Random random, DiscreteModeChoiceModel modeChoiceModel, - PopulationFactory populationFactory, TripListConverter tripListConverter) { - this.random = random; - this.modeChoiceModel = modeChoiceModel; - this.populationFactory = populationFactory; - this.tripListConverter = tripListConverter; - } - - @Override - /** - * Performs mode choice on a plan. We assume that TripsToLegs has been called - * before, hence the code is working diretly on legs. - */ - public void run(Plan plan) { - // I) First build a list of DiscreteModeChoiceTrips - List trips = tripListConverter.convert(plan); - - // II) Run mode choice - - try { - // Perform mode choice and retrieve candidates - List chosenCandidates = modeChoiceModel.chooseModes(plan.getPerson(), trips, random); - - for (int i = 0; i < trips.size(); i++) { - DiscreteModeChoiceTrip trip = trips.get(i); - TripCandidate candidate = chosenCandidates.get(i); - - List insertElements; - - if (candidate instanceof RoutedTripCandidate) { - RoutedTripCandidate routedCandidate = (RoutedTripCandidate) candidate; - insertElements = routedCandidate.getRoutedPlanElements(); - } else { - Leg insertLeg = populationFactory.createLeg(candidate.getMode()); - insertElements = Collections.singletonList(insertLeg); - } - - TripRouter.insertTrip(plan, trip.getOriginActivity(), insertElements, trip.getDestinationActivity()); - } - } catch (NoFeasibleChoiceException e) { - throw new IllegalStateException(e); - } - } -} +package org.matsim.contribs.discrete_mode_choice.replanning; + +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.api.core.v01.population.PopulationFactory; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceModel; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceModel.NoFeasibleChoiceException; +import org.matsim.contribs.discrete_mode_choice.model.trip_based.candidates.RoutedTripCandidate; +import org.matsim.contribs.discrete_mode_choice.model.trip_based.candidates.TripCandidate; +import org.matsim.contribs.discrete_mode_choice.model.utilities.UtilityCandidate; +import org.matsim.core.population.algorithms.PlanAlgorithm; +import org.matsim.core.router.TripRouter; + +/** + * This replanning algorithm uses a predefined discrete mode choice model to + * perform mode decisions for a given plan. + * + * @author sebhoerl + */ +public class DiscreteModeChoiceAlgorithm implements PlanAlgorithm { + private final Random random; + private final DiscreteModeChoiceModel modeChoiceModel; + private final TripListConverter tripListConverter; + + private final PopulationFactory populationFactory; + + public DiscreteModeChoiceAlgorithm(Random random, DiscreteModeChoiceModel modeChoiceModel, + PopulationFactory populationFactory, TripListConverter tripListConverter) { + this.random = random; + this.modeChoiceModel = modeChoiceModel; + this.populationFactory = populationFactory; + this.tripListConverter = tripListConverter; + } + + @Override + /** + * Performs mode choice on a plan. We assume that TripsToLegs has been called + * before, hence the code is working diretly on legs. + */ + public void run(Plan plan) { + // I) First build a list of DiscreteModeChoiceTrips + List trips = tripListConverter.convert(plan); + + // II) Run mode choice + + try { + // Perform mode choice and retrieve candidates + List chosenCandidates = modeChoiceModel.chooseModes(plan.getPerson(), trips, random); + + for (int i = 0; i < trips.size(); i++) { + DiscreteModeChoiceTrip trip = trips.get(i); + TripCandidate candidate = chosenCandidates.get(i); + + List insertElements; + + if (candidate instanceof RoutedTripCandidate) { + RoutedTripCandidate routedCandidate = (RoutedTripCandidate) candidate; + insertElements = routedCandidate.getRoutedPlanElements(); + } else { + Leg insertLeg = populationFactory.createLeg(candidate.getMode()); + insertElements = Collections.singletonList(insertLeg); + } + + TripRouter.insertTrip(plan, trip.getOriginActivity(), insertElements, trip.getDestinationActivity()); + } + plan.getAttributes().putAttribute("utility", chosenCandidates.stream().mapToDouble(UtilityCandidate::getUtility).sum()); + } catch (NoFeasibleChoiceException e) { + throw new IllegalStateException(e); + } + } +} From 2a67490362bc290ce02a1bcd5cdbe5fdc140e583 Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Wed, 4 Sep 2024 17:45:01 +0200 Subject: [PATCH 2/3] fix: add missing ExtractPlanUtilities class --- .../modules/utils/ExtractPlanUtilities.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/utils/ExtractPlanUtilities.java diff --git a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/utils/ExtractPlanUtilities.java b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/utils/ExtractPlanUtilities.java new file mode 100644 index 00000000000..d68b7176728 --- /dev/null +++ b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/utils/ExtractPlanUtilities.java @@ -0,0 +1,51 @@ +package org.matsim.contribs.discrete_mode_choice.modules.utils; + +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.Population; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.population.io.PopulationReader; +import org.matsim.core.scenario.ScenarioUtils; + +import java.io.FileWriter; +import java.io.IOException; + +public class ExtractPlanUtilities { + public static void writePlanUtilities(Population population, String filePath) throws IOException { + FileWriter writer = new FileWriter(filePath); + writer.write(String.join(CSV_SEPARATOR, CSV_HEADER)); + writer.write("\n"); + + for(Person person: population.getPersons().values()) { + double utility = Double.NaN; + Plan plan = person.getSelectedPlan(); + if(plan != null && plan.getAttributes().getAttribute("utility") != null) { + utility = (double) plan.getAttributes().getAttribute("utility"); + } + writer.write(String.join(CSV_SEPARATOR, new String[]{ + person.getId().toString(), + String.valueOf(utility) + })); + writer.write("\n"); + } + writer.close(); + } + + public static final String CMD_PLANS_PATH = "plans-path"; + public static final String CMD_OUTPUT_PATH = "output-path"; + public static final String CSV_SEPARATOR = ";"; + public static final String[] CSV_HEADER = new String[]{"person_id", "utility"}; + + public static void main(String[] args) throws CommandLine.ConfigurationException, IOException { + CommandLine commandLine = new CommandLine.Builder(args).requireOptions(CMD_PLANS_PATH, CMD_OUTPUT_PATH).build(); + + Config config = ConfigUtils.createConfig(); + Scenario scenario = ScenarioUtils.createScenario(config); + new PopulationReader(scenario).readFile(commandLine.getOptionStrict(CMD_PLANS_PATH)); + + writePlanUtilities(scenario.getPopulation(), commandLine.getOptionStrict(CMD_OUTPUT_PATH)); + } +} From e9b8fd5c577e360dcae62b079e488dd8d5fa0142 Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Wed, 4 Sep 2024 18:05:58 +0200 Subject: [PATCH 3/3] fix: default value of writeUtilitiesInterval --- .../modules/config/DiscreteModeChoiceConfigGroup.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java index f85779aed35..5a437d97b22 100644 --- a/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java +++ b/contribs/discrete_mode_choice/src/main/java/org/matsim/contribs/discrete_mode_choice/modules/config/DiscreteModeChoiceConfigGroup.java @@ -55,7 +55,7 @@ public class DiscreteModeChoiceConfigGroup extends ReflectiveConfigGroup { private Collection cachedModes = new HashSet<>(); @Positive - private int writeUtilitiesInterval = 0; + private int writeUtilitiesInterval = 1; public static final String GROUP_NAME = "DiscreteModeChoice"; public static final String PERFORM_REROUTE = "performReroute";