diff --git a/CHANGELOG.md b/CHANGELOG.md index e34aa958e..f3adaa375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ included in the (note yet determined) next version number. **Development version** +- Improve Emissions tools in order to handle unknown Osm highway tag values when mapping HBEFA road types +- add configurable policies for IDF - Introduce `travelTimeRecordingInterval` config option that decouples travel time writing from general analysis - Add eqasim_activities.csv for analysis - The cutters now take a GeoPackage file as an alterative to a ShapeFile diff --git a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegItem.java b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegItem.java index 96f2fb674..11de4d061 100644 --- a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegItem.java +++ b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegItem.java @@ -2,10 +2,7 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.population.Person; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitStopArea; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.pt.transitSchedule.api.*; public class PublicTransportLegItem { public Id personId; @@ -20,12 +17,13 @@ public class PublicTransportLegItem { public Id accessAreaId; public Id egressAreaId; + public Id departureId; public String transitMode; public PublicTransportLegItem(Id personId, int personTripId, int legIndex, Id accessStopId, Id egressStopId, Id transitLineId, - Id transitRouteId, Id accessAreaId, Id egressAreaId, + Id transitRouteId, Id accessAreaId, Id egressAreaId, Id departureId, String transitMode) { this.personId = personId; this.personTripId = personTripId; @@ -39,6 +37,7 @@ public PublicTransportLegItem(Id personId, int personTripId, int legInde this.accessAreaId = accessAreaId; this.egressAreaId = egressAreaId; + this.departureId = departureId; this.transitMode = transitMode; } diff --git a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegListener.java b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegListener.java index de3b30f84..99f5cf865 100644 --- a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegListener.java +++ b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegListener.java @@ -56,7 +56,8 @@ public void handleEvent(PublicTransitEvent event) { event.getTransitLineId(), // event.getTransitRouteId(), // accessAreaId, // - egressAreaId, // + egressAreaId, + event.getDepartureId(),// routeMode // )); } diff --git a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegReaderFromPopulation.java b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegReaderFromPopulation.java index fb6860823..b1513d7d5 100644 --- a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegReaderFromPopulation.java +++ b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegReaderFromPopulation.java @@ -69,7 +69,8 @@ public Collection getPublicTransportLegs(Person person) Id egressStopAreaId = this.transitSchedule.getFacilities().get(transitPassengerRoute.getEgressStopId()).getStopAreaId(); String mode = this.transitSchedule.getTransitLines().get(transitPassengerRoute.getLineId()).getRoutes().get(transitPassengerRoute.getRouteId()).getTransportMode(); - PublicTransportLegItem item = new PublicTransportLegItem(person.getId(), tripIndex, legIndex, transitPassengerRoute.getAccessStopId(), transitPassengerRoute.getEgressStopId(), transitPassengerRoute.getLineId(), transitPassengerRoute.getRouteId(), accessStopAreaId, egressStopAreaId, mode); + //Cannot read a departure ID from a population, as it is determined during MOBSim. We put a null value here. + PublicTransportLegItem item = new PublicTransportLegItem(person.getId(), tripIndex, legIndex, transitPassengerRoute.getAccessStopId(), transitPassengerRoute.getEgressStopId(), transitPassengerRoute.getLineId(), transitPassengerRoute.getRouteId(), accessStopAreaId, egressStopAreaId, null, mode); legItems.add(item); } return legItems; diff --git a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegWriter.java b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegWriter.java index 778c5f5a7..a6b681356 100644 --- a/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegWriter.java +++ b/core/src/main/java/org/eqasim/core/analysis/pt/PublicTransportLegWriter.java @@ -42,7 +42,8 @@ private String formatHeader() { "access_stop_id", // "egress_stop_id", // "transit_line_id", // - "transit_route_id", // + "transit_route_id", + "departure_id",// "access_area_id", // "egress_area_id", // "transit_mode" // @@ -57,7 +58,8 @@ private String formatTrip(PublicTransportLegItem trip) { trip.accessStopId.toString(), // trip.egressStopId.toString(), // trip.transitLineId.toString(), // - trip.transitRouteId.toString(), // + trip.transitRouteId.toString(), + trip.departureId == null ? "" : trip.departureId.toString(), // trip.accessAreaId == null ? "" : trip.accessAreaId.toString(), // trip.egressAreaId == null ? "" : trip.egressAreaId.toString(), // trip.transitMode // diff --git a/core/src/main/java/org/eqasim/core/components/emissions/RunComputeEmissionsEvents.java b/core/src/main/java/org/eqasim/core/components/emissions/RunComputeEmissionsEvents.java index a3aa06fdf..c7c9984b5 100644 --- a/core/src/main/java/org/eqasim/core/components/emissions/RunComputeEmissionsEvents.java +++ b/core/src/main/java/org/eqasim/core/components/emissions/RunComputeEmissionsEvents.java @@ -7,7 +7,6 @@ import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.contrib.emissions.EmissionModule; -import org.matsim.contrib.emissions.OsmHbefaMapping; import org.matsim.contrib.emissions.utils.EmissionsConfigGroup; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.config.CommandLine; @@ -60,7 +59,10 @@ public static void main(String[] args) throws CommandLine.ConfigurationException Scenario scenario = ScenarioUtils.createScenario(config); ScenarioUtils.loadScenario(scenario); - OsmHbefaMapping osmHbefaMapping = OsmHbefaMapping.build(); + // the default hbefa type is URB/Acess/30 but can be changed like this + // SafeOsmHbefaMapping.defaultType = "URB/Local/50"; + SafeOsmHbefaMapping osmHbefaMapping = new SafeOsmHbefaMapping(); + Network network = scenario.getNetwork(); // if the network is from pt2matsim it might not have "type" but "osm:way:highway" attribute instead for (Link link: network.getLinks().values()) { diff --git a/core/src/main/java/org/eqasim/core/components/emissions/SafeOsmHbefaMapping.java b/core/src/main/java/org/eqasim/core/components/emissions/SafeOsmHbefaMapping.java new file mode 100644 index 000000000..d779fbd2a --- /dev/null +++ b/core/src/main/java/org/eqasim/core/components/emissions/SafeOsmHbefaMapping.java @@ -0,0 +1,30 @@ +package org.eqasim.core.components.emissions; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.network.Link; +import org.matsim.contrib.emissions.HbefaRoadTypeMapping; +import org.matsim.contrib.emissions.OsmHbefaMapping; +import org.matsim.core.network.NetworkUtils; + +// This class is designed to wrap the matsim-libs emissions contrib OsmHbefaMapping by providing a default hbefa type for unknown osm keys (instead of throwing a RuntimeException +public class SafeOsmHbefaMapping extends HbefaRoadTypeMapping { + + private final static OsmHbefaMapping osmHbefaMapping = OsmHbefaMapping.build(); + private final static Logger log = LogManager.getLogger(SafeOsmHbefaMapping.class); + public static String defaultType = "URB/Access/30"; + + @Override + public String determineHbefaType(Link link) { + String result; + try { + result = osmHbefaMapping.determineHbefaType(link); + } catch (RuntimeException runtimeException) { + String type = (String) link.getAttributes().getAttribute(NetworkUtils.TYPE); + log.warn("'" + type + "' not in hbefa map; setting to " + defaultType); + result = defaultType; + } + return result; + } + +} diff --git a/core/src/main/java/org/eqasim/core/components/transit/EqasimTransitEngine.java b/core/src/main/java/org/eqasim/core/components/transit/EqasimTransitEngine.java index 46c39510a..fff6c2f24 100644 --- a/core/src/main/java/org/eqasim/core/components/transit/EqasimTransitEngine.java +++ b/core/src/main/java/org/eqasim/core/components/transit/EqasimTransitEngine.java @@ -110,7 +110,7 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id departure Id arrivalLinkId = transitSchedule.getFacilities().get(route.getEgressStopId()).getLinkId(); PublicTransitEvent transitEvent = new PublicTransitEvent(arrivalTime, agent.getId(), - transitLine.getId(), transitRoute.getId(), route.getAccessStopId(), route.getEgressStopId(), + transitLine.getId(), transitRoute.getId(), route.getAccessStopId(), route.getEgressStopId(), stopDeparture.departure.getId(), vehicleDepartureTime, route.getDistance()); internalInterface.registerAdditionalAgentOnLink(agent); diff --git a/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEvent.java b/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEvent.java index ebb9ea369..77af56ada 100644 --- a/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEvent.java +++ b/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEvent.java @@ -6,6 +6,7 @@ import org.matsim.api.core.v01.events.GenericEvent; import org.matsim.api.core.v01.events.HasPersonId; import org.matsim.api.core.v01.population.Person; +import org.matsim.pt.transitSchedule.api.Departure; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.pt.transitSchedule.api.TransitStopFacility; @@ -18,11 +19,12 @@ public class PublicTransitEvent extends GenericEvent implements HasPersonId { final private Id transitRouteId; final private Id accessStopId; final private Id egressStopId; + final private Id departureId; final private double vehicleDepartureTime; final private double travelDistance; public PublicTransitEvent(double arrivalTime, Id personId, Id transitLineId, - Id transitRouteId, Id accessStopId, Id egressStopId, + Id transitRouteId, Id accessStopId, Id egressStopId, Id departureId, double vehicleDepartureTime, double travelDistance) { super(TYPE, arrivalTime); @@ -31,13 +33,14 @@ public PublicTransitEvent(double arrivalTime, Id personId, Id getEgressStopId() { return egressStopId; } + public Id getDepartureId() { + return departureId; + } + public double getVehicleDepartureTime() { return vehicleDepartureTime; } @@ -83,6 +90,7 @@ public Map getAttributes() { attributes.put("route", transitRouteId.toString()); attributes.put("accessStop", accessStopId.toString()); attributes.put("egressStop", egressStopId.toString()); + attributes.put("departure", departureId.toString()); attributes.put("vehicleDepartureTime", String.valueOf(vehicleDepartureTime)); attributes.put("travelDistance", String.valueOf(travelDistance)); return attributes; @@ -98,9 +106,10 @@ public static PublicTransitEvent convert(GenericEvent genericEvent) { Id transitRouteId = Id.create(attributes.get("route"), TransitRoute.class); Id accessStopId = Id.create(attributes.get("accessStop"), TransitStopFacility.class); Id egressStopId = Id.create(attributes.get("egressStop"), TransitStopFacility.class); + Id departureId = Id.create(attributes.get("departure"), Departure.class); double vehicleDepartureTime = Double.parseDouble(attributes.get("vehicleDepartureTime")); double travelDistance = Double.parseDouble(attributes.get("travelDistance")); - return new PublicTransitEvent(genericEvent.getTime(), personId, transitLineId, transitRouteId, accessStopId, egressStopId, vehicleDepartureTime, travelDistance); + return new PublicTransitEvent(genericEvent.getTime(), personId, transitLineId, transitRouteId, accessStopId, egressStopId, departureId, vehicleDepartureTime, travelDistance); } } diff --git a/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEventMapper.java b/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEventMapper.java index 6ade10901..9c7673f4a 100644 --- a/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEventMapper.java +++ b/core/src/main/java/org/eqasim/core/components/transit/events/PublicTransitEventMapper.java @@ -4,6 +4,7 @@ import org.matsim.api.core.v01.events.GenericEvent; import org.matsim.api.core.v01.population.Person; import org.matsim.core.events.MatsimEventsReader.CustomEventMapper; +import org.matsim.pt.transitSchedule.api.Departure; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.pt.transitSchedule.api.TransitStopFacility; @@ -19,10 +20,11 @@ public PublicTransitEvent apply(GenericEvent event) { TransitStopFacility.class); Id egressStopId = Id.create(event.getAttributes().get("egressStop"), TransitStopFacility.class); + Id departureId = Id.create(event.getAttributes().get("departure"), Departure.class); double vehicleDepartureTime = Double.parseDouble(event.getAttributes().get("vehicleDepartureTime")); double travelDistance = Double.parseDouble(event.getAttributes().get("travelDistance")); - return new PublicTransitEvent(arrivalTime, personId, transitLineId, transitRouteId, accessStopId, egressStopId, + return new PublicTransitEvent(arrivalTime, personId, transitLineId, transitRouteId, accessStopId, egressStopId, departureId, vehicleDepartureTime, travelDistance); } } diff --git a/core/src/main/java/org/eqasim/core/scenario/RunInsertVehicles.java b/core/src/main/java/org/eqasim/core/scenario/RunInsertVehicles.java index d5f5f16bc..92669dc2b 100644 --- a/core/src/main/java/org/eqasim/core/scenario/RunInsertVehicles.java +++ b/core/src/main/java/org/eqasim/core/scenario/RunInsertVehicles.java @@ -23,6 +23,7 @@ static public void insertVehicles(Config config, Scenario scenario) { VehiclesFactory factory = vehicles.getFactory(); VehicleType vehicleType = VehicleUtils.createVehicleType(Id.create("defaultVehicleType", VehicleType.class)); + vehicleType.setNetworkMode("car"); vehicles.addVehicleType(vehicleType); for (Person person : scenario.getPopulation().getPersons().values()) { Map> personVehicles = new HashMap<>(); diff --git a/core/src/main/java/org/eqasim/core/tools/ExportTransitLinesToShapefile.java b/core/src/main/java/org/eqasim/core/tools/ExportTransitLinesToShapefile.java index e08622b41..fc2b9542b 100644 --- a/core/src/main/java/org/eqasim/core/tools/ExportTransitLinesToShapefile.java +++ b/core/src/main/java/org/eqasim/core/tools/ExportTransitLinesToShapefile.java @@ -1,11 +1,13 @@ package org.eqasim.core.tools; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.locationtech.jts.geom.Coordinate; +import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.IdSet; import org.matsim.api.core.v01.Scenario; @@ -20,9 +22,7 @@ import org.matsim.core.utils.geometry.geotools.MGC; import org.matsim.core.utils.gis.PolylineFeatureFactory; import org.matsim.core.utils.gis.ShapeFileWriter; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitScheduleReader; +import org.matsim.pt.transitSchedule.api.*; import org.geotools.api.feature.simple.SimpleFeature; public class ExportTransitLinesToShapefile { @@ -84,25 +84,34 @@ public static void main(String[] args) throws Exception { continue; } NetworkRoute networkRoute = transitRoute.getRoute(); - List links = new ArrayList<>(networkRoute.getLinkIds().size() + 2); - links.add(network.getLinks().get(networkRoute.getStartLinkId())); - networkRoute.getLinkIds().forEach(id -> links.add(network.getLinks().get(id))); - links.add(network.getLinks().get(networkRoute.getEndLinkId())); - - Coordinate[] coordinates = new Coordinate[links.size() + 1]; - - for (int i = 0; i < links.size(); i++) { - Link link = links.get(i); - - if (i == 0) { - coordinates[i] = new Coordinate(link.getFromNode().getCoord().getX(), - link.getFromNode().getCoord().getY()); + Coordinate[] coordinates; + if(networkRoute == null) { + Function coordToCoordinate = coord -> new Coordinate(coord.getX(), coord.getY()); + coordinates = transitRoute.getStops().stream() + .map(TransitRouteStop::getStopFacility) + .map(TransitStopFacility::getCoord) + .map(coordToCoordinate) + .toArray(Coordinate[]::new); + } else { + List links = new ArrayList<>(networkRoute.getLinkIds().size() + 2); + links.add(network.getLinks().get(networkRoute.getStartLinkId())); + networkRoute.getLinkIds().forEach(id -> links.add(network.getLinks().get(id))); + links.add(network.getLinks().get(networkRoute.getEndLinkId())); + + coordinates = new Coordinate[links.size() + 1]; + + for (int i = 0; i < links.size(); i++) { + Link link = links.get(i); + + if (i == 0) { + coordinates[i] = new Coordinate(link.getFromNode().getCoord().getX(), + link.getFromNode().getCoord().getY()); + } + + coordinates[i + 1] = new Coordinate(link.getToNode().getCoord().getX(), + link.getToNode().getCoord().getY()); } - - coordinates[i + 1] = new Coordinate(link.getToNode().getCoord().getX(), - link.getToNode().getCoord().getY()); } - SimpleFeature feature = linkFactory.createPolyline( // coordinates, // new Object[] { // @@ -112,7 +121,6 @@ public static void main(String[] args) throws Exception { transitRoute.getTransportMode(), // transitRoute.getDescription() // }, null); - features.add(feature); } } diff --git a/core/src/main/resources/melun/network.xml.gz b/core/src/main/resources/melun/network.xml.gz index f5f7a50cd..48f307365 100644 Binary files a/core/src/main/resources/melun/network.xml.gz and b/core/src/main/resources/melun/network.xml.gz differ diff --git a/core/src/test/java/org/eqasim/TestEmissions.java b/core/src/test/java/org/eqasim/TestEmissions.java index 7ce129995..9486550d9 100644 --- a/core/src/test/java/org/eqasim/TestEmissions.java +++ b/core/src/test/java/org/eqasim/TestEmissions.java @@ -12,16 +12,13 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.zip.GZIPInputStream; import org.apache.commons.io.FileUtils; import org.eqasim.core.components.emissions.RunComputeEmissionsEvents; import org.eqasim.core.components.emissions.RunExportEmissionsNetwork; +import org.eqasim.core.components.emissions.SafeOsmHbefaMapping; import org.eqasim.core.simulation.EqasimConfigurator; import org.eqasim.core.simulation.analysis.EqasimAnalysisModule; import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; @@ -143,8 +140,16 @@ private void runModifyNetwork() { Scenario scenario = ScenarioUtils.loadScenario(config); Network network = scenario.getNetwork(); for (Link link : network.getLinks().values()) { + // this forces the OSM Mapping code to use URB/Local/50 as it the only thing we // have in the sample HBEFA. + // We intentionally leave the 'pedestrian' links here to test the SafeOsmHbefaMapping class + String linkType = NetworkUtils.getType(link); + if (linkType != null) { + if (!linkType.equals("pedestrian")) { + continue; + } + } NetworkUtils.setType(link, "tertiary"); link.getAttributes().putAttribute(NetworkUtils.ALLOWED_SPEED, 50 / 3.6); } @@ -159,6 +164,8 @@ private void runMelunEmissions() throws CommandLine.ConfigurationException, IOEx Assert.assertEquals(3412, (long) counts.getOrDefault("bike", 0L)); Assert.assertEquals(2108, (long) counts.get("pt")); + SafeOsmHbefaMapping.defaultType = "URB/Loca/50"; + RunComputeEmissionsEvents.main(new String[] { "--config-path", "melun_test/input/config.xml", "--hbefa-cold-avg", "sample_41_EFA_ColdStart_vehcat_2020average.csv", "--hbefa-hot-avg", "sample_41_EFA_HOT_vehcat_2020average.csv", "--hbefa-cold-detailed", diff --git a/core/src/test/java/org/eqasim/TestSimulationPipeline.java b/core/src/test/java/org/eqasim/TestSimulationPipeline.java index e8f75d857..bc9477ba3 100644 --- a/core/src/test/java/org/eqasim/TestSimulationPipeline.java +++ b/core/src/test/java/org/eqasim/TestSimulationPipeline.java @@ -3,13 +3,11 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.eqasim.core.analysis.run.RunLegAnalysis; import org.eqasim.core.analysis.run.RunPublicTransportLegAnalysis; import org.eqasim.core.analysis.run.RunTripAnalysis; @@ -36,18 +34,13 @@ import org.eqasim.core.simulation.vdf.utils.AdaptConfigForVDF; import org.eqasim.core.standalone_mode_choice.RunStandaloneModeChoice; import org.eqasim.core.standalone_mode_choice.StandaloneModeChoiceConfigurator; -import org.eqasim.core.tools.ExportActivitiesToShapefile; -import org.eqasim.core.tools.ExportNetworkRoutesToGeopackage; -import org.eqasim.core.tools.ExportNetworkToShapefile; -import org.eqasim.core.tools.ExportPopulationToCSV; -import org.eqasim.core.tools.ExportTransitLinesToShapefile; -import org.eqasim.core.tools.ExportTransitStopsToShapefile; +import org.eqasim.core.tools.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; -import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.*; import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; import org.matsim.contribs.discrete_mode_choice.model.mode_availability.ModeAvailability; import org.matsim.core.config.CommandLine; @@ -57,6 +50,7 @@ import org.matsim.core.controler.AbstractModule; import org.matsim.core.controler.Controler; import org.matsim.core.scenario.ScenarioUtils; + import org.matsim.core.utils.misc.CRCChecksum; import com.google.inject.Inject; @@ -75,7 +69,7 @@ public void setUp() throws IOException { @After public void tearDown() throws IOException { - FileUtils.deleteDirectory(new File("melun_test")); + //FileUtils.deleteDirectory(new File("melun_test")); } private void runMelunSimulation(String configPath, String outputPath) { @@ -83,8 +77,13 @@ private void runMelunSimulation(String configPath, String outputPath) { } private void runMelunSimulation(String configPath, String outputPath, String inputPlansFile, Integer lastIteration) { - EqasimConfigurator eqasimConfigurator = new EqasimConfigurator(); Config config = ConfigUtils.loadConfig(configPath); + runMelunSimulation(config, outputPath, inputPlansFile, lastIteration); + } + + private void runMelunSimulation(Config config, String outputPath, String inputPlansFile, Integer lastIteration) { + EqasimConfigurator eqasimConfigurator = new EqasimConfigurator(); + eqasimConfigurator.updateConfig(config); ((ControllerConfigGroup) config.getModules().get(ControllerConfigGroup.GROUP_NAME)).setOutputDirectory(outputPath); if(inputPlansFile != null) { @@ -441,11 +440,32 @@ public void testPipeline() throws Exception { runCutterV2(); } + @Test + public void testBaseDeterminism() throws Exception { + Logger logger = LogManager.getLogger(TestSimulationPipeline.class); + Config config = ConfigUtils.loadConfig("melun_test/input/config.xml"); + runMelunSimulation(config, "melun_test/output_determinism_1", null, 2); + + config = ConfigUtils.loadConfig("melun_test/input/config.xml"); + runMelunSimulation(config, "melun_test/output_determinism_2", null, 2 ); + + for(String comparedFile: new String[]{"output_plans.xml.gz"}) { + long firstCrc = CRCChecksum.getCRCFromFile("melun_test/output_determinism_1/" + comparedFile); + long secondCrc = CRCChecksum.getCRCFromFile("melun_test/output_determinism_2/"+comparedFile); + assert firstCrc == secondCrc; + } + } + public void runPopulationRouting() throws CommandLine.ConfigurationException, IOException, InterruptedException { RunPopulationRouting.main(new String[] { "--config-path", "melun_test/input/config.xml", - "--output-path", "melun_test/output/routed_population.xml.gz" + "--output-path", "melun_test/output/routed_population.xml" + }); + RunPopulationRouting.main(new String[] { + "--config-path", "melun_test/input/config.xml", + "--output-path", "melun_test/output/routed_population_again.xml" }); + assert CRCChecksum.getCRCFromFile("melun_test/output/routed_population.xml") == CRCChecksum.getCRCFromFile("melun_test/output/routed_population_again.xml"); } public void runStandaloneModeChoice() throws CommandLine.ConfigurationException, IOException, InterruptedException { diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java index 788f9b82d..db1a5f5cc 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java @@ -1,7 +1,12 @@ package org.eqasim.ile_de_france; import org.eqasim.core.simulation.EqasimConfigurator; +import org.eqasim.ile_de_france.policies.PoliciesConfigGroup; public class IDFConfigurator extends EqasimConfigurator { + public IDFConfigurator() { + super(); + registerConfigGroup(new PoliciesConfigGroup(), true); + } } diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java index 5ea5614c7..f5234accb 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java @@ -4,6 +4,7 @@ import org.eqasim.core.simulation.analysis.EqasimAnalysisModule; import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule; import org.eqasim.ile_de_france.mode_choice.IDFModeChoiceModule; +import org.eqasim.ile_de_france.policies.PolicyExtension; import org.matsim.api.core.v01.Scenario; import org.matsim.core.config.CommandLine; import org.matsim.core.config.CommandLine.ConfigurationException; @@ -25,6 +26,9 @@ static public void main(String[] args) throws ConfigurationException { cmd.applyConfiguration(config); VehiclesValidator.validate(config); + PolicyExtension policies = new PolicyExtension(); + policies.adaptConfiguration(config); + Scenario scenario = ScenarioUtils.createScenario(config); configurator.configureScenario(scenario); ScenarioUtils.loadScenario(scenario); @@ -35,6 +39,7 @@ static public void main(String[] args) throws ConfigurationException { controller.addOverridingModule(new EqasimAnalysisModule()); controller.addOverridingModule(new EqasimModeChoiceModule()); controller.addOverridingModule(new IDFModeChoiceModule(cmd)); + controller.addOverridingModule(policies); controller.run(); } } \ No newline at end of file diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/DefaultPolicy.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/DefaultPolicy.java new file mode 100644 index 000000000..9772a4375 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/DefaultPolicy.java @@ -0,0 +1,25 @@ +package org.eqasim.ile_de_france.policies; + +import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty; +import org.eqasim.ile_de_france.policies.routing.RoutingPenalty; + +public class DefaultPolicy implements Policy { + private final RoutingPenalty routingPenalty; + private final UtilityPenalty utilityPenalty; + + public DefaultPolicy(RoutingPenalty routingPenalty, UtilityPenalty utilityPenalty) { + this.routingPenalty = routingPenalty; + this.utilityPenalty = utilityPenalty; + } + + @Override + public RoutingPenalty getRoutingPenalty() { + return routingPenalty; + } + + @Override + public UtilityPenalty getUtilityPenalty() { + return utilityPenalty; + } + +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PoliciesConfigGroup.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PoliciesConfigGroup.java new file mode 100644 index 000000000..cb521df66 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PoliciesConfigGroup.java @@ -0,0 +1,37 @@ +package org.eqasim.ile_de_france.policies; + +import org.eqasim.ile_de_france.policies.city_tax.CityTaxConfigGroup; +import org.eqasim.ile_de_france.policies.city_tax.CityTaxPolicyFactory; +import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZoneConfigGroup; +import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZonePolicyFactory; +import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountConfigGroup; +import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountPolicyFactory; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.config.ReflectiveConfigGroup; + +public class PoliciesConfigGroup extends ReflectiveConfigGroup { + static public final String CONFIG_NAME = "eqasim:policies"; + + public PoliciesConfigGroup() { + super(CONFIG_NAME); + } + + @Override + public ConfigGroup createParameterSet(String type) { + switch (type) { + case CityTaxPolicyFactory.POLICY_NAME: + return new CityTaxConfigGroup(); + case LimitedTrafficZonePolicyFactory.POLICY_NAME: + return new LimitedTrafficZoneConfigGroup(); + case TransitDiscountPolicyFactory.POLICY_NAME: + return new TransitDiscountConfigGroup(); + default: + throw new IllegalStateException(); + } + } + + static public PoliciesConfigGroup get(Config config) { + return (PoliciesConfigGroup) config.getModules().get(CONFIG_NAME); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/Policy.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/Policy.java new file mode 100644 index 000000000..969f55aa1 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/Policy.java @@ -0,0 +1,10 @@ +package org.eqasim.ile_de_france.policies; + +import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty; +import org.eqasim.ile_de_france.policies.routing.RoutingPenalty; + +public interface Policy { + RoutingPenalty getRoutingPenalty(); + + UtilityPenalty getUtilityPenalty(); +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyConfigGroup.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyConfigGroup.java new file mode 100644 index 000000000..3b9728d31 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyConfigGroup.java @@ -0,0 +1,18 @@ +package org.eqasim.ile_de_france.policies; + +import org.matsim.core.config.ReflectiveConfigGroup; + +public abstract class PolicyConfigGroup extends ReflectiveConfigGroup { + protected PolicyConfigGroup(String name) { + super(name); + } + + @Parameter + public String policyName; + + @Parameter + public boolean active = true; + + @Parameter + public String personFilter; +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyExtension.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyExtension.java new file mode 100644 index 000000000..ca1465110 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyExtension.java @@ -0,0 +1,167 @@ +package org.eqasim.ile_de_france.policies; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eqasim.core.components.config.EqasimConfigGroup; +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator; +import org.eqasim.ile_de_france.policies.city_tax.CityTaxPolicyExtension; +import org.eqasim.ile_de_france.policies.city_tax.CityTaxPolicyFactory; +import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZonePolicyExtension; +import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZonePolicyFactory; +import org.eqasim.ile_de_france.policies.mode_choice.PolicyUtilityEstimator; +import org.eqasim.ile_de_france.policies.mode_choice.SumUtilityPenalty; +import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty; +import org.eqasim.ile_de_france.policies.routing.PolicyTravelDisutilityFactory; +import org.eqasim.ile_de_france.policies.routing.RoutingPenalty; +import org.eqasim.ile_de_france.policies.routing.SumRoutingPenalty; +import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountPolicyExtension; +import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountPolicyFactory; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.population.Population; +import org.matsim.core.config.Config; +import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutilityFactory; + +import com.google.common.base.Verify; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.multibindings.MapBinder; +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +public class PolicyExtension extends AbstractEqasimExtension { + private final static String ESTIMATOR_PREFIX = "policy:"; + + private String delegateCarEstimator; + private String delegateTransitEstimator; + + public void adaptConfiguration(Config config) { + EqasimConfigGroup eqasimConfig = EqasimConfigGroup.get(config); + + delegateCarEstimator = eqasimConfig.getEstimators().get(TransportMode.car); + delegateTransitEstimator = eqasimConfig.getEstimators().get(TransportMode.pt); + + delegateCarEstimator = delegateCarEstimator.replace(ESTIMATOR_PREFIX, ""); + delegateTransitEstimator = delegateTransitEstimator.replace(ESTIMATOR_PREFIX, ""); + + eqasimConfig.setEstimator(TransportMode.car, ESTIMATOR_PREFIX + delegateCarEstimator); + eqasimConfig.setEstimator(TransportMode.pt, ESTIMATOR_PREFIX + delegateTransitEstimator); + } + + @Override + protected void installEqasimExtension() { + Verify.verifyNotNull(delegateCarEstimator, "Need to run PolicyExtension.adaptConfiguration first"); + Verify.verifyNotNull(delegateTransitEstimator, "Need to run PolicyExtension.adaptConfiguration first"); + + // set up travel disutility for routing + addTravelDisutilityFactoryBinding(TransportMode.car).to(PolicyTravelDisutilityFactory.class); + addTravelDisutilityFactoryBinding("car_passenger").to(OnlyTimeDependentTravelDisutilityFactory.class); + + install(new CityTaxPolicyExtension()); + install(new LimitedTrafficZonePolicyExtension()); + install(new TransitDiscountPolicyExtension()); + + var policyBinder = MapBinder.newMapBinder(binder(), String.class, PolicyFactory.class); + policyBinder.addBinding(CityTaxPolicyFactory.POLICY_NAME).to(CityTaxPolicyFactory.class); + policyBinder.addBinding(LimitedTrafficZonePolicyFactory.POLICY_NAME).to(LimitedTrafficZonePolicyFactory.class); + policyBinder.addBinding(TransitDiscountPolicyFactory.POLICY_NAME).to(TransitDiscountPolicyFactory.class); + + bindUtilityEstimator(ESTIMATOR_PREFIX + delegateCarEstimator) + .to(Key.get(PolicyUtilityEstimator.class, Names.named(TransportMode.car))); + + bindUtilityEstimator(ESTIMATOR_PREFIX + delegateTransitEstimator) + .to(Key.get(PolicyUtilityEstimator.class, Names.named(TransportMode.pt))); + } + + @Provides + @Singleton + Map providePolicies(Map factories, Population population) { + PoliciesConfigGroup policyConfig = PoliciesConfigGroup.get(getConfig()); + Map policies = new HashMap<>(); + + Set names = new HashSet<>(); + + if (policyConfig != null) { + for (var collection : policyConfig.getParameterSets().values()) { + for (var raw : collection) { + PolicyConfigGroup policy = (PolicyConfigGroup) raw; + + if (policy.active) { + Verify.verify(policy.policyName != null && policy.policyName.length() > 0, + "Policy names must be set"); + + if (!names.add(policy.policyName)) { + throw new IllegalStateException("Duplicate policy name: " + policy.policyName); + } + + PolicyPersonFilter filter = PolicyPersonFilter.create(population, policy); + + policies.put(policy.policyName, + factories.get(policy.getName()).createPolicy(policy.policyName, filter)); + } + } + } + } + + return policies; + } + + @Provides + @Singleton + PolicyTravelDisutilityFactory providePolicyTravelDisutilityFactory(RoutingPenalty linkPenalty) { + return new PolicyTravelDisutilityFactory(linkPenalty); + } + + @Provides + @Named(TransportMode.car) + PolicyUtilityEstimator providePolicyUtilityEstimatorForCar(Map> providers, + UtilityPenalty penalty) { + UtilityEstimator delegate = providers.get(delegateCarEstimator).get(); + return new PolicyUtilityEstimator(delegate, penalty, TransportMode.car); + } + + @Provides + @Named(TransportMode.pt) + PolicyUtilityEstimator providePolicyUtilityEstimatorForTransit(Map> providers, + UtilityPenalty penalty) { + UtilityEstimator delegate = providers.get(delegateTransitEstimator).get(); + return new PolicyUtilityEstimator(delegate, penalty, TransportMode.pt); + } + + @Provides + UtilityPenalty provideUtilityPenalty(Map policies) { + List penalties = new LinkedList<>(); + + for (Policy policy : policies.values()) { + UtilityPenalty penalty = policy.getUtilityPenalty(); + + if (penalty != null) { + penalties.add(penalty); + } + } + + return new SumUtilityPenalty(penalties); + } + + @Provides + RoutingPenalty provideRoutingPenalty(Map policies) { + List penalties = new LinkedList<>(); + + for (Policy policy : policies.values()) { + RoutingPenalty penalty = policy.getRoutingPenalty(); + + if (penalty != null) { + penalties.add(penalty); + } + } + + return new SumRoutingPenalty(penalties); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyFactory.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyFactory.java new file mode 100644 index 000000000..691a3a103 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyFactory.java @@ -0,0 +1,5 @@ +package org.eqasim.ile_de_france.policies; + +public interface PolicyFactory { + Policy createPolicy(String name, PolicyPersonFilter personFilter); +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyPersonFilter.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyPersonFilter.java new file mode 100644 index 000000000..60ae6b7d3 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/PolicyPersonFilter.java @@ -0,0 +1,36 @@ +package org.eqasim.ile_de_france.policies; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Population; + +public class PolicyPersonFilter { + private final IdSet selection; + + PolicyPersonFilter(IdSet selection) { + this.selection = selection; + } + + public boolean applies(Id personId) { + return selection == null ? true : selection.contains(personId); + } + + static public PolicyPersonFilter create(Population population, PolicyConfigGroup policy) { + if (policy.personFilter != null && policy.personFilter.length() > 0) { + IdSet selection = new IdSet<>(Person.class); + + for (Person person : population.getPersons().values()) { + Boolean indicator = (Boolean) person.getAttributes().getAttribute(policy.personFilter); + + if (indicator != null && indicator) { + selection.add(person.getId()); + } + } + + return new PolicyPersonFilter(selection); + } else { + return new PolicyPersonFilter(null); + } + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxConfigGroup.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxConfigGroup.java new file mode 100644 index 000000000..5c72523f0 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxConfigGroup.java @@ -0,0 +1,16 @@ +package org.eqasim.ile_de_france.policies.city_tax; + +import org.eqasim.ile_de_france.policies.PolicyConfigGroup; +import org.matsim.core.config.ReflectiveConfigGroup.Parameter; + +public class CityTaxConfigGroup extends PolicyConfigGroup { + public CityTaxConfigGroup() { + super(CityTaxPolicyFactory.POLICY_NAME); + } + + @Parameter + public double tax_EUR = 0.0; + + @Parameter + public String perimetersPath; +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxPolicyExtension.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxPolicyExtension.java new file mode 100644 index 000000000..208cbf6d3 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxPolicyExtension.java @@ -0,0 +1,20 @@ +package org.eqasim.ile_de_france.policies.city_tax; + +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.matsim.api.core.v01.network.Network; + +import com.google.inject.Provides; +import com.google.inject.Singleton; + +public class CityTaxPolicyExtension extends AbstractEqasimExtension { + @Override + protected void installEqasimExtension() { + } + + @Provides + @Singleton + CityTaxPolicyFactory provideCityTaxPolicyFactory(Network network, IDFModeParameters modeParameters) { + return new CityTaxPolicyFactory(getConfig(), network, modeParameters); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxPolicyFactory.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxPolicyFactory.java new file mode 100644 index 000000000..9102d50a0 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxPolicyFactory.java @@ -0,0 +1,73 @@ +package org.eqasim.ile_de_france.policies.city_tax; + +import java.io.File; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.eqasim.ile_de_france.policies.DefaultPolicy; +import org.eqasim.ile_de_france.policies.PoliciesConfigGroup; +import org.eqasim.ile_de_france.policies.Policy; +import org.eqasim.ile_de_france.policies.PolicyFactory; +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.eqasim.ile_de_france.policies.routing.FixedRoutingPenalty; +import org.eqasim.ile_de_france.policies.routing.PolicyLinkFinder; +import org.eqasim.ile_de_france.policies.routing.PolicyLinkFinder.Predicate; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; + +public class CityTaxPolicyFactory implements PolicyFactory { + private static final Logger logger = LogManager.getLogger(CityTaxPolicyFactory.class); + + static public final String POLICY_NAME = "cityTax"; + + private final Config config; + private final Network network; + private final IDFModeParameters modeParameters; + + public CityTaxPolicyFactory(Config config, Network network, IDFModeParameters modeParameters) { + this.config = config; + this.network = network; + this.modeParameters = modeParameters; + } + + @Override + public Policy createPolicy(String name, PolicyPersonFilter personFilter) { + for (ConfigGroup item : PoliciesConfigGroup.get(config).getParameterSets(CityTaxPolicyFactory.POLICY_NAME)) { + CityTaxConfigGroup policyItem = (CityTaxConfigGroup) item; + + if (policyItem.policyName.equals(name)) { + return createPolicy(policyItem, personFilter); + } + } + + throw new IllegalStateException( + "Configuration not found for policy " + name + " of type " + CityTaxPolicyFactory.POLICY_NAME); + } + + private Policy createPolicy(CityTaxConfigGroup enterConfig, PolicyPersonFilter personFilter) { + logger.info("Creating policy " + enterConfig.policyName + " of type " + CityTaxPolicyFactory.POLICY_NAME); + logger.info(" Perimeters: " + enterConfig.perimetersPath); + logger.info(" Tax level: " + enterConfig.tax_EUR + " EUR"); + + IdSet linkIds = PolicyLinkFinder + .create(new File( + ConfigGroup.getInputFileURL(config.getContext(), enterConfig.perimetersPath).getPath())) + .findLinks(network, Predicate.Entering); + + logger.info(" Affected entering links: " + linkIds.size()); + + return new DefaultPolicy( + new FixedRoutingPenalty(linkIds, calculateEnterTaxPenalty(enterConfig.tax_EUR, modeParameters), personFilter), + new CityTaxUtilityPenalty(linkIds, modeParameters, enterConfig.tax_EUR, personFilter)); + } + + private double calculateEnterTaxPenalty(double enterTax_EUR, IDFModeParameters parameters) { + double penalty_u = enterTax_EUR * parameters.betaCost_u_MU; + double penalty_min = penalty_u / parameters.car.betaTravelTime_u_min; + return penalty_min * 60.0; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxUtilityPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxUtilityPenalty.java new file mode 100644 index 000000000..72fab899b --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/city_tax/CityTaxUtilityPenalty.java @@ -0,0 +1,59 @@ +package org.eqasim.ile_de_france.policies.city_tax; + +import java.util.List; + +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; +import org.matsim.core.population.routes.NetworkRoute; + +public class CityTaxUtilityPenalty implements UtilityPenalty { + private final IDFModeParameters parameters; + private final IdSet taxedLinkIds; + private final double enterTax_EUR; + private final PolicyPersonFilter personFilter; + + public CityTaxUtilityPenalty(IdSet taxedLinkIds, IDFModeParameters parameters, double enterTax_EUR, + PolicyPersonFilter personFilter) { + this.taxedLinkIds = taxedLinkIds; + this.parameters = parameters; + this.enterTax_EUR = enterTax_EUR; + this.personFilter = personFilter; + } + + @Override + public double calculatePenalty(String mode, Person person, DiscreteModeChoiceTrip trip, + List elements) { + if (mode.equals(TransportMode.car) && personFilter.applies(person.getId())) { + return parameters.betaCost_u_MU * estimateTax_EUR(elements); + } else { + return 0.0; + } + } + + private double estimateTax_EUR(List elements) { + double routeTax_EUR = 0.0; + + for (PlanElement element : elements) { + if (element instanceof Leg leg) { + if (leg.getRoute() instanceof NetworkRoute route) { + for (Id linkId : route.getLinkIds()) { + if (taxedLinkIds.contains(linkId)) { + routeTax_EUR += enterTax_EUR; + } + } + } + } + } + + return routeTax_EUR; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZoneConfigGroup.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZoneConfigGroup.java new file mode 100644 index 000000000..7abc0b00f --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZoneConfigGroup.java @@ -0,0 +1,17 @@ +package org.eqasim.ile_de_france.policies.limited_traffic_zone; + +import org.eqasim.ile_de_france.policies.PolicyConfigGroup; + +public class LimitedTrafficZoneConfigGroup extends PolicyConfigGroup { + public LimitedTrafficZoneConfigGroup() { + super(LimitedTrafficZonePolicyFactory.POLICY_NAME); + } + + @Parameter + @Comment("Path to a GeoPackage file containing polygons that cover (from and to node) the links that are part of the limited traffic zone") + public String perimetersPath = ""; + + @Parameter + @Comment("Alternative: Path to a file containing one link id per line. Those links are tagged as being inside of the zone.") + public String linkListPath = ""; +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZonePolicyExtension.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZonePolicyExtension.java new file mode 100644 index 000000000..be9292118 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZonePolicyExtension.java @@ -0,0 +1,19 @@ +package org.eqasim.ile_de_france.policies.limited_traffic_zone; + +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.matsim.api.core.v01.network.Network; + +import com.google.inject.Provides; +import com.google.inject.Singleton; + +public class LimitedTrafficZonePolicyExtension extends AbstractEqasimExtension { + @Override + protected void installEqasimExtension() { + } + + @Provides + @Singleton + LimitedTrafficZonePolicyFactory provideLimitedTrafficZonePolicyFactory(Network network) { + return new LimitedTrafficZonePolicyFactory(getConfig(), network); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZonePolicyFactory.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZonePolicyFactory.java new file mode 100644 index 000000000..108770bf6 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/limited_traffic_zone/LimitedTrafficZonePolicyFactory.java @@ -0,0 +1,117 @@ +package org.eqasim.ile_de_france.policies.limited_traffic_zone; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eqasim.ile_de_france.policies.DefaultPolicy; +import org.eqasim.ile_de_france.policies.PoliciesConfigGroup; +import org.eqasim.ile_de_france.policies.Policy; +import org.eqasim.ile_de_france.policies.PolicyFactory; +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.eqasim.ile_de_france.policies.routing.FactorRoutingPenalty; +import org.eqasim.ile_de_france.policies.routing.PolicyLinkFinder; +import org.eqasim.ile_de_france.policies.routing.PolicyLinkFinder.Predicate; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.utils.io.IOUtils; + +public class LimitedTrafficZonePolicyFactory implements PolicyFactory { + private static final Logger logger = LogManager.getLogger(LimitedTrafficZonePolicyFactory.class); + + static public final String POLICY_NAME = "limitedTrafficZone"; + private final double insideFactor = 3600.0; + + private final Config config; + private final Network network; + + public LimitedTrafficZonePolicyFactory(Config config, Network network) { + this.config = config; + this.network = network; + } + + @Override + public Policy createPolicy(String name, PolicyPersonFilter personFilter) { + for (ConfigGroup item : PoliciesConfigGroup.get(config) + .getParameterSets(LimitedTrafficZonePolicyFactory.POLICY_NAME)) { + LimitedTrafficZoneConfigGroup policyItem = (LimitedTrafficZoneConfigGroup) item; + + if (policyItem.policyName.equals(name)) { + return createPolicy(policyItem, personFilter); + } + } + + throw new IllegalStateException("Configuration not found for policy " + name + " of type " + + LimitedTrafficZonePolicyFactory.POLICY_NAME); + } + + private Policy createPolicy(LimitedTrafficZoneConfigGroup ltzConfig, PolicyPersonFilter personFilter) { + logger.info( + "Creating policy " + ltzConfig.policyName + " of type " + LimitedTrafficZonePolicyFactory.POLICY_NAME); + + if (!ltzConfig.perimetersPath.isEmpty() && !ltzConfig.linkListPath.isEmpty()) { + throw new IllegalStateException( + "Only one of perimetersPath and linkListPath can be set for policy " + ltzConfig.policyName); + } + + final IdSet linkIds; + if (!ltzConfig.perimetersPath.isEmpty()) { + logger.info(" Perimeters: " + ltzConfig.perimetersPath); + + linkIds = PolicyLinkFinder + .create(new File( + ConfigGroup.getInputFileURL(config.getContext(), ltzConfig.perimetersPath).getPath())) + .findLinks(network, Predicate.Inside); + + logger.info(" Affected inside links: " + linkIds.size()); + } else if (!ltzConfig.linkListPath.isEmpty()) { + logger.info(" Link list: " + ltzConfig.linkListPath); + + linkIds = loadLinkList(ConfigGroup.getInputFileURL(config.getContext(), ltzConfig.linkListPath).getPath(), + network, ltzConfig.policyName); + + logger.info(" Affected links: " + linkIds.size()); + } else { + throw new IllegalStateException( + "One of perimetersPath and linkListPath must be set for policy " + ltzConfig.policyName); + } + + return new DefaultPolicy(new FactorRoutingPenalty(linkIds, insideFactor, personFilter), null); + } + + private static IdSet loadLinkList(String path, Network network, String policy) { + IdSet linkList = new IdSet<>(Link.class); + + try { + BufferedReader reader = IOUtils.getBufferedReader(path); + + String line = null; + while ((line = reader.readLine()) != null) { + line = line.trim(); + + if (!line.isEmpty()) { + Link link = network.getLinks().get(Id.createLinkId(line)); + + if (link == null) { + throw new IllegalStateException("Link list of policy " + policy + " contains link " + line + + " which is not included in network"); + } + + linkList.add(link.getId()); + } + } + + reader.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return linkList; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/PolicyUtilityEstimator.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/PolicyUtilityEstimator.java new file mode 100644 index 000000000..66aeccb87 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/PolicyUtilityEstimator.java @@ -0,0 +1,27 @@ +package org.eqasim.ile_de_france.policies.mode_choice; + +import java.util.List; + +import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +public class PolicyUtilityEstimator implements UtilityEstimator { + private final UtilityPenalty penalty; + private final UtilityEstimator delegate; + private final String mode; + + public PolicyUtilityEstimator(UtilityEstimator delegate, UtilityPenalty penalty, String mode) { + this.delegate = delegate; + this.penalty = penalty; + this.mode = mode; + } + + @Override + public double estimateUtility(Person person, DiscreteModeChoiceTrip trip, List elements) { + double utility = delegate.estimateUtility(person, trip, elements); + utility += penalty.calculatePenalty(mode, person, trip, elements); + return utility; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/SumUtilityPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/SumUtilityPenalty.java new file mode 100644 index 000000000..1d003701a --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/SumUtilityPenalty.java @@ -0,0 +1,28 @@ +package org.eqasim.ile_de_france.policies.mode_choice; + +import java.util.ArrayList; +import java.util.List; + +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +public class SumUtilityPenalty implements UtilityPenalty { + private final List items = new ArrayList<>(); + + public SumUtilityPenalty(List items) { + this.items.addAll(items); + } + + @Override + public double calculatePenalty(String mode, Person person, DiscreteModeChoiceTrip trip, + List elements) { + double penalty = 0.0; + + for (UtilityPenalty item : items) { + penalty += item.calculatePenalty(mode, person, trip, elements); + } + + return penalty; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/UtilityPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/UtilityPenalty.java new file mode 100644 index 000000000..d21d62ea5 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/mode_choice/UtilityPenalty.java @@ -0,0 +1,12 @@ +package org.eqasim.ile_de_france.policies.mode_choice; + +import java.util.List; + +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +public interface UtilityPenalty { + double calculatePenalty(String mode, Person person, DiscreteModeChoiceTrip trip, + List elements); +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/FactorRoutingPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/FactorRoutingPenalty.java new file mode 100644 index 000000000..8f9ae5684 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/FactorRoutingPenalty.java @@ -0,0 +1,23 @@ +package org.eqasim.ile_de_france.policies.routing; + +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; + +public class FactorRoutingPenalty implements RoutingPenalty { + private final IdSet linkIds; + private final PolicyPersonFilter personFilter; + private final double factor; + + public FactorRoutingPenalty(IdSet linkIds, double factor, PolicyPersonFilter personFilter) { + this.linkIds = linkIds; + this.factor = factor; + this.personFilter = personFilter; + } + + @Override + public double getLinkPenalty(Link link, Person person, double time, double baseDisutility) { + return linkIds.contains(link.getId()) && personFilter.applies(person.getId()) ? baseDisutility * factor : 0.0; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/FixedRoutingPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/FixedRoutingPenalty.java new file mode 100644 index 000000000..56aa1f9c2 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/FixedRoutingPenalty.java @@ -0,0 +1,23 @@ +package org.eqasim.ile_de_france.policies.routing; + +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; + +public class FixedRoutingPenalty implements RoutingPenalty { + private final IdSet linkIds; + private final double penalty; + private final PolicyPersonFilter personFilter; + + public FixedRoutingPenalty(IdSet linkIds, double penalty, PolicyPersonFilter personFilter) { + this.linkIds = linkIds; + this.penalty = penalty; + this.personFilter = personFilter; + } + + @Override + public double getLinkPenalty(Link link, Person person, double time, double baseDisutility) { + return linkIds.contains(link.getId()) && personFilter.applies(person.getId()) ? penalty : 0.0; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyLinkFinder.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyLinkFinder.java new file mode 100644 index 000000000..46f24dcfe --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyLinkFinder.java @@ -0,0 +1,86 @@ +package org.eqasim.ile_de_france.policies.routing; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.geotools.api.data.SimpleFeatureReader; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.geopkg.FeatureEntry; +import org.geotools.geopkg.GeoPackage; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; + +public class PolicyLinkFinder { + private final static GeometryFactory geometryFactory = new GeometryFactory(); + + private final List shapes; + + public PolicyLinkFinder(List shapes) { + this.shapes = shapes; + } + + public enum Predicate { + Entering, Exiting, Crossing, Inside + } + + public IdSet findLinks(Network network, Predicate predicate) { + IdSet linkIds = new IdSet<>(Link.class); + + for (Link link : network.getLinks().values()) { + for (Geometry shape : shapes) { + Coord fromCoord = link.getFromNode().getCoord(); + Coord toCoord = link.getToNode().getCoord(); + + Point fromPoint = geometryFactory.createPoint(new Coordinate(fromCoord.getX(), fromCoord.getY())); + Point toPoint = geometryFactory.createPoint(new Coordinate(toCoord.getX(), toCoord.getY())); + + boolean fromInside = shape.contains(fromPoint); + boolean toInside = shape.contains(toPoint); + + boolean isRelvant = switch (predicate) { + case Exiting -> fromInside && !toInside; + case Entering -> !fromInside && toInside; + case Crossing -> fromInside || toInside; + case Inside -> fromInside && toInside; + }; + + if (isRelvant) { + linkIds.add(link.getId()); + } + } + } + + return linkIds; + } + + static public PolicyLinkFinder create(File path) { + try { + List shapes = new LinkedList<>(); + + try (GeoPackage source = new GeoPackage(path)) { + source.init(); + + for (FeatureEntry featureEntry : source.features()) { + try (SimpleFeatureReader reader = source.reader(featureEntry, null, null)) { + while (reader.hasNext()) { + SimpleFeature feature = reader.next(); + shapes.add((Geometry) feature.getDefaultGeometry()); + } + } + } + } + + return new PolicyLinkFinder(shapes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyTravelDisutility.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyTravelDisutility.java new file mode 100644 index 000000000..aada2f1ac --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyTravelDisutility.java @@ -0,0 +1,28 @@ +package org.eqasim.ile_de_france.policies.routing; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; +import org.matsim.core.router.util.TravelDisutility; +import org.matsim.vehicles.Vehicle; + +public class PolicyTravelDisutility implements TravelDisutility { + private final TravelDisutility delegate; + private final RoutingPenalty penalty; + + public PolicyTravelDisutility(TravelDisutility delegate, RoutingPenalty penalty) { + this.delegate = delegate; + this.penalty = penalty; + } + + @Override + public double getLinkTravelDisutility(Link link, double time, Person person, Vehicle vehicle) { + double disutility = delegate.getLinkTravelDisutility(link, time, person, vehicle); + disutility += penalty.getLinkPenalty(link, person, time, disutility); + return disutility; + } + + @Override + public double getLinkMinimumTravelDisutility(Link link) { + return delegate.getLinkMinimumTravelDisutility(link); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyTravelDisutilityFactory.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyTravelDisutilityFactory.java new file mode 100644 index 000000000..4de34f80c --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/PolicyTravelDisutilityFactory.java @@ -0,0 +1,20 @@ +package org.eqasim.ile_de_france.policies.routing; + +import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutilityFactory; +import org.matsim.core.router.costcalculators.TravelDisutilityFactory; +import org.matsim.core.router.util.TravelDisutility; +import org.matsim.core.router.util.TravelTime; + +public class PolicyTravelDisutilityFactory implements TravelDisutilityFactory { + private final OnlyTimeDependentTravelDisutilityFactory delegate = new OnlyTimeDependentTravelDisutilityFactory(); + private final RoutingPenalty linkPenalty; + + public PolicyTravelDisutilityFactory(RoutingPenalty linkPenalty) { + this.linkPenalty = linkPenalty; + } + + @Override + public TravelDisutility createTravelDisutility(TravelTime travelTime) { + return new PolicyTravelDisutility(delegate.createTravelDisutility(travelTime), linkPenalty); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/RoutingPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/RoutingPenalty.java new file mode 100644 index 000000000..f36b8d32e --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/RoutingPenalty.java @@ -0,0 +1,8 @@ +package org.eqasim.ile_de_france.policies.routing; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; + +public interface RoutingPenalty { + double getLinkPenalty(Link link, Person person, double time, double baseDisutility); +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/SumRoutingPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/SumRoutingPenalty.java new file mode 100644 index 000000000..d3ee26290 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/routing/SumRoutingPenalty.java @@ -0,0 +1,26 @@ +package org.eqasim.ile_de_france.policies.routing; + +import java.util.ArrayList; +import java.util.List; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; + +public class SumRoutingPenalty implements RoutingPenalty { + private final List items = new ArrayList<>(); + + public SumRoutingPenalty(List items) { + this.items.addAll(items); + } + + @Override + public double getLinkPenalty(Link link, Person person, double time, double baseDisutility) { + double penalty = 0.0; + + for (RoutingPenalty item : items) { + penalty += item.getLinkPenalty(link, person, time, baseDisutility); + } + + return penalty; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountConfigGroup.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountConfigGroup.java new file mode 100644 index 000000000..6509acccb --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountConfigGroup.java @@ -0,0 +1,13 @@ +package org.eqasim.ile_de_france.policies.transit_discount; + +import org.eqasim.ile_de_france.policies.PolicyConfigGroup; +import org.matsim.core.config.ReflectiveConfigGroup.Parameter; + +public class TransitDiscountConfigGroup extends PolicyConfigGroup { + public TransitDiscountConfigGroup() { + super(TransitDiscountPolicyFactory.POLICY_NAME); + } + + @Parameter + public double priceFactor = 1.0; +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountPolicyExtension.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountPolicyExtension.java new file mode 100644 index 000000000..99727f74f --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountPolicyExtension.java @@ -0,0 +1,29 @@ +package org.eqasim.ile_de_france.policies.transit_discount; + +import java.util.Map; +import java.util.Objects; + +import org.eqasim.core.components.config.EqasimConfigGroup; +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.eqasim.core.simulation.mode_choice.cost.CostModel; +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Network; + +import com.google.inject.Provides; +import com.google.inject.Singleton; + +public class TransitDiscountPolicyExtension extends AbstractEqasimExtension { + @Override + protected void installEqasimExtension() { + } + + @Provides + @Singleton + TransitDiscountPolicyFactory provideTransitDiscountPolicyFactory(Network network, IDFModeParameters modeParameters, + Map costModels, EqasimConfigGroup eqasimConfig) { + CostModel costModel = Objects + .requireNonNull(costModels.get(eqasimConfig.getCostModels().get(TransportMode.pt))); + return new TransitDiscountPolicyFactory(getConfig(), costModel, modeParameters); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountPolicyFactory.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountPolicyFactory.java new file mode 100644 index 000000000..ac0bab9f6 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountPolicyFactory.java @@ -0,0 +1,53 @@ +package org.eqasim.ile_de_france.policies.transit_discount; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eqasim.core.simulation.mode_choice.cost.CostModel; +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.eqasim.ile_de_france.policies.DefaultPolicy; +import org.eqasim.ile_de_france.policies.PoliciesConfigGroup; +import org.eqasim.ile_de_france.policies.Policy; +import org.eqasim.ile_de_france.policies.PolicyFactory; +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; + +public class TransitDiscountPolicyFactory implements PolicyFactory { + private static final Logger logger = LogManager.getLogger(TransitDiscountPolicyFactory.class); + + static public final String POLICY_NAME = "transitDiscount"; + + private final Config config; + private final CostModel costModel; + private final IDFModeParameters modeParameters; + + public TransitDiscountPolicyFactory(Config config, CostModel costModel, IDFModeParameters modeParameters) { + this.config = config; + this.costModel = costModel; + this.modeParameters = modeParameters; + } + + @Override + public Policy createPolicy(String name, PolicyPersonFilter personFilter) { + for (ConfigGroup item : PoliciesConfigGroup.get(config) + .getParameterSets(TransitDiscountPolicyFactory.POLICY_NAME)) { + TransitDiscountConfigGroup policyItem = (TransitDiscountConfigGroup) item; + + if (policyItem.policyName.equals(name)) { + return createPolicy(policyItem, personFilter); + } + } + + throw new IllegalStateException( + "Configuration not found for policy " + name + " of type " + TransitDiscountPolicyFactory.POLICY_NAME); + } + + private Policy createPolicy(TransitDiscountConfigGroup discountConfig, PolicyPersonFilter personFilter) { + logger.info("Creating policy " + discountConfig.policyName + " of type " + + TransitDiscountPolicyFactory.POLICY_NAME); + logger.info(" Price factor: " + discountConfig.priceFactor); + + return new DefaultPolicy(null, + new TransitDiscountUtilityPenalty(costModel, modeParameters, discountConfig.priceFactor, personFilter)); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountUtilityPenalty.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountUtilityPenalty.java new file mode 100644 index 000000000..685801b2d --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/policies/transit_discount/TransitDiscountUtilityPenalty.java @@ -0,0 +1,39 @@ +package org.eqasim.ile_de_france.policies.transit_discount; + +import java.util.List; + +import org.eqasim.core.simulation.mode_choice.cost.CostModel; +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.eqasim.ile_de_france.policies.PolicyPersonFilter; +import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +public class TransitDiscountUtilityPenalty implements UtilityPenalty { + private final CostModel costModel; + private final IDFModeParameters modeParameters; + private final double costFactor; + private final PolicyPersonFilter personFilter; + + public TransitDiscountUtilityPenalty(CostModel costModel, IDFModeParameters modeParameters, double costFactor, + PolicyPersonFilter personFilter) { + this.costModel = costModel; + this.modeParameters = modeParameters; + this.costFactor = costFactor; + this.personFilter = personFilter; + } + + @Override + public double calculatePenalty(String mode, Person person, DiscreteModeChoiceTrip trip, + List elements) { + if (mode.equals(TransportMode.pt) && personFilter.applies(person.getId())) { + double initialCost = costModel.calculateCost_MU(person, trip, elements); + double updatedCost = initialCost * costFactor; + return modeParameters.betaCost_u_MU * (updatedCost - initialCost); + } else { + return 0.0; + } + } +} diff --git a/pom.xml b/pom.xml index ff665c764..1567f3997 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 21 21 - 2025.0-PR3483 + 2025.0-PR3568