org.matsim.integration.weekly.fundamentaldiagram.CreateAutomatedFDTest.java Source code

Java tutorial

Introduction

Here is the source code for org.matsim.integration.weekly.fundamentaldiagram.CreateAutomatedFDTest.java

Source

/* *********************************************************************** *
 * project: org.matsim.*
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 * copyright       : (C) 2014 by the members listed in the COPYING,        *
 *                   LICENSE and WARRANTY file.                            *
 * email           : info at matsim dot org                                *
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *   See also COPYING, LICENSE and WARRANTY file                           *
 *                                                                         *
 * *********************************************************************** */
package org.matsim.integration.weekly.fundamentaldiagram;

import java.io.File;
import java.io.IOException;
import java.util.*;
import org.apache.log4j.Logger;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
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.api.core.v01.network.Node;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.config.groups.QSimConfigGroup.LinkDynamics;
import org.matsim.core.config.groups.QSimConfigGroup.TrafficDynamics;
import org.matsim.core.config.groups.VspExperimentalConfigGroup.VspDefaultsCheckingLevel;
import org.matsim.core.events.EventsUtils;
import org.matsim.core.gbl.MatsimRandom;
import org.matsim.core.mobsim.framework.AgentSource;
import org.matsim.core.mobsim.framework.MobsimAgent;
import org.matsim.core.mobsim.framework.MobsimDriverAgent;
import org.matsim.core.mobsim.qsim.ActivityEngine;
import org.matsim.core.mobsim.qsim.QSim;
import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle;
import org.matsim.core.mobsim.qsim.qnetsimengine.QNetsimEngine;
import org.matsim.core.network.NetworkUtils;
import org.matsim.core.scenario.ScenarioUtils;
import org.matsim.core.utils.collections.Tuple;
import org.matsim.facilities.Facility;
import org.matsim.testcases.MatsimTestUtils;
import org.matsim.vehicles.Vehicle;
import org.matsim.vehicles.VehicleType;
import org.matsim.vehicles.VehicleUtils;

/**
 * Generates fundamental diagrams for the all combination of link and traffic dynamics for car/bike and car/truck groups.
 * Also generates car/bike FDs using fast capacity update method.
 * Results are archived at @see <a href="https://svn.vsp.tu-berlin.de/testresults/">https://svn.vsp.tu-berlin.de/testresults/</a>
 * @author amit
 */

@RunWith(Parameterized.class)
public class CreateAutomatedFDTest {

    /**
     * Constructor.  Even if it does not look like one.
     */
    public CreateAutomatedFDTest(LinkDynamics linkDynamics, TrafficDynamics trafficDynamics) {
        this.linkDynamics = linkDynamics;
        this.trafficDynamics = trafficDynamics;
        this.travelModes = new String[] { "car", "bike" };
    }

    private LinkDynamics linkDynamics;
    private TrafficDynamics trafficDynamics;
    private final Map<Id<Person>, String> person2Mode = new HashMap<>();

    @Parameters(name = "{index}: LinkDynamics == {0}; Traffic dynamics == {1};")
    public static Collection<Object[]> createFds() {
        int combos = LinkDynamics.values().length * TrafficDynamics.values().length;
        Object[][] combos2run = new Object[combos][2]; // #ld x #td x #params
        int index = 0;
        for (LinkDynamics ld : LinkDynamics.values()) {
            for (TrafficDynamics td : TrafficDynamics.values()) {
                combos2run[index] = new Object[] { ld, td };
                index++;
            }
        }
        return Arrays.asList(combos2run);

        // the convention, I think, is that the output of the method marked by "@Parameters" is taken as input to the constructor
        // before running each test. kai, jul'16
    }

    @Test
    public void fdsCarTruck() {
        this.travelModes = new String[] { "car", "truck" };
        run(false);
    }

    @Test
    public void fdsCarBike() {
        run(false);
    }

    @Test
    public void fdsCarBikeFastCapacityUpdate() {
        run(true);
    }

    @Test
    public void fdsCarOnly() {
        this.travelModes = new String[] { "car" };
        run(false);
    }

    @Rule
    public MatsimTestUtils helper = new MatsimTestUtils();

    private String[] travelModes;
    public final Id<Link> flowDynamicsMeasurementLinkId = Id.createLinkId(0);
    private Map<String, VehicleType> modeVehicleTypes;
    private Map<Id<VehicleType>, TravelModesFlowDynamicsUpdator> mode2FlowData;
    static GlobalFlowDynamicsUpdator globalFlowDynamicsUpdator;

    private final static Logger LOG = Logger.getLogger(CreateAutomatedFDTest.class);

