diff --git a/CITATION.cff b/CITATION.cff index 1a99114..6834048 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -16,9 +16,8 @@ authors: affiliation: Technische Universität Berlin orcid: 'https://orcid.org/0000-0002-6354-4529' -url: "https://github.com/matsim-scenarios/matsim-mexico-city" -TODO: adapt release date -date-released: 2023-01-01 -year: 2023 +url: "https://github.com/matsim-scenarios/matsim-mexico-city/tree/1.0" +date-released: 2024-03-26 +year: 2024 version: 1.0 license: AGPL-3.0 diff --git a/pom.xml b/pom.xml index 5a9a3b5..e13a521 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.github.matsim-scenarios matsim-mexico-city - 1.0-SNAPSHOT + 1.0 MATSim Open Mexico-City Model MATSim Open Mexico-City Model diff --git a/src/main/java/org/matsim/analysis/roadpricing/RoadPricingAnalysis.java b/src/main/java/org/matsim/analysis/roadpricing/RoadPricingAnalysis.java index 368db13..08c048e 100644 --- a/src/main/java/org/matsim/analysis/roadpricing/RoadPricingAnalysis.java +++ b/src/main/java/org/matsim/analysis/roadpricing/RoadPricingAnalysis.java @@ -30,14 +30,16 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Path; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.*; -import static tech.tablesaw.aggregate.AggregateFunctions.count; +import static tech.tablesaw.aggregate.AggregateFunctions.*; @CommandLine.Command(name = "roadPricing", description = "Calculates various road pricing related metrics.") @CommandSpec( requires = {"personMoneyEvents.tsv", "persons.csv", "config.xml"}, - produces = {"roadPricing_income_groups.csv", "roadPricing_tolled_agents.csv", "roadPricing_daytime_groups.csv", "roadPricing_tolled_agents_home_locations.csv", "roadPricing_area.shp"} + produces = {"roadPricing_income_groups.csv", "roadPricing_avg_toll_income_groups.csv", "roadPricing_tolled_agents.csv", "roadPricing_daytime_groups.csv", "roadPricing_tolled_agents_home_locations.csv", "roadPricing_area.shp"} ) public class RoadPricingAnalysis implements MATSimAppCommand { @@ -58,6 +60,7 @@ public class RoadPricingAnalysis implements MATSimAppCommand { String share = "share"; String person = "person"; String incomeGroup = "incomeGroup"; + String amount = "amount"; public static void main(String[] args) { new RoadPricingAnalysis().execute(args); @@ -75,7 +78,7 @@ public Integer call() throws Exception { .separator(csv.detectDelimiter(input.getPath("persons.csv"))).build()); Table moneyEvents = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(input.getPath("personMoneyEvents.tsv"))) - .columnTypesPartial(Map.of(person, ColumnType.TEXT, "time", ColumnType.DOUBLE, "purpose", ColumnType.TEXT, "amount", ColumnType.DOUBLE)) + .columnTypesPartial(Map.of(person, ColumnType.TEXT, "time", ColumnType.DOUBLE, "purpose", ColumnType.TEXT, amount, ColumnType.DOUBLE)) .sample(false) .separator(csv.detectDelimiter(input.getPath("personMoneyEvents.tsv"))).build()); @@ -83,17 +86,23 @@ public Integer call() throws Exception { IntList idx = new IntArrayList(); for (int i = 0; i < moneyEvents.rowCount(); i++) { Row row = moneyEvents.row(i); - if (row.getString("purpose").contains("toll")) { + if (row.getString("purpose").toLowerCase().contains("toll")) { idx.add(i); } } Table filtered = moneyEvents.where(Selection.with(idx.toIntArray())); - double totalToll = (double) filtered.summarize("amount", AggregateFunctions.sum).apply().column("Sum [amount]").get(0); + double totalToll = (double) filtered.summarize(amount, AggregateFunctions.sum).apply().column("Sum [amount]").get(0); + double medianTollPaid = MexicoCityUtils.calcMedian(filtered.doubleColumn(amount).asList()); + double meanTollPaid = totalToll / filtered.rowCount(); + + DecimalFormat f = new DecimalFormat("0.00", new DecimalFormatSymbols(Locale.ENGLISH)); try (CSVPrinter printer = new CSVPrinter(new FileWriter(output.getPath("roadPricing_tolled_agents.csv").toString()), CSVFormat.DEFAULT)) { - printer.printRecord("total toll paid [MXN]", totalToll, "paid_FILL1_wght400_GRAD0_opsz48.png", "https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/mx/mexico-city/mexico-city-v1.0/input/roadPricing/"); - printer.printRecord("number of tolled agents", filtered.rowCount(), "tag_FILL1_wght400_GRAD0_opsz48.png", "https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/mx/mexico-city/mexico-city-v1.0/input/roadPricing/"); + printer.printRecord("\"total toll paid [MXN]\"", f.format(totalToll)); + printer.printRecord("\"number of tolled agents\"", filtered.rowCount()); + printer.printRecord("\"mean toll paid [MXN]\"", f.format(meanTollPaid)); + printer.printRecord("\"median toll paid [MXN]\"", f.format(medianTollPaid)); } Table joined = new DataFrameJoiner(moneyEvents, person).inner(persons); @@ -198,24 +207,31 @@ private void writeIncomeDistr(Table joined) { Table aggr = joined.summarize(person, count).by(incomeGroup); -// how to sort rows here? this does not work! Using workaround instead. -sme0324 + aggr = new DataFrameJoiner(new DataFrameJoiner(aggr, incomeGroup) + .inner(joined.summarize(amount, mean).by(incomeGroup)), incomeGroup) + .inner(joined.summarize(amount, median).by(incomeGroup)); + +// how to sort rows here? agg.sortOn does not work! Using workaround instead. -sme0324 DoubleColumn shareCol = aggr.numberColumn(1).divide(aggr.numberColumn(1).sum()).setName(this.share); aggr.addColumns(shareCol); aggr.sortOn(this.share); List incomeDistr = new ArrayList<>(); + List avgTolls = new ArrayList<>(); for (String k : labels.keySet()) { for (int i = 0; i < aggr.rowCount() - 1; i++) { Row row = aggr.row(i); if (row.getString(incomeGroup).equals(k)) { incomeDistr.add(k + "," + row.getDouble("Count [person]") + "," + row.getDouble("share")); + avgTolls.add(k + "," + Math.abs(row.getDouble("Mean [amount]")) + "," + Math.abs(row.getDouble("Median [amount]"))); break; } } } incomeDistr.sort(Comparator.comparingInt(RoadPricingAnalysis::getLowerBound)); + avgTolls.sort(Comparator.comparingInt(RoadPricingAnalysis::getLowerBound)); CSVFormat format = CSVFormat.DEFAULT.builder() .setQuote(null) @@ -224,13 +240,25 @@ private void writeIncomeDistr(Table joined) { .build(); +// print income distr try (CSVPrinter printer = new CSVPrinter(new FileWriter(output.getPath("roadPricing_income_groups.csv").toString()), format)) { + printer.printRecord("incomeGroup,Count [person],share"); for (String s : incomeDistr) { printer.printRecord(s); } } catch (IOException e) { throw new IllegalArgumentException(); } + +// print avg toll paid per income group + try (CSVPrinter printer = new CSVPrinter(new FileWriter(output.getPath("roadPricing_avg_toll_income_groups.csv").toString()), format)) { + printer.printRecord("incomeGroup,Mean [amount],Median [amount]"); + for (String s : avgTolls) { + printer.printRecord(s); + } + } catch (IOException e) { + throw new IllegalArgumentException(); + } } private static int getLowerBound(String s) { diff --git a/src/main/java/org/matsim/dashboard/RoadPricingDashboard.java b/src/main/java/org/matsim/dashboard/RoadPricingDashboard.java index ab697be..f9117ce 100644 --- a/src/main/java/org/matsim/dashboard/RoadPricingDashboard.java +++ b/src/main/java/org/matsim/dashboard/RoadPricingDashboard.java @@ -69,5 +69,22 @@ public void configure(Header header, Layout layout) { viz.width = 2.0; viz.setShape(data.compute(RoadPricingAnalysis.class, "roadPricing_area.shp"), "id"); }); + + layout.row("fourth") + .el(Bar.class, (viz, data) -> { + viz.title = "Paid toll per income group"; + viz.description = "mean and median values"; + viz.stacked = false; + viz.dataset = data.compute(RoadPricingAnalysis.class, "roadPricing_avg_toll_income_groups.csv"); + viz.x = "incomeGroup"; + viz.xAxisName = "income group"; + viz.yAxisName = "Toll paid [MXN]"; + viz.columns = List.of("Mean [amount]", "Median [amount]"); + }); + } + + @Override + public double priority() { + return -2; } } diff --git a/src/main/java/org/matsim/prepare/MexicoCityUtils.java b/src/main/java/org/matsim/prepare/MexicoCityUtils.java index 0586065..e222f86 100644 --- a/src/main/java/org/matsim/prepare/MexicoCityUtils.java +++ b/src/main/java/org/matsim/prepare/MexicoCityUtils.java @@ -8,6 +8,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -88,5 +90,25 @@ public static Coord roundCoord(Coord coord) { return new Coord(roundNumber(coord.getX()), roundNumber(coord.getY())); } + /** + * helper method to calc a median of a list of doubles. + */ + public static Double calcMedian(List values) { + + Collections.sort(values); + + int length = values.size(); + // Check if the length of the array is odd or even + if (length % 2 != 0) { + // If odd, return the middle element + return values.get(length / 2); + } else { + // If even, return the average of the two middle elements + int midIndex1 = length / 2 - 1; + int midIndex2 = length / 2; + return (values.get(midIndex1) + values.get(midIndex2)) / 2.0; + } + } + } diff --git a/src/main/java/org/matsim/run/CalcAverageTolledTripLength.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/CalcAverageTolledTripLength.java similarity index 99% rename from src/main/java/org/matsim/run/CalcAverageTolledTripLength.java rename to src/main/java/org/matsim/run/MexicoCityRoadPricing/CalcAverageTolledTripLength.java index 3778013..6a7339f 100644 --- a/src/main/java/org/matsim/run/CalcAverageTolledTripLength.java +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/CalcAverageTolledTripLength.java @@ -18,7 +18,7 @@ * * * *********************************************************************** */ -package org.matsim.run; +package org.matsim.run.MexicoCityRoadPricing; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/org/matsim/run/IncomeRelatedRoadPricingTollCalculator.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/IncomeRelatedRoadPricingTollCalculator.java similarity index 99% rename from src/main/java/org/matsim/run/IncomeRelatedRoadPricingTollCalculator.java rename to src/main/java/org/matsim/run/MexicoCityRoadPricing/IncomeRelatedRoadPricingTollCalculator.java index a1faae9..c1e9081 100644 --- a/src/main/java/org/matsim/run/IncomeRelatedRoadPricingTollCalculator.java +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/IncomeRelatedRoadPricingTollCalculator.java @@ -18,7 +18,7 @@ * * * *********************************************************************** */ -package org.matsim.run; +package org.matsim.run.MexicoCityRoadPricing; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityPlansCalcRouteWithTollOrNot.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityPlansCalcRouteWithTollOrNot.java new file mode 100644 index 0000000..415f5c6 --- /dev/null +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityPlansCalcRouteWithTollOrNot.java @@ -0,0 +1,115 @@ +/* + * *********************************************************************** * + * * project: org.matsim.* + * * PlansCalcRouteWithTollOrNot.java + * * * + * * *********************************************************************** * + * * * + * * copyright : (C) 2015 by the members listed in the COPYING, * + * * LICENSE and WARRANTY file. * + * * email : info at matsim dot org * + * * * + * * *********************************************************************** * + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * See also COPYING, LICENSE and WARRANTY file * + * * * + * * *********************************************************************** + */ + +package org.matsim.run.MexicoCityRoadPricing; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contrib.roadpricing.RoadPricingScheme; +import org.matsim.core.population.algorithms.PlanAlgorithm; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.core.router.PlanRouter; +import org.matsim.core.router.TripRouter; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.utils.timing.TimeInterpretation; + + +/** + * copied from org.matsim.contrib.roadpricing. + */ +class MexicoCityPlansCalcRouteWithTollOrNot implements PlanAlgorithm { + + static final String CAR_WITH_PAYED_AREA_TOLL = "car_with_payed_area_toll"; + private RoadPricingScheme roadPricingScheme; + private final PlanRouter planRouter ; + + @Inject + MexicoCityPlansCalcRouteWithTollOrNot(RoadPricingScheme roadPricingScheme, Provider tripRouterProvider, TimeInterpretation timeInterpretation) { + this.roadPricingScheme = roadPricingScheme; + this.planRouter = new PlanRouter( tripRouterProvider.get(), timeInterpretation ) ; + } + + @Override + public void run(final Plan plan) { + handlePlan(plan); + } + + private void handlePlan(Plan plan) { + // This calculates a best-response plan from the two options, paying area toll or not. + // From what I understand, it may be simpler/better to just throw a coin and produce + // one of the two options. + replaceCarModeWithTolledCarMode(plan); + planRouter.run( plan ); + double areaToll = roadPricingScheme.getTypicalCosts().iterator().next().amount; + double routeCostWithAreaToll = sumNetworkModeCosts(plan) + areaToll; + replaceTolledCarModeWithCarMode(plan); + planRouter.run( plan ); + double routeCostWithoutAreaToll = sumNetworkModeCosts(plan); + if (routeCostWithAreaToll < routeCostWithoutAreaToll) { + replaceCarModeWithTolledCarMode(plan); + planRouter.run( plan ); + } + } + + // This most likely will not work for intermodal setups with car e.g. as access mode to pt and routing mode of the trip + // something else than car. + // However, it did not work before the switch to routing mode either. - gl-nov'19 + private void replaceCarModeWithTolledCarMode(Plan plan) { + for (PlanElement planElement : plan.getPlanElements()) { + if (planElement instanceof Leg) { + if (TripStructureUtils.getRoutingMode((Leg) planElement).equals(TransportMode.car)) { + TripStructureUtils.setRoutingMode( (Leg) planElement , CAR_WITH_PAYED_AREA_TOLL ); + } + } + } + } + + private void replaceTolledCarModeWithCarMode(Plan plan) { + for (PlanElement planElement : plan.getPlanElements()) { + if (planElement instanceof Leg) { + if (TripStructureUtils.getRoutingMode((Leg) planElement).equals(CAR_WITH_PAYED_AREA_TOLL)) { + TripStructureUtils.setRoutingMode( (Leg) planElement , TransportMode.car ); + } + } + } + } + + private double sumNetworkModeCosts(Plan plan) { + double sum = 0.0; + for (PlanElement planElement : plan.getPlanElements()) { + if (planElement instanceof Leg) { + Leg leg = (Leg) planElement; + if (leg.getRoute() instanceof NetworkRoute) { + NetworkRoute networkRoute = (NetworkRoute) leg.getRoute(); + sum += networkRoute.getTravelCost(); + } + } + } + return sum; + } + +} + diff --git a/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityReRouteAreaToll.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityReRouteAreaToll.java new file mode 100644 index 0000000..0216de8 --- /dev/null +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityReRouteAreaToll.java @@ -0,0 +1,68 @@ +/* + * *********************************************************************** * + * * project: org.matsim.* + * * ReRouteAreaToll.java + * * * + * * *********************************************************************** * + * * * + * * copyright : (C) 2015 by the members listed in the COPYING, * + * * LICENSE and WARRANTY file. * + * * email : info at matsim dot org * + * * * + * * *********************************************************************** * + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * See also COPYING, LICENSE and WARRANTY file * + * * * + * * *********************************************************************** + */ + +package org.matsim.run.MexicoCityRoadPricing; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import org.matsim.contrib.roadpricing.RoadPricingScheme; +import org.matsim.core.config.Config; +import org.matsim.core.population.algorithms.PlanAlgorithm; +import org.matsim.core.replanning.PlanStrategy; +import org.matsim.core.replanning.PlanStrategyImpl; +import org.matsim.core.replanning.modules.AbstractMultithreadedModule; +import org.matsim.core.replanning.selectors.RandomPlanSelector; +import org.matsim.core.router.TripRouter; +import org.matsim.core.utils.timing.TimeInterpretation; + +/** + * copied from org.matsim.contrib.roadpricing. + */ +class MexicoCityReRouteAreaToll implements Provider { + + private final Config config; + private RoadPricingScheme roadPricingScheme; + private Provider tripRouterFactory; + private final TimeInterpretation timeInterpretation; +// private final Provider factory; + + @Inject + MexicoCityReRouteAreaToll(Config config, RoadPricingScheme roadPricingScheme, Provider tripRouterFactory, TimeInterpretation timeInterpretation ) { + this.config = config; + // this.factory = factory; + this.roadPricingScheme = roadPricingScheme; + this.tripRouterFactory = tripRouterFactory; + this.timeInterpretation = timeInterpretation; + } + + @Override + public PlanStrategy get() { + PlanStrategyImpl.Builder builder = new PlanStrategyImpl.Builder(new RandomPlanSelector<>()); + builder.addStrategyModule(new AbstractMultithreadedModule(config.global()) { + @Override + public PlanAlgorithm getPlanAlgoInstance() { + return new MexicoCityPlansCalcRouteWithTollOrNot( roadPricingScheme, tripRouterFactory, timeInterpretation ) ; + } + }); + return builder.build(); + } +} diff --git a/src/main/java/org/matsim/run/MexicoCityRoadPricingControlerListener.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingControlerListener.java similarity index 98% rename from src/main/java/org/matsim/run/MexicoCityRoadPricingControlerListener.java rename to src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingControlerListener.java index f514491..db07557 100644 --- a/src/main/java/org/matsim/run/MexicoCityRoadPricingControlerListener.java +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingControlerListener.java @@ -18,7 +18,7 @@ * * * *********************************************************************** */ -package org.matsim.run; +package org.matsim.run.MexicoCityRoadPricing; import jakarta.inject.Inject; import org.apache.logging.log4j.LogManager; diff --git a/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingModule.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingModule.java new file mode 100644 index 0000000..ee18df3 --- /dev/null +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingModule.java @@ -0,0 +1,65 @@ +package org.matsim.run.MexicoCityRoadPricing; + +import com.google.inject.Singleton; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.contrib.roadpricing.*; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.router.costcalculators.RandomizingTimeDistanceTravelDisutilityFactory; +import org.matsim.run.MexicoCityRoadPricing.MexicoCityRoadPricingModuleDefaults.*; + +/** + * copied from org.matsim.contrib.roadpricing. + */ +public final class MexicoCityRoadPricingModule extends AbstractModule { + final Logger log = LogManager.getLogger(MexicoCityRoadPricingModule.class); + + private RoadPricingScheme scheme; + + public MexicoCityRoadPricingModule() {} + + /* For the time being this has to be public, otherwise the roadpricing TollFactor + cannot be considered, rendering integration tests useless, JWJ Jan'20 */ + public MexicoCityRoadPricingModule(RoadPricingScheme scheme ) { + this.scheme = scheme; + } + + @Override + public void install() { + ConfigUtils.addOrGetModule(getConfig(), RoadPricingConfigGroup.class); + + log.warn("Installing scenario specific module {}. Make sure, this is what you want!", MexicoCityRoadPricingModule.class.getName()); + + // TODO sort out different ways to set toll schemes; reduce automagic + // TODO JWJ: is this still too "automagic"? + if ( scheme != null) { + // scheme has come in from the constructor, use that one: + bind(RoadPricingScheme.class).toInstance(scheme); + } else { + // no scheme has come in from the constructor, use a class that reads it from file: + bind(RoadPricingScheme.class).toProvider(RoadPricingSchemeProvider.class).in(Singleton.class); + } + // also add RoadPricingScheme as ScenarioElement. yyyy TODO might try to get rid of this; binding it is safer + // (My personal preference is actually to have it as scenario element ... since then it can be set before controler is even called. Which + // certainly makes more sense for a clean build sequence. kai, oct'19) + bind(RoadPricingInitializer.class).in( Singleton.class ); + + // add the toll to the routing disutility. also includes "randomizing": + addTravelDisutilityFactoryBinding(TransportMode.car).toProvider(TravelDisutilityIncludingTollFactoryProvider.class); + +// // specific re-routing strategy for area toll: +// // yyyy TODO could probably combine them somewhat + addPlanStrategyBinding("ReRouteAreaToll").toProvider(MexicoCityReRouteAreaToll.class); + addTravelDisutilityFactoryBinding( MexicoCityPlansCalcRouteWithTollOrNot.CAR_WITH_PAYED_AREA_TOLL ).toInstance(new RandomizingTimeDistanceTravelDisutilityFactory(TransportMode.car, getConfig()) ); + addRoutingModuleBinding( MexicoCityPlansCalcRouteWithTollOrNot.CAR_WITH_PAYED_AREA_TOLL ).toProvider(new MexicoCityRoadPricingNetworkRouting() ); + + // yyyy TODO It might be possible that the area stuff is adequately resolved by the randomizing approach. Would need to try + // that out. kai, sep'16 + + // this is what makes the mobsim compute tolls and generate money events + addControlerListenerBinding().to(MexicoCityRoadPricingControlerListener.class); + + } +} diff --git a/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingModuleDefaults.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingModuleDefaults.java new file mode 100644 index 0000000..3830110 --- /dev/null +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingModuleDefaults.java @@ -0,0 +1,139 @@ +/* + * *********************************************************************** * + * * project: org.matsim.* + * * RoadPricingModule.java + * * * + * * *********************************************************************** * + * * * + * * copyright : (C) 2015 by the members listed in the COPYING, * + * * LICENSE and WARRANTY file. * + * * email : info at matsim dot org * + * * * + * * *********************************************************************** * + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * See also COPYING, LICENSE and WARRANTY file * + * * * + * * *********************************************************************** + */ + +package org.matsim.run.MexicoCityRoadPricing; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import org.matsim.api.core.v01.Scenario; +import org.matsim.contrib.roadpricing.*; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.ControlerDefaults; +import org.matsim.core.controler.ControlerDefaultsModule; +import org.matsim.core.router.costcalculators.TravelDisutilityFactory; + +import java.net.URL; +import java.util.Collections; + +/** + * copied from org.matsim.contrib.roadpricing. + */ +final class MexicoCityRoadPricingModuleDefaults extends AbstractModule { + + private final RoadPricingScheme roadPricingScheme; + + MexicoCityRoadPricingModuleDefaults(RoadPricingScheme roadPricingScheme) { + this.roadPricingScheme = roadPricingScheme; + } + + @Override + public void install() { + // This is not optimal yet. Modules should not need to have parameters. + // But I am not quite sure yet how to best handle custom scenario elements. mz + + // use ControlerDefaults configuration, replacing the TravelDisutility with a toll-dependent one + install(AbstractModule.override(Collections.singletonList(new ControlerDefaultsModule()), new RoadPricingModule(roadPricingScheme))); + } + + + /** + * This class binding ensure that there is a SINGLE, consistent RoadPricingScheme in the Scenario. + */ + static class RoadPricingInitializer { + @Inject + RoadPricingInitializer(RoadPricingScheme roadPricingScheme, Scenario scenario) { + RoadPricingScheme scenarioRoadPricingScheme = (RoadPricingScheme) scenario.getScenarioElement(RoadPricingScheme.ELEMENT_NAME); + if (scenarioRoadPricingScheme == null) { + scenario.addScenarioElement(RoadPricingScheme.ELEMENT_NAME, roadPricingScheme); + } else { + if (roadPricingScheme != scenarioRoadPricingScheme) { + throw new RuntimeException("Trying to bind multiple, different RoadPricingSchemes (must be singleton)"); + } + } + } + } + + + /** + * Provides the {@link RoadPricingScheme} from either the given instance, or + * read from file using the file specified in the {@link RoadPricingConfigGroup}. + */ + static class RoadPricingSchemeProvider implements Provider { + + private final Config config; + private Scenario scenario; + + @Inject + RoadPricingSchemeProvider(Config config, Scenario scenario) { + /* TODO Check if we can get the Config from the Scenario */ + this.config = config; + this.scenario = scenario; + } + + @Override + public RoadPricingScheme get() { + RoadPricingScheme scenarioRoadPricingScheme = (RoadPricingScheme) scenario.getScenarioElement(RoadPricingScheme.ELEMENT_NAME); + if (scenarioRoadPricingScheme != null) { + return scenarioRoadPricingScheme; + } else { + RoadPricingConfigGroup rpConfig = ConfigUtils.addOrGetModule(config, RoadPricingConfigGroup.GROUP_NAME, RoadPricingConfigGroup.class); + + if (rpConfig.getTollLinksFile() == null) { + throw new RuntimeException("Road pricing inserted but neither toll links file nor RoadPricingScheme given. " + + "Such an execution path is not allowed. If you want a base case without toll, " + + "construct a zero toll file and insert that. "); + } + URL tollLinksFile = ConfigGroup.getInputFileURL(this.config.getContext(), rpConfig.getTollLinksFile()); + RoadPricingSchemeImpl rpsImpl = RoadPricingUtils.addOrGetMutableRoadPricingScheme(scenario ); + new RoadPricingReaderXMLv1(rpsImpl).parse(tollLinksFile); + return rpsImpl; + } + } + } + + + static class TravelDisutilityIncludingTollFactoryProvider implements Provider { + + private final Scenario scenario; + private final RoadPricingScheme scheme; + + @Inject + TravelDisutilityIncludingTollFactoryProvider(Scenario scenario, RoadPricingScheme scheme) { + this.scenario = scenario; + this.scheme = scheme; + } + + @Override + public TravelDisutilityFactory get() { + final Config config = scenario.getConfig(); + final TravelDisutilityFactory originalTravelDisutilityFactory = ControlerDefaults.createDefaultTravelDisutilityFactory(scenario); + RoadPricingTravelDisutilityFactory travelDisutilityFactory = new RoadPricingTravelDisutilityFactory( originalTravelDisutilityFactory, scheme, config ); + travelDisutilityFactory.setSigma(config.routing().getRoutingRandomness()); + return travelDisutilityFactory; + } + + } + +} diff --git a/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingNetworkRouting.java b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingNetworkRouting.java new file mode 100644 index 0000000..e37126f --- /dev/null +++ b/src/main/java/org/matsim/run/MexicoCityRoadPricing/MexicoCityRoadPricingNetworkRouting.java @@ -0,0 +1,106 @@ +package org.matsim.run.MexicoCityRoadPricing; + +import com.google.inject.name.Named; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.PopulationFactory; +import org.matsim.core.config.groups.RoutingConfigGroup; +import org.matsim.core.config.groups.ScoringConfigGroup; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.algorithms.TransportModeNetworkFilter; +import org.matsim.core.router.DefaultRoutingModules; +import org.matsim.core.router.MultimodalLinkChooser; +import org.matsim.core.router.RoutingModule; +import org.matsim.core.router.SingleModeNetworksCache; +import org.matsim.core.router.costcalculators.TravelDisutilityFactory; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.LeastCostPathCalculatorFactory; +import org.matsim.core.router.util.TravelDisutility; +import org.matsim.core.router.util.TravelTime; +import org.matsim.core.utils.timing.TimeInterpretation; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Custom router which routes on the "car" network, but uses a custom + * {@link TravelDisutility} which does *not* contain extra link cost. + * The *regular* "car" router gets a {@link TravelDisutility} which makes + * "car" prohibitively expensive, and {@link MexicoCityPlansCalcRouteWithTollOrNot} uses + * this setup to calculate a best response plan (with paid toll or not). + * + * TODO I'm sure this can be made easier and more flexible (michaz 2016) + * copied from org.matsim.contrib.roadpricing. + */ +class MexicoCityRoadPricingNetworkRouting implements Provider { + + @Inject + Map travelTimes; + + @Inject + Map travelDisutilityFactory; + + @Inject + SingleModeNetworksCache singleModeNetworksCache; + + @Inject + ScoringConfigGroup scoringConfigGroup; + + @Inject + RoutingConfigGroup routingConfigGroup; + + @Inject + Network network; + + @Inject + PopulationFactory populationFactory; + + @Inject + Scenario scenario ; + + @Inject + LeastCostPathCalculatorFactory leastCostPathCalculatorFactory; + + @Inject + @Named(TransportMode.walk) + RoutingModule walkRouter; + + @Inject + TimeInterpretation timeInterpretation; + + @Inject + MultimodalLinkChooser multimodalLinkChooser; + + private + Network filteredNetwork; + + @Override + public RoutingModule get() { + if (filteredNetwork == null) { + TransportModeNetworkFilter filter = new TransportModeNetworkFilter(network); + Set modes = new HashSet<>(); + modes.add(TransportMode.car); + filteredNetwork = NetworkUtils.createNetwork(scenario.getConfig().network()); + filter.filter(filteredNetwork, modes); + } + TravelDisutilityFactory travelDisutilityFactory = this.travelDisutilityFactory.get(MexicoCityPlansCalcRouteWithTollOrNot.CAR_WITH_PAYED_AREA_TOLL); + TravelTime travelTime = travelTimes.get(TransportMode.car); + LeastCostPathCalculator routeAlgo = + leastCostPathCalculatorFactory.createPathCalculator( + filteredNetwork, + travelDisutilityFactory.createTravelDisutility(travelTime), + travelTime); + if (!routingConfigGroup.getAccessEgressType().equals(RoutingConfigGroup.AccessEgressType.none)) { + return DefaultRoutingModules.createAccessEgressNetworkRouter(TransportMode.car, + routeAlgo, scenario, filteredNetwork, walkRouter, timeInterpretation, multimodalLinkChooser ); + } else { + return DefaultRoutingModules.createPureNetworkRouter(TransportMode.car, populationFactory, + filteredNetwork, routeAlgo); + } + // yy not so great that this differentiation is here; need to push it down a bit (again). kai, feb'2016 + } +} diff --git a/src/main/java/org/matsim/run/RunMexicoCityScenario.java b/src/main/java/org/matsim/run/RunMexicoCityScenario.java index 085719c..86a36a9 100644 --- a/src/main/java/org/matsim/run/RunMexicoCityScenario.java +++ b/src/main/java/org/matsim/run/RunMexicoCityScenario.java @@ -8,6 +8,7 @@ import org.matsim.analysis.MexicoCityMainModeIdentifier; import org.matsim.analysis.ModeChoiceCoverageControlerListener; import org.matsim.analysis.personMoney.PersonMoneyEventsAnalysisModule; +import org.matsim.analysis.roadpricing.RoadPricingAnalysis; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; @@ -38,6 +39,7 @@ import org.matsim.prepare.opt.RunCountOptimization; import org.matsim.prepare.opt.SelectPlansFromIndex; import org.matsim.prepare.population.*; +import org.matsim.run.MexicoCityRoadPricing.MexicoCityRoadPricingModule; import org.matsim.simwrapper.SimWrapperConfigGroup; import org.matsim.simwrapper.SimWrapperModule; import org.matsim.vehicles.VehicleType; @@ -60,7 +62,7 @@ SelectPlansFromIndex.class, SplitActivityTypesDuration.class, XYToLinks.class }) @MATSimApplication.Analysis({ - LinkStats.class, CheckPopulation.class, CheckPtNetwork.class + LinkStats.class, CheckPopulation.class, CheckPtNetwork.class, RoadPricingAnalysis.class }) public class RunMexicoCityScenario extends MATSimApplication { @@ -299,17 +301,16 @@ public void install() { } if (MexicoCityUtils.isDefined(RoadPricingOptions.roadPricingAreaPath)) { - install(new RoadPricingModule()); - // use own RoadPricingControlerListener, which throws person money events by multiplying the toll (factor) by the agent's income if (RoadPricingOptions.roadPricingType.equals(RoadPricingOptions.RoadPricingType.RELATIVE_TO_INCOME)) { if (!MexicoCityUtils.isDefined(incomeAreaPath)) { log.error("Path to shp file for income assignment is not given. Simulation will fail without income attributes. Please define the path to the shp as run param."); throw new NoSuchElementException(); } - addControlerListenerBinding().to(MexicoCityRoadPricingControlerListener.class); + install(new MexicoCityRoadPricingModule()); log.warn("Running road pricing scenario with a toll value of {}. Make sure, that this is a relative value.", RoadPricingOptions.toll); } else { + install(new RoadPricingModule()); log.warn("Running road pricing scenario with a toll value of {}. Make sure, that this is an absolute value.", RoadPricingOptions.toll); } }
+ * TODO I'm sure this can be made easier and more flexible (michaz 2016) + * copied from org.matsim.contrib.roadpricing. + */ +class MexicoCityRoadPricingNetworkRouting implements Provider { + + @Inject + Map travelTimes; + + @Inject + Map travelDisutilityFactory; + + @Inject + SingleModeNetworksCache singleModeNetworksCache; + + @Inject + ScoringConfigGroup scoringConfigGroup; + + @Inject + RoutingConfigGroup routingConfigGroup; + + @Inject + Network network; + + @Inject + PopulationFactory populationFactory; + + @Inject + Scenario scenario ; + + @Inject + LeastCostPathCalculatorFactory leastCostPathCalculatorFactory; + + @Inject + @Named(TransportMode.walk) + RoutingModule walkRouter; + + @Inject + TimeInterpretation timeInterpretation; + + @Inject + MultimodalLinkChooser multimodalLinkChooser; + + private + Network filteredNetwork; + + @Override + public RoutingModule get() { + if (filteredNetwork == null) { + TransportModeNetworkFilter filter = new TransportModeNetworkFilter(network); + Set modes = new HashSet<>(); + modes.add(TransportMode.car); + filteredNetwork = NetworkUtils.createNetwork(scenario.getConfig().network()); + filter.filter(filteredNetwork, modes); + } + TravelDisutilityFactory travelDisutilityFactory = this.travelDisutilityFactory.get(MexicoCityPlansCalcRouteWithTollOrNot.CAR_WITH_PAYED_AREA_TOLL); + TravelTime travelTime = travelTimes.get(TransportMode.car); + LeastCostPathCalculator routeAlgo = + leastCostPathCalculatorFactory.createPathCalculator( + filteredNetwork, + travelDisutilityFactory.createTravelDisutility(travelTime), + travelTime); + if (!routingConfigGroup.getAccessEgressType().equals(RoutingConfigGroup.AccessEgressType.none)) { + return DefaultRoutingModules.createAccessEgressNetworkRouter(TransportMode.car, + routeAlgo, scenario, filteredNetwork, walkRouter, timeInterpretation, multimodalLinkChooser ); + } else { + return DefaultRoutingModules.createPureNetworkRouter(TransportMode.car, populationFactory, + filteredNetwork, routeAlgo); + } + // yy not so great that this differentiation is here; need to push it down a bit (again). kai, feb'2016 + } +} diff --git a/src/main/java/org/matsim/run/RunMexicoCityScenario.java b/src/main/java/org/matsim/run/RunMexicoCityScenario.java index 085719c..86a36a9 100644 --- a/src/main/java/org/matsim/run/RunMexicoCityScenario.java +++ b/src/main/java/org/matsim/run/RunMexicoCityScenario.java @@ -8,6 +8,7 @@ import org.matsim.analysis.MexicoCityMainModeIdentifier; import org.matsim.analysis.ModeChoiceCoverageControlerListener; import org.matsim.analysis.personMoney.PersonMoneyEventsAnalysisModule; +import org.matsim.analysis.roadpricing.RoadPricingAnalysis; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; @@ -38,6 +39,7 @@ import org.matsim.prepare.opt.RunCountOptimization; import org.matsim.prepare.opt.SelectPlansFromIndex; import org.matsim.prepare.population.*; +import org.matsim.run.MexicoCityRoadPricing.MexicoCityRoadPricingModule; import org.matsim.simwrapper.SimWrapperConfigGroup; import org.matsim.simwrapper.SimWrapperModule; import org.matsim.vehicles.VehicleType; @@ -60,7 +62,7 @@ SelectPlansFromIndex.class, SplitActivityTypesDuration.class, XYToLinks.class }) @MATSimApplication.Analysis({ - LinkStats.class, CheckPopulation.class, CheckPtNetwork.class + LinkStats.class, CheckPopulation.class, CheckPtNetwork.class, RoadPricingAnalysis.class }) public class RunMexicoCityScenario extends MATSimApplication { @@ -299,17 +301,16 @@ public void install() { } if (MexicoCityUtils.isDefined(RoadPricingOptions.roadPricingAreaPath)) { - install(new RoadPricingModule()); - // use own RoadPricingControlerListener, which throws person money events by multiplying the toll (factor) by the agent's income if (RoadPricingOptions.roadPricingType.equals(RoadPricingOptions.RoadPricingType.RELATIVE_TO_INCOME)) { if (!MexicoCityUtils.isDefined(incomeAreaPath)) { log.error("Path to shp file for income assignment is not given. Simulation will fail without income attributes. Please define the path to the shp as run param."); throw new NoSuchElementException(); } - addControlerListenerBinding().to(MexicoCityRoadPricingControlerListener.class); + install(new MexicoCityRoadPricingModule()); log.warn("Running road pricing scenario with a toll value of {}. Make sure, that this is a relative value.", RoadPricingOptions.toll); } else { + install(new RoadPricingModule()); log.warn("Running road pricing scenario with a toll value of {}. Make sure, that this is an absolute value.", RoadPricingOptions.toll); } }