From cb21340d86a754d40bf806d15001166e15873d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=B6rl?= Date: Tue, 5 Mar 2024 17:37:41 +0100 Subject: [PATCH] feat: update vdf to latest internal version (#193) * feat: update vdf to latest internal version * update * Update README.md Make things clearer for the adjustment of capacity factors in the config. --------- Co-authored-by: Milos Balac --- .../RunCorsicaVDFEngineSimulation.java | 96 +++++++ .../corsica_vdf/RunCorsicaVDFSimulation.java | 11 + .../corsica_vdf/CorsicaVDFSimulationTest.java | 7 +- vdf/README.md | 40 +++ .../java/org/eqasim/vdf/VDFConfigGroup.java | 122 +++++++++ .../main/java/org/eqasim/vdf/VDFModule.java | 73 ++++- .../java/org/eqasim/vdf/VDFQSimModule.java | 2 + .../main/java/org/eqasim/vdf/VDFScope.java | 53 ++++ .../org/eqasim/vdf/VDFTrafficHandler.java | 79 ------ .../org/eqasim/vdf/VDFUpdateListener.java | 113 ++++++++ .../org/eqasim/vdf/analysis/FlowWriter.java | 54 ++++ .../java/org/eqasim/vdf/engine/VDFEngine.java | 190 +++++++++++++ .../vdf/engine/VDFEngineConfigGroup.java | 64 +++++ .../eqasim/vdf/engine/VDFEngineModule.java | 39 +++ .../vdf/handlers/VDFHorizonHandler.java | 254 ++++++++++++++++++ .../vdf/handlers/VDFInterpolationHandler.java | 133 +++++++++ .../vdf/handlers/VDFTrafficHandler.java | 19 ++ .../org/eqasim/vdf/io/VDFReaderInterface.java | 7 + .../org/eqasim/vdf/io/VDFWriterInterface.java | 7 + .../VDFLinkSpeedCalculator.java | 2 +- .../vdf/{ => travel_time}/VDFTravelTime.java | 64 +++-- .../function/BPRFunction.java | 2 +- .../function/VolumeDelayFunction.java | 2 +- 23 files changed, 1308 insertions(+), 125 deletions(-) create mode 100644 examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFEngineSimulation.java create mode 100644 vdf/README.md create mode 100644 vdf/src/main/java/org/eqasim/vdf/VDFScope.java delete mode 100644 vdf/src/main/java/org/eqasim/vdf/VDFTrafficHandler.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/VDFUpdateListener.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/analysis/FlowWriter.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/engine/VDFEngine.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineConfigGroup.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineModule.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/handlers/VDFHorizonHandler.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/handlers/VDFInterpolationHandler.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/handlers/VDFTrafficHandler.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/io/VDFReaderInterface.java create mode 100644 vdf/src/main/java/org/eqasim/vdf/io/VDFWriterInterface.java rename vdf/src/main/java/org/eqasim/vdf/{ => travel_time}/VDFLinkSpeedCalculator.java (96%) rename vdf/src/main/java/org/eqasim/vdf/{ => travel_time}/VDFTravelTime.java (55%) rename vdf/src/main/java/org/eqasim/vdf/{ => travel_time}/function/BPRFunction.java (92%) rename vdf/src/main/java/org/eqasim/vdf/{ => travel_time}/function/VolumeDelayFunction.java (78%) diff --git a/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFEngineSimulation.java b/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFEngineSimulation.java new file mode 100644 index 000000000..340d070ca --- /dev/null +++ b/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFEngineSimulation.java @@ -0,0 +1,96 @@ +package org.eqasim.examples.corsica_vdf; + +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import org.eqasim.core.components.traffic.EqasimTrafficQSimModule; +import org.eqasim.core.components.transit.EqasimTransitQSimModule; +import org.eqasim.core.simulation.analysis.EqasimAnalysisModule; +import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule; +import org.eqasim.ile_de_france.IDFConfigurator; +import org.eqasim.ile_de_france.mode_choice.IDFModeChoiceModule; +import org.eqasim.vdf.VDFConfigGroup; +import org.eqasim.vdf.VDFModule; +import org.eqasim.vdf.VDFQSimModule; +import org.eqasim.vdf.engine.VDFEngineConfigGroup; +import org.eqasim.vdf.engine.VDFEngineModule; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.CommandLine.ConfigurationException; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.core.scenario.ScenarioUtils; + +import com.google.common.io.Resources; + +/** + * This is an example run script that runs the Corsica test scenario with a + * volume-delay function to simulate travel times. + */ +public class RunCorsicaVDFEngineSimulation { + static public void main(String[] args) throws ConfigurationException { + CommandLine cmd = new CommandLine.Builder(args) // + .allowPrefixes("mode-parameter", "cost-parameter") // + .build(); + + IDFConfigurator configurator = new IDFConfigurator(); + configurator.getQSimModules().removeIf(m -> m instanceof EqasimTrafficQSimModule); + + URL configUrl = Resources.getResource("corsica/corsica_config.xml"); + Config config = ConfigUtils.loadConfig(configUrl, configurator.getConfigGroups()); + + config.controler().setLastIteration(2); + + // VDF: Add config group + config.addModule(new VDFConfigGroup()); + + // VDF: Disable queue logic + config.qsim().setFlowCapFactor(1e9); + config.qsim().setStorageCapFactor(1e9); + + // VDF: Set capacity factor instead (~0.1 for a 10% simulation in theory... any better advice?) + VDFConfigGroup.getOrCreate(config).setCapacityFactor(0.1); + + // VDF: Optional + VDFConfigGroup.getOrCreate(config).setWriteInterval(1); + VDFConfigGroup.getOrCreate(config).setWriteFlowInterval(1); + + // VDF Engine: Add config group + config.addModule(new VDFEngineConfigGroup()); + + // VDF Engine: Decide whether to genertae link events or not + VDFEngineConfigGroup.getOrCreate(config).setGenerateNetworkEvents(false); + + // VDF Engine: Remove car from main modes + Set mainModes = new HashSet<>(config.qsim().getMainModes()); + mainModes.remove("car"); + config.qsim().setMainModes(mainModes); + + Scenario scenario = ScenarioUtils.createScenario(config); + configurator.configureScenario(scenario); + ScenarioUtils.loadScenario(scenario); + + Controler controller = new Controler(scenario); + configurator.configureController(controller); + controller.addOverridingModule(new EqasimAnalysisModule()); + controller.addOverridingModule(new EqasimModeChoiceModule()); + controller.addOverridingModule(new IDFModeChoiceModule(cmd)); + + // VDF: Add modules + controller.addOverridingModule(new VDFModule()); + controller.addOverridingQSimModule(new VDFQSimModule()); + + // VDF Engine: Add modules + controller.addOverridingModule(new VDFEngineModule()); + + // VDF Engine: Active engine + controller.configureQSimComponents(cfg -> { + EqasimTransitQSimModule.configure(cfg, controller.getConfig()); + cfg.addNamedComponent(VDFEngineModule.COMPONENT_NAME); // here + }); + + controller.run(); + } +} diff --git a/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFSimulation.java b/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFSimulation.java index 57e6398eb..573c2b99c 100644 --- a/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFSimulation.java +++ b/examples/src/main/java/org/eqasim/examples/corsica_vdf/RunCorsicaVDFSimulation.java @@ -37,8 +37,18 @@ static public void main(String[] args) throws ConfigurationException { Config config = ConfigUtils.loadConfig(configUrl, configurator.getConfigGroups()); config.controler().setLastIteration(2); + + // VDF: Add config group config.addModule(new VDFConfigGroup()); + // VDF: Disable queue logic + config.qsim().setFlowCapFactor(1e9); + config.qsim().setStorageCapFactor(1e9); + + // VDF: Optional + VDFConfigGroup.getOrCreate(config).setWriteInterval(1); + VDFConfigGroup.getOrCreate(config).setWriteFlowInterval(1); + Scenario scenario = ScenarioUtils.createScenario(config); configurator.configureScenario(scenario); ScenarioUtils.loadScenario(scenario); @@ -49,6 +59,7 @@ static public void main(String[] args) throws ConfigurationException { controller.addOverridingModule(new EqasimModeChoiceModule()); controller.addOverridingModule(new IDFModeChoiceModule(cmd)); + // VDF: Add modules controller.addOverridingModule(new VDFModule()); controller.addOverridingQSimModule(new VDFQSimModule()); diff --git a/examples/src/test/java/org/eqasim/examples/corsica_vdf/CorsicaVDFSimulationTest.java b/examples/src/test/java/org/eqasim/examples/corsica_vdf/CorsicaVDFSimulationTest.java index b4b1b3d5e..acdb1022b 100644 --- a/examples/src/test/java/org/eqasim/examples/corsica_vdf/CorsicaVDFSimulationTest.java +++ b/examples/src/test/java/org/eqasim/examples/corsica_vdf/CorsicaVDFSimulationTest.java @@ -7,7 +7,12 @@ public class CorsicaVDFSimulationTest { @Test - public void testCorsicaDrtSimulationTest() throws ConfigurationException, IOException { + public void testCorsicaVDFSimulationTest() throws ConfigurationException, IOException { RunCorsicaVDFSimulation.main(new String[] {}); } + + @Test + public void testCorsicaVDFEngineSimulationTest() throws ConfigurationException, IOException { + RunCorsicaVDFEngineSimulation.main(new String[] {}); + } } diff --git a/vdf/README.md b/vdf/README.md new file mode 100644 index 000000000..48c16f08c --- /dev/null +++ b/vdf/README.md @@ -0,0 +1,40 @@ +# VDF Module + +The following describes how volume-delay-functions (VDF) can be used in eqasim. There are several shortcomings with the queue-based simulation of MATSim, especially when thinking, for instance, of spillback from a city network onto highways and similar phenomena. This could be covered by lanes in MATSim, but this functionality does not seem to be widely used and data acquisition seems tricky. + +Therefore, we propose to simply treat each link in the network as a line that needs to be traversed by all vehicles with a specific duration. This duration, as in the classic BPR function, is directly dependent on the flow on that link. Our idea is now to track the flow on each link from the previous iteration(s) and then impose the calculated travel time. The same travel time is used in the routing and decision-making, so that agents will avoid using links with high flow relative to its capacity, because traversal times are high. + +## VDF Module + +The main components of this extension are the `VDF(QSim)Module` and the `VDFConfigGroup`. The interval on which flows are evaluated can be configured, but is set to one hour by default. The volume-delay-function used is the classic BPR function that calculates a traversal time based on the free-flow traversal time and the ratio between detected flow and link capacity. + +For the VDF functionality to work: +- Set the storage capacity of all links to infinity (`1e9`), we effectively disable the queue logic using the `storageCapacityFactor` in the QSim configuration +- Set the flow capacity of all links to infinity (`1e9`), we effectively disable the queue logic using the `flowCapacityFactor` in the QSim configuration +- Remove `car` from the `mainModes` in the QSim configuration +- Add `VDFModule` and `VDFQSimModule` + +On the technical side, the logic is that the VDF component tracks link enter/leave events and thus established the flows on the links. Based on those flows, the traversal times are calculated and bound via `TravelTime`. This travel time is then used by the standard network routing modules for the relevant modes. + +Note that it would be unstable to always use the flows of the previous iteration. Therefore, we interpolate over multiple iterations. There are various ways of doing so, by default we use a horizon-based approach in which we track the flows on all links over `N` (default 10) iterations and then calculate the mean (MA, moving average approach). Another approach is to always blend between the flows of the previous iteration and the current one (AR, auto-regressive approach). It can be selected in the config group. + +Furthermore, averaging over multiple iterations means that we need to recover this state if we want to restart a simulation later on at a specific iteration. The config group provides a `inputFile` parameter that does exactly this, based on the VDF output of a previous simulation. + +The binary output can be controlled by setting `writeInterval` in the config group. If set to a very large value, only the last iteration will be saved. Optionally, a file containing the flows on all links will be generated by setting `writeFlowInterval`. + +**Attention**: The VDF (default BPR) is defined for a full-size simulation, and so are the capacities in the network. To obtain proper travel times, the observed flows, hence, need to be scaled up if a down-scaled demand is used. This is done through the `capacityFactor` parameter in the config group. It works analogously to QSim's flow capacity factor. A factor of *0.1* performs the calculations as if the capacities were only *10%* of their nominal values. + +## VDF Engine + +The next step after imposing travel times in the QSim is to simplify simulation altogether. For this, there is the `VDFEngineModule`. It replaces the simulation of selected modes (like car) completely in the QSim. In particular, it removes the queue simulation as we don't need this in a VDF simulation. The agents are simply removed from the simulation at departure and added back at the destination after the trip has been finished. + +There are two options to work with the generated flows: +- Either, the engine manually generates link enter/leave events that are later tracked by the VDF module. +- Or, we send all link traversals as a batch to the flow trackers (much more performant), but we lose the ability to analyze link enter/leave events otherwise. + +By default, the `VDFEngineConfigGroup` is configured to generate events and replace the `car` mode. + +## Example + +An example for the configuration of both cases can be found in the `examples` package for `corsica_vdf`. + diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFConfigGroup.java b/vdf/src/main/java/org/eqasim/vdf/VDFConfigGroup.java index 164350984..d3e1f41a8 100644 --- a/vdf/src/main/java/org/eqasim/vdf/VDFConfigGroup.java +++ b/vdf/src/main/java/org/eqasim/vdf/VDFConfigGroup.java @@ -1,5 +1,10 @@ package org.eqasim.vdf; +import java.util.Arrays; +import java.util.Set; + +import org.matsim.api.core.v01.TransportMode; +import org.matsim.core.config.Config; import org.matsim.core.config.ReflectiveConfigGroup; import org.matsim.core.utils.misc.Time; @@ -11,10 +16,20 @@ public class VDFConfigGroup extends ReflectiveConfigGroup { static private final String INTERVAL = "interval"; static private final String MINIMUM_SPEED = "minimumSpeed"; static private final String HORIZON = "horizon"; + static private final String CAPACITY_FACTOR = "capacityFactor"; static private final String BPR_FACTOR = "bpr:factor"; static private final String BPR_EXPONENT = "bpr:exponent"; + static private final String MODES = "modes"; + + static private final String HANDLER = "handler"; + static private final String INPUT_FILE = "inputFile"; + static private final String WRITE_INTERVAL = "writeInterval"; + static private final String WRITE_FLOW_INTERVAL = "writeFlowInterval"; + + static private final String GENERATE_NETWORK_EVENTS = "generateNetworkEvents"; + private double startTime = 0.0 * 3600.0; private double endTime = 24.0 * 3600.0; private double interval = 3600.0; @@ -24,6 +39,22 @@ public class VDFConfigGroup extends ReflectiveConfigGroup { private double bprFactor = 0.15; private double bprExponent = 4.0; + private Set modes = Set.of(TransportMode.car, "car_passenger"); + + private double capacityFactor = 1.0; + + private String inputFile = null; + private int writeInterval = 0; + private int writeFlowInterval = 0; + + private boolean generateNetworkEvents = true; + + public enum HandlerType { + Horizon, Interpolation + } + + private HandlerType handler = HandlerType.Horizon; + public VDFConfigGroup() { super(GROUP_NAME); } @@ -121,4 +152,95 @@ public double getBprExponent() { public void setBprExponent(double bprExponent) { this.bprExponent = bprExponent; } + + @StringGetter(CAPACITY_FACTOR) + public double getCapacityFactor() { + return capacityFactor; + } + + @StringSetter(CAPACITY_FACTOR) + public void setCapacityFactor(double capacityFactor) { + this.capacityFactor = capacityFactor; + } + + public Set getModes() { + return modes; + } + + public void setModes(Set modes) { + this.modes.clear(); + this.modes.addAll(modes); + } + + @StringGetter(MODES) + public String getModesAsString() { + return String.join(",", modes); + } + + @StringSetter(MODES) + public void setModesAsString(String modes) { + this.modes.clear(); + Arrays.asList(modes.split(",")).stream().map(String::trim).forEach(this.modes::add); + } + + @StringGetter(HANDLER) + public HandlerType getHandler() { + return handler; + } + + @StringSetter(HANDLER) + public void setHandler(HandlerType handler) { + this.handler = handler; + } + + @StringGetter(INPUT_FILE) + public String getInputFile() { + return inputFile; + } + + @StringSetter(INPUT_FILE) + public void setInputFile(String inputFile) { + this.inputFile = inputFile; + } + + @StringGetter(WRITE_INTERVAL) + public int getWriteInterval() { + return writeInterval; + } + + @StringSetter(WRITE_INTERVAL) + public void setWriteInterval(int writeInterval) { + this.writeInterval = writeInterval; + } + + @StringGetter(WRITE_FLOW_INTERVAL) + public int getWriteFlowInterval() { + return writeFlowInterval; + } + + @StringSetter(WRITE_FLOW_INTERVAL) + public void setWriteFlowInterval(int val) { + this.writeFlowInterval = val; + } + + @StringGetter(GENERATE_NETWORK_EVENTS) + public boolean getNetworkEvents() { + return generateNetworkEvents; + } + + @StringSetter(GENERATE_NETWORK_EVENTS) + public void setNetworkEvents(boolean val) { + this.generateNetworkEvents = val; + } + + public static VDFConfigGroup getOrCreate(Config config) { + VDFConfigGroup group = (VDFConfigGroup) config.getModules().get(GROUP_NAME); + + if (group == null) { + group = new VDFConfigGroup(); + config.addModule(group); + } + + return group; + } } diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFModule.java b/vdf/src/main/java/org/eqasim/vdf/VDFModule.java index 9bfb693ca..91843d20c 100644 --- a/vdf/src/main/java/org/eqasim/vdf/VDFModule.java +++ b/vdf/src/main/java/org/eqasim/vdf/VDFModule.java @@ -1,12 +1,19 @@ package org.eqasim.vdf; +import java.net.URL; + import org.eqasim.core.components.config.EqasimConfigGroup; -import org.eqasim.vdf.function.BPRFunction; -import org.eqasim.vdf.function.VolumeDelayFunction; -import org.matsim.api.core.v01.TransportMode; +import org.eqasim.vdf.handlers.VDFHorizonHandler; +import org.eqasim.vdf.handlers.VDFInterpolationHandler; +import org.eqasim.vdf.handlers.VDFTrafficHandler; +import org.eqasim.vdf.travel_time.VDFTravelTime; +import org.eqasim.vdf.travel_time.function.BPRFunction; +import org.eqasim.vdf.travel_time.function.VolumeDelayFunction; import org.matsim.api.core.v01.network.Network; +import org.matsim.core.config.ConfigGroup; import org.matsim.core.config.groups.QSimConfigGroup; import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.OutputDirectoryHierarchy; import com.google.inject.Provides; import com.google.inject.Singleton; @@ -14,27 +21,65 @@ public class VDFModule extends AbstractModule { @Override public void install() { - addTravelTimeBinding(TransportMode.car).to(VDFTravelTime.class); - addEventHandlerBinding().to(VDFTrafficHandler.class); + VDFConfigGroup vdfConfig = VDFConfigGroup.getOrCreate(getConfig()); + + for (String mode : vdfConfig.getModes()) { + addTravelTimeBinding(mode).to(VDFTravelTime.class); + } + addControlerListenerBinding().to(VDFUpdateListener.class); bind(VolumeDelayFunction.class).to(BPRFunction.class); + + switch (vdfConfig.getHandler()) { + case Horizon: + bind(VDFTrafficHandler.class).to(VDFHorizonHandler.class); + addEventHandlerBinding().to(VDFHorizonHandler.class); + break; + case Interpolation: + bind(VDFTrafficHandler.class).to(VDFInterpolationHandler.class); + addEventHandlerBinding().to(VDFInterpolationHandler.class); + break; + default: + throw new IllegalStateException(); + } + } + + @Provides + @Singleton + public VDFUpdateListener provideVDFUpdateListener(VDFScope scope, VDFTrafficHandler handler, + VDFTravelTime travelTime, VDFConfigGroup config, OutputDirectoryHierarchy outputHierarchy, + Network network) { + URL inputFile = config.getInputFile() == null ? null + : ConfigGroup.getInputFileURL(getConfig().getContext(), config.getInputFile()); + return new VDFUpdateListener(network, scope, handler, travelTime, outputHierarchy, config.getWriteInterval(), + config.getWriteFlowInterval(), inputFile); + } + + @Provides + @Singleton + public VDFScope provideVDFScope(VDFConfigGroup config) { + return new VDFScope(config.getStartTime(), config.getEndTime(), config.getInterval()); + } + + @Provides + @Singleton + public VDFTravelTime provideVDFTravelTime(VDFConfigGroup config, VDFScope scope, Network network, + VolumeDelayFunction vdf, QSimConfigGroup qsimConfig, EqasimConfigGroup eqasimConfig) { + return new VDFTravelTime(scope, config.getMinimumSpeed(), config.getCapacityFactor(), + eqasimConfig.getSampleSize(), network, vdf, eqasimConfig.getCrossingPenalty()); } @Provides @Singleton - public VDFTravelTime provideVDFTravelTime(VDFConfigGroup config, Network network, VolumeDelayFunction vdf, - QSimConfigGroup qsimConfig, EqasimConfigGroup eqasimConfig) { - int numberOfIntervals = (int) Math.floor((config.getEndTime() - config.getStartTime()) / config.getInterval()) - + 1; - return new VDFTravelTime(config.getEndTime(), config.getInterval(), numberOfIntervals, config.getMinimumSpeed(), - qsimConfig.getFlowCapFactor(), network, vdf, eqasimConfig.getCrossingPenalty()); + public VDFHorizonHandler provideVDFHorizonHandler(VDFConfigGroup config, Network network, VDFScope scope) { + return new VDFHorizonHandler(network, scope, config.getHorizon(), getConfig().global().getNumberOfThreads()); } @Provides @Singleton - public VDFTrafficHandler provideVDFTrafficHandler(VDFConfigGroup config, Network network, - VDFTravelTime travelTime) { - return new VDFTrafficHandler(network, travelTime, config.getHorizon()); + public VDFInterpolationHandler provideVDFInterpolationHandler(VDFConfigGroup config, Network network, + VDFScope scope) { + return new VDFInterpolationHandler(network, scope, 1.0 / config.getHorizon()); } @Provides diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFQSimModule.java b/vdf/src/main/java/org/eqasim/vdf/VDFQSimModule.java index 0a8c9bed7..197c596e6 100644 --- a/vdf/src/main/java/org/eqasim/vdf/VDFQSimModule.java +++ b/vdf/src/main/java/org/eqasim/vdf/VDFQSimModule.java @@ -1,5 +1,7 @@ package org.eqasim.vdf; +import org.eqasim.vdf.travel_time.VDFLinkSpeedCalculator; +import org.eqasim.vdf.travel_time.VDFTravelTime; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.population.Population; import org.matsim.core.api.experimental.events.EventsManager; diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFScope.java b/vdf/src/main/java/org/eqasim/vdf/VDFScope.java new file mode 100644 index 000000000..c38b5341d --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/VDFScope.java @@ -0,0 +1,53 @@ +package org.eqasim.vdf; + +import java.util.List; + +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; + +import com.google.common.base.Verify; + +public class VDFScope { + private final double startTime; + private final double endTime; + private final double intervalTime; + + private final int intervals; + + public VDFScope(double startTime, double endTime, double intervalTime) { + this.startTime = startTime; + this.endTime = endTime; + this.intervalTime = intervalTime; + this.intervals = (int) Math.floor((endTime - startTime) / intervalTime) + 1; + } + + public int getIntervals() { + return intervals; + } + + public int getIntervalIndex(double time) { + return Math.min(Math.max(0, (int) Math.floor((time - startTime) / intervalTime)), intervals - 1); + } + + public double getIntervalTime() { + return intervalTime; + } + + public double getStartTime() { + return startTime; + } + + public double getEndTime() { + return endTime; + } + + public void verify(List values, String reason) { + Verify.verify(values.size() == intervals, reason); + } + + public void verify(IdMap> values, String reason) { + for (var item : values.values()) { + Verify.verify(item.size() == intervals, reason); + } + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFTrafficHandler.java b/vdf/src/main/java/org/eqasim/vdf/VDFTrafficHandler.java deleted file mode 100644 index 10547ff08..000000000 --- a/vdf/src/main/java/org/eqasim/vdf/VDFTrafficHandler.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.eqasim.vdf; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.IdMap; -import org.matsim.api.core.v01.events.LinkEnterEvent; -import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Network; - -public class VDFTrafficHandler implements LinkEnterEventHandler { - private final VDFTravelTime travelTime; - private final Network network; - private final int horizon; - - private final IdMap> counts = new IdMap<>(Link.class); - private final List>> history = new LinkedList<>(); - - public VDFTrafficHandler(Network network, VDFTravelTime travelTime, int horizon) { - this.travelTime = travelTime; - this.network = network; - this.horizon = horizon; - - reset(0); - } - - @Override - public synchronized void handleEvent(LinkEnterEvent event) { - int i = travelTime.getInterval(event.getTime()); - int currentValue = counts.get(event.getLinkId()).get(i); - counts.get(event.getLinkId()).set(i, currentValue + 1); - } - - @Override - public void reset(int iteration) { - if (history.size() == horizon) { - history.remove(0); - } - - // Make a copy to add to the history - - IdMap> copy = new IdMap<>(Link.class); - - for (Map.Entry, List> entry : counts.entrySet()) { - copy.put(entry.getKey(), new ArrayList<>(entry.getValue())); - } - - history.add(copy); - - IdMap> aggregated = new IdMap<>(Link.class); - - for (Id linkId : network.getLinks().keySet()) { - // Reset current counts - counts.put(linkId, new ArrayList<>(Collections.nCopies(travelTime.getNumberOfIntervals(), 0))); - - // Initialize aggregated counts - aggregated.put(linkId, new ArrayList<>(Collections.nCopies(travelTime.getNumberOfIntervals(), 0.0))); - } - - // Aggregate - - for (IdMap> item : history) { - for (Map.Entry, List> entry : item.entrySet()) { - List aggregatedList = aggregated.get(entry.getKey()); - - for (int i = 0; i < aggregatedList.size(); i++) { - aggregatedList.set(i, aggregatedList.get(i) + (double) entry.getValue().get(i) / (double) history.size()); - } - } - } - - travelTime.update(aggregated); - } -} diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFUpdateListener.java b/vdf/src/main/java/org/eqasim/vdf/VDFUpdateListener.java new file mode 100644 index 000000000..49e4c1e8a --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/VDFUpdateListener.java @@ -0,0 +1,113 @@ +package org.eqasim.vdf; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import org.apache.log4j.Logger; +import org.eqasim.vdf.analysis.FlowWriter; +import org.eqasim.vdf.handlers.VDFTrafficHandler; +import org.eqasim.vdf.travel_time.VDFTravelTime; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.events.ShutdownEvent; +import org.matsim.core.controler.events.StartupEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.controler.listener.ShutdownListener; +import org.matsim.core.controler.listener.StartupListener; + +import com.google.common.io.Files; + +public class VDFUpdateListener implements IterationEndsListener, StartupListener, ShutdownListener { + private final static Logger logger = Logger.getLogger(VDFUpdateListener.class); + + private final String VDF_FILE = "vdf.bin"; + private final String FLOW_FILE = "vdf_flow.csv"; + + private final VDFScope scope; + private final VDFTrafficHandler handler; + private final VDFTravelTime travelTime; + + private final int writeInterval; + private final int writeFlowInterval; + private final URL inputFile; + + private final OutputDirectoryHierarchy outputHierarchy; + private final Network network; + + public VDFUpdateListener(Network network, VDFScope scope, VDFTrafficHandler handler, VDFTravelTime travelTime, + OutputDirectoryHierarchy outputHierarchy, int writeInterval, int writeFlowInterval, URL inputFile) { + this.network = network; + this.scope = scope; + this.handler = handler; + this.travelTime = travelTime; + this.writeInterval = writeInterval; + this.writeFlowInterval = writeFlowInterval; + this.outputHierarchy = outputHierarchy; + this.inputFile = inputFile; + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + IdMap> data = handler.aggregate(); + scope.verify(data, "Wrong flow format"); + travelTime.update(data); + + if (writeInterval > 0 && (event.getIteration() % writeInterval == 0 || event.isLastIteration())) { + File outputFile = new File(outputHierarchy.getIterationFilename(event.getIteration(), VDF_FILE)); + + logger.info("Writing VDF data to " + outputFile.toString() + "..."); + handler.getWriter().writeFile(outputFile); + logger.info(" Done"); + + } + + if (writeFlowInterval > 0 && (event.getIteration() % writeFlowInterval == 0 || event.isLastIteration())) { + File flowFile = new File(outputHierarchy.getIterationFilename(event.getIteration(), FLOW_FILE)); + + logger.info("Writing flow information to " + flowFile.toString() + "..."); + new FlowWriter(data, network, scope).write(flowFile); + logger.info(" Done"); + } + } + + @Override + public void notifyStartup(StartupEvent event) { + if (inputFile != null) { + logger.info("Reading VDF data from " + inputFile.toString() + "..."); + + handler.getReader().readFile(inputFile); + + IdMap> data = handler.aggregate(); + scope.verify(data, "Wrong flow format"); + travelTime.update(data); + + logger.info(" Done"); + } + } + + @Override + public void notifyShutdown(ShutdownEvent event) { + try { + File fromFile = new File(outputHierarchy.getIterationFilename(event.getIteration(), VDF_FILE)); + File toFile = new File(outputHierarchy.getOutputFilename(VDF_FILE)); + + if (fromFile.exists()) { + Files.copy(fromFile, toFile); + } + + File fromFlowFile = new File(outputHierarchy.getIterationFilename(event.getIteration(), FLOW_FILE)); + File toFlowFile = new File(outputHierarchy.getOutputFilename(FLOW_FILE)); + + if (fromFlowFile.exists()) { + Files.copy(fromFlowFile, toFlowFile); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/analysis/FlowWriter.java b/vdf/src/main/java/org/eqasim/vdf/analysis/FlowWriter.java new file mode 100644 index 000000000..1f6793e05 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/analysis/FlowWriter.java @@ -0,0 +1,54 @@ +package org.eqasim.vdf.analysis; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.List; +import java.util.Map; + +import org.eqasim.vdf.VDFScope; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; + +public class FlowWriter { + private final IdMap> flows; + private final Network network; + private final VDFScope scope; + + public FlowWriter(IdMap> flows, Network network, VDFScope scope) { + this.flows = flows; + this.network = network; + this.scope = scope; + } + + public void write(File path) { + try { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path))); + + writer.write(String.join(";", new String[] { "link_id", "interval", "start_time", "flow", "lanes", "osm" }) + + "\n"); + + for (Map.Entry, List> item : flows.entrySet()) { + for (int interval = 0; interval < scope.getIntervals(); interval++) { + writer.write(String.join(";", new String[] { // + item.getKey().toString(), // + String.valueOf(interval), // + String.valueOf(scope.getStartTime() + interval * scope.getIntervalTime()), // + String.valueOf(item.getValue().get(interval)), // + String.valueOf(network.getLinks().get(item.getKey()).getNumberOfLanes()), // + String.valueOf(network.getLinks().get(item.getKey()).getAttributes() + .getAttribute("osm:way:highway")) // + }) + "\n"); + } + } + + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngine.java b/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngine.java new file mode 100644 index 000000000..c2664fc78 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngine.java @@ -0,0 +1,190 @@ +package org.eqasim.vdf.engine; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; + +import org.eqasim.vdf.handlers.VDFTrafficHandler; +import org.eqasim.vdf.travel_time.VDFTravelTime; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.LinkEnterEvent; +import org.matsim.api.core.v01.events.LinkLeaveEvent; +import org.matsim.api.core.v01.events.PersonEntersVehicleEvent; +import org.matsim.api.core.v01.events.PersonLeavesVehicleEvent; +import org.matsim.api.core.v01.events.PersonStuckEvent; +import org.matsim.api.core.v01.events.VehicleEntersTrafficEvent; +import org.matsim.api.core.v01.events.VehicleLeavesTrafficEvent; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.api.experimental.events.TeleportationArrivalEvent; +import org.matsim.core.mobsim.framework.HasPerson; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.mobsim.framework.PlanAgent; +import org.matsim.core.mobsim.qsim.InternalInterface; +import org.matsim.core.mobsim.qsim.interfaces.DepartureHandler; +import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.vehicles.Vehicle; + +public class VDFEngine implements DepartureHandler, MobsimEngine { + private final List modes; + + private final VDFTravelTime travelTime; + private final Network network; + + private final PriorityQueue traversals = new PriorityQueue<>(new TraversalComparator()); + + private InternalInterface internalInterface; + + private final VDFTrafficHandler handler; + private final boolean generateNetworkEvents; + + public VDFEngine(Collection modes, VDFTravelTime travelTime, Network network, VDFTrafficHandler handler, + boolean generateNetworkEvents) { + this.modes = new ArrayList<>(modes); + this.travelTime = travelTime; + this.network = network; + this.handler = handler; + this.generateNetworkEvents = generateNetworkEvents; + } + + @Override + public boolean handleDeparture(double now, MobsimAgent agent, Id linkId) { + Leg leg = (Leg) ((PlanAgent) agent).getCurrentPlanElement(); + + if (!modes.contains(leg.getMode())) { + return false; + } + + MobsimDriverAgent driverAgent = (MobsimDriverAgent) agent; + + if (generateNetworkEvents) { + EventsManager eventsManager = internalInterface.getMobsim().getEventsManager(); + + eventsManager.processEvent( + new PersonEntersVehicleEvent(now, driverAgent.getId(), driverAgent.getPlannedVehicleId())); + + Traversal traversal = new Traversal(); + traversal.agent = (MobsimDriverAgent) agent; + traversal.linkId = agent.getCurrentLinkId(); + traversal.arrivalTime = now + getTraversalTime(now, linkId, driverAgent); + traversal.modeIndex = modes.indexOf(leg.getMode()); + traversals.add(traversal); + + eventsManager.processEvent(new VehicleEntersTrafficEvent(now, traversal.agent.getId(), traversal.linkId, + Id.createVehicleId(agent.getId()), modes.get(traversal.modeIndex), 1.0)); + } else { // We have a handler and register traversals directly + NetworkRoute route = (NetworkRoute) leg.getRoute(); + now += getTraversalTime(now, route.getStartLinkId(), driverAgent); + + for (Id nextLinkId : route.getLinkIds()) { + handler.processEnterLink(now, nextLinkId); + now += getTraversalTime(now, nextLinkId, driverAgent); + } + + Traversal traversal = new Traversal(); + traversal.agent = (MobsimDriverAgent) agent; + traversal.linkId = route.getEndLinkId(); + traversal.arrivalTime = now; + traversal.modeIndex = modes.indexOf(leg.getMode()); + traversal.distance = route.getDistance(); + traversals.add(traversal); + } + + return true; + } + + @Override + public void doSimStep(double now) { + + EventsManager eventsManager = internalInterface.getMobsim().getEventsManager(); + Traversal traversal = null; + + while (!traversals.isEmpty() && traversals.peek().arrivalTime <= now) { + traversal = traversals.poll(); + + if (generateNetworkEvents) { + if (traversal.agent.isWantingToArriveOnCurrentLink()) { + eventsManager + .processEvent(new VehicleLeavesTrafficEvent(now, traversal.agent.getId(), traversal.linkId, + Id.createVehicleId(traversal.agent.getId()), modes.get(traversal.modeIndex), 1.0)); + + eventsManager.processEvent(new PersonLeavesVehicleEvent(now, traversal.agent.getId(), + Id.createVehicleId(traversal.agent.getId()))); + + traversal.agent.endLegAndComputeNextState(now); + internalInterface.arrangeNextAgentState(traversal.agent); + } else { + eventsManager.processEvent( + new LinkLeaveEvent(now, Id.createVehicleId(traversal.agent.getId()), traversal.linkId)); + + traversal.linkId = traversal.agent.chooseNextLinkId(); + traversal.arrivalTime = now + getTraversalTime(now, traversal.linkId, traversal.agent); + traversals.add(traversal); + + traversal.agent.notifyMoveOverNode(traversal.linkId); + + eventsManager.processEvent( + new LinkEnterEvent(now, Id.createVehicleId(traversal.agent.getId()), traversal.linkId)); + } + } else { + eventsManager.processEvent(new TeleportationArrivalEvent(now, traversal.agent.getId(), + traversal.distance, modes.get(traversal.modeIndex))); + traversal.agent.notifyArrivalOnLinkByNonNetworkMode(traversal.linkId); + + traversal.agent.endLegAndComputeNextState(now); + internalInterface.arrangeNextAgentState(traversal.agent); + } + } + } + + @Override + public void onPrepareSim() { + // Nothing to do + } + + @Override + public void afterSim() { + EventsManager eventsManager = internalInterface.getMobsim().getEventsManager(); + double now = internalInterface.getMobsim().getSimTimer().getTimeOfDay(); + + for (Traversal traversal : traversals) { + eventsManager.processEvent(new PersonStuckEvent(now, traversal.agent.getId(), + traversal.agent.getCurrentLinkId(), modes.get(traversal.modeIndex))); + } + } + + double getTraversalTime(double now, Id linkId, MobsimDriverAgent agent) { + Person person = ((HasPerson) agent).getPerson(); + Vehicle vehicle = null; // agent.getVehicle().getVehicle(); + Link link = network.getLinks().get(linkId); + + return travelTime.getLinkTravelTime(link, now, person, vehicle); + } + + @Override + public void setInternalInterface(InternalInterface internalInterface) { + this.internalInterface = internalInterface; + } + + private class Traversal { + MobsimDriverAgent agent; + Id linkId; + double arrivalTime; + int modeIndex; + double distance; + } + + private class TraversalComparator implements Comparator { + @Override + public int compare(Traversal a, Traversal b) { + return Double.compare(a.arrivalTime, b.arrivalTime); + } + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineConfigGroup.java b/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineConfigGroup.java new file mode 100644 index 000000000..885dfb930 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineConfigGroup.java @@ -0,0 +1,64 @@ +package org.eqasim.vdf.engine; + +import java.util.Arrays; +import java.util.Set; + +import org.matsim.api.core.v01.TransportMode; +import org.matsim.core.config.Config; +import org.matsim.core.config.ReflectiveConfigGroup; + +public class VDFEngineConfigGroup extends ReflectiveConfigGroup { + static public final String GROUP_NAME = "eqasim:vdf_engine"; + + static private final String MODES = "modes"; + static private final String GENERATE_NETWORK_EVENTS = "generateNetworkEvents"; + + private Set modes = Set.of(TransportMode.car); + + private boolean generateNetworkEvents = true; + + public VDFEngineConfigGroup() { + super(GROUP_NAME); + } + + public Set getModes() { + return modes; + } + + public void setModes(Set modes) { + this.modes.clear(); + this.modes.addAll(modes); + } + + @StringGetter(MODES) + public String getModesAsString() { + return String.join(",", modes); + } + + @StringSetter(MODES) + public void setModesAsString(String modes) { + this.modes.clear(); + Arrays.asList(modes.split(",")).stream().map(String::trim).forEach(this.modes::add); + } + + @StringGetter(GENERATE_NETWORK_EVENTS) + public boolean getGenerateNetworkEvents() { + return generateNetworkEvents; + } + + @StringSetter(GENERATE_NETWORK_EVENTS) + public void setGenerateNetworkEvents(boolean val) { + this.generateNetworkEvents = val; + } + + public static VDFEngineConfigGroup getOrCreate(Config config) { + VDFEngineConfigGroup group = (VDFEngineConfigGroup) config.getModules().get(GROUP_NAME); + + if (group == null) { + group = new VDFEngineConfigGroup(); + config.addModule(group); + } + + return group; + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineModule.java b/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineModule.java new file mode 100644 index 000000000..82b44d537 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/engine/VDFEngineModule.java @@ -0,0 +1,39 @@ +package org.eqasim.vdf.engine; + +import org.eqasim.vdf.handlers.VDFTrafficHandler; +import org.eqasim.vdf.travel_time.VDFTravelTime; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.mobsim.qsim.AbstractQSimModule; + +import com.google.common.base.Verify; +import com.google.inject.Provides; +import com.google.inject.Singleton; + +public class VDFEngineModule extends AbstractModule { + public static final String COMPONENT_NAME = "VDFEngine"; + + @Override + public void install() { + VDFEngineConfigGroup engineConfig = VDFEngineConfigGroup.getOrCreate(getConfig()); + + for (String mode : engineConfig.getModes()) { + Verify.verify(!getConfig().qsim().getMainModes().contains(mode)); + Verify.verify(!getConfig().plansCalcRoute().getModeRoutingParams().containsKey(mode)); + } + + installQSimModule(new AbstractQSimModule() { + @Override + protected void configureQSim() { + addQSimComponentBinding(COMPONENT_NAME).to(VDFEngine.class); + } + + @Provides + @Singleton + public VDFEngine provideVDFEngine(VDFTravelTime travelTime, Network network, VDFTrafficHandler handler) { + return new VDFEngine(engineConfig.getModes(), travelTime, network, handler, + engineConfig.getGenerateNetworkEvents()); + } + }); + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/handlers/VDFHorizonHandler.java b/vdf/src/main/java/org/eqasim/vdf/handlers/VDFHorizonHandler.java new file mode 100644 index 000000000..57c5fe381 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/handlers/VDFHorizonHandler.java @@ -0,0 +1,254 @@ +package org.eqasim.vdf.handlers; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.eqasim.vdf.VDFScope; +import org.eqasim.vdf.io.VDFReaderInterface; +import org.eqasim.vdf.io.VDFWriterInterface; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.events.LinkEnterEvent; +import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.utils.io.IOUtils; + +import com.google.common.base.Verify; + +public class VDFHorizonHandler implements VDFTrafficHandler, LinkEnterEventHandler { + private final VDFScope scope; + + private final Network network; + private final int horizon; + private final int numberOfThreads; + + private final IdMap> counts = new IdMap<>(Link.class); + private final List>> state = new LinkedList<>(); + + private final static Logger logger = Logger.getLogger(VDFHorizonHandler.class); + + public VDFHorizonHandler(Network network, VDFScope scope, int horizon, int numberOfThreads) { + this.scope = scope; + this.network = network; + this.horizon = horizon; + this.numberOfThreads = numberOfThreads; + + for (Id linkId : network.getLinks().keySet()) { + counts.put(linkId, new ArrayList<>(Collections.nCopies(scope.getIntervals(), 0.0))); + } + } + + @Override + public synchronized void handleEvent(LinkEnterEvent event) { + processEnterLink(event.getTime(), event.getLinkId()); + } + + public void processEnterLink(double time, Id linkId) { + int i = scope.getIntervalIndex(time); + double currentValue = counts.get(linkId).get(i); + counts.get(linkId).set(i, currentValue + 1); + } + + @Override + public IdMap> aggregate() { + while (state.size() > horizon) { + state.remove(0); + } + + logger.info(String.format("Starting aggregation of %d slices", state.size())); + + // Make a copy to add to the history + + IdMap> copy = new IdMap<>(Link.class); + + for (Map.Entry, List> entry : counts.entrySet()) { + copy.put(entry.getKey(), new ArrayList<>(entry.getValue())); + } + + state.add(copy); + + IdMap> aggregated = new IdMap<>(Link.class); + + for (Id linkId : network.getLinks().keySet()) { + // Reset current counts + counts.put(linkId, new ArrayList<>(Collections.nCopies(scope.getIntervals(), 0.0))); + + // Initialize aggregated counts + aggregated.put(linkId, new ArrayList<>(Collections.nCopies(scope.getIntervals(), 0.0))); + } + + // Aggregate + Iterator> linkIterator = network.getLinks().keySet().iterator(); + + Runnable worker = () -> { + Id currentLinkId = null; + + while (true) { + // Fetch new link in queue + synchronized (linkIterator) { + if (linkIterator.hasNext()) { + currentLinkId = linkIterator.next(); + } else { + break; // Done + } + } + + // Go through history for this link and aggregate by time slot + for (int k = 0; k < state.size(); k++) { + IdMap> historyItem = state.get(k); + List linkValues = historyItem.get(currentLinkId); + List linkAggregator = aggregated.get(currentLinkId); + + for (int i = 0; i < linkValues.size(); i++) { + linkAggregator.set(i, + linkAggregator.get(i) + (double) linkValues.get(i) / (double) state.size()); + } + } + } + }; + + if (numberOfThreads < 2) { + worker.run(); + } else { + List threads = new ArrayList<>(numberOfThreads); + + for (int k = 0; k < numberOfThreads; k++) { + threads.add(new Thread(worker)); + } + + for (int k = 0; k < numberOfThreads; k++) { + threads.get(k).start(); + } + + try { + for (int k = 0; k < numberOfThreads; k++) { + threads.get(k).join(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + logger.info(String.format(" Finished aggregation")); + + return aggregated; + } + + @Override + public VDFReaderInterface getReader() { + return new Reader(); + } + + @Override + public VDFWriterInterface getWriter() { + return new Writer(); + } + + public class Reader implements VDFReaderInterface { + @Override + public void readFile(URL inputFile) { + state.clear(); + + try { + DataInputStream inputStream = new DataInputStream(IOUtils.getInputStream(inputFile)); + + Verify.verify(inputStream.readDouble() == scope.getStartTime()); + Verify.verify(inputStream.readDouble() == scope.getEndTime()); + Verify.verify(inputStream.readDouble() == scope.getIntervalTime()); + Verify.verify(inputStream.readInt() == scope.getIntervals()); + Verify.verify(inputStream.readInt() == horizon); + + int slices = (int) inputStream.readInt(); + int links = (int) inputStream.readInt(); + + List> linkIds = new ArrayList<>(links); + for (int linkIndex = 0; linkIndex < links; linkIndex++) { + linkIds.add(Id.createLinkId(inputStream.readUTF())); + } + + logger.info(String.format("Loading %d slices with %d links", slices, links)); + + for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) { + IdMap> slice = new IdMap<>(Link.class); + state.add(slice); + + double totalLinkValue = 0.0; + double maximumLinkValue = 0.0; + + for (int linkIndex = 0; linkIndex < links; linkIndex++) { + List linkValues = new LinkedList<>(); + + Id linkId = linkIds.get(linkIndex); + slice.put(linkId, linkValues); + + for (int valueIndex = 0; valueIndex < scope.getIntervals(); valueIndex++) { + double linkValue = inputStream.readDouble(); + + linkValues.add(linkValue); + totalLinkValue += linkValue; + maximumLinkValue = Math.max(maximumLinkValue, linkValue); + } + } + + logger.info(String.format(" Slice %d: avg. value %f; max. value %f", sliceIndex, + totalLinkValue / links, maximumLinkValue)); + } + + Verify.verify(inputStream.available() == 0); + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public class Writer implements VDFWriterInterface { + @Override + public void writeFile(File outputFile) { + try { + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(outputFile.toString())); + + outputStream.writeDouble(scope.getStartTime()); + outputStream.writeDouble(scope.getEndTime()); + outputStream.writeDouble(scope.getIntervalTime()); + outputStream.writeInt(scope.getIntervals()); + outputStream.writeInt(horizon); + outputStream.writeInt(state.size()); + outputStream.writeInt(counts.size()); + + List> linkIds = new ArrayList<>(counts.keySet()); + for (int linkIndex = 0; linkIndex < linkIds.size(); linkIndex++) { + outputStream.writeUTF(linkIds.get(linkIndex).toString()); + } + + for (int sliceIndex = 0; sliceIndex < state.size(); sliceIndex++) { + IdMap> slice = state.get(sliceIndex); + + for (Id linkId : linkIds) { + List linkValues = slice.get(linkId); + + for (int valueIndex = 0; valueIndex < scope.getIntervals(); valueIndex++) { + outputStream.writeDouble(linkValues.get(valueIndex)); + } + } + } + + outputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/handlers/VDFInterpolationHandler.java b/vdf/src/main/java/org/eqasim/vdf/handlers/VDFInterpolationHandler.java new file mode 100644 index 000000000..ff550f0a9 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/handlers/VDFInterpolationHandler.java @@ -0,0 +1,133 @@ +package org.eqasim.vdf.handlers; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eqasim.vdf.VDFScope; +import org.eqasim.vdf.io.VDFReaderInterface; +import org.eqasim.vdf.io.VDFWriterInterface; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.events.LinkEnterEvent; +import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.utils.io.IOUtils; + +import com.google.common.base.Verify; + +public class VDFInterpolationHandler implements VDFTrafficHandler, LinkEnterEventHandler { + private final VDFScope scope; + + private final double updateFactor; + + private final IdMap> interpolatedCounts = new IdMap<>(Link.class); + private final IdMap> currentCounts = new IdMap<>(Link.class); + + public VDFInterpolationHandler(Network network, VDFScope scope, double updateFactor) { + this.scope = scope; + this.updateFactor = updateFactor; + + for (Id linkId : network.getLinks().keySet()) { + interpolatedCounts.put(linkId, new ArrayList<>(Collections.nCopies(scope.getIntervals(), 0.0))); + currentCounts.put(linkId, new ArrayList<>(Collections.nCopies(scope.getIntervals(), 0.0))); + } + } + + @Override + public synchronized void handleEvent(LinkEnterEvent event) { + processEnterLink(event.getTime(), event.getLinkId()); + } + + public void processEnterLink(double time, Id linkId) { + int i = scope.getIntervalIndex(time); + double currentValue = currentCounts.get(linkId).get(i); + currentCounts.get(linkId).set(i, currentValue + 1); + } + + @Override + public IdMap> aggregate() { + interpolatedCounts.forEach((id, interpolated) -> { + List current = currentCounts.get(id); + + for (int i = 0; i < interpolated.size(); i++) { + interpolated.set(i, (1.0 - updateFactor) * interpolated.get(i) + updateFactor * current.get(i)); + } + }); + + return interpolatedCounts; + } + + @Override + public VDFReaderInterface getReader() { + return new Reader(); + } + + @Override + public VDFWriterInterface getWriter() { + return new Writer(); + } + + public class Reader implements VDFReaderInterface { + @Override + public void readFile(URL inputFile) { + try { + DataInputStream inputStream = new DataInputStream(IOUtils.getInputStream(inputFile)); + + Verify.verify(inputStream.readDouble() == scope.getStartTime()); + Verify.verify(inputStream.readDouble() == scope.getEndTime()); + Verify.verify(inputStream.readDouble() == scope.getIntervalTime()); + Verify.verify(inputStream.readInt() == scope.getIntervals()); + + while (inputStream.available() > 0) { + Id linkId = Id.createLinkId(inputStream.readUTF()); + + List values = new ArrayList<>(Collections.nCopies(scope.getIntervals(), 0.0)); + for (int i = 0; i < values.size(); i++) { + values.set(i, inputStream.readDouble()); + } + + interpolatedCounts.put(linkId, values); + } + + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public class Writer implements VDFWriterInterface { + @Override + public void writeFile(File outputFile) { + try { + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(outputFile.toString())); + + outputStream.writeDouble(scope.getStartTime()); + outputStream.writeDouble(scope.getEndTime()); + outputStream.writeDouble(scope.getIntervalTime()); + outputStream.writeDouble(scope.getIntervals()); + + for (var entry : interpolatedCounts.entrySet()) { + outputStream.writeUTF(entry.getKey().toString()); + + List values = entry.getValue(); + for (int i = 0; i < values.size(); i++) { + outputStream.writeDouble(values.get(i)); + } + } + + outputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/vdf/src/main/java/org/eqasim/vdf/handlers/VDFTrafficHandler.java b/vdf/src/main/java/org/eqasim/vdf/handlers/VDFTrafficHandler.java new file mode 100644 index 000000000..e23bd690d --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/handlers/VDFTrafficHandler.java @@ -0,0 +1,19 @@ +package org.eqasim.vdf.handlers; + +import java.util.List; + +import org.eqasim.vdf.io.VDFReaderInterface; +import org.eqasim.vdf.io.VDFWriterInterface; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; + +public interface VDFTrafficHandler { + void processEnterLink(double time, Id linkId); + + IdMap> aggregate(); + + VDFReaderInterface getReader(); + + VDFWriterInterface getWriter(); +} diff --git a/vdf/src/main/java/org/eqasim/vdf/io/VDFReaderInterface.java b/vdf/src/main/java/org/eqasim/vdf/io/VDFReaderInterface.java new file mode 100644 index 000000000..f37622f5c --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/io/VDFReaderInterface.java @@ -0,0 +1,7 @@ +package org.eqasim.vdf.io; + +import java.net.URL; + +public interface VDFReaderInterface { + public void readFile(URL inputFile); +} diff --git a/vdf/src/main/java/org/eqasim/vdf/io/VDFWriterInterface.java b/vdf/src/main/java/org/eqasim/vdf/io/VDFWriterInterface.java new file mode 100644 index 000000000..c9c2ffa92 --- /dev/null +++ b/vdf/src/main/java/org/eqasim/vdf/io/VDFWriterInterface.java @@ -0,0 +1,7 @@ +package org.eqasim.vdf.io; + +import java.io.File; + +public interface VDFWriterInterface { + public void writeFile(File outputFile); +} diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFLinkSpeedCalculator.java b/vdf/src/main/java/org/eqasim/vdf/travel_time/VDFLinkSpeedCalculator.java similarity index 96% rename from vdf/src/main/java/org/eqasim/vdf/VDFLinkSpeedCalculator.java rename to vdf/src/main/java/org/eqasim/vdf/travel_time/VDFLinkSpeedCalculator.java index 2c031e5b0..f1df5d07e 100644 --- a/vdf/src/main/java/org/eqasim/vdf/VDFLinkSpeedCalculator.java +++ b/vdf/src/main/java/org/eqasim/vdf/travel_time/VDFLinkSpeedCalculator.java @@ -1,4 +1,4 @@ -package org.eqasim.vdf; +package org.eqasim.vdf.travel_time; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.population.Person; diff --git a/vdf/src/main/java/org/eqasim/vdf/VDFTravelTime.java b/vdf/src/main/java/org/eqasim/vdf/travel_time/VDFTravelTime.java similarity index 55% rename from vdf/src/main/java/org/eqasim/vdf/VDFTravelTime.java rename to vdf/src/main/java/org/eqasim/vdf/travel_time/VDFTravelTime.java index b684f928b..f129967ac 100644 --- a/vdf/src/main/java/org/eqasim/vdf/VDFTravelTime.java +++ b/vdf/src/main/java/org/eqasim/vdf/travel_time/VDFTravelTime.java @@ -1,11 +1,13 @@ -package org.eqasim.vdf; +package org.eqasim.vdf.travel_time; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import org.eqasim.vdf.function.VolumeDelayFunction; +import org.apache.log4j.Logger; +import org.eqasim.vdf.VDFScope; +import org.eqasim.vdf.travel_time.function.VolumeDelayFunction; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.network.Link; @@ -15,11 +17,11 @@ import org.matsim.vehicles.Vehicle; public class VDFTravelTime implements TravelTime { - private final double startTime; - private final double interval; - private final int numberOfIntervals; + private final VDFScope scope; + private final double minimumSpeed; - private final double flowCapacityFactor; + private final double capacityFactor; + private final double samplingRate; private final double crossingPenalty; private final Network network; @@ -27,49 +29,63 @@ public class VDFTravelTime implements TravelTime { private final IdMap> travelTimes = new IdMap<>(Link.class); - public VDFTravelTime(double startTime, double interval, int numberOfIntervals, double minimumSpeed, - double flowCapacityFacotor, Network network, VolumeDelayFunction vdf, double crossingPenalty) { - this.startTime = startTime; - this.interval = interval; - this.numberOfIntervals = numberOfIntervals; + private final Logger logger = Logger.getLogger(VDFTravelTime.class); + + public VDFTravelTime(VDFScope scope, double minimumSpeed, double capacityFacotor, double samplingRate, + Network network, VolumeDelayFunction vdf, double crossingPenalty) { + this.scope = scope; this.network = network; this.vdf = vdf; this.minimumSpeed = minimumSpeed; - this.flowCapacityFactor = flowCapacityFacotor; + this.capacityFactor = capacityFacotor; + this.samplingRate = samplingRate; this.crossingPenalty = crossingPenalty; for (Link link : network.getLinks().values()) { double travelTime = Math.max(1.0, - Math.max(link.getLength() / minimumSpeed, link.getLength() / link.getFreespeed())); - travelTimes.put(link.getId(), new ArrayList<>(Collections.nCopies(numberOfIntervals, travelTime))); + Math.min(link.getLength() / minimumSpeed, link.getLength() / link.getFreespeed())); + travelTimes.put(link.getId(), new ArrayList<>( + Collections.nCopies(scope.getIntervals(), considerCrossingPenalty(link, travelTime)))); } } @Override public double getLinkTravelTime(Link link, double time, Person person, Vehicle vehicle) { - int i = getInterval(time); + int i = scope.getIntervalIndex(time); return travelTimes.get(link.getId()).get(i); } public void update(IdMap> counts) { + logger.info(String.format("Updating VDFTravelTime ...")); + + long totalCount = counts.size() * scope.getIntervals(); + long nonFreespeedCount = 0; + for (Map.Entry, List> entry : counts.entrySet()) { Link link = network.getLinks().get(entry.getKey()); List linkCounts = entry.getValue(); List linkTravelTimes = travelTimes.get(entry.getKey()); - for (int i = 0; i < numberOfIntervals; i++) { - double time = startTime + i * interval; + for (int i = 0; i < scope.getIntervals(); i++) { + double time = scope.getStartTime() + i * scope.getIntervalTime(); // Pass per interval - double flow = linkCounts.get(i) / flowCapacityFactor; - double capacity = interval * link.getCapacity(time) / network.getCapacityPeriod(); + double flow = linkCounts.get(i) / samplingRate; + double capacity = capacityFactor * scope.getIntervalTime() * link.getCapacity(time) + / network.getCapacityPeriod(); double travelTime = Math.max(1.0, - Math.max(link.getLength() / minimumSpeed, vdf.getTravelTime(time, flow, capacity, link))); + Math.min(link.getLength() / minimumSpeed, vdf.getTravelTime(time, flow, capacity, link))); linkTravelTimes.set(i, considerCrossingPenalty(link, travelTime)); + + if (travelTime > link.getLength() / link.getFreespeed()) { + nonFreespeedCount += 1; + } } } + + logger.info(String.format(" Done: %d/%d are slower than freespeed", nonFreespeedCount, totalCount)); } private double considerCrossingPenalty(Link link, double baseTravelTime) { @@ -87,12 +103,4 @@ private double considerCrossingPenalty(Link link, double baseTravelTime) { return baseTravelTime + crossingPenalty; } } - - public int getNumberOfIntervals() { - return numberOfIntervals; - } - - public int getInterval(double time) { - return Math.min(Math.max(0, (int) Math.floor((time - startTime) / interval)), numberOfIntervals - 1); - } } diff --git a/vdf/src/main/java/org/eqasim/vdf/function/BPRFunction.java b/vdf/src/main/java/org/eqasim/vdf/travel_time/function/BPRFunction.java similarity index 92% rename from vdf/src/main/java/org/eqasim/vdf/function/BPRFunction.java rename to vdf/src/main/java/org/eqasim/vdf/travel_time/function/BPRFunction.java index 8e4560b47..9ccf49816 100644 --- a/vdf/src/main/java/org/eqasim/vdf/function/BPRFunction.java +++ b/vdf/src/main/java/org/eqasim/vdf/travel_time/function/BPRFunction.java @@ -1,4 +1,4 @@ -package org.eqasim.vdf.function; +package org.eqasim.vdf.travel_time.function; import org.matsim.api.core.v01.network.Link; diff --git a/vdf/src/main/java/org/eqasim/vdf/function/VolumeDelayFunction.java b/vdf/src/main/java/org/eqasim/vdf/travel_time/function/VolumeDelayFunction.java similarity index 78% rename from vdf/src/main/java/org/eqasim/vdf/function/VolumeDelayFunction.java rename to vdf/src/main/java/org/eqasim/vdf/travel_time/function/VolumeDelayFunction.java index c1b8350ea..8a8f65950 100644 --- a/vdf/src/main/java/org/eqasim/vdf/function/VolumeDelayFunction.java +++ b/vdf/src/main/java/org/eqasim/vdf/travel_time/function/VolumeDelayFunction.java @@ -1,4 +1,4 @@ -package org.eqasim.vdf.function; +package org.eqasim.vdf.travel_time.function; import org.matsim.api.core.v01.network.Link;