MATSIM
ReplanningAnnealer.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.* *
3  * *
4  * *********************************************************************** *
5  * *
6  * copyright : (C) 2008 by the members listed in the COPYING, *
7  * LICENSE and WARRANTY file. *
8  * email : info at matsim dot org *
9  * *
10  * *********************************************************************** *
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * See also COPYING, LICENSE and WARRANTY file *
17  * *
18  * *********************************************************************** */
19 
20 package org.matsim.core.replanning.annealing;
21 
22 import java.io.BufferedWriter;
23 import java.io.IOException;
24 import java.util.*;
25 import java.util.stream.Collectors;
26 import jakarta.inject.Inject;
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
31 import org.matsim.core.config.Config;
45 import org.matsim.core.utils.io.IOUtils;
46 
52 
53  private static final Logger log = LogManager.getLogger(ReplanningAnnealer.class);
54  private static final String ANNEAL_FILENAME = "annealingRates.txt";
55  private static final String COL_IT = "it";
56  private final Config config;
58  private final int innovationStop;
59  private final String sep;
60  private final EnumMap<AnnealParameterOption,Map<String, Double>> currentValuesPerSubpopulation;
61  private int currentIter;
62  private List<String> header;
63  @Inject
65 
66  @Inject
67  public ReplanningAnnealer(Config config) {
68  this.config = config;
69  this.saConfig = ConfigUtils.addOrGetModule(config, ReplanningAnnealerConfigGroup.class);
70  this.currentValuesPerSubpopulation = new EnumMap<>(AnnealParameterOption.class);
71  this.innovationStop = getInnovationStop(config);
72  this.sep = config.global().getDefaultDelimiter();
73  }
74 
75  private static boolean isInnovationStrategy(String strategyName) {
76  List<String> selectors = Arrays.asList(DefaultSelector.BestScore, DefaultSelector.ChangeExpBeta,
79  return !(selectors.contains(strategyName) ||
80  ((strategyName.toLowerCase().contains("selector") || strategyName.toLowerCase().contains("expbeta")) && !strategyName.contains("_")));
81  }
82 
83  @Override
84  public void notifyStartup(StartupEvent event) {
85  header = new ArrayList<>();
86  for (AnnealingVariable av : this.saConfig.getAllAnnealingVariables()) {
87  if (!av.getAnnealType().equals(AnnealOption.disabled)) {
88  // check and fix initial value if needed
89  checkAndFixStartValue(av, event);
90 
91  var mapPerSubpopulation = this.currentValuesPerSubpopulation.computeIfAbsent(av.getAnnealParameter(),a-> new HashMap<>());
92  mapPerSubpopulation.put(av.getSubpopulation(),av.getStartValue());
93  String subpopulationString = av.getSubpopulation()!=null? "_"+av.getSubpopulation() :"";
94  header.add(av.getAnnealParameter().name()+subpopulationString);
95  if (av.getAnnealParameter().equals(AnnealParameterOption.globalInnovationRate)) {
96  header.addAll(this.config.replanning().getStrategySettings().stream()
97  .filter(s -> Objects.equals(av.getSubpopulation(), s.getSubpopulation()))
98  .map(strategySettings -> strategySettings.getStrategyName()+subpopulationString)
99  .collect(Collectors.toList()));
100  }
101  } else { // if disabled, better remove it
102  this.saConfig.removeParameterSet(av);
103  }
104  }
105  // prepare output file
106  try (BufferedWriter bw = IOUtils.getBufferedWriter(outputDirectoryHierarchy.getOutputFilename(ANNEAL_FILENAME))) {
107  bw.write(COL_IT + sep + header.stream().collect(Collectors.joining(sep)));
108  bw.newLine();
109  } catch (IOException e) {
110  e.printStackTrace();
111  }
112 
113  }
114 
115  @Override
117  this.currentIter = event.getIteration() - this.config.controller().getFirstIteration();
118  Map<String, String> annealStats = new HashMap<>();
119  List<AnnealingVariable> allVariables = this.saConfig.getAllAnnealingVariables();
120  for (AnnealingVariable av : allVariables) {
121  if (this.currentIter > 0) {
122  switch (av.getAnnealType()) {
123  case geometric:
124  this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).compute(av.getSubpopulation(), (k, v) ->
125  v * av.getShapeFactor());
126  break;
127  case exponential:
128  int halfLifeIter = av.getHalfLife() <= 1.0 ?
129  (int) (av.getHalfLife() * this.innovationStop) : (int) av.getHalfLife();
130  this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).compute(av.getSubpopulation(), (k, v) ->
131  av.getStartValue() / Math.exp((double) this.currentIter / halfLifeIter));
132  break;
133  case msa:
134  this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).compute(av.getSubpopulation(), (k, v) ->
135  av.getStartValue() / Math.pow(this.currentIter, av.getShapeFactor()));
136  break;
137  case sigmoid:
138  halfLifeIter = av.getHalfLife() <= 1.0 ?
139  (int) (av.getHalfLife() * this.innovationStop) : (int) av.getHalfLife();
140  this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).compute(av.getSubpopulation(), (k, v) ->
141  av.getEndValue() + (av.getStartValue() - av.getEndValue()) /
142  (1 + Math.exp(av.getShapeFactor() * (this.currentIter - halfLifeIter))));
143  break;
144  case linear:
145  double slope = (av.getStartValue() - av.getEndValue())
146  / (this.config.controller().getFirstIteration() - this.innovationStop);
147  this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).compute(av.getSubpopulation(), (k, v) ->
148  this.currentIter * slope + av.getStartValue());
149  break;
150  case disabled:
151  return;
152  default:
153  throw new IllegalArgumentException();
154  }
155 
156  log.info("Annealling will be performed on parameter " + av.getAnnealParameter() +". Subpopulation: "+av.getSubpopulation()+
157  ". Value: " +this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).get(av.getSubpopulation()));
158 
159  this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).compute(av.getSubpopulation(), (k, v) ->
160  Math.max(v, av.getEndValue()));
161  }
162  double annealValue = this.currentValuesPerSubpopulation.get(av.getAnnealParameter()).get(av.getSubpopulation());
163  String subpopulationString = av.getSubpopulation()!=null? "_"+av.getSubpopulation() :"";
164 
165  annealStats.put(av.getAnnealParameter().name()+subpopulationString, String.format(Locale.US, "%.4f", annealValue));
166  anneal(event, av, annealValue, annealStats);
167  }
168 
169  writeIterationstats(currentIter, annealStats);
170  }
171 
172  private void writeIterationstats(int currentIter, Map<String, String> annealStats) {
173  try (BufferedWriter bw = IOUtils.getAppendingBufferedWriter(outputDirectoryHierarchy.getOutputFilename(ANNEAL_FILENAME))) {
174  bw.write(Integer.toString(currentIter));
175  for (String v : header) {
176  String s = sep + annealStats.get(v);
177  bw.write(s);
178  }
179  bw.newLine();
180  } catch (IOException e) {
181  e.printStackTrace();
182  }
183  }
184 
185  private void anneal(IterationStartsEvent event, AnnealingVariable av, double annealValue, Map<String, String> annealStats) {
186  String subpopulationString = av.getSubpopulation()!=null? "_"+av.getSubpopulation() :"";
187 
188  switch (av.getAnnealParameter()) {
189  case BrainExpBeta:
190  this.config.scoring().setBrainExpBeta(annealValue);
191  break;
192  case PathSizeLogitBeta:
193  this.config.scoring().setPathSizeLogitBeta(annealValue);
194  break;
195  case learningRate:
196  this.config.scoring().setLearningRate(annealValue);
197  break;
198  case globalInnovationRate:
199  if (this.currentIter > this.innovationStop) {
200  annealValue = 0.0;
201  }
202  List<Double> annealValues = annealReplanning(annealValue,
204  int i = 0;
205  for (ReplanningConfigGroup.StrategySettings ss : this.config.replanning().getStrategySettings()) {
206  if (Objects.equals(ss.getSubpopulation(), av.getSubpopulation())) {
207  annealStats.put(ss.getStrategyName()+subpopulationString, String.format(Locale.US, "%.4f", annealValues.get(i)));
208  i++;
209  }
210  }
211 
212  annealStats.put(av.getAnnealParameter().name()+subpopulationString, String.format(Locale.US, "%.4f", // update value in case of switchoff
214  break;
215  default:
216  throw new IllegalArgumentException();
217  }
218  }
219 
220  private List<Double> annealReplanning( double globalInnovationValue, StrategyManager stratMan, String subpopulation ) {
221  List<Double> annealValues = new ArrayList<>();
222  double totalInnovationWeights = getStrategyWeights(stratMan, subpopulation, StratType.allInnovation);
223  double totalSelectorWeights = getStrategyWeights(stratMan, subpopulation, StratType.allSelectors);
224  List<GenericPlanStrategy<Plan, Person>> strategies = stratMan.getStrategies(subpopulation);
225  for (GenericPlanStrategy<Plan, Person> strategy : strategies) {
226  double weight = stratMan.getWeights(subpopulation).get(strategies.indexOf(strategy));
227  if (isInnovationStrategy(strategy.toString())) {
228  weight = totalInnovationWeights > 0 ?
229  globalInnovationValue * weight / totalInnovationWeights : 0.0;
230  } else {
231  weight = totalSelectorWeights > 0 ?
232  (1 - globalInnovationValue) * weight / totalSelectorWeights : 0.0000001;
233  }
234  stratMan.changeWeightOfStrategy(strategy, subpopulation, weight);
235  annealValues.add(weight);
236  }
237  return annealValues;
238  }
239 
240  private double getStrategyWeights(StrategyManager stratMan, String subpopulation, StratType stratType) {
241  if (this.currentIter == this.innovationStop + 1 && stratType.equals(StratType.allInnovation)) {
242  return 0.0;
243  }
244  List<GenericPlanStrategy<Plan, Person>> strategies = stratMan.getStrategies(subpopulation);
245  double totalWeights = 0.0;
246  for (GenericPlanStrategy<Plan, Person> strategy : strategies) {
247  double weight = stratMan.getWeights(subpopulation).get(strategies.indexOf(strategy));
248  switch (stratType) {
249  case allSelectors:
250  if (!isInnovationStrategy(strategy.toString())) {
251  totalWeights += weight;
252  }
253  break;
254  case allInnovation:
255  if (isInnovationStrategy(strategy.toString())) {
256  totalWeights += weight;
257  }
258  break;
259  case allStrategies:
260  totalWeights += weight;
261  break;
262  default:
263  break;
264  }
265  }
266  return totalWeights;
267  }
268 
269  private double getStrategyWeights(Config config, String subpopulation, StratType stratType) {
270  if (this.currentIter == this.innovationStop + 1 && stratType.equals(StratType.allInnovation)) {
271  return 0.0;
272  }
273  Collection<ReplanningConfigGroup.StrategySettings> strategies = config.replanning().getStrategySettings();
274  double totalWeights = 0.0;
275  for (ReplanningConfigGroup.StrategySettings strategy : strategies) {
276  if (Objects.equals(strategy.getSubpopulation(), subpopulation)) {
277  switch (stratType) {
278  case allSelectors:
279  if (!isInnovationStrategy(strategy.toString())) {
280  totalWeights += strategy.getWeight();
281  }
282  break;
283  case allInnovation:
284  if (isInnovationStrategy(strategy.toString())) {
285  totalWeights += strategy.getWeight();
286  }
287  break;
288  case allStrategies:
289  totalWeights += strategy.getWeight();
290  break;
291  default:
292  break;
293  }
294  }
295  }
296  return totalWeights;
297  }
298 
299  private int getInnovationStop(Config config) {
300  int globalInnovationDisableAfter = (int) ((config.controller().getLastIteration() - config.controller().getFirstIteration())
302 
303  int innoStop = -1;
304 
306  // check if this modules should be disabled after some iterations
307  int maxIter = strategy.getDisableAfter();
308  if ((maxIter > globalInnovationDisableAfter || maxIter == -1) && isInnovationStrategy(strategy.getStrategyName())) {
309  maxIter = globalInnovationDisableAfter;
310  }
311 
312  if (innoStop == -1) {
313  innoStop = maxIter;
314  }
315 
316  if (innoStop != maxIter) {
317  log.warn("Different 'Disable After Interation' values are set for different replaning modules." +
318  " Annealing doesn't support this function and will be performed according to the 'Disable After Interation' setting of the first replanning module " +
319  "or 'globalInnovationDisableAfter', which ever value is lower.");
320  }
321  }
322 
323  return Math.min(innoStop, config.controller().getLastIteration());
324  }
325 
327  double configValue;
328  switch (av.getAnnealParameter()) {
329  case BrainExpBeta:
330  configValue = this.config.scoring().getBrainExpBeta();
331  break;
332  case PathSizeLogitBeta:
333  configValue = this.config.scoring().getPathSizeLogitBeta();
334  break;
335  case learningRate:
336  configValue = this.config.scoring().getLearningRate();
337  break;
338  case globalInnovationRate:
339  double innovationWeights = getStrategyWeights(this.config, av.getSubpopulation(), StratType.allInnovation);
340  double selectorWeights = getStrategyWeights(this.config, av.getSubpopulation(), StratType.allSelectors);
341  if (innovationWeights + selectorWeights != 1.0) {
342  log.warn("Initial sum of strategy weights different from 1.0. Rescaling.");
343  double innovationStartValue = av.getStartValue() == null ? innovationWeights : av.getStartValue();
344  rescaleStartupWeights(innovationStartValue, this.config, event.getServices().getStrategyManager(), av.getSubpopulation());
345  }
346  configValue = getStrategyWeights(this.config, av.getSubpopulation(), StratType.allInnovation);
347  break;
348  default:
349  throw new IllegalArgumentException();
350  }
351  if (av.getStartValue() == null) {
352  log.warn("Anneal start value not set. Config value will be used.");
353  av.setStartValue(configValue);
354  }
355  }
356 
357  private void rescaleStartupWeights(double innovationStartValue, Config config, StrategyManager stratMan, String subpopulation) {
358  double selectorStartValue = 1 - innovationStartValue;
359  // adapt simulation weights
360  List<GenericPlanStrategy<Plan, Person>> strategies = stratMan.getStrategies(subpopulation);
361  for (GenericPlanStrategy<Plan, Person> strategy : strategies) {
362  double weight = stratMan.getWeights(subpopulation).get(strategies.indexOf(strategy));
363  if (isInnovationStrategy(strategy.toString())) {
364  weight = innovationStartValue > 0 ?
365  weight / innovationStartValue : 0.0;
366  } else {
367  weight = selectorStartValue > 0 ?
368  weight / selectorStartValue : 0.0;
369  }
370  stratMan.changeWeightOfStrategy(strategy, subpopulation, weight);
371  }
372  // adapt also in config for the record
373  Collection<ReplanningConfigGroup.StrategySettings> strategiesConfig = config.replanning().getStrategySettings();
374  for (ReplanningConfigGroup.StrategySettings strategy : strategiesConfig) {
375  if (Objects.equals(strategy.getSubpopulation(), subpopulation)) {
376  double weight = strategy.getWeight();
377  if (isInnovationStrategy(strategy.toString())) {
378  weight = innovationStartValue > 0 ?
379  weight / innovationStartValue : 0.0;
380  } else {
381  weight = selectorStartValue > 0 ?
382  weight / selectorStartValue : 0.0;
383  }
384  strategy.setWeight(weight);
385  }
386  }
387  }
388 
389  private enum StratType {allInnovation, allSelectors, allStrategies}
390 }
void anneal(IterationStartsEvent event, AnnealingVariable av, double annealValue, Map< String, String > annealStats)
static< T extends ConfigGroup > T addOrGetModule(Config config, Class< T > moduleClass)
static boolean isInnovationStrategy(String strategyName)
List< Double > annealReplanning(double globalInnovationValue, StrategyManager stratMan, String subpopulation)
final List< GenericPlanStrategy< Plan, Person > > getStrategies(final String subpopulation)
final ScoringConfigGroup scoring()
Definition: Config.java:407
static BufferedWriter getBufferedWriter(URL url, Charset charset, boolean append)
Definition: IOUtils.java:390
final List< Double > getWeights(final String subpopulation)
double getStrategyWeights(Config config, String subpopulation, StratType stratType)
void checkAndFixStartValue(ReplanningAnnealerConfigGroup.AnnealingVariable av, StartupEvent event)
void writeIterationstats(int currentIter, Map< String, String > annealStats)
static BufferedWriter getAppendingBufferedWriter(String filename)
Definition: IOUtils.java:553
boolean removeParameterSet(final ConfigGroup set)
final EnumMap< AnnealParameterOption, Map< String, Double > > currentValuesPerSubpopulation
final boolean changeWeightOfStrategy(final GenericPlanStrategy< Plan, Person > strategy, final String subpopulation, final double newWeight)
final ReplanningConfigGroup replanning()
Definition: Config.java:427
void rescaleStartupWeights(double innovationStartValue, Config config, StrategyManager stratMan, String subpopulation)
final ControllerConfigGroup controller()
Definition: Config.java:399
final GlobalConfigGroup global()
Definition: Config.java:395
StrategySettings getStrategySettings(final Id< StrategySettings > index, final boolean createIfMissing)
double getStrategyWeights(StrategyManager stratMan, String subpopulation, StratType stratType)