Skip to content

Commit 9d5c063

Browse files
fixed priority order of dashboards so that scenarioComparison runs at the end. Started implementing a trips comparison tab with easy logic for testing (boolean in sceanrioComparison class to trigger trip calculations). Next ToDos: look into changing visualization types for modal split; add functionality so that multplie scenarios can be compared, not just base and a policy. Finish trips tab.
1 parent 466fac3 commit 9d5c063

9 files changed

Lines changed: 387 additions & 194 deletions

File tree

contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public class TripAnalysis implements MATSimAppCommand {
8787
private LocationFilter filter;
8888
@CommandLine.Mixin
8989
private ShpOptions shp;
90+
@CommandLine.Option(names = "--input-comparison-data", description = "Optional path to comparison scenario data", required = false)
91+
private String comparisonData;
9092

9193
public static void main(String[] args) {
9294
new TripAnalysis().execute(args);
@@ -262,6 +264,11 @@ public Integer call() throws Exception {
262264
groups.groupPersons(persons);
263265
}
264266

267+
if (comparisonData != null) {
268+
groups = new TripByGroupAnalysis(comparisonData);
269+
groups.groupPersons(persons);
270+
}
271+
265272
// Use longest_distance_mode where main_mode is not present
266273
trips.stringColumn("main_mode")
267274
.set(trips.stringColumn("main_mode").isMissing(),

contribs/application/src/main/java/org/matsim/application/analysis/scenarioComparison/ScenarioComparisonAnalysis.java

Lines changed: 197 additions & 149 deletions
Large diffs are not rendered by default.

contribs/simwrapper/src/main/java/org/matsim/simwrapper/CreateSingleSimWrapperDashboard.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public Integer call() throws Exception {
127127
if (simwrapperCfg.getBasePath() == null) {
128128
throw new IllegalArgumentException("ScenarioComparisonDashboard requires a base run to be set in the config.");
129129
}
130-
sw.addDashboard(new ScenarioComparisonDashboard(simwrapperCfg.getBasePath()));
130+
sw.addDashboard(new ScenarioComparisonDashboard(simwrapperCfg.getBasePath(), true));
131131
}
132132
default -> throw new IllegalArgumentException("unkown dashboard type: " + dashboardType);
133133
}

contribs/simwrapper/src/main/java/org/matsim/simwrapper/DefaultDashboardProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public List<Dashboard> getDashboards(Config config, SimWrapper simWrapper) {
4141
}
4242

4343
if (simWrapper.getConfigGroup().getBasePath() != null) {
44-
result.add(new ScenarioComparisonDashboard(simWrapper.getConfigGroup().getBasePath()));
44+
result.add(new ScenarioComparisonDashboard(simWrapper.getConfigGroup().getBasePath(), true));
4545
}
4646

4747
result.add(new StuckAgentDashboard());

contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/EmissionsDashboard.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,4 @@ public void configure(Header header, Layout layout) {
8282

8383
}
8484

85-
@Override
86-
public double priority() {
87-
return 1;
88-
}
8985
}
Lines changed: 152 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package org.matsim.simwrapper.dashboard;
22

3+
import jakarta.annotation.Nullable;
4+
import org.matsim.application.analysis.population.TripAnalysis;
35
import org.matsim.application.analysis.scenarioComparison.ScenarioComparisonAnalysis;
46
import org.matsim.simwrapper.ComparisonDashboard;
5-
import org.matsim.simwrapper.Dashboard;
67
import org.matsim.simwrapper.Header;
78
import org.matsim.simwrapper.Layout;
8-
import org.matsim.simwrapper.viz.Tile;
9+
import org.matsim.simwrapper.viz.*;
10+
import tech.tablesaw.plotly.components.Axis;
11+
import tech.tablesaw.plotly.traces.BarTrace;
12+
13+
import java.util.List;
14+
import java.util.Map;
915

1016
public class ScenarioComparisonDashboard implements ComparisonDashboard {
1117

@@ -23,35 +29,167 @@ public void setPathToBaseCase(String path) {
2329

2430
private String constructorBasePath;
2531

26-
public ScenarioComparisonDashboard(String basePath) {
32+
private boolean tripsComparison;
33+
private boolean scenarioComparison;
34+
private ScenarioComparisonAnalysis scenarioComparisonAnalysis;
35+
36+
/**
37+
* Default scenario comparison dashboard constructor.
38+
*/
39+
public ScenarioComparisonDashboard(String basePath, boolean tripsComparison) {
40+
this.tripsComparison = tripsComparison;
2741
constructorBasePath = basePath;
2842
}
2943

44+
@Nullable
45+
private String modeShareTripCsv;
46+
@Nullable
47+
private String modeShareDistTripCsv;
48+
@Nullable
49+
private String modeUsersTripCsv;
50+
51+
@Override
3052
public void configure(Header header, Layout layout) {
3153

3254
header.title = "Scenario Comparison: Policy to Base Case";
3355
header.description = "Shows the differences in a variety of metrics between the policy and base case.";
3456

35-
layout.row("trip stats")
36-
.el(Tile.class, (viz, data) -> {
37-
viz.title = "Difference in Time & Distance Traveled per Mode (KMs and Hours)";
38-
viz.dataset = data.compute(ScenarioComparisonAnalysis.class, "difference_trips.csv", "--input-base-path=" + constructorBasePath);
39-
viz.height = 0.1;
57+
String tab = "Trips Comparison";
58+
59+
Layout.Row first = layout.row("first", tab);
60+
first.el(Plotly.class, (viz, data) -> {
61+
viz.title = "Modal split";
62+
63+
viz.layout = tech.tablesaw.plotly.components.Layout.builder()
64+
.barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK)
65+
.build();
66+
67+
Plotly.DataSet ds = viz.addDataset(data.compute(TripAnalysis.class, "mode_share.csv"))
68+
.constant("source", "Simulated")
69+
.aggregate(List.of("main_mode"), "share", Plotly.AggrFunc.SUM);
70+
71+
if (this.tripsComparison) {
72+
this.modeShareTripCsv = this.constructorBasePath + "/analysis/population/mode_share.csv";
73+
viz.addDataset(data.resource(modeShareTripCsv))
74+
.constant("source", "Base")
75+
.aggregate(List.of("main_mode"), "share", Plotly.AggrFunc.SUM);
76+
77+
viz.mergeDatasets = true;
78+
}
79+
80+
81+
viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).orientation(BarTrace.Orientation.HORIZONTAL).build(),
82+
ds.mapping()
83+
.name("main_mode")
84+
.y("source")
85+
.x("share")
86+
);
87+
});
88+
89+
first.el(Plotly.class, (viz, data) -> {
90+
91+
viz.title = "Trip distance distribution";
92+
viz.colorRamp = ColorScheme.Viridis;
93+
94+
viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).name("Simulated").build(),
95+
viz.addDataset(data.compute(TripAnalysis.class, "mode_share.csv"))
96+
.aggregate(List.of("dist_group"), "share", Plotly.AggrFunc.SUM)
97+
.mapping()
98+
.x("dist_group")
99+
.y("share")
100+
);
101+
102+
if (this.tripsComparison) {
103+
viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).name("Base").build(),
104+
viz.addDataset(data.resource(modeShareTripCsv))
105+
.aggregate(List.of("dist_group"), "share", Plotly.AggrFunc.SUM)
106+
.mapping()
107+
.x("dist_group")
108+
.y("share")
109+
);
110+
}
111+
});
112+
113+
layout.row("second", tab)
114+
.el(Table.class, (viz, data) -> {
115+
viz.title = "Mode Statistics";
116+
viz.description = "by main mode, over whole trip (including access & egress)";
117+
viz.dataset = data.compute(ScenarioComparisonAnalysis.class, "trip_stats_comparison.csv",
118+
"--input-base-path=" + constructorBasePath);
119+
viz.showAllRows = true;
120+
})
121+
.el(Plotly.class, (viz, data) -> {
122+
123+
viz.title = "Modal distance distribution";
124+
125+
viz.layout = tech.tablesaw.plotly.components.Layout.builder()
126+
.xAxis(Axis.builder().title("Distance group").build())
127+
.yAxis(Axis.builder().title("Share").build())
128+
.barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK)
129+
.build();
130+
131+
Plotly.DataSet sim = viz.addDataset(data.compute(TripAnalysis.class, "mode_share_per_dist.csv"))
132+
.constant("source", "Sim");
133+
134+
viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(),
135+
sim.mapping()
136+
.name("main_mode")
137+
.x("dist_group")
138+
.y("share")
139+
);
140+
141+
if (tripsComparison) {
142+
this.modeShareDistTripCsv = this.constructorBasePath + "/analysis/population/mode_share_per_dist.csv";
143+
Plotly.DataSet ref = viz.addDataset(data.resource(modeShareDistTripCsv))
144+
.constant("source", "Base");
145+
146+
viz.multiIndex = Map.of("dist_group", "source");
147+
viz.mergeDatasets = true;
148+
}
149+
40150
});
41151