    private void run(final boolean isUsingFastCapacityUpdate) {

        MatsimRandom.reset();

        final Config config = ConfigUtils.createConfig();
        config.qsim().setMainModes(Arrays.asList(travelModes));
        config.qsim().setEndTime(14 * 3600);
        config.qsim().setLinkDynamics(linkDynamics);

        if (linkDynamics.equals(LinkDynamics.SeepageQ)) {
            config.qsim().setSeepModes(Arrays.asList("bike"));
            config.qsim().setSeepModeStorageFree(false);
            config.qsim().setRestrictingSeepage(true);
        }

        config.vspExperimental().setVspDefaultsCheckingLevel(VspDefaultsCheckingLevel.abort);
        config.qsim().setTrafficDynamics(trafficDynamics);

        config.qsim().setUsingFastCapacityUpdate(isUsingFastCapacityUpdate);

        // ---

        Scenario scenario = ScenarioUtils.loadScenario(config);
        createNetwork(scenario);

        storeVehicleTypeInfo();

        double networkDensity = 3. * (1000. / 7.5);

        double sumOfPCUInEachStep = 0.;

        //equal modal split run
        for (String mode : travelModes) {
            sumOfPCUInEachStep += modeVehicleTypes.get(mode).getPcuEquivalents() * getMinNumberOfAgentAtStart(mode);
        }
        ;

        int reduceNoOfDataPointsInPlot = 4; // 1--> will generate all possible data points;
        if (sumOfPCUInEachStep >= 3)
            reduceNoOfDataPointsInPlot = 1;

        int numberOfPoints = (int) Math.ceil(networkDensity / (reduceNoOfDataPointsInPlot * sumOfPCUInEachStep))
                + 5;

        List<Map<String, Integer>> points2Run = new ArrayList<>();

        for (int m = 1; m < numberOfPoints; m++) {
            Map<String, Integer> pointToRun = new HashMap<>();
            for (String mode : travelModes) {
                pointToRun.put(mode, getMinNumberOfAgentAtStart(mode) * m * reduceNoOfDataPointsInPlot);
            }

            double density = 0;
            for (String mode : pointToRun.keySet()) {
                double pcu = this.modeVehicleTypes.get(mode).getPcuEquivalents();
                density += pcu * pointToRun.get(mode);
            }

            if (density <= networkDensity + 10) {
                System.out.println("Number of Agents - \t" + pointToRun.toString());
                points2Run.add(pointToRun);
            }
        }

        Map<Double, Map<String, Tuple<Double, Double>>> outData = new HashMap<>();

        for (Map<String, Integer> point2run : points2Run) {

            System.out.println("\n \n \t \t Running points " + point2run.toString() + "\n \n");

            int count = 0;
            person2Mode.clear();

            for (String mode : point2run.keySet()) {
                for (int ii = 0; ii < point2run.get(mode); ii++) {
                    person2Mode.put(Id.createPersonId(count++), mode);
                }
                this.mode2FlowData.get(modeVehicleTypes.get(mode).getId()).setnumberOfAgents(point2run.get(mode));
            }

            EventsManager events = EventsUtils.createEventsManager();
            globalFlowDynamicsUpdator = new GlobalFlowDynamicsUpdator(mode2FlowData);
            events.addHandler(globalFlowDynamicsUpdator);

            final QSim qSim = new QSim(scenario, events);
            ActivityEngine activityEngine = new ActivityEngine(events, qSim.getAgentCounter());
            qSim.addMobsimEngine(activityEngine);
            qSim.addActivityHandler(activityEngine);
            QNetsimEngine netsimEngine;
            //         if ( config.qsim().getTrafficDynamics()==TrafficDynamics.assignmentEmulating ) {
            //            QNetworkFactory networkFactory = new AssignmentEmulatingQLaneNetworkFactory(scenario,events) ;
            //            netsimEngine = new QNetsimEngine( qSim, networkFactory ) ;
            //         } else {
            netsimEngine = new QNetsimEngine(qSim);
            //         }
            qSim.addMobsimEngine(netsimEngine);
            qSim.addDepartureHandler(netsimEngine.getDepartureHandler());

            final Map<String, VehicleType> travelModesTypes = new HashMap<>();

            for (String mode : travelModes) {
                travelModesTypes.put(mode, modeVehicleTypes.get(mode));
            }

            AgentSource agentSource = new AgentSource() {
                @Override
                public void insertAgentsIntoMobsim() {
                    for (Id<Person> personId : person2Mode.keySet()) {
                        String travelMode = person2Mode.get(personId);
                        double actEndTime = (MatsimRandom.getRandom().nextDouble()) * 900;

                        MobsimAgent agent = new MySimplifiedRoundAndRoundAgent(personId, actEndTime, travelMode);
                        qSim.insertAgentIntoMobsim(agent);

                        final Vehicle vehicle = VehicleUtils.getFactory().createVehicle(
                                Id.create(agent.getId(), Vehicle.class), travelModesTypes.get(travelMode));
                        final Id<Link> linkId4VehicleInsertion = Id.createLinkId("home");
                        qSim.createAndParkVehicleOnLink(vehicle, linkId4VehicleInsertion);
                    }
                }
            };

            qSim.addAgentSource(agentSource);

            qSim.run();

            Map<String, Tuple<Double, Double>> mode2FlowSpeed = new HashMap<>();
            for (int i = 0; i < travelModes.length; i++) {

                Tuple<Double, Double> flowSpeed = new Tuple<>(
                        this.mode2FlowData.get(Id.create(travelModes[i], VehicleType.class)).getPermanentFlow(),
                        this.mode2FlowData.get(Id.create(travelModes[i], VehicleType.class))
                                .getPermanentAverageVelocity());
                mode2FlowSpeed.put(travelModes[i], flowSpeed);
                outData.put(globalFlowDynamicsUpdator.getGlobalData().getPermanentDensity(), mode2FlowSpeed);
            }
        }

        /*
         *   Basically overriding the helper.getOutputDirectory() method, such that,
         *   if file directory does not exists or same file already exists, remove and re-creates the whole dir hierarchy so that
         *   all existing files are re-written 
         *   else, just keep adding files in the directory.   
         *   This is necessary in order to allow writing different tests results from JUnit parameterization.
         */

        String outDir = "test/output/" + CreateAutomatedFDTest.class.getCanonicalName().replace('.', '/') + "/"
                + helper.getMethodName() + "/";
        String fileName = linkDynamics + "_" + trafficDynamics + ".png";

        String outFile;
        //TODO : what if, there exists some different directory (or files with old filename) => changing method name will keep collecting the old data.
        if (!new File(outDir).exists() || new File(outDir + fileName).exists()) {
            outFile = helper.getOutputDirectory() + fileName;
        } else {
            outFile = outDir + fileName;
        }

        //plotting data
        scatterPlot(outData, outFile);
    }

