From 7bb99a71ad386f59d8e22140fd51a24474cc1f4e Mon Sep 17 00:00:00 2001 From: simei94 <67737999+simei94@users.noreply.github.com> Date: Mon, 27 May 2024 15:28:16 +0200 Subject: [PATCH] Documentation mode choice + make mode choice analysis standard (#3285) * add comments to clarify configs * add ModeChoiceCoverageControlerListener as default analysis * fix tests * fix tests --- .../ModeChoiceCoverageControlerListener.java | 185 +++++++++--------- .../org/matsim/analysis/ModeStatsModule.java | 2 + .../config/groups/ReplanningConfigGroup.java | 2 +- .../groups/SubtourModeChoiceConfigGroup.java | 13 +- .../ReplanningAnnealerConfigGroup.java | 2 +- .../replanning/modules/SubtourModeChoice.java | 38 +++- 6 files changed, 136 insertions(+), 106 deletions(-) diff --git a/matsim/src/main/java/org/matsim/analysis/ModeChoiceCoverageControlerListener.java b/matsim/src/main/java/org/matsim/analysis/ModeChoiceCoverageControlerListener.java index ce1bd875272..068e11e8cd8 100644 --- a/matsim/src/main/java/org/matsim/analysis/ModeChoiceCoverageControlerListener.java +++ b/matsim/src/main/java/org/matsim/analysis/ModeChoiceCoverageControlerListener.java @@ -98,95 +98,102 @@ public void notifyIterationEnds(final IterationEndsEvent event) { updateModesUsedPerPerson(); - - /* - * Looks through modesUsedPerPersonTrip at each person-trip. How many of those person trips have used each mode more than the - * predefined limits. - */ - int totalPersonTripCount = 0; - Map> modeCountCurrentIteration = new TreeMap<>(); - //Map> - - for (Map> mapForPerson : modesUsedPerPersonTrip.values()) { - //Map> - for (Map mapForPersonTrip : mapForPerson.values()) { - //Map - totalPersonTripCount++; - for (String mode : mapForPersonTrip.keySet()) { - Integer realCount = mapForPersonTrip.get(mode); - for (Integer limit : limits) { - Map modeCountMap = modeCountCurrentIteration.computeIfAbsent(limit, k -> new TreeMap<>()); - Double modeCount = modeCountMap.computeIfAbsent(mode, k -> 0.); - if (realCount >= limit) { - modeCount++; - } - modeCountMap.put(mode, modeCount); - modeCountCurrentIteration.put(limit, modeCountMap); - } - } - } - } - // Calculates mcc share for each mode in current iteration, and updates modeCCHistory accordingly - for (Integer limit : limits) { - Map modeCnt = modeCountCurrentIteration.get(limit); - this.modes.addAll(modeCnt.keySet()); // potentially adds new modes to setthat just showed up in current iter - Map> modeIterationShareMap = modeCCHistory.computeIfAbsent(limit, k -> new HashMap<>()); - for (String mode : modes) { - Double cnt = modeCnt.get(mode); - double share = 0.; - if (cnt != null) { - share = cnt / totalPersonTripCount; - } - - log.info("-- mode choice coverage (" + limit + "x) of mode " + mode + " = " + share); - - Map iterationShareMap = modeIterationShareMap.get(mode); - - // If this is the first iteration where the mode shows up, add zeros to all previous iterations in history - if (iterationShareMap == null) { - iterationShareMap = new TreeMap<>(); - for (int iter = firstIteration; iter < event.getIteration(); iter++) { - iterationShareMap.put(iter, 0.0); - } - modeIterationShareMap.put(mode, iterationShareMap); - } - - iterationShareMap.put(event.getIteration(), share); - } - } - - - // Print MCC Stats to output file - for (Integer limit : limits) { - Map> modeIterationShareMap = modeCCHistory.get(limit); - - BufferedWriter modeOut = IOUtils.getBufferedWriter(this.modeFileName + limit + "x.txt"); - try { - modeOut.write("Iteration"); - for (String mode : modes) { - modeOut.write("\t" + mode); - } - modeOut.write("\n"); - for (int iter = firstIteration; iter <= event.getIteration(); iter++) { - modeOut.write(String.valueOf(iter)); - for (String mode : modes) { - modeOut.write("\t" + modeIterationShareMap.get(mode).get(iter)); - } - modeOut.write("\n"); - } - - modeOut.flush(); - modeOut.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - // Produce Graphs - if (this.createPNG && event.getIteration() > this.minIteration) { - produceGraphs(); - } - + /* + * Looks through modesUsedPerPersonTrip at each person-trip. How many of those person trips have used each mode more than the + * predefined limits. + */ + int totalPersonTripCount = 0; + Map> modeCountCurrentIteration = new TreeMap<>(); + //Map> + + for (Map> mapForPerson : modesUsedPerPersonTrip.values()) { + //Map> + for (Map mapForPersonTrip : mapForPerson.values()) { + //Map + totalPersonTripCount++; + for (String mode : mapForPersonTrip.keySet()) { + Integer realCount = mapForPersonTrip.get(mode); + for (Integer limit : limits) { + Map modeCountMap = modeCountCurrentIteration.computeIfAbsent(limit, k -> new TreeMap<>()); + Double modeCount = modeCountMap.computeIfAbsent(mode, k -> 0.); + if (realCount >= limit) { + modeCount++; + } + modeCountMap.put(mode, modeCount); + modeCountCurrentIteration.put(limit, modeCountMap); + } + } + } + } + + + // for testing purposes: if there are any trips, do analysis. If not, it is probably a test or a faulty / empty population. -sme0524 + if (!modeCountCurrentIteration.isEmpty()) { + // Calculates mcc share for each mode in current iteration, and updates modeCCHistory accordingly + for (Integer limit : limits) { + Map modeCnt = modeCountCurrentIteration.get(limit); + this.modes.addAll(modeCnt.keySet()); // potentially adds new modes to setthat just showed up in current iter + Map> modeIterationShareMap = modeCCHistory.computeIfAbsent(limit, k -> new HashMap<>()); + for (String mode : modes) { + Double cnt = modeCnt.get(mode); + double share = 0.; + if (cnt != null) { + share = cnt / totalPersonTripCount; + } + + log.info("-- mode choice coverage (" + limit + "x) of mode " + mode + " = " + share); + + Map iterationShareMap = modeIterationShareMap.get(mode); + + // If this is the first iteration where the mode shows up, add zeros to all previous iterations in history + if (iterationShareMap == null) { + iterationShareMap = new TreeMap<>(); + for (int iter = firstIteration; iter < event.getIteration(); iter++) { + iterationShareMap.put(iter, 0.0); + } + modeIterationShareMap.put(mode, iterationShareMap); + } + + iterationShareMap.put(event.getIteration(), share); + } + } + + + // Print MCC Stats to output file + for (Integer limit : limits) { + Map> modeIterationShareMap = modeCCHistory.get(limit); + + BufferedWriter modeOut = IOUtils.getBufferedWriter(this.modeFileName + limit + "x.txt"); + try { + modeOut.write("Iteration"); + for (String mode : modes) { + modeOut.write("\t" + mode); + } + modeOut.write("\n"); + for (int iter = firstIteration; iter <= event.getIteration(); iter++) { + modeOut.write(String.valueOf(iter)); + for (String mode : modes) { + modeOut.write("\t" + modeIterationShareMap.get(mode).get(iter)); + } + modeOut.write("\n"); + } + + modeOut.flush(); + modeOut.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Produce Graphs + if (this.createPNG && event.getIteration() > this.minIteration) { + produceGraphs(); + } + + } else { + log.warn("There are no trips conducted by the analyzed population. This should only be the case for tests. If you are running a simulation run, " + + " this should not happen. Check your population."); + } } private void updateModesUsedPerPerson() { diff --git a/matsim/src/main/java/org/matsim/analysis/ModeStatsModule.java b/matsim/src/main/java/org/matsim/analysis/ModeStatsModule.java index ded90a3ec76..96ff6bc4b7a 100644 --- a/matsim/src/main/java/org/matsim/analysis/ModeStatsModule.java +++ b/matsim/src/main/java/org/matsim/analysis/ModeStatsModule.java @@ -30,5 +30,7 @@ public class ModeStatsModule extends AbstractModule { public void install() { bind(ModeStatsControlerListener.class).in(Singleton.class); addControlerListenerBinding().to(ModeStatsControlerListener.class); +// KN: if this is a somewhat standard analysis it should be added by default rather than adding it in every scenario run class + addControlerListenerBinding().to(ModeChoiceCoverageControlerListener.class); } } diff --git a/matsim/src/main/java/org/matsim/core/config/groups/ReplanningConfigGroup.java b/matsim/src/main/java/org/matsim/core/config/groups/ReplanningConfigGroup.java index 4a9b0de4f20..1daf2c459c9 100644 --- a/matsim/src/main/java/org/matsim/core/config/groups/ReplanningConfigGroup.java +++ b/matsim/src/main/java/org/matsim/core/config/groups/ReplanningConfigGroup.java @@ -275,7 +275,7 @@ private StrategySettings getStrategySettings(final Id index, f @Override public final Map getComments() { Map map = super.getComments(); - map.put(ReflectiveDelegate.ITERATION_FRACTION_TO_DISABLE_INNOVATION, "fraction of iterations where innovative strategies are switched off. Something like 0.8 should be good. E.g. if you run from iteration 400 to iteration 500, innovation is switched off at iteration 480" ) ; + map.put(ReflectiveDelegate.ITERATION_FRACTION_TO_DISABLE_INNOVATION, "fraction of iterations where innovative strategies are switched off. Something like 0.8 should be good. E.g. if you run from iteration 400 to iteration 500, innovation is switched off at iteration 480. If the ReplanningAnnealer is used, it will also be switched off." ) ; map.put(ReflectiveDelegate.MAX_AGENT_PLAN_MEMORY_SIZE, "maximum number of plans per agent. ``0'' means ``infinity''. Currently (2010), ``5'' is a good number"); StringBuilder strb = new StringBuilder() ; diff --git a/matsim/src/main/java/org/matsim/core/config/groups/SubtourModeChoiceConfigGroup.java b/matsim/src/main/java/org/matsim/core/config/groups/SubtourModeChoiceConfigGroup.java index 1a701ecff8e..b0adab12124 100644 --- a/matsim/src/main/java/org/matsim/core/config/groups/SubtourModeChoiceConfigGroup.java +++ b/matsim/src/main/java/org/matsim/core/config/groups/SubtourModeChoiceConfigGroup.java @@ -32,7 +32,7 @@ public final class SubtourModeChoiceConfigGroup extends ReflectiveConfigGroup { public static final String GROUP_NAME = "subtourModeChoice"; - + public final static String MODES = "modes"; public final static String CHAINBASEDMODES = "chainBasedModes"; public final static String CARAVAIL = "considerCarAvailability"; @@ -40,17 +40,17 @@ public final class SubtourModeChoiceConfigGroup extends ReflectiveConfigGroup { public final static String COORD_DISTANCE = "coordDistance"; private static final String BEHAVIOR = "behavior"; - + private String[] chainBasedModes = new String[] { TransportMode.car, TransportMode.bike }; private String[] allModes = new String[] { TransportMode.car, TransportMode.pt, TransportMode.bike, TransportMode.walk }; // default is false for backward compatibility private boolean considerCarAvailability = false; private SubtourModeChoice.Behavior behavior = SubtourModeChoice.Behavior.fromSpecifiedModesToSpecifiedModes ; - + private double probaForRandomSingleTripMode = 0. ; // yyyyyy backwards compatibility setting; should be change. kai, may'18 private double coordDistance = 0; - + public SubtourModeChoiceConfigGroup() { super(GROUP_NAME); } @@ -67,7 +67,7 @@ private String getChainBaseModesString() { private static String toString( final String[] modes ) { // (not same as toString() because of argument!) - + StringBuilder b = new StringBuilder(); if (modes.length > 0) b.append( modes[ 0 ] ); @@ -106,7 +106,8 @@ public Map getComments() { comments.put(CHAINBASEDMODES, "Defines the chain-based modes, seperated by commas" ); comments.put(CARAVAIL, "Defines whether car availability must be considered or not. A agent has no car only if it has no license, or never access to a car" ); comments.put(SINGLE_PROBA, "Defines the probability of changing a single trip for a unchained mode instead of subtour."); - comments.put(COORD_DISTANCE, "If greater than 0, subtours will also consider coordinates to be at the same location when smaller than set distance."); + comments.put(COORD_DISTANCE, "If greater than 0, activities that are closer than coordDistance, to each other, will be considered part of the same subtour." + + "i.e. if two activities are close to each other, the agent is allowed to use the same 'chain-based' vehicle for both subtours."); { StringBuilder msg = new StringBuilder("Only for backwards compatibility. Defines if only trips from modes list should change mode, or all trips. Options: "); diff --git a/matsim/src/main/java/org/matsim/core/replanning/annealing/ReplanningAnnealerConfigGroup.java b/matsim/src/main/java/org/matsim/core/replanning/annealing/ReplanningAnnealerConfigGroup.java index 8b01a61e7a4..41dd22aa4d5 100644 --- a/matsim/src/main/java/org/matsim/core/replanning/annealing/ReplanningAnnealerConfigGroup.java +++ b/matsim/src/main/java/org/matsim/core/replanning/annealing/ReplanningAnnealerConfigGroup.java @@ -229,7 +229,7 @@ public Map getComments() { map.put(HALFLIFE, "this parameter enters the exponential and sigmoid formulas. May be an iteration or a share, i.e. 0.5 for halfLife at 50% of iterations."); map.put(SHAPE_FACTOR, "see comment of parameter annealType."); - map.put(ANNEAL_TYPE, "options: linear, exponential, geometric, msa, sigmoid and disabled (no annealing)." + " sigmoid: 1/(1+e^(shapeFactor*(it - halfLife))); geometric: startValue * shapeFactor^it; msa: startValue / it^shapeFactor. Exponential: startValue / exp(it/halfLife)"); + map.put(ANNEAL_TYPE, "options: linear, exponential, geometric, msa, sigmoid and disabled (no annealing). sigmoid: 1/(1+e^(shapeFactor*(it - halfLife))); geometric: startValue * shapeFactor^it; msa: startValue / it^shapeFactor. Exponential: startValue / exp(it/halfLife)"); map.put(ANNEAL_PARAM, "list of config parameters that shall be annealed. Currently supported: globalInnovationRate, BrainExpBeta, PathSizeLogitBeta, learningRate. Default is globalInnovationRate"); map.put(SUBPOPULATION, "subpopulation to have the global innovation rate adjusted. Not applicable when annealing with other parameters."); diff --git a/matsim/src/main/java/org/matsim/core/replanning/modules/SubtourModeChoice.java b/matsim/src/main/java/org/matsim/core/replanning/modules/SubtourModeChoice.java index 75d7625669b..fff07e0bd4d 100644 --- a/matsim/src/main/java/org/matsim/core/replanning/modules/SubtourModeChoice.java +++ b/matsim/src/main/java/org/matsim/core/replanning/modules/SubtourModeChoice.java @@ -33,16 +33,16 @@ * different mode given a list of possible modes. * * A subtour is a consecutive subset of a plan which starts and ends at the same link. - * + * * Certain modes are considered only if the choice would not require some resource to appear * out of thin air. For example, you can only drive your car back from work if you have previously parked it * there. These are called chain-based modes. - * + * * The assumption is that each chain-based mode requires one resource (car, bike, ...) and that this * resource is initially positioned at home. Home is the location of the first activity in the plan. - * - * If the plan initially violates this constraint, this module may (!) repair it. - * + * + * If the plan initially violates this constraint, this module may (!) repair it. + * * @author michaz * */ @@ -51,7 +51,27 @@ public class SubtourModeChoice extends AbstractMultithreadedModule { private final double probaForChangeSingleTripMode; private final double coordDist; - public enum Behavior {fromAllModesToSpecifiedModes, fromSpecifiedModesToSpecifiedModes, betweenAllAndFewerConstraints} + public enum Behavior { + /** + * Allow agents to switch to specified modes from all other modes. + * This implies that agents might switch to a specified mode, but won't be able to switch back + * to their original mode. This option should not be used. + */ + @Deprecated + fromAllModesToSpecifiedModes, + + /** + * Allow agents switching from one of a specified mode to another specified mode. + * Note, that agents that have an unclosed subtour, are not able to switch mode. + * If you have unclosed/open subtours in your data, consider using {@link #betweenAllAndFewerConstraints}. + */ + fromSpecifiedModesToSpecifiedModes, + + /** + * Same as "fromSpecifiedModesToSpecifiedModes", but also allow agents with open subtours to switch modes. + */ + betweenAllAndFewerConstraints + } private Behavior behavior = Behavior.fromSpecifiedModesToSpecifiedModes; @@ -85,19 +105,19 @@ public SubtourModeChoice(GlobalConfigGroup globalConfigGroup, this.probaForChangeSingleTripMode = probaForChangeSingleTripMode; this.coordDist = coordDist; } - + @Deprecated // only use when backwards compatibility is needed. kai, may'18 public final void setBehavior ( Behavior behavior ) { this.behavior = behavior ; } - + protected String[] getModes() { return modes.clone(); } @Override public PlanAlgorithm getPlanAlgoInstance() { - + final ChooseRandomLegModeForSubtour chooseRandomLegMode = new ChooseRandomLegModeForSubtour( TripStructureUtils.getRoutingModeIdentifier(),