42-
layout.row("emission stats")
43-
.el(Tile.class, (viz, data) -> {
44-
viz.title = "Difference in Emissions (KGs)";
45-
viz.dataset = data.compute(ScenarioComparisonAnalysis.class, "difference_emissions.csv", "--input-base-path=" + constructorBasePath);
46-
viz.height = 0.1;
152+
layout.row("third", tab)
153+
.el(Table.class, (viz, data) -> {
154+
viz.title = "Population statistics";
155+
viz.description = "over simulated persons (not scaled by sample size)";
156+
viz.showAllRows = true;
157+
viz.dataset = data.compute(TripAnalysis.class, "population_trip_stats.csv");
158+
viz.dataset = data.compute(ScenarioComparisonAnalysis.class, "population_trip_stats_comparison.csv",
159+
"--input-base-path=" + constructorBasePath);
160+
})
161+
.el(Plotly.class, (viz, data) -> {
162+
163+
viz.title = "Mode usage";
164+
viz.description = "Share of persons using a main mode at least once per day";
165+
viz.width = 2d;
166+
167+
Plotly.DataSet ds = viz.addDataset(data.compute(TripAnalysis.class, "mode_users.csv"));
168+
viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), ds.mapping()
169+
.x("main_mode")
170+
.y("user")
171+
.name("main_mode")
172+
);
173+
174+
if (tripsComparison) {
175+
ds.constant("source", "sim");
176+
177+
modeUsersTripCsv = this.constructorBasePath + "/analysis/population/mode_users.csv";
178+
viz.addDataset(data.resource(modeUsersTripCsv))
179+
.constant("source", "Base");
180+
181+
viz.multiIndex = Map.of("main_mode", "source");
182+
viz.mergeDatasets = true;
183+
}
184+
47185
});
48186