    static int getMinNumberOfAgentAtStart(final String mode) {//equal modal split run
        // only three different modes (and pcus) are used in this test ie -- car(1), truck(3), bike(0.25)
        switch (mode) {
        case "car":
            return 1;
        case "truck":
            return 3;
        case "motorbike":
        case "bike":
            return 4;
        default:
            throw new RuntimeException("The test is not designed for this " + mode + "yet.");
        }
    }

    static class MySimplifiedRoundAndRoundAgent implements MobsimAgent, MobsimDriverAgent {

        private static final Id<Link> ORIGIN_LINK_ID = Id.createLinkId("home");
        private static final Id<Link> BASE_LINK_ID = Id.createLinkId(0);
        private static final Id<Link> MIDDEL_LINK_ID_OF_TRACK = Id.createLinkId(1);
        private static final Id<Link> LAST_LINK_ID_OF_TRACK = Id.createLinkId(2);
        private static final Id<Link> DESTINATION_LINK_ID = Id.createLinkId("work");

        public MySimplifiedRoundAndRoundAgent(Id<Person> agentId, double actEndTime, String travelMode) {
            personId = agentId;
            mode = travelMode;
            this.actEndTime = actEndTime;
            this.plannedVehicleId = Id.create(agentId, Vehicle.class);
        }

        private final Id<Person> personId;
        private final Id<Vehicle> plannedVehicleId;
        private final String mode;
        private final double actEndTime;

        private MobsimVehicle vehicle;
        public boolean isArriving = false;

        private Id<Link> currentLinkId = ORIGIN_LINK_ID;
        private State agentState = MobsimAgent.State.ACTIVITY;;

        @Override
        public Id<Link> getCurrentLinkId() {
            return this.currentLinkId;
        }

        @Override
        public Id<Link> getDestinationLinkId() {
            return DESTINATION_LINK_ID;
        }

        @Override
        public Id<Person> getId() {
            return this.personId;
        }

        @Override
        public Id<Link> chooseNextLinkId() {
            if (globalFlowDynamicsUpdator.isPermanent()) {
                isArriving = true;
            }

            if (ORIGIN_LINK_ID.equals(this.currentLinkId)) {
                return BASE_LINK_ID;
            } else if (BASE_LINK_ID.equals(this.currentLinkId)) {
                if (isArriving) {
                    return DESTINATION_LINK_ID;
                } else {
                    return MIDDEL_LINK_ID_OF_TRACK;
                }
            } else if (MIDDEL_LINK_ID_OF_TRACK.equals(this.currentLinkId)) {
                return LAST_LINK_ID_OF_TRACK;
            } else if (LAST_LINK_ID_OF_TRACK.equals(this.currentLinkId)) {
                return BASE_LINK_ID;
            } else
                return null; // returning null so that agent will arrive.
        }

