Skip to content

Commit

Permalink
feat: update vdf to latest internal version (#193)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
sebhoerl and balacmi authored Mar 5, 2024
1 parent 81a0bb1 commit cb21340
Show file tree
Hide file tree
Showing 23 changed files with 1,308 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -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<String> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {});
}
}
40 changes: 40 additions & 0 deletions vdf/README.md
Original file line number Diff line number Diff line change
@@ -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`.

122 changes: 122 additions & 0 deletions vdf/src/main/java/org/eqasim/vdf/VDFConfigGroup.java
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand All @@ -24,6 +39,22 @@ public class VDFConfigGroup extends ReflectiveConfigGroup {
private double bprFactor = 0.15;
private double bprExponent = 4.0;

private Set<String> 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);
}
Expand Down Expand Up @@ -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<String> getModes() {
return modes;
}

public void setModes(Set<String> 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;
}
}
Loading

0 comments on commit cb21340

Please sign in to comment.