Skip to content

Commit 47417e7

Browse files
authored
feat: a new scenario cutter without mode choice limitations (#251)
* feat: ability to skip routing during RunScenarioCutter * chore: required and optional args of scenario cutter are now accessible through static attributes * feat: RunScenarioCutterV2 * chore: Testing the RunScenarioCutterV2 functionality with a DRT service
1 parent 4477954 commit 47417e7

File tree

3 files changed

+213
-10
lines changed

3 files changed

+213
-10
lines changed

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import java.io.File;
44
import java.io.IOException;
5-
import java.net.MalformedURLException;
5+
import java.util.Collection;
66
import java.util.Optional;
7+
import java.util.Set;
78

89
import org.eqasim.core.components.travel_time.RecordedTravelTime;
910
import org.eqasim.core.misc.InjectorBuilder;
@@ -40,11 +41,15 @@
4041
import com.google.inject.Injector;
4142

4243
public class RunScenarioCutter {
44+
45+
public static final Collection<String> REQUIRED_ARGS = Set.of("config-path", "output-path", "extent-path");
46+
public static final Collection<String> OPTIONAL_ARGS = Set.of("threads", "prefix", "extent-attribute", "extent-value", "plans-path", "events-path", "skip-routing");
47+
4348
static public void main(String[] args)
44-
throws ConfigurationException, MalformedURLException, IOException, InterruptedException {
49+
throws ConfigurationException, IOException, InterruptedException {
4550
CommandLine cmd = new CommandLine.Builder(args) //
46-
.requireOptions("config-path", "output-path", "extent-path") //
47-
.allowOptions("threads", "prefix", "extent-attribute", "extent-value", "plans-path", "events-path") //
51+
.requireOptions(REQUIRED_ARGS) //
52+
.allowOptions(OPTIONAL_ARGS) //
4853
.build();
4954

5055
// Load some configuration
@@ -160,11 +165,14 @@ static public void main(String[] args)
160165
.addOverridingModule(new TimeInterpretationModule()) //
161166
.build();
162167

163-
PopulationRouter router = routingInjector.getInstance(PopulationRouter.class);
164-
router.run(scenario.getPopulation());
168+
boolean skipRouting = Boolean.parseBoolean(cmd.getOption("skip-routing").orElse("false"));
165169

166-
// Check validity after cutting
167-
scenarioValidator.checkScenario(scenario);
170+
if(!skipRouting) {
171+
PopulationRouter router = routingInjector.getInstance(PopulationRouter.class);
172+
router.run(scenario.getPopulation());
173+
// Check validity after cutting
174+
scenarioValidator.checkScenario(scenario);
175+
}
168176

169177
// Write scenario
170178
ScenarioWriter scenarioWriter = new ScenarioWriter(config, scenario, prefix);
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package org.eqasim.core.scenario.cutter;
2+
3+
import org.apache.commons.io.FileUtils;
4+
import org.eqasim.core.scenario.cutter.extent.ScenarioExtent;
5+
import org.eqasim.core.scenario.cutter.extent.ShapeScenarioExtent;
6+
import org.eqasim.core.simulation.EqasimConfigurator;
7+
import org.eqasim.core.simulation.vdf.VDFConfigGroup;
8+
import org.eqasim.core.simulation.vdf.engine.VDFEngineConfigGroup;
9+
import org.matsim.api.core.v01.IdSet;
10+
import org.matsim.api.core.v01.Scenario;
11+
import org.matsim.api.core.v01.network.Link;
12+
import org.matsim.api.core.v01.network.Network;
13+
import org.matsim.api.core.v01.population.Person;
14+
import org.matsim.core.config.CommandLine;
15+
import org.matsim.core.config.CommandLine.ConfigurationException;
16+
import org.matsim.core.config.Config;
17+
import org.matsim.core.config.ConfigUtils;
18+
import org.matsim.core.network.NetworkUtils;
19+
import org.matsim.core.network.algorithms.TransportModeNetworkFilter;
20+
import org.matsim.core.population.io.PopulationReader;
21+
import org.matsim.core.scenario.ScenarioUtils;
22+
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.nio.file.Paths;
26+
import java.util.*;
27+
28+
public class RunScenarioCutterV2 {
29+
30+
public static final String[] SHAPEFILE_EXTENSIONS = new String[]{".shp", ".cpg", ".dbf", ".qmd", ".shx", ".prj"};
31+
32+
static public void main(String[] args)
33+
throws ConfigurationException, IOException, InterruptedException {
34+
CommandLine cmd = new CommandLine.Builder(args) //
35+
.requireOptions("config-path", "output-path", "extent-path", "vdf-travel-times-path") //
36+
.allowOptions("threads", "prefix", "extent-attribute", "extent-value", "plans-path", "events-path") //
37+
.allowOptions("flag-area-link-modes") //
38+
.build();
39+
40+
String outputPath = cmd.getOptionStrict("output-path");
41+
42+
EqasimConfigurator eqasimConfigurator = new EqasimConfigurator();
43+
Config config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), eqasimConfigurator.getConfigGroups());
44+
cmd.applyConfiguration(config);
45+
eqasimConfigurator.addOptionalConfigGroups(config);
46+
47+
if(!config.getModules().containsKey(VDFConfigGroup.GROUP_NAME) || !config.getModules().containsKey(VDFEngineConfigGroup.GROUP_NAME)) {
48+
throw new IllegalStateException(String.format("This scenario cutter only works with configs where both '%s' and '%s' modules are used", VDFConfigGroup.GROUP_NAME, VDFEngineConfigGroup.GROUP_NAME));
49+
}
50+
51+
List<String> scenarioCutterArgs = new ArrayList<>();
52+
for(String requiredOption: RunScenarioCutter.REQUIRED_ARGS) {
53+
scenarioCutterArgs.add("--"+requiredOption);
54+
scenarioCutterArgs.add(cmd.getOptionStrict(requiredOption));
55+
}
56+
for(String optionalOption: RunScenarioCutter.OPTIONAL_ARGS) {
57+
if(cmd.hasOption(optionalOption)) {
58+
scenarioCutterArgs.add("--"+optionalOption);
59+
scenarioCutterArgs.add(cmd.getOptionStrict(optionalOption));
60+
}
61+
}
62+
scenarioCutterArgs.add("--skip-routing");
63+
scenarioCutterArgs.add("true");
64+
65+
RunScenarioCutter.main(scenarioCutterArgs.toArray(String[]::new));
66+
67+
String prefix = cmd.getOption("prefix").orElse("");
68+
69+
Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig());
70+
eqasimConfigurator.configureScenario(scenario);
71+
// We first load the population resulting from the legacy cutter and store the person ids
72+
new PopulationReader(scenario).readFile(Paths.get(outputPath, prefix+"population.xml.gz").toString());
73+
IdSet<Person> personIds = new IdSet<>(Person.class);
74+
scenario.getPopulation().getPersons().values().stream().map(Person::getId).forEach(personIds::add);
75+
76+
// We now read the data from the original scenario
77+
scenario = ScenarioUtils.createScenario(config);
78+
eqasimConfigurator.configureScenario(scenario);
79+
ScenarioUtils.loadScenario(scenario);
80+
eqasimConfigurator.adjustScenario(scenario);
81+
82+
// We remove from the original population, the persons that do not appear in the one resulting from the legacy cutter
83+
IdSet<Person> personsToRemove = new IdSet<>(Person.class);
84+
scenario.getPopulation().getPersons().values().stream().map(Person::getId).filter(personId -> !personIds.contains(personId)).forEach(personsToRemove::add);
85+
personsToRemove.forEach(scenario.getPopulation()::removePerson);
86+
87+
// Now we process the network
88+
File extentPath = new File(cmd.getOptionStrict("extent-path"));
89+
Optional<String> extentAttribute = cmd.getOption("extent-attribute");
90+
Optional<String> extentValue = cmd.getOption("extent-value");
91+
ScenarioExtent extent = new ShapeScenarioExtent.Builder(extentPath, extentAttribute, extentValue).build();
92+
93+
Set<String> insideModes = new HashSet<>();
94+
if(Boolean.parseBoolean(cmd.getOption("flag-area-link-modes").orElse("false"))) {
95+
scenario.getNetwork().getLinks().values()
96+
.stream().filter(link -> extent.isInside(link.getFromNode().getCoord()) && extent.isInside(link.getFromNode().getCoord()))
97+
.forEach(link -> {
98+
Set<String> linkModes = new HashSet<>(link.getAllowedModes());
99+
for(String mode: link.getAllowedModes()) {
100+
String insideMode = "inside_"+mode;
101+
insideModes.add(insideMode);
102+
linkModes.add(insideMode);
103+
}
104+
link.setAllowedModes(linkModes);
105+
});
106+
107+
for(String mode: insideModes) {
108+
findLargestFullyConnectedSubnetwork(scenario.getNetwork(), mode);
109+
}
110+
}
111+
112+
// "Cut" config
113+
// (we need to reload it, because it has become locked at this point)
114+
config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), eqasimConfigurator.getConfigGroups());
115+
cmd.applyConfiguration(config);
116+
eqasimConfigurator.addOptionalConfigGroups(config);
117+
ConfigCutter configCutter = new ConfigCutter(prefix);
118+
configCutter.run(config);
119+
120+
// Before writing the config, we make sure we configure VDF to update the travel times only in the study area
121+
String extentBasePath = Paths.get(outputPath, "extent").toAbsolutePath().toString();
122+
String copiedExtentPath = Paths.get(extentBasePath, extentPath.getName()).toString();
123+
FileUtils.forceMkdir(new File(extentBasePath));
124+
copyExtentFiles(extentPath.getAbsolutePath(), copiedExtentPath);
125+
VDFConfigGroup vdfConfigGroup = VDFConfigGroup.getOrCreate(config);
126+
vdfConfigGroup.setUpdateAreaShapefile("extent/" + extentPath.getName());
127+
// We also set the VDF config to use the vdf.bin file for initial travel times
128+
vdfConfigGroup.setInputFile("vdf.bin");
129+
130+
new ScenarioWriter(config, scenario, prefix).run(new File(outputPath).getAbsoluteFile());
131+
132+
FileUtils.copyFile(new File(cmd.getOptionStrict("vdf-travel-times-path")), new File(outputPath, "vdf.bin"));
133+
}
134+
135+
public static void findLargestFullyConnectedSubnetwork(Network network, String mode) {
136+
Network subNetwork = NetworkUtils.createNetwork();
137+
new TransportModeNetworkFilter(network).filter(subNetwork, Set.of(mode));
138+
139+
NetworkUtils.runNetworkCleaner(subNetwork);
140+
141+
for(Link link: network.getLinks().values()) {
142+
if(link.getAllowedModes().contains(mode) && !subNetwork.getLinks().containsKey(link.getId())) {
143+
Set<String> modes = new HashSet<>(link.getAllowedModes());
144+
modes.remove(mode);
145+
link.setAllowedModes(modes);
146+
}
147+
}
148+
}
149+
150+
private static void copyExtentFiles(String sourcePath, String destPath) throws IOException {
151+
if(sourcePath.endsWith(".shp")) {
152+
sourcePath = sourcePath.substring(0, sourcePath.length()-4);
153+
destPath = destPath.substring(0, destPath.length()-4);
154+
for(String extension: SHAPEFILE_EXTENSIONS) {
155+
FileUtils.copyFile(new File(sourcePath + extension), new File(destPath + extension));
156+
}
157+
} else {
158+
FileUtils.copyFile(new File(sourcePath), new File(destPath));
159+
}
160+
}
161+
}