        @Override
        public void notifyMoveOverNode(Id<Link> newLinkId) {
            this.currentLinkId = newLinkId;
        }

        @Override
        public boolean isWantingToArriveOnCurrentLink() {
            if (this.chooseNextLinkId() == null) {
                return true;
            } else {
                return false;
            }
        }

        @Override
        public void setVehicle(MobsimVehicle veh) {
            this.vehicle = veh;
        }

        @Override
        public MobsimVehicle getVehicle() {
            return this.vehicle;
        }

        @Override
        public Id<Vehicle> getPlannedVehicleId() {
            return this.plannedVehicleId;
        }

        @Override
        public State getState() {
            return agentState;
        }

        @Override
        public double getActivityEndTime() {
            if (isArriving && agentState.equals(MobsimAgent.State.ACTIVITY)) {
                return Double.POSITIVE_INFINITY;
            }
            return this.actEndTime;
        }

        @Override
        public void endActivityAndComputeNextState(double now) {
            agentState = MobsimAgent.State.LEG;
        }

        @Override
        public void endLegAndComputeNextState(double now) {
            agentState = MobsimAgent.State.ACTIVITY;
        }

        @Override
        public void setStateToAbort(double now) {
            throw new RuntimeException("not implemented");
        }

        @Override
        public Double getExpectedTravelTime() {
            throw new RuntimeException("not implemented");
        }

        @Override
        public Double getExpectedTravelDistance() {
            throw new RuntimeException("not implemented");
        }

        @Override
        public String getMode() {
            return mode;
        }

        @Override
        public void notifyArrivalOnLinkByNonNetworkMode(Id<Link> linkId) {
            throw new RuntimeException("not implemented");
        }

        @Override
        public Facility<? extends Facility<?>> getCurrentFacility() {
            throw new RuntimeException("not implemented");
        }

        @Override
        public Facility<? extends Facility<?>> getDestinationFacility() {
            throw new RuntimeException("not implemented");
        }
    }

    private void storeVehicleTypeInfo() {
        modeVehicleTypes = new HashMap<>();
        mode2FlowData = new HashMap<>();

        VehicleType car = VehicleUtils.getFactory().createVehicleType(Id.create("car", VehicleType.class));
        car.setMaximumVelocity(16.667);
        car.setPcuEquivalents(1.0);
        modeVehicleTypes.put("car", car);

        VehicleType bike = VehicleUtils.getFactory().createVehicleType(Id.create("bike", VehicleType.class));
        bike.setMaximumVelocity(4.167);
        bike.setPcuEquivalents(0.25);
        modeVehicleTypes.put("bike", bike);

        VehicleType truck = VehicleUtils.getFactory().createVehicleType(Id.create("truck", VehicleType.class));
        truck.setMaximumVelocity(8.33);
        truck.setPcuEquivalents(3.);
        modeVehicleTypes.put("truck", truck);

        for (String mode : travelModes) {
            TravelModesFlowDynamicsUpdator modeUpdator = new TravelModesFlowDynamicsUpdator(
                    modeVehicleTypes.get(mode));
            mode2FlowData.put(modeVehicleTypes.get(mode).getId(), modeUpdator);
        }
    }

    private void createNetwork(Scenario scenario) {
        Network network = scenario.getNetwork();

        double x = -50;
        Node nodeHome = NetworkUtils.createAndAddNode(network, Id.createNodeId("home"), new Coord(x, (double) 0));
        Node node1 = NetworkUtils.createAndAddNode(network, Id.createNodeId(0), new Coord((double) 0, (double) 0));
        Node node2 = NetworkUtils.createAndAddNode(network, Id.createNodeId(1),
                new Coord((double) 1000, (double) 0));
        Node node3 = NetworkUtils.createAndAddNode(network, Id.createNodeId(2), new Coord((double) 500, 866.0));
        Node nodeWork = NetworkUtils.createAndAddNode(network, Id.createNodeId("work"),
                new Coord((double) 1050, (double) 0));

        double freeSpeedOnLink = 60 / 3.6;
        final Node fromNode = nodeHome;
        final Node toNode = node1;
        final double freespeed = freeSpeedOnLink;
        NetworkUtils.createAndAddLink(network, Id.createLinkId("home"), fromNode, toNode, (double) 25, freespeed,
                (double) 7200, (double) 1);
        final Node fromNode1 = node1;
        final Node toNode1 = node2;
        final double freespeed1 = freeSpeedOnLink;
        NetworkUtils.createAndAddLink(network, Id.createLinkId(0), fromNode1, toNode1, (double) 1000, freespeed1,
                (double) 1600, (double) 1);
        final Node fromNode2 = node2;
        final Node toNode2 = node3;
        final double freespeed2 = freeSpeedOnLink;
        NetworkUtils.createAndAddLink(network, Id.createLinkId(1), fromNode2, toNode2, (double) 1000, freespeed2,
                (double) 1600, (double) 1);
        final Node fromNode3 = node3;
        final Node toNode3 = node1;
        final double freespeed3 = freeSpeedOnLink;
        NetworkUtils.createAndAddLink(network, Id.createLinkId(2), fromNode3, toNode3, (double) 1000, freespeed3,
                (double) 1600, (double) 1);
        final Node fromNode4 = node2;
        final Node toNode4 = nodeWork;
        final double freespeed4 = freeSpeedOnLink;
        NetworkUtils.createAndAddLink(network, Id.createLinkId("work"), fromNode4, toNode4, (double) 25, freespeed4,
                (double) 7200, (double) 1);

        Set<String> allowedModes = new HashSet<>();
        allowedModes.addAll(Arrays.asList(travelModes));

        for (Link l : network.getLinks().values()) {
            l.setAllowedModes(allowedModes);
        }
    }

    private void scatterPlot(Map<Double, Map<String, Tuple<Double, Double>>> inputData, String outFile) {

        String mode1 = travelModes[0];
        XYSeries carFlow = new XYSeries(mode1 + " flow");
        XYSeries carSpeed = new XYSeries(mode1 + " speed");

        XYSeries bikeFlow = null;
        XYSeries bikeSpeed = null;

        if (travelModes.length == 2) {
            bikeFlow = new XYSeries(travelModes[1] + " flow");
            bikeSpeed = new XYSeries(travelModes[1] + " speed");
        }

        for (double d : inputData.keySet()) {
            carFlow.add(d, inputData.get(d).get(mode1).getFirst());
            carSpeed.add(d, inputData.get(d).get(mode1).getSecond());

            if (travelModes.length == 2) {
                bikeFlow.add(d, inputData.get(d).get(travelModes[1]).getFirst());
                bikeSpeed.add(d, inputData.get(d).get(travelModes[1]).getSecond());
            }
        }

        // flow vs density
        XYSeriesCollection flowDataset = new XYSeriesCollection();
        flowDataset.addSeries(carFlow);

        NumberAxis flowAxis = new NumberAxis("Flow (PCU/h)");
        flowAxis.setRange(0.0, 1700.0);

        XYPlot plot1 = new XYPlot(flowDataset, null, flowAxis, new XYLineAndShapeRenderer(false, true));
        plot1.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);

        // speed vs density
        XYSeriesCollection speedDataset = new XYSeriesCollection();
        speedDataset.addSeries(carSpeed);

        if (travelModes.length == 2) {
            flowDataset.addSeries(bikeFlow);
            speedDataset.addSeries(bikeSpeed);
        }

        NumberAxis speedAxis = new NumberAxis("Speed (m/s)");
        speedAxis.setRange(0.0, 17.0);

