Skip to content

Commit 330682f

Browse files
authored
feat: termination criterion (#201)
* feat: termination criterion * update configuration generator * fix tools and tests that don't need termination criterion
1 parent d858836 commit 330682f

22 files changed

+788
-327
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ included in the (note yet determined) next version number.
66

77
**Development version**
88

9+
- Add terminaton criterion
10+
- Several cleanups in the recent commits
911
- Updated to MATSim 15
1012
- Improve emissions tools and add tests
1113
- Add support for multi-stage taxi trips in Sao Paulo

core/src/main/java/org/eqasim/core/analysis/trips/TripListener.java

-5
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,16 @@
2525
import org.matsim.api.core.v01.events.handler.PersonLeavesVehicleEventHandler;
2626
import org.matsim.api.core.v01.network.Network;
2727
import org.matsim.api.core.v01.population.Person;
28-
import org.matsim.api.core.v01.population.PopulationFactory;
2928
import org.matsim.core.api.experimental.events.TeleportationArrivalEvent;
3029
import org.matsim.core.api.experimental.events.handler.TeleportationArrivalEventHandler;
31-
import org.matsim.core.config.ConfigUtils;
3230
import org.matsim.core.router.TripStructureUtils;
33-
import org.matsim.core.scenario.ScenarioUtils;
3431
import org.matsim.core.utils.geometry.CoordUtils;
3532
import org.matsim.vehicles.Vehicle;
3633

3734
public class TripListener implements ActivityStartEventHandler, ActivityEndEventHandler, PersonDepartureEventHandler,
3835
PersonEntersVehicleEventHandler, PersonLeavesVehicleEventHandler, LinkEnterEventHandler,
3936
TeleportationArrivalEventHandler, GenericEventHandler {
4037
final private Network network;
41-
final private PopulationFactory factory;
4238

4339
final private Collection<TripItem> trips = new LinkedList<>();
4440
final private Map<Id<Person>, TripListenerItem> ongoing = new HashMap<>();
@@ -49,7 +45,6 @@ public class TripListener implements ActivityStartEventHandler, ActivityEndEvent
4945

5046
public TripListener(Network network, PersonAnalysisFilter personFilter) {
5147
this.network = network;
52-
this.factory = ScenarioUtils.createScenario(ConfigUtils.createConfig()).getPopulation().getFactory();
5348
this.personFilter = personFilter;
5449
}
5550

core/src/main/java/org/eqasim/core/scenario/config/GenerateConfig.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import org.eqasim.core.components.config.EqasimConfigGroup;
88
import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule;
9+
import org.eqasim.core.simulation.termination.EqasimTerminationConfigGroup;
910
import org.matsim.api.core.v01.TransportMode;
1011
import org.matsim.contribs.discrete_mode_choice.modules.ConstraintModule;
1112
import org.matsim.contribs.discrete_mode_choice.modules.DiscreteModeChoiceConfigurator;
@@ -49,7 +50,12 @@ public GenerateConfig(CommandLine cmd, String prefix, double sampleSize, int ran
4950
this.threads = threads;
5051
}
5152

52-
private final static int DEFAULT_ITERATIONS = 60;
53+
/**
54+
* This value is the last resort to stop the simulation, in case the termination
55+
* criterion is never fulfilled. Otherwise, the simulation is stopped when the
56+
* termination criterion kicks in.
57+
*/
58+
private final static int DEFAULT_ITERATIONS = 1000;
5359

5460
protected void adaptConfiguration(Config config) {
5561
// General settings
@@ -79,6 +85,10 @@ protected void adaptConfiguration(Config config) {
7985
eqasimConfig.setCrossingPenalty(3.0);
8086
eqasimConfig.setSampleSize(sampleSize);
8187
eqasimConfig.setAnalysisInterval(DEFAULT_ITERATIONS);
88+
89+
// Termination settings
90+
EqasimTerminationConfigGroup terminationConfig = EqasimTerminationConfigGroup.getOrCreate(config);
91+
terminationConfig.setModes(MODES);
8292

8393
// Scoring config
8494
PlanCalcScoreConfigGroup scoringConfig = config.planCalcScore();

core/src/main/java/org/eqasim/core/scenario/cutter/RunScenarioCutter.java

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.eqasim.core.scenario.routing.PopulationRouterModule;
2828
import org.eqasim.core.scenario.validation.ScenarioValidator;
2929
import org.eqasim.core.simulation.EqasimConfigurator;
30+
import org.eqasim.core.simulation.termination.EqasimTerminationModule;
3031
import org.matsim.api.core.v01.Scenario;
3132
import org.matsim.core.config.CommandLine;
3233
import org.matsim.core.config.CommandLine.ConfigurationException;
@@ -61,6 +62,8 @@ static public void main(String[] args)
6162

6263
// Load scenario
6364
EqasimConfigurator configurator = new EqasimConfigurator();
65+
configurator.getModules().removeIf(m -> m instanceof EqasimTerminationModule);
66+
6467
Config config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), configurator.getConfigGroups());
6568
cmd.applyConfiguration(config);
6669

core/src/main/java/org/eqasim/core/simulation/EqasimConfigurator.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import org.eqasim.core.components.transit.EqasimTransitQSimModule;
1111
import org.eqasim.core.simulation.calibration.CalibrationConfigGroup;
1212
import org.eqasim.core.simulation.mode_choice.epsilon.EpsilonModule;
13+
import org.eqasim.core.simulation.termination.EqasimTerminationModule;
14+
import org.eqasim.core.simulation.termination.mode_share.ModeShareModule;
1315
import org.matsim.api.core.v01.Id;
1416
import org.matsim.api.core.v01.Scenario;
1517
import org.matsim.api.core.v01.population.Person;
@@ -55,8 +57,10 @@ public EqasimConfigurator() {
5557
new SwissRailRaptorModule(), //
5658
new EqasimTransitModule(), //
5759
new DiscreteModeChoiceModule(), //
58-
new EqasimComponentsModule(),
59-
new EpsilonModule()//
60+
new EqasimComponentsModule(), //
61+
new EpsilonModule(), //
62+
new EqasimTerminationModule(), //
63+
new ModeShareModule() //
6064
));
6165

6266
qsimModules.addAll(Arrays.asList( //
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package org.eqasim.core.simulation.termination;
2+
3+
import java.util.Arrays;
4+
import java.util.LinkedList;
5+
import java.util.List;
6+
import java.util.stream.Collectors;
7+
8+
import org.matsim.core.config.Config;
9+
import org.matsim.core.config.ReflectiveConfigGroup;
10+
11+
public class EqasimTerminationConfigGroup extends ReflectiveConfigGroup {
12+
static public final String GROUP_NAME = "eqasim:termination";
13+
14+
public EqasimTerminationConfigGroup() {
15+
super(GROUP_NAME);
16+
}
17+
18+
static private final String HORIZON = "horizon";
19+
static private final String SMOOTHING = "smoothing";
20+
static private final String THRESHOLD = "threshold";
21+
static private final String MODES = "modes";
22+
static private final String HISTORY_FILE = "historyFile";
23+
24+
private int horizon = 10;
25+
private int smoothing = 20;
26+
private double threshold = 0.001;
27+
28+
private List<String> modes = new LinkedList<>(Arrays.asList("car", "pt", "bike", "walk"));
29+
30+
private String historyFile = null;
31+
32+
@StringGetter(HORIZON)
33+
public int getHorizon() {
34+
return horizon;
35+
}
36+
37+
@StringSetter(HORIZON)
38+
public void setHorizon(int value) {
39+
this.horizon = value;
40+
}
41+
42+
@StringGetter(SMOOTHING)
43+
public int getSmoothing() {
44+
return smoothing;
45+
}
46+
47+
@StringSetter(SMOOTHING)
48+
public void setSmoothing(int value) {
49+
this.smoothing = value;
50+
}
51+
52+
@StringGetter(THRESHOLD)
53+
public double getThreshold() {
54+
return threshold;
55+
}
56+
57+
@StringSetter(THRESHOLD)
58+
public void setThreshold(double value) {
59+
this.threshold = value;
60+
}
61+
62+
@StringGetter(MODES)
63+
public String getModesAsString() {
64+
return String.join(",", modes);
65+
}
66+
67+
@StringSetter(MODES)
68+
public void setModesAsString(String value) {
69+
modes.clear();
70+
modes.addAll(Arrays.asList(value.split(",")).stream().map(String::trim).collect(Collectors.toList()));
71+
}
72+
73+
public List<String> getModes() {
74+
return modes;
75+
}
76+
77+
public void setModes(List<String> value) {
78+
modes.clear();
79+
modes.addAll(value);
80+
}
81+
82+
@StringSetter(HISTORY_FILE)
83+
public void setHistoryFile(String value) {
84+
this.historyFile = value;
85+
}
86+
87+
@StringGetter(HISTORY_FILE)
88+
public String getHistoryFile() {
89+
return historyFile;
90+
}
91+
92+
static public EqasimTerminationConfigGroup getOrCreate(Config config) {
93+
if (!config.getModules().containsKey(GROUP_NAME)) {
94+
config.addModule(new EqasimTerminationConfigGroup());
95+
}
96+
97+
return (EqasimTerminationConfigGroup) config.getModules().get(GROUP_NAME);
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package org.eqasim.core.simulation.termination;
2+
3+
import java.util.LinkedList;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
import org.matsim.core.controler.TerminationCriterion;
8+
9+
import com.google.common.base.Verify;
10+
import com.google.common.collect.ImmutableMap;
11+
12+
public class EqasimTerminationCriterion implements TerminationCriterion {
13+
private final Map<String, TerminationIndicatorSupplier> indicators;
14+
private final Map<String, TerminationCriterionCalculator> criteria;
15+
16+
private final List<TerminationData> history = new LinkedList<>();
17+
18+
private final int firstIteration;
19+
private final int lastIteration;
20+
21+
private final TerminationWriter writer;
22+
23+
public EqasimTerminationCriterion(int firstIteration, int lastIteration,
24+
Map<String, TerminationIndicatorSupplier> indicators, Map<String, TerminationCriterionCalculator> criteria,
25+
TerminationWriter writer) {
26+
this.firstIteration = firstIteration;
27+
this.lastIteration = lastIteration;
28+
29+
this.indicators = indicators;
30+
this.criteria = criteria;
31+
32+
this.writer = writer;
33+
}
34+
35+
@Override
36+
public boolean mayTerminateAfterIteration(int iteration) {
37+
// called at the beginning of an iteration to check if we may terminate now
38+
// this happens before IterationStartsEvent
39+
40+
boolean mayTerminate = false;
41+
42+
if (iteration > firstIteration) {
43+
// check if we may terminate
44+
TerminationData terminationData = prepareTerminationData(iteration);
45+
history.add(terminationData);
46+
writer.write(history);
47+
48+
mayTerminate = terminationData.criteria.values().stream().mapToDouble(d -> d).sum() == 0.0;
49+
}
50+
51+
if (iteration >= lastIteration) {
52+
mayTerminate = true;
53+
}
54+
55+
return mayTerminate;
56+
}
57+
58+
@Override
59+
public boolean doTerminate(int iteration) {
60+
// called at the very end of an iteration *if* we said that we might terminate
61+
// if it is called, this happens after IterationEndsEvent
62+
63+
// obtain data for the current iteration
64+
TerminationData terminationData = prepareTerminationData(iteration);
65+
boolean doTerminate = terminationData.criteria.values().stream().mapToDouble(d -> d).sum() == 0.0;
66+
67+
if (iteration >= lastIteration) {
68+
doTerminate = true;
69+
}
70+
71+
if (doTerminate) {
72+
// add information for last iteration and write it (since we won't call
73+
// mayTerminate again)
74+
history.add(terminationData);
75+
writer.write(history);
76+
}
77+
78+
return doTerminate;
79+
}
80+
81+
private TerminationData prepareTerminationData(int iteration) {
82+
// obtain indicator values
83+
ImmutableMap.Builder<String, Double> indicatorValues = ImmutableMap.builder();
84+
85+
for (var item : indicators.entrySet()) {
86+
indicatorValues.put(item.getKey(), item.getValue().getValue());
87+
}
88+
89+
IterationData iterationData = new IterationData(iteration - 1, indicatorValues.build());
90+
91+
// obtain criterion values
92+
ImmutableMap.Builder<String, Double> criterionValues = ImmutableMap.builder();
93+
94+
for (var item : criteria.entrySet()) {
95+
criterionValues.put(item.getKey(), item.getValue().calculate(history, iterationData));
96+
}
97+
98+
TerminationData terminationData = new TerminationData(iteration - 1, iterationData.indicators,
99+
criterionValues.build());
100+
101+
return terminationData;
102+
}
103+
104+
public void replay(List<TerminationData> replayData) {
105+
for (int k = 0; k < replayData.size(); k++) {
106+
TerminationData item = replayData.get(k);
107+
108+
if (item.iteration == firstIteration) {
109+
Verify.verify(k == replayData.size() - 1,
110+
"Replay data should contain iterations until (inclusive) firstIteration");
111+
return;
112+
}
113+
}
114+
115+
throw new IllegalStateException("Did not find iteration " + firstIteration + " in replay data");
116+
}
117+
}

0 commit comments

Comments
 (0)