core/src/test/java/org/eqasim/TestSimulationPipeline.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.eqasim.core.analysis.run.RunPublicTransportLegAnalysis;
1515
import org.eqasim.core.analysis.run.RunTripAnalysis;
1616
import org.eqasim.core.scenario.cutter.RunScenarioCutter;
17+
import org.eqasim.core.scenario.cutter.RunScenarioCutterV2;
1718
import org.eqasim.core.simulation.EqasimConfigurator;
1819
import org.eqasim.core.simulation.analysis.EqasimAnalysisModule;
1920
import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension;
@@ -213,6 +214,36 @@ private void runCutter() throws Exception {
213214
});
214215
}
215216

217+
public void runCutterV2() throws CommandLine.ConfigurationException, IOException, InterruptedException {
218+
RunScenarioCutterV2.main(new String[] {
219+
"--config-path", "melun_test/input/config_vdf.xml",
220+
"--events-path", "melun_test/output_vdf/output_events.xml.gz",
221+
"--vdf-travel-times-path", "melun_test/output_vdf/vdf.bin",
222+
"--output-path", "melun_test/cutter_v2",
223+
"--prefix", "center_",
224+
"--extent-path", "melun_test/input/center.shp",
225+
"--flag-area-link-modes", "true"
226+
});
227+
228+
CreateDrtVehicles.main(new String[]{
229+
"--network-path", "melun_test/cutter_v2/center_network.xml.gz",
230+
"--output-vehicles-path", "melun_test/cutter_v2/drt_vehicles.xml",
231+
"--vehicles-number", "25",
232+
"--network-modes", "inside_car"
233+
});
234+
235+
AdaptConfigForDrt.main(new String[]{
236+
"--input-config-path", "melun_test/cutter_v2/center_config.xml",
237+
"--output-config-path", "melun_test/cutter_v2/center_config_drt.xml",
238+
"--vehicles-paths", "melun_test/cutter_v2/drt_vehicles.xml",
239+
"--operational-schemes", "serviceAreaBased",
240+
"--config:multiModeDrt.drt[mode=drt].drtServiceAreaShapeFile", "extent/center.shp",
241+
"--config:dvrp.networkModes", "inside_car"
242+
});
243+
244+
runMelunSimulation("melun_test/cutter_v2/center_config_drt.xml", "melun_test/output_cutter_v2_drt");
245+
}
246+
216247
@Test
217248
public void testDrt() throws IOException, CommandLine.ConfigurationException {
218249
CreateDrtVehicles.main(new String[]{
@@ -335,8 +366,7 @@ public void testTransitWithAbstractAccess() throws CommandLine.ConfigurationExce
335366
runMelunSimulation("melun_test/input/config_abstract_access.xml", "melun_test/output_abstract_access");
336367
}
337368

338-
@Test
339-
public void testVDF() throws CommandLine.ConfigurationException, IOException {
369+
public void runVdf() throws CommandLine.ConfigurationException, IOException {
340370
AdaptConfigForVDF.main(new String[] {
341371
"--input-config-path", "melun_test/input/config.xml",
342372
"--output-config-path", "melun_test/input/config_vdf.xml",
@@ -345,6 +375,8 @@ public void testVDF() throws CommandLine.ConfigurationException, IOException {
345375
"--config:eqasim:vdf_engine.generateNetworkEvents", "true"
346376
});
347377

378+
runMelunSimulation("melun_test/input/config_vdf.xml", "melun_test/output_vdf");
379+
348380
CreateDrtVehicles.main(new String[]{
349381
"--network-path", "melun_test/input/network.xml.gz",
350382
"--output-vehicles-path", "melun_test/input/drt_vehicles.xml.gz",
@@ -365,9 +397,11 @@ public void testVDF() throws CommandLine.ConfigurationException, IOException {
365397
public void testPipeline() throws Exception {
366398
runMelunSimulation("melun_test/input/config.xml", "melun_test/output");
367399
runStandaloneModeChoice();
400+
runVdf();
368401
runAnalyses();
369402
runExports();
370403
runCutter();
404+
runCutterV2();
371405
}
372406

373407

0 commit comments

Comments
 (0)