49187
}
50188

51189
// priority is set to a lower number in order to force this class to be executed after population and emissions folders are already generated
52190
@Override
53191
public double priority() {
54-
return -20;
192+
return -2;
55193
}
56194

57195
}

contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,5 @@ private void createGroupedTab(Layout layout, String[] args) {
511511

512512
}
513513
}
514-
@Override
515-
public double priority() {
516-
return 1;
517-
}
518514

519515
}

contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/ScenarioComparisonDashboardTest.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,26 @@ void generate() {
6262
SimWrapperConfigGroup.ContextParams contextParams = simWrapperConfigGroup.defaultParams();
6363
contextParams.setMapCenter("12,48.95");
6464
contextParams.setMapZoomLevel(9.0);
65-
66-
EmissionsConfigGroup emissionsConfig = ConfigUtils.addOrGetModule(config, EmissionsConfigGroup.class);
67-
68-
emissionsConfig.setAverageColdEmissionFactorsFile(HBEFA_FILE_COLD_AVERAGE);
69-
emissionsConfig.setDetailedColdEmissionFactorsFile(HBEFA_FILE_COLD_DETAILED);
70-
emissionsConfig.setAverageWarmEmissionFactorsFile(HBEFA_FILE_WARM_AVERAGE);
71-
emissionsConfig.setDetailedWarmEmissionFactorsFile(HBEFA_FILE_WARM_DETAILED);
72-
emissionsConfig.setHbefaTableConsistencyCheckingLevel(EmissionsConfigGroup.HbefaTableConsistencyCheckingLevel.consistent);
73-
74-
emissionsConfig.setDetailedVsAverageLookupBehavior(EmissionsConfigGroup.DetailedVsAverageLookupBehavior.tryDetailedThenTechnologyAverageThenAverageTable);
75-
65+
//
66+
// EmissionsConfigGroup emissionsConfig = ConfigUtils.addOrGetModule(config, EmissionsConfigGroup.class);
67+
//
68+
// emissionsConfig.setAverageColdEmissionFactorsFile(HBEFA_FILE_COLD_AVERAGE);
69+
// emissionsConfig.setDetailedColdEmissionFactorsFile(HBEFA_FILE_COLD_DETAILED);
70+
// emissionsConfig.setAverageWarmEmissionFactorsFile(HBEFA_FILE_WARM_AVERAGE);
71+
// emissionsConfig.setDetailedWarmEmissionFactorsFile(HBEFA_FILE_WARM_DETAILED);
72+
// emissionsConfig.setHbefaTableConsistencyCheckingLevel(EmissionsConfigGroup.HbefaTableConsistencyCheckingLevel.consistent);
73+
//
74+
// emissionsConfig.setDetailedVsAverageLookupBehavior(EmissionsConfigGroup.DetailedVsAverageLookupBehavior.tryDetailedThenTechnologyAverageThenAverageTable);
75+
//
7676

7777
SimWrapper sw = SimWrapper.create(config)
78-
.addDashboard(new EmissionsDashboard(config.global().getCoordinateSystem()))
79-
.addDashboard(new ImpactAnalysisDashboard(Set.of("car")));
80-
81-
// Set base path to output folder of base scenario that you wish to compare a policy with. Example:
82-
sw.getConfigGroup().setBasePath("/home/brendan/git/matsim-libs/contribs/simwrapper/test/output/org/matsim/simwrapper/dashboard/EmissionsDashboardTest/generate");
78+
// .addDashboard(new EmissionsDashboard(config.global().getCoordinateSystem()))
79+
// .addDashboard(new ImpactAnalysisDashboard(Set.of("car")))
80+
.addDashboard(new TripDashboard())
81+
.addDashboard(new ScenarioComparisonDashboard(
82+
"/home/brendan/git/matsim-berlin/output/berlin-v6.4-0.1pct_RidePCUSetToZero",
83+
true
84+
));
8385

8486
Controler controler = MATSimApplication.prepare(new TestScenario(sw), config);
8587

@@ -89,14 +91,14 @@ void generate() {
8991
link.setFreespeed(30);
9092
}
9193

92-
prepareVehicleTypes(scenario);
93-
prepareHbefaNetwork(scenario);
94+
// prepareVehicleTypes(scenario);
95+
// prepareHbefaNetwork(scenario);
9496

9597
controler.addOverridingModule(new CountsModule());
9698
controler.run();
9799

98-
Assertions.assertThat(Path.of(utils.getOutputDirectory(), "analysis", "emissions"))
99-
.isDirectoryContaining("glob:**emissions_total.csv");
100+
// Assertions.assertThat(Path.of(utils.getOutputDirectory(), "analysis", "emissions"))
101+
// .isDirectoryContaining("glob:**emissions_total.csv");
100102

101103

102104
Assertions.assertThat(Path.of(utils.getOutputDirectory(), "analysis", "population"))

matsim/src/main/java/org/matsim/application/CommandRunner.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jgrapht.graph.DefaultEdge;
88
import org.jgrapht.graph.DirectedAcyclicGraph;
99
import org.jgrapht.traverse.BreadthFirstIterator;
10+
import org.jgrapht.traverse.TopologicalOrderIterator;
1011
import org.matsim.application.options.*;
1112

1213
import java.nio.file.Files;
@@ -87,8 +88,13 @@ public void run(Path input) {
8788
if (!hasDependencies)
8889
start.add(clazz);
8990
}
91+
// BreadthFirst traverses level-by-level, not respecting dependencies. Topological seems to fix bug for scenarioComparison, so that all other dependent analyses run first.
92+
// BreadthFirstIterator<Class<? extends MATSimAppCommand>, DefaultEdge> it = new BreadthFirstIterator<>(graph, start);
93+
Iterator<Class<? extends MATSimAppCommand>> it = new org.jgrapht.traverse.TopologicalOrderIterator<>(graph);
94+
95+
log.info("Execution order of dashboard analysis classes will be:");
96+
new TopologicalOrderIterator<>(graph).forEachRemaining(c -> log.info(" -> {}", c.getSimpleName()));
9097

91-
BreadthFirstIterator<Class<? extends MATSimAppCommand>, DefaultEdge> it = new BreadthFirstIterator<>(graph, start);
9298
while (it.hasNext()) {
9399
Class<? extends MATSimAppCommand> clazz = it.next();
94100
try {

0 commit comments

Comments
 (0)