        XYPlot plot2 = new XYPlot(speedDataset, null, speedAxis, new XYLineAndShapeRenderer(false, true));
        plot2.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT);

        NumberAxis densityAxis = new NumberAxis("Overall density (PCU/km)");
        densityAxis.setRange(0.0, 140.00);

        CombinedDomainXYPlot plot = new CombinedDomainXYPlot(densityAxis);
        plot.setGap(10.);
        plot.add(plot1);
        plot.add(plot2);
        plot.setOrientation(PlotOrientation.VERTICAL);

        JFreeChart chart = new JFreeChart("Fundamental diagrams", JFreeChart.DEFAULT_TITLE_FONT, plot, true);

        try {
            ChartUtilities.saveChartAsPNG(new File(outFile), chart, 800, 600);
        } catch (IOException e) {
            throw new RuntimeException("Data is not plotted. Reason " + e);
        }
    }

    //==============================================

    class TravelModesFlowDynamicsUpdator {

        private final int NUMBER_OF_MEMORIZED_FLOWS = 10;
        private Id<VehicleType> modeId;
        private VehicleType vehicleType = null;//      Maybe keeping global data in the EventHandler can be smart (ssix, 25.09.13)
        //        So far programmed to contain also global data, i.e. data without a specific vehicleType (ssix, 30.09.13)
        public int numberOfAgents;
        private double permanentDensity;
        private double permanentAverageVelocity;
        private double permanentFlow;

        private Map<Id<Vehicle>, Double> lastSeenOnStudiedLinkEnter;//records last entry time for every person, but also useful for getting actual number of people in the simulation
        private int speedTableSize;
        private List<Double> speedTable;
        private Double flowTime;
        private List<Double> flowTable900;
        private List<Double> lastXFlows900;;//recording a number of flows to ensure stability
        private boolean speedStability;
        private boolean flowStability;

        public TravelModesFlowDynamicsUpdator() {
        }

        public TravelModesFlowDynamicsUpdator(VehicleType vT) {
            this.vehicleType = vT;
            this.modeId = this.vehicleType.getId();
        }

        public void handle(LinkEnterEvent event) {
            if (event.getLinkId().equals(flowDynamicsMeasurementLinkId)) {
                //            Id<Person> personId = Id.createPersonId(event.getDriverId());
                double nowTime = event.getTime();

                this.updateFlow900(nowTime, this.vehicleType.getPcuEquivalents());
                //            this.updateSpeedTable(nowTime, personId);
                this.updateSpeedTable(nowTime, event.getVehicleId());

                //Checking for stability
                //Making sure all agents are on the track before testing stability
                //Also waiting half an hour to let the database build itself.

                if ((this.getNumberOfDrivingAgents() == this.numberOfAgents) && (nowTime > 1800)) {//TODO empirical factor
                    if (!(this.speedStability)) {
                        this.checkSpeedStability();
                    }
                    if (!(this.flowStability)) {
                        this.checkFlowStability900();
                    }
                }
            }
        }

        private void updateFlow900(double nowTime, double pcuVeh) {
            if (nowTime == this.flowTime.doubleValue()) {//Still measuring the flow of the same second
                Double nowFlow = this.flowTable900.get(0);
                this.flowTable900.set(0, nowFlow.doubleValue() + pcuVeh);
            } else {//Need to offset the new flow table from existing flow table.
                int timeDifference = (int) (nowTime - this.flowTime.doubleValue());
                if (timeDifference < 900) {
                    for (int i = 899 - timeDifference; i >= 0; i--) {
                        this.flowTable900.set(i + timeDifference, this.flowTable900.get(i).doubleValue());
                    }
                    if (timeDifference > 1) {
                        for (int i = 1; i < timeDifference; i++) {
                            this.flowTable900.set(i, 0.);
                        }
                    }
                    this.flowTable900.set(0, pcuVeh);
                } else {
                    flowTableReset();
                }
                this.flowTime = new Double(nowTime);
            }
            updateLastXFlows900();
        }

        private void updateLastXFlows900() {
            Double nowFlow = new Double(this.getCurrentHourlyFlow());
            for (int i = NUMBER_OF_MEMORIZED_FLOWS - 2; i >= 0; i--) {
                this.lastXFlows900.set(i + 1, this.lastXFlows900.get(i).doubleValue());
            }
            this.lastXFlows900.set(0, nowFlow);
        }

        private void updateSpeedTable(double nowTime, Id<Vehicle> vehicleId) {
            if (this.lastSeenOnStudiedLinkEnter.containsKey(vehicleId)) {
                double lastSeenTime = lastSeenOnStudiedLinkEnter.get(vehicleId);
                double speed = 1000 * 3 / (nowTime - lastSeenTime);//in m/s!!
                for (int i = speedTableSize - 2; i >= 0; i--) {
                    this.speedTable.set(i + 1, this.speedTable.get(i).doubleValue());
                }
                this.speedTable.set(0, speed);

                this.lastSeenOnStudiedLinkEnter.put(vehicleId, nowTime);
            } else {
                this.lastSeenOnStudiedLinkEnter.put(vehicleId, nowTime);
            }
            //this.numberOfDrivingAgents = this.lastSeenOnStudiedLinkEnter.size();
        }

        private void checkSpeedStability() {
            double relativeDeviances = 0.;
            double averageSpeed = 0;
            for (int i = 0; i < this.speedTableSize; i++) {
                averageSpeed += this.speedTable.get(i).doubleValue();
            }
            averageSpeed /= this.speedTableSize;
            for (int i = 0; i < this.speedTableSize; i++) {
                relativeDeviances += Math
                        .pow(((this.speedTable.get(i).doubleValue() - averageSpeed) / averageSpeed), 2);
            }
            relativeDeviances /= travelModes.length;//taking dependence on number of modes away
            if (relativeDeviances < 0.0005) {
                this.speedStability = true;
            } else {
                this.speedStability = false;
            }
        }

        private void checkFlowStability900() {
            double absoluteDeviances = this.lastXFlows900.get(this.lastXFlows900.size() - 1)
                    - this.lastXFlows900.get(0);
            if (Math.abs(absoluteDeviances) < 1) {
                this.flowStability = true;
                if (modeId == null)
                    LOG.info("========== Reaching a certain flow stability for global flow.");
                else
                    LOG.info("========== Reaching a certain flow stability in mode: " + modeId.toString());
            } else {
                this.flowStability = false;
            }
        }

        private void initDynamicVariables() {
            //numberOfAgents for each mode should be initialized at this point
            this.decideSpeedTableSize();
            this.speedTable = new LinkedList<>();
            for (int i = 0; i < this.speedTableSize; i++) {
                this.speedTable.add(0.);
            }
            this.flowTime = 0.;
            this.flowTable900 = new LinkedList<>();

            flowTableReset();

            this.lastXFlows900 = new LinkedList<>();
            for (int i = 0; i < NUMBER_OF_MEMORIZED_FLOWS; i++) {
                this.lastXFlows900.add(0.);
            }
            this.speedStability = false;
            this.flowStability = false;
            this.lastSeenOnStudiedLinkEnter = new TreeMap<>();
            this.permanentDensity = 0.;
            this.permanentAverageVelocity = 0.;
            this.permanentFlow = 0.;
        }

        private void reset() {
            this.speedTable.clear();
            this.speedStability = false;
            this.flowStability = false;
        }

        private void decideSpeedTableSize() {
            //Ensures a significant speed sampling for every mode size
            //Is pretty empirical and can be changed if necessary (ssix, 16.10.13)
            if (this.numberOfAgents >= 500) {
                this.speedTableSize = 50;
            } else if (this.numberOfAgents >= 100) {
                this.speedTableSize = 20;
            } else if (this.numberOfAgents >= 10) {
                this.speedTableSize = 10;
            } else if (this.numberOfAgents > 0) {
                this.speedTableSize = this.numberOfAgents;
            } else { //case no agents in mode
                this.speedTableSize = 1;
            }
        }

        private void flowTableReset() {
            for (int i = 0; i < 900; i++) {
                this.flowTable900.add(0.);
            }
        }

        private void saveDynamicVariables() {
            //NB: Should not be called upon a modeData without a vehicleType, as this.vehicleType will be null and will throw an exception.
            this.permanentDensity = this.numberOfAgents / (1000. * 3) * 1000.
                    * this.vehicleType.getPcuEquivalents();
            this.permanentAverageVelocity = this.getActualAverageVelocity();
            LOG.info("Calculated permanent Speed from " + modeId + "'s lastXSpeeds : " + speedTable
                    + "\nResult is : " + this.permanentAverageVelocity);
            this.permanentFlow = this.getSlidingAverageLastXFlows900();
            LOG.info("Calculated permanent Flow from " + modeId + "'s lastXFlows900 : " + lastXFlows900
                    + "\nResult is :" + this.permanentFlow);
        }

        //Getters/Setters
        public double getActualAverageVelocity() {
            double nowSpeed = 0.;
            for (int i = 0; i < this.speedTableSize; i++) {
                nowSpeed += this.speedTable.get(i);
            }
            nowSpeed /= this.speedTableSize;
            return nowSpeed;
        }

        public double getCurrentHourlyFlow() {
            double nowFlow = 0.;
            for (int i = 0; i < 900; i++) {
                nowFlow += this.flowTable900.get(i);
            }
            return nowFlow * 4;
        }

        public double getSlidingAverageLastXFlows900() {
            double average = 0;
            for (double flow : this.lastXFlows900) {
                average += flow;
            }
            return average / NUMBER_OF_MEMORIZED_FLOWS;
        }

        public boolean isSpeedStable() {
            return this.speedStability;
        }

        public boolean isFlowStable() {
            return this.flowStability;
        }

        public void setnumberOfAgents(int n) {
            this.numberOfAgents = n;
        }

        public double getPermanentDensity() {
            return this.permanentDensity;
        }

        public void setPermanentDensity(double permanentDensity) {
            this.permanentDensity = permanentDensity;
        }

        public double getPermanentAverageVelocity() {
            return this.permanentAverageVelocity;
        }

        public void setPermanentAverageVelocity(double permanentAverageVelocity) {
            this.permanentAverageVelocity = permanentAverageVelocity;
        }

        public double getPermanentFlow() {
            return this.permanentFlow;
        }

        public void setPermanentFlow(double permanentFlow) {
            this.permanentFlow = permanentFlow;
        }

        public int getNumberOfDrivingAgents() {
            return this.lastSeenOnStudiedLinkEnter.size();
        }
    }

    //=======================================

    class GlobalFlowDynamicsUpdator implements LinkEnterEventHandler {

        private Map<Id<VehicleType>, TravelModesFlowDynamicsUpdator> travelModesFlowData;
        private TravelModesFlowDynamicsUpdator globalFlowData;

        private boolean permanentRegime;

        /**
         * container to store static properties of vehicles and dynamic flow properties during simulation 
         */
        public GlobalFlowDynamicsUpdator(
                Map<Id<VehicleType>, TravelModesFlowDynamicsUpdator> travelModeFlowDataContainer) {
            this.travelModesFlowData = travelModeFlowDataContainer;
            for (Id<VehicleType> vehTyp : this.travelModesFlowData.keySet()) {
                this.travelModesFlowData.get(vehTyp).initDynamicVariables();
            }
            this.globalFlowData = new TravelModesFlowDynamicsUpdator();
            this.globalFlowData.setnumberOfAgents(person2Mode.size());
            this.globalFlowData.initDynamicVariables();
            this.permanentRegime = false;
        }

        @Override
        public void reset(int iteration) {
            for (Id<VehicleType> vehTyp : this.travelModesFlowData.keySet()) {
                this.travelModesFlowData.get(vehTyp).reset();
            }
            this.globalFlowData.reset();
            this.permanentRegime = false;
        }

        @Override
        public void handleEvent(LinkEnterEvent event) {
            if (!(permanentRegime)) {
                //            Id<Person> personId = Id.createPersonId(event.getDriverId());

                //Disaggregated data updating methods
                String travelMode = person2Mode.get(event.getVehicleId());

                Id<VehicleType> transportMode = modeVehicleTypes.get(travelMode).getId();
                this.travelModesFlowData.get(transportMode).handle(event);
                double pcuVeh = modeVehicleTypes.get(travelMode).getPcuEquivalents();

                //Aggregated data update
                double nowTime = event.getTime();
                if (event.getLinkId().equals(flowDynamicsMeasurementLinkId)) {
                    this.globalFlowData.updateFlow900(nowTime, pcuVeh);
                    this.globalFlowData.updateSpeedTable(nowTime, event.getVehicleId());
                    //Waiting for all agents to be on the track before studying stability
                    if ((this.globalFlowData.getNumberOfDrivingAgents() == this.globalFlowData.numberOfAgents)
                            && (nowTime > 1800)) { //TODO parametrize this correctly
                        /*//Taking speed check out, as it is not reliable on the global speed table
                         *  Maybe making a list of moving averages could be smart, 
                         *  but there is no reliable converging process even in that case. (ssix, 25.10.13)
                         * if (!(this.globalData.isSpeedStable())){
                           this.globalData.checkSpeedStability(); 
                           System.out.println("Checking speed stability in global data for: "+this.globalData.getSpeedTable());
                        }*/
                        if (!(this.globalFlowData.isFlowStable())) {
                            this.globalFlowData.checkFlowStability900();
                        }

                        //Checking modes stability
                        boolean modesStable = true;
                        for (Id<VehicleType> vehTyp : this.travelModesFlowData.keySet()) {
                            if (this.travelModesFlowData.get(vehTyp).numberOfAgents != 0) {
                                if (!this.travelModesFlowData.get(vehTyp).isSpeedStable()
                                        || !(this.travelModesFlowData.get(vehTyp).isFlowStable())) {
                                    modesStable = false;
                                    break;
                                }
                            }
                        }
                        if (modesStable) {
                            //Checking global stability
                            if ( /*this.globalData.isSpeedStable() &&*/ this.globalFlowData.isFlowStable()) {
                                LOG.info("========== Global permanent regime is attained");
                                for (Id<VehicleType> vehTyp : this.travelModesFlowData.keySet()) {
                                    this.travelModesFlowData.get(vehTyp).saveDynamicVariables();
                                }
                                this.globalFlowData.setPermanentAverageVelocity(
                                        this.globalFlowData.getActualAverageVelocity());
                                //this.permanentFlow = this.getActualFlow();
                                this.globalFlowData.setPermanentFlow(this.globalFlowData.getCurrentHourlyFlow());
                                double globalDensity = 0.;
                                for (TravelModesFlowDynamicsUpdator mode : this.travelModesFlowData.values()) {
                                    globalDensity += mode.getPermanentDensity();
                                }
                                this.globalFlowData.setPermanentDensity(globalDensity);
                                this.permanentRegime = true;
                            }
                        }
                    }
                }
            }
        }

        public boolean isPermanent() {
            return permanentRegime;
        }

        public TravelModesFlowDynamicsUpdator getGlobalData() {
            return this.globalFlowData;
        }
    }
}