package org.opentrafficsim.demo.ntm; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.djunits.unit.DurationUnit; import org.djunits.unit.FrequencyUnit; import org.djunits.unit.LengthUnit; import org.djunits.unit.SpeedUnit; import org.djunits.unit.TimeUnit; import org.djunits.value.vdouble.scalar.Frequency; import org.djunits.value.vdouble.scalar.Speed; import org.djunits.value.vdouble.scalar.Time; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.opentrafficsim.core.network.LinkEdge; import org.opentrafficsim.demo.ntm.NTMNode.TrafficBehaviourType; import org.opentrafficsim.demo.ntm.trafficdemand.TripDemand; import org.opentrafficsim.demo.ntm.trafficdemand.TripInfoTimeDynamic; /** *

* Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* $LastChangedDate$, @version $Revision$, by $Author$, * initial version 29 Oct 2014
* @author Alexander Verbraeck * @author Hans van Lint * @author Peter Knoppers * @author Guus Tamminga * @author Yufei Yuan */ public class NTMsimulation { // variables for data output during the simulation /** */ static int steps = 0; /** For reporting */ static int MAXSTEPS = 1080; /** */ static LinkedHashMap> fluxAreaToNeighbours = new LinkedHashMap<>(); /** * @param model NTMModel; * @throws Exception */ public static void simulate(final NTMModel model) throws Exception { @SuppressWarnings("unchecked") Time currentTime = null; // debug for fastest path and write data?? steps++; currentTime = new Time( model.getSettingsNTM().getDurationSinceMidnight().getSI() + model.getSimulator().getSimulatorTime().getSI(), TimeUnit.BASE_SECOND); // retrieve information from the Area Graph containing the NTM areas and the selected highways // The AreaGraph contains EDGES with differing characteristics: // - "NTM"-links between NTM areas // - "Cordon" links at the border of the study area that act as feeders and sinks of traffic to the "real" Areas // - "Flow" links representing higher order roads with behaviour that deviates from the NTM area links // (only for specific "main" roads) // The VERTICES (type: BoundedNode) represent the NTM areas, or the entrance / exit of a Flow link. // NTM nodes and FlowLink entrances/exits are connected by a "Transfer" link // // The "NTM" links between a pair of nodes, visualise the connection of that node to its "neighbours" with NTM // characteristics // The "Flow" links represent a connection where traffic behaves differently than in the NTM areas: // once traffic is on a homogeneous link, the capacity remains stable // // The simulation of Traffic becomes a nested process: // - the NTM process is the parent of the simulation // - the Flow process acts as a "child" process // ******************************************************************************************************** // STEP 1 of the simulation describes the initialisation of Demand from the Traffic Demand file: // - this is generated by the OD matrix from the model // first loop through the network nodes and select the NTM and Cordon "Area nodes" // These nodes generate traffic from the trip demand file (feeders) // There are two ESSENTIAL variables / class types: // - the Node.ClassBehaviour represents the aggregated traffic flow dynamics between nodes/cells // - the TripInfoTimeDynamic of an Area, showing for every OD-pair: // . - number of trips between origin (or intermediate areas on a path from O to D) - destination pairs (from // . . demand file) // . - neighbour area on path form Origin to Destination // . - time profile curve of trip departures from this origin (only used for the real origins!) // . - accumulated cars in this area/node/cell, heading on the way to a certain destination // . - flow in this time-step to neighbour from this area to destination if (model.getSettingsNTM().isReRoute()) { if (model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.SECOND) * steps % model.getSettingsNTM().getReRouteTimeInterval().getInUnit(DurationUnit.SECOND) == 0) { System.out.println("reroute"); // new K-shortest paths creation Routes.createRoutes(model, model.getSettingsNTM().getNumberOfRoutes(), model.getSettingsNTM().getWeightNewRoutes(), model.getSettingsNTM().getVarianceRoutes(), false, steps, MAXSTEPS); } } int found = 1; // initial settings of SUPPLY at first step if (steps == 1) { NTMTestApplication.textArea.append("The simulation has started right now, \n" + "Wait for the next message... \n" + "This may take a while! \n" + " \n"); for (NTMNode node : model.getAreaGraph().vertexSet()) { BoundedNode origin = (BoundedNode) node; // **** RELEVANT: set SUPPLY of all areas at maximum in first step if (origin.getBehaviourType() == TrafficBehaviourType.NTM) { CellBehaviourNTM cellBehaviourNTM = (CellBehaviourNTM) origin.getCellBehaviour(); double tripByTimeStep = model.getSettingsNTM().getTimeStepDurationNTM().getSI() * cellBehaviourNTM.getMaxCapacityNTMArea().getSI(); cellBehaviourNTM.setSupply(tripByTimeStep); // initial speed (no accumulation yet) cellBehaviourNTM.setCurrentSpeed(cellBehaviourNTM.getParametersNTM().getFreeSpeed()); } else if (origin.getBehaviourType() == TrafficBehaviourType.CORDON) { // Supply of Cordon nodes/areas always infinite! origin.getCellBehaviour().setSupply(java.lang.Double.POSITIVE_INFINITY); } } } // STEP 1: Loop through all area-nodes (called BoundedNode)to detect the trips (demand) to the destination area // The FLOW nodes are intermediate destinations, and dealt with in the next loop!!! // the variable CellBehaviour(NTM) defines the traffic process within an area (the area is // represented by the "BoundedNode"). This can be NTM behaviour, Cell transmission or other. // during the simulation traffic enters and leaves the NTM areas. The number of "accumulated cars" // represents the net balance of cars within the Nodes/areas. The new demand will be added! // The variable TripsFrom contains information on trips from an origin/node to ALL other // destinations. // This origin can be the real origin or an intermediate area on the path to destination // (neighbours). // The structure (or Class in Java) named TripInfoDynamic is stored in a LinkedHashMap (lookup array) that // contains this information for all destinations separately. for (NTMNode node : model.getAreaGraph().vertexSet()) { BoundedNode origin = (BoundedNode) node; try { if (origin.getBehaviourType() == TrafficBehaviourType.NTM || origin.getBehaviourType() == TrafficBehaviourType.CORDON) { // only production, if there are accumulated cars!! if (origin.getCellBehaviour().getAccumulatedCars() > 0.0) { // The object CellBehaviour contains simulation data, i.e. the number of accumulated cars in // this cell, the demand/supply and others // First update the demand per Area, as a function of the ACCUMULATION if (origin.getBehaviourType() == TrafficBehaviourType.NTM) { // CellBehaviourNTM extends CellBehaviour and contains additional behaviour CellBehaviourNTM cellBehaviourNTM = (CellBehaviourNTM) origin.getCellBehaviour(); // compute the total Demand (production) from an Area to all other Destinations (the level // is based on the accumulation, the capacity of an area and the NFD algorithm). // The new demand of this area is derived via the method RetrieveDemand that is based on the // network fundamental diagram (see there for further details) // **** RELEVANT: set DEMAND double tripByHourPerLengthUnit = cellBehaviourNTM .retrieveDemandPerLengthUnit(origin.getCellBehaviour().getAccumulatedCars(), cellBehaviourNTM.getArea().getRoadLength(), cellBehaviourNTM.getParametersNTM()) .getInUnit(FrequencyUnit.PER_HOUR); double tripByHour = tripByHourPerLengthUnit * cellBehaviourNTM.getArea().getRoadLength().getInUnit(LengthUnit.KILOMETER); double tripByTimeStep = model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.HOUR) * tripByHour; cellBehaviourNTM.setDemand(tripByTimeStep); // compute the total supply (maximum) from neighbours to this Area (again based on the // accumulation and NFD/area characteristics) // TODO implement roadLength and length simulation step !!!!!!!!!!!!!!!!!!!!!!!! // **** RELEVANT: set SUPPLY double tripByHourSupply = cellBehaviourNTM .retrieveSupplyPerLengthUnit(origin.getCellBehaviour().getAccumulatedCars(), cellBehaviourNTM.getArea().getRoadLength(), cellBehaviourNTM.getParametersNTM()) .getInUnit(FrequencyUnit.PER_HOUR) * cellBehaviourNTM.getArea().getRoadLength().getInUnit(LengthUnit.KILOMETER); tripByTimeStep = model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.HOUR) * tripByHourSupply; cellBehaviourNTM.setSupply(tripByTimeStep); Speed speed = cellBehaviourNTM.retrieveCurrentSpeed(cellBehaviourNTM.getAccumulatedCars(), cellBehaviourNTM.getArea().getRoadLength()); if (speed == null) { speed = new Speed(0, SpeedUnit.KM_PER_HOUR); } origin.getArea().setCurrentSpeed(cellBehaviourNTM.retrieveCurrentSpeed( cellBehaviourNTM.getAccumulatedCars(), cellBehaviourNTM.getArea().getRoadLength())); } // the border, or CORDON areas, act as sink/source for traffic // The flow nodes act as entrances, intermediate or exits of the cell transmission model // For this last category, we assume that the links are putting a restriction on capacity else if (origin.getBehaviourType() == TrafficBehaviourType.CORDON) { // demand is the sum of new demand and accumulated traffic from previous time steps // (possibly if the neighbour area does not accept all traffic) // Within cordon areas all traffic can directly move to another area or Node! // **** RELEVANT origin.getCellBehaviour().setDemand(origin.getCellBehaviour().getAccumulatedCars()); // the total supply is infinite for Cordon and Flow nodes (sinks with no limit on in-flow) // TODO can go to the initiation of this cellBehaviour } if (origin.getArea() != null) { origin.getArea().setAccumulatedCars(origin.getCellBehaviour().getAccumulatedCars()); } else { System.out.println("test"); } } else // if NO ACCUMULATION in a node, supply needs to be set, else it may become too low!!!! // TODO: is this necessary????????? { if (origin.getBehaviourType() == TrafficBehaviourType.NTM) { // CellBehaviourNTM extends CellBehaviour (additional or different behaviour) CellBehaviourNTM cellBehaviourNTM = (CellBehaviourNTM) origin.getCellBehaviour(); double tripByHour = cellBehaviourNTM .retrieveSupplyPerLengthUnit(origin.getCellBehaviour().getAccumulatedCars(), cellBehaviourNTM.getArea().getRoadLength(), cellBehaviourNTM.getParametersNTM()) .getInUnit(FrequencyUnit.PER_HOUR) * cellBehaviourNTM.getArea().getRoadLength().getInUnit(LengthUnit.KILOMETER); double tripByTimeStep = model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.HOUR) * tripByHour; cellBehaviourNTM.setSupply(tripByTimeStep); } } // STEP 2 // After determining the initial demand from an area, we measure the potential demand to specific // neighbour areas, and compare this to the capacity. // When there are any capacity restrictions between two areas, this may restrict demand! // TODO: also for other types of areas???? if (origin.getBehaviourType() == TrafficBehaviourType.NTM) { CellBehaviourNTM cellBehaviour = (CellBehaviourNTM) origin.getCellBehaviour(); LinkedHashMap borderDemand = new LinkedHashMap(); cellBehaviour.setBorderDemand(borderDemand); for (TripInfoByDestination tripInfoByDestination : cellBehaviour.getTripInfoByDestinationMap().values()) { if (tripInfoByDestination.getAccumulatedCarsToDestination() > 0) { // Compute the share of the accumulated trips to a certain destination as part of the // total accumulation Set neighbours = tripInfoByDestination.getRouteFractionToNeighbours().keySet(); for (BoundedNode neighbour : neighbours) { if (neighbour != null) { double demandToNeighbour = (tripInfoByDestination.getAccumulatedCarsToNeighbour().get(neighbour) / cellBehaviour.getAccumulatedCars()) * cellBehaviour.getDemand(); double demandToNeighbourPerHour = demandToNeighbour * 3600 / model.getSettingsNTM() .getTimeStepDurationNTM().getInUnit(DurationUnit.SECOND); Frequency demand = new Frequency(demandToNeighbourPerHour, FrequencyUnit.PER_HOUR); cellBehaviour.addBorderDemand(neighbour, demand); } } } } } // STEP 3 // after determining potential demand, we compute the demand for traffic to other nodes or areas // only if we have demand of traffic for this area!! if (origin.getCellBehaviour().getDemand() > 0) { for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour() .getTripInfoByDestinationMap().values()) { // The tripInfoByNode includes information about the trips, specified by all destination // zones // In this step we are interested in the first zone we encounter ("neighbour") of the // cars on their path to a certain destination Area. // We determine the total DEMAND that wants to "enter" a neighbour Set neighbours = tripInfoByDestination.getAccumulatedCarsToNeighbour().keySet(); for (BoundedNode neighbour : neighbours) { // Compute the share of the accumulated trips to a certain destination as part of the // total accumulation if (tripInfoByDestination.getAccumulatedCarsToNeighbour().get(neighbour) > 0) { // this potential out-flow is heading to the neighbour that is on its path to // destination // **** RELEVANT double demandToNeighbourByDestination = 0; demandToNeighbourByDestination = tripInfoByDestination.getAccumulatedCarsToNeighbour().get(neighbour) / origin.getCellBehaviour().getAccumulatedCars() * origin.getCellBehaviour().getDemand(); // in case of CAPACITY RESTRAINTS at borders, the actual demand could be lower if (origin.getBehaviourType() == TrafficBehaviourType.NTM || origin.getBehaviourType() == TrafficBehaviourType.CORDON) { double ratioCapacityVersusDemand = 1.0; CellBehaviour cellBehaviour = origin.getCellBehaviour(); if (cellBehaviour.getBorderDemand() != null) { if (cellBehaviour.getBorderDemand().get(neighbour) != null) { if (cellBehaviour.getBorderDemand().get(neighbour).getSI() > 0) { if (cellBehaviour.getBorderCapacity().get(neighbour) != null) { ratioCapacityVersusDemand = cellBehaviour.getBorderCapacity() .get(neighbour).getSI() / cellBehaviour.getBorderDemand().get(neighbour).getSI(); } else { ratioCapacityVersusDemand = 1; } } } } demandToNeighbourByDestination = Math.min(ratioCapacityVersusDemand, 1) * demandToNeighbourByDestination; } // **** RELEVANT tripInfoByDestination.getDemandToNeighbour().put(neighbour, demandToNeighbourByDestination); tripInfoByDestination.addDemandToDestination(demandToNeighbourByDestination); // first add these trips to the number of trips that want to transfer to their // neighbour if (neighbour.getBehaviourType() == TrafficBehaviourType.NTM || neighbour.getBehaviourType() == TrafficBehaviourType.CORDON) { // this flow is also added to the total sum of traffic that wants to enter this // neighbour Area. // **** RELEVANT neighbour.getCellBehaviour().addDemandToEnter(demandToNeighbourByDestination); } // the trips that enter the flow Links, are processed immediately (no timestep // delay) to the first cell of the succeeding transmission link else if (neighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { Set nextNeighbours = neighbour.getCellBehaviour() .getTripInfoByDestinationMap().get(tripInfoByDestination.getDestination()) .getDemandToNeighbour().keySet(); for (BoundedNode nextNeighbour : nextNeighbours) { double shareOfNeighbour = neighbour.getCellBehaviour().getTripInfoByDestinationMap() .get(tripInfoByDestination.getDestination()).getRouteFractionToNeighbours() .get(nextNeighbour); // when entering a flow link, loop through all succeeding flow links until // reaching // a NTM or CORODN node : if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { // Retrieve the cell transmission link // all cells (should) have identical characteristics LinkCellTransmission ctmLink = (LinkCellTransmission) model.getAreaGraph() .getEdge(neighbour, nextNeighbour).getLink(); // add the demand of trips that want to enter the first cell // **** RELEVANT ctmLink.getCells().get(0).getCellBehaviourFlow() .addDemandToEnter(shareOfNeighbour * demandToNeighbourByDestination); } else if (neighbour.getBehaviourType() == TrafficBehaviourType.NTM || neighbour.getBehaviourType() == TrafficBehaviourType.CORDON) { nextNeighbour.getCellBehaviour() .addDemandToEnter(shareOfNeighbour * demandToNeighbourByDestination); } } } } else { // System.out.println("NTMSimulation line 379: Strange: no neighbour"); } } // In the next step, see whether this demand from nodes is able to enter completely or // just partly (when supply is restricted) } } } } // in the next steps, the dynamics of demand and supply create a certain flow between areas catch (Exception e) { e.printStackTrace(); } } // Step 4: FLOW links // Here we regard the generation of demand as a result of propagation of traffic on FLOW links for (LinkEdge link : model.getAreaGraph().edgeSet()) { { if (link.getLink().getBehaviourType() == TrafficBehaviourType.FLOW) { LinkCellTransmission ctmLink = (LinkCellTransmission) link.getLink(); // Loop through the cells and do transmission for (FlowCell cell : ctmLink.getCells()) { double accumulationCell = cell.getCellBehaviourFlow().getAccumulatedCars(); // at the first cell, demand that wants to enter comes from the FlowNode!! // maximum to enter this cell // **** RELEVANT // SUPPLY Frequency tripByHour = cell.getCellBehaviourFlow().retrieveSupply(accumulationCell, cell.getCellBehaviourFlow().getParametersFundamentalDiagram()); double tripByStep = model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.SECOND) * tripByHour.getInUnit(FrequencyUnit.PER_SECOND); cell.getCellBehaviourFlow().setSupply(tripByStep); // DEMAND if (accumulationCell > 0) { tripByHour = cell.getCellBehaviourFlow().retrieveDemand(accumulationCell, cell.getCellBehaviourFlow().getParametersFundamentalDiagram()); tripByStep = model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.SECOND) * tripByHour.getInUnit(FrequencyUnit.PER_SECOND); cell.getCellBehaviourFlow().setDemand(tripByStep); } } } } } // STEP 5; after determining the total demand on FLOW links, here the demand by destination is determined for (NTMNode node : model.getAreaGraph().vertexSet()) { BoundedNode origin = (BoundedNode) node; try { if (origin.getBehaviourType() == TrafficBehaviourType.FLOW) { // Update Demand and supply of the flow links // Every node has "packages" of information for trips to a certain destination: the // TripInfoByDestination // Loop through this info to retrieve the demand by destination for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByDestinationMap() .values()) { // Every trips to a destination may have more than one route (and therefore have trips via // different neighbours) Set neighbours = tripInfoByDestination.getRouteFractionToNeighbours().keySet(); for (BoundedNode neighbour : neighbours) { if (neighbour.getId() != null) { // Only the flow links are considered in the CTM model, so we need a Flow node at // the end of the link if (neighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { // In case of Cell Transmission Links, there is a process of // traffic moving over a link. The demand via CELLS is computed here. // In Step 6 see if this demand is below the capacity of the CELLS (supply) try { LinkCellTransmission ctmLink = (LinkCellTransmission) model.getAreaGraph() .getEdge(origin, neighbour).getLink(); // Loop through the cells and do transmission for (FlowCell cell : ctmLink.getCells()) { double accumulationCell = cell.getCellBehaviourFlow().getAccumulatedCars(); // retrieve the current info of trips in this cell // at the first cell, demand comes from the FlowNode!! // maximum to enter this cell if (accumulationCell > 0) { // **** RELEVANT // Distribute demand over destinations TripInfoByDestination cellInfoByDestinationMap = cell.getCellBehaviourFlow().getTripInfoByDestinationMap() .get(tripInfoByDestination.getDestination()); // we don't have to iterate over multiple neighbours: there is no // specific one... if (cellInfoByDestinationMap.getAccumulatedCarsToNeighbour() .get(neighbour) != null) { double demandToDestination = cellInfoByDestinationMap .getAccumulatedCarsToNeighbour().get(neighbour) / accumulationCell * cell.getCellBehaviourFlow().getDemand(); // **** RELEVANT cellInfoByDestinationMap.getDemandToNeighbour().put(neighbour, demandToDestination); } } } // demand from last Cell to the first cell of the down stream flow link FlowCell lastCell = ctmLink.getCells().get(ctmLink.getCells().size() - 1); if (lastCell.getCellBehaviourFlow().getDemand() > 0) { Set nextNeighbours = neighbour.getCellBehaviour() .getTripInfoByDestinationMap().get(tripInfoByDestination.getDestination()) .getRouteFractionToNeighbours().keySet(); for (BoundedNode nextNeighbour : nextNeighbours) { // double shareOfNeighbour = // neighbour.getCellBehaviour().getTripInfoByDestinationMap() // .get(tripInfoByDestination.getDestination()) // .getAccumulatedCarsToNeighbour().get(nextNeighbour); if (lastCell.getCellBehaviourFlow().getTripInfoByDestinationMap() .get(tripInfoByDestination.getDestination()) == null) { if (model.getInputNTM().isDEBUG()) { System.out.println("Stop"); } } if (lastCell.getCellBehaviourFlow().getTripInfoByDestinationMap() .get(tripInfoByDestination.getDestination()).getDemandToNeighbour() .get(neighbour) != null) { double addDemandToDestination = lastCell.getCellBehaviourFlow().getTripInfoByDestinationMap() .get(tripInfoByDestination.getDestination()) .getDemandToNeighbour().get(neighbour); // lastCell.getCellBehaviourFlow().getTripInfoByDestinationMap() // .get(tripInfoByDestination.getDestination()) // .getDemandToDestination(); // * tripInfoByDestination.getNeighbourAndRouteShare() // .get(neighbour); if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { LinkCellTransmission ctmLinkNext = (LinkCellTransmission) model .getAreaGraph().getEdge(neighbour, nextNeighbour).getLink(); ctmLinkNext.getCells().get(0).getCellBehaviourFlow() .addDemandToEnter(addDemandToDestination); // shareOfNeighbour // * } else if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.NTM || nextNeighbour.getBehaviourType() == TrafficBehaviourType.CORDON) { nextNeighbour.getCellBehaviour() .addDemandToEnter(addDemandToDestination); // shareOfNeighbour * } } else { System.out.println("No demand to destination"); } } } } catch (Exception e) { e.printStackTrace(); } } else if (neighbour.getBehaviourType() == TrafficBehaviourType.NTM || neighbour.getBehaviourType() == TrafficBehaviourType.CORDON) { System.out.println("CTMsimulation line 595: FLOW node has neighbour of type NTM"); } else if (neighbour.getBehaviourType() != TrafficBehaviourType.NTM && neighbour.getBehaviourType() != TrafficBehaviourType.CORDON) { if (model.getInputNTM().isDEBUG()) { System.out.println( "CTMsimulation line 560: FLOW node has neighbour of type NTM or Cordon"); } } } else { System.out.println("NTMSimulation line 565: Strange and to repair: nodeTo equals null"); } } } } } // in the next steps, the dynamics of demand and supply create a certain flow between areas catch (Exception e) { e.printStackTrace(); } } // ******************************************************************************************************** // STEP 6: FLUXES!!!!! // Monitor whether the demand of traffic from outside areas is able to enter a certain Area // Perhaps SUPPLY poses an upper bound on the Demand! // First, NTM and Cordon nodes are being passed for (NTMNode node : model.getAreaGraph().vertexSet()) { BoundedNode origin = (BoundedNode) node; try { if (origin.getCellBehaviour().getAccumulatedCars() < 0) { System.out.println("Negative accumulation: step " + steps); } else if (origin.getCellBehaviour().getAccumulatedCars() >= 0.0) { // Trips always start from an NTM or Cordon Node. If they pass a Flow Node they by definition enter // a flow Link directly. The FLOW node is rather artificial: putting through but not storing // anything // The simulation of these flow links is carried out sequentially if (origin.getBehaviourType() == TrafficBehaviourType.NTM || origin.getBehaviourType() == TrafficBehaviourType.CORDON) { for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour() .getTripInfoByDestinationMap().values()) { if (tripInfoByDestination.getDemandToDestination() > 0) { Set neighbours = tripInfoByDestination.getDemandToNeighbour().keySet(); for (BoundedNode neighbour : neighbours) { BoundedNode destination = (BoundedNode) tripInfoByDestination.getDestination(); if (neighbour.getBehaviourType() == TrafficBehaviourType.NTM || neighbour.getBehaviourType() == TrafficBehaviourType.CORDON) { // retrieve the neighbour area on the path to a certain destination if (neighbour != null && tripInfoByDestination.getDemandToNeighbour().get(neighbour) > 0.0) { double demandToNeighbour = tripInfoByDestination.getDemandToNeighbour().get(neighbour); // compute the share of traffic that wants to enter this Neighbour area from // a certain origin - destination pair as part of the total demand that // wants to enter the neighbour cell. The total supply to the neighbour may // be restricted (by calling getSupply that provides the maximum Supply). double totalDemandThatCanEnter = Math.min(neighbour.getCellBehaviour().getDemandToEnter(), neighbour.getCellBehaviour().getSupply()); double flowToNeighbour = 0; if (neighbour.getCellBehaviour().getDemandToEnter() > 0 && demandToNeighbour > 0) { flowToNeighbour = (demandToNeighbour / neighbour.getCellBehaviour().getDemandToEnter()) * totalDemandThatCanEnter; } // Compute the final flow based on the share of Trips // to a certain destination and this maximum supply of the Cell. // set the final flow to the neighbour // **** RELEVANT if (!neighbour.getId().equals(destination.getId())) { if (neighbour.getCellBehaviour().getTripInfoByDestinationMap() .get(destination) == null) { if (model.getInputNTM().isDEBUG()) { System.out.println( found + " Step " + steps + ": Neighbour: " + neighbour.getId() + " has no destination " + destination.getId()); } } else { neighbour.getCellBehaviour().addAccumulatedCars(flowToNeighbour); neighbour.getCellBehaviour().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(flowToNeighbour); // TODO check this Set nextNeighbours = neighbour.getCellBehaviour().getTripInfoByDestinationMap() .get(destination).getRouteFractionToNeighbours().keySet(); for (BoundedNode nextNeighbour : nextNeighbours) { double oldAccumulated = neighbour.getCellBehaviour() .getTripInfoByDestinationMap().get(destination) .getAccumulatedCarsToNeighbour().get(nextNeighbour); double routeFraction = neighbour.getCellBehaviour() .getTripInfoByDestinationMap().get(destination) .getRouteFractionToNeighbours().get(nextNeighbour); neighbour.getCellBehaviour().getTripInfoByDestinationMap() .get(destination).getAccumulatedCarsToNeighbour() .put(nextNeighbour, routeFraction * flowToNeighbour + oldAccumulated); } } } else { if (flowToNeighbour > 0) { // TODO (what to do here??) destination.getCellBehaviour().addArrivals(flowToNeighbour); tripInfoByDestination.addArrivedTrips(flowToNeighbour); } } double oldAccumulated = tripInfoByDestination.getAccumulatedCarsToNeighbour().get(neighbour); tripInfoByDestination.getAccumulatedCarsToNeighbour().put(neighbour, oldAccumulated - flowToNeighbour); tripInfoByDestination.addAccumulatedCarsToDestination(-flowToNeighbour); tripInfoByDestination.addFluxToNeighbour(flowToNeighbour); origin.getCellBehaviour().addAccumulatedCars(-flowToNeighbour); } else if (tripInfoByDestination.getDemandToDestination() == 0.0) { // System.out.println("NTMSimulation line 471: no demand to neighbour"); } else { if (model.getInputNTM().isDEBUG()) { System.out.println("NTMSimulation line 475: no neighbour"); } } } // else flux to the first cell of the flow link: Do the simulation of flow links else if (neighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { // determine the next movement: a flow link double demandToNeighbour = tripInfoByDestination.getDemandToNeighbour().get(neighbour); // tripInfoByDestination.getDemandToDestination() // * tripInfoByDestination.getRouteFractionToNeighbours().get( // neighbour); if (demandToNeighbour > 0) { // retrieve the downstream Flow node opportunities Set nextNeighbours = neighbour.getCellBehaviour().getTripInfoByDestinationMap().get(destination) .getDemandToNeighbour().keySet(); // tripInfoByDestination.getNeighbourAndRouteShare().keySet(); for (BoundedNode nextNeighbour : nextNeighbours) { if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { LinkCellTransmission ctmLink = (LinkCellTransmission) model.getAreaGraph() .getEdge(neighbour, nextNeighbour).getLink(); TripInfoByDestination neighbourTripInfoByDestination = neighbour .getCellBehaviour().getTripInfoByDestinationMap().get(destination); double demandCell; double supplyCell; // at the first cell after the NTM/Cordon Node, demand comes from // the FlowNode!! FlowCell cell = ctmLink.getCells().get(0); double share = neighbourTripInfoByDestination.getRouteFractionToNeighbours() .get(nextNeighbour); // * tripInfoByDestination.getNeighbourAndRouteShare().get( // nextNeighbour); double demandToNextNeighbour = demandToNeighbour * share; if (demandToNextNeighbour > 0) { // maximum to enter this cell supplyCell = cell.getCellBehaviourFlow().getSupply(); demandCell = cell.getCellBehaviourFlow().getDemandToEnter(); // tripInfoByDestination.getDemandToNeighbour(); double flowToDestination = 0; if (demandCell > 0) { flowToDestination = Math.min(supplyCell / demandCell, 1.0) * demandToNextNeighbour; } // **** RELEVANT cell.getCellBehaviourFlow().addAccumulatedCars(flowToDestination); if (cell.getCellBehaviourFlow().getTripInfoByDestinationMap() .get(destination) == null) { if (model.getInputNTM().isDEBUG()) { System.out.println("NTM-Flow-NTM"); } } cell.getCellBehaviourFlow().getTripInfoByDestinationMap() .get(destination) .addAccumulatedCarsToDestination(flowToDestination); tripInfoByDestination .addAccumulatedCarsToDestination(-flowToDestination); origin.getCellBehaviour().addAccumulatedCars(-flowToDestination); } } else { if (model.getInputNTM().isDEBUG()) { System.out.println("NTM-Flow-NTM"); } } } } } } } } } } } catch (Exception e) { e.printStackTrace(); } } // STEP 6b: FLUXES of flow links!!!!! for (LinkEdge link : model.getAreaGraph().edgeSet()) { { if (link.getLink().getBehaviourType() == TrafficBehaviourType.FLOW) { LinkCellTransmission ctmLink = (LinkCellTransmission) link.getLink(); simulateFlowLink(model.getAreaGraph(), model.getNodeAreaGraphMap(), ctmLink); } } } // FIRST: Loop through all nodes and reset the relevant variables such as DemandToEnter (for nodes with more // than one entrance) to zero for (LinkEdge link : model.getAreaGraph().edgeSet()) { { // only the feeding areas of the type NTM and Cordon can generate new traffic from the trip demand // matrix if (link.getLink().getBehaviourType() == TrafficBehaviourType.NTM || link.getLink().getBehaviourType() == TrafficBehaviourType.CORDON) { BoundedNode node = (BoundedNode) link.getLink().getStartNode(); BoundedNode nodeGraph = (BoundedNode) model.getNodeAreaGraphMap().get(node.getId()); nodeGraph.getCellBehaviour().setDepartures(0); // set all fluxes from node to neighbour by destination to zero for (TripInfoByDestination tripInfoByDestination : nodeGraph.getCellBehaviour() .getTripInfoByDestinationMap().values()) { tripInfoByDestination.setDepartedTrips(0); } } } } // STEP 7: Add new Demand from the trip matrix to prepare for the next simulation cycle // The variable TripsFrom contains information on trips from an origin/node to ALL other // destinations. for (NTMNode node : model.getAreaGraph().vertexSet()) { BoundedNode origin = (BoundedNode) node; try { // only the feeding areas of the type NTM and Cordon can generate new traffic from the trip demand // matrix if (origin.getBehaviourType() == TrafficBehaviourType.NTM || origin.getBehaviourType() == TrafficBehaviourType.CORDON) { Double maximumNumberOfTripsToAdd = Double.POSITIVE_INFINITY; if (origin.getBehaviourType() == TrafficBehaviourType.NTM) { CellBehaviourNTM celBehaviourNTM = (CellBehaviourNTM) origin.getCellBehaviour(); int crit = celBehaviourNTM.getParametersNTM().getAccCritical().size(); double critDensityPerHour = celBehaviourNTM.getParametersNTM().getAccCritical().get(crit - 1); double roadLength = 0.0; if (origin.getArea() != null) { roadLength = origin.getArea().getRoadLength().getInUnit(LengthUnit.KILOMETER); } else { if (model.getInputNTM().isDEBUG()) { System.out.println("no area connected to: " + origin.getId()); } } // double share = // model.getSettingsNTM().getTimeStepDurationNTM().getInUnit(DurationUnit.SECOND) / 3600; Double maxAccumulationThisArea = roadLength * critDensityPerHour; maximumNumberOfTripsToAdd = maxAccumulationThisArea - origin.getCellBehaviour().getAccumulatedCars(); } // total departures during this timestep: double tripsInReservoir = 0; // loop through all destinations to get total demand and supply per origin and add the new trips for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByDestinationMap() .values()) // only select the final destinations: where Trips are heading to { tripsInReservoir += tripInfoByDestination.getTripsInReservoir(); } Double totalTrips = TripDemand.getTotalNumberOfTripsFromOrigin(model.tripDemandToUse, origin.getId(), currentTime, model.getSettingsNTM().getTimeStepDurationNTM()) + tripsInReservoir; Double shareToAdd = Math.min(1, maximumNumberOfTripsToAdd / totalTrips); if (shareToAdd < 1.0) { System.out.println("full"); } Map tripsFrom = model.tripDemandToUse.getTripInfo().get(origin.getId()); // loop through all destinations to get total demand and supply per origin and add the new trips for (TripInfoByDestination tripInfoByDestination : origin.getCellBehaviour().getTripInfoByDestinationMap() .values()) // only select the final destinations: where Trips are heading to { BoundedNode destination = (BoundedNode) tripInfoByDestination.getDestination(); if (tripsFrom != null) { if (tripsFrom.get(destination.getId()) != null) { if (tripsFrom.get(destination.getId()).getNumberOfTrips() > 0) { // get the share of Trips of this time slice (NTM simulation step of 10 seconds) // these new Trips are added to the TRIPS that are already on their way (passing an // NTM area): the AccumulatedCars specified by their specific destination // TODO if maxAccumulation, put TRIPS in a reservoir double startingTrips = shareToAdd * TripDemand.getTotalNumberOfTripsFromOriginToDestinationByTimeStep( model.tripDemandToUse, origin.getId(), destination.getId(), currentTime, model.getSettingsNTM().getTimeStepDurationNTM()); startingTrips += shareToAdd * tripInfoByDestination.getTripsInReservoir(); tripInfoByDestination.addTripsInReservoir((1 - shareToAdd) * startingTrips); // **** RELEVANT (scale to other time periods or likewise) if (model.getSettingsNTM().isIncreaseDemandByArea()) { startingTrips *= model.getSettingsNTM().getScalingFactorDemand(); } for (BoundedNode neighbour : origin.getCellBehaviour().getTripInfoByDestinationMap() .get(destination).getRouteFractionToNeighbours().keySet()) { double share = origin.getCellBehaviour().getTripInfoByDestinationMap().get(destination) .getRouteFractionToNeighbours().get(neighbour); double oldAccumulated = tripInfoByDestination.getAccumulatedCarsToNeighbour().get(neighbour); tripInfoByDestination.getAccumulatedCarsToNeighbour().put(neighbour, oldAccumulated + share * startingTrips); } tripInfoByDestination.addAccumulatedCarsToDestination(startingTrips); tripInfoByDestination.addDepartedTrips(startingTrips); // increases the total number of accumulated cars in the area, that is // used for NTM computations // **** RELEVANT origin.getCellBehaviour().addAccumulatedCars(startingTrips); origin.getCellBehaviour().addDepartures(startingTrips); } } } } } } // in the next steps, the dynamics of demand and supply create a certain flow between areas catch (Exception e) { e.printStackTrace(); } } if (model.getInputNTM().isWRITEDATA()) { WriteOutput.writeOutputDataFlowLinks(model, steps, MAXSTEPS); WriteOutput.writeOutputDataNTM(model, steps, MAXSTEPS); } // FIRST: Loop through all nodes and reset the relevant variables such as DemandToEnter (for nodes with more // than one entrance) to zero for (LinkEdge link : model.getAreaGraph().edgeSet()) { { // only the feeding areas of the type NTM and Cordon can generate new traffic from the trip demand // matrix if (link.getLink().getBehaviourType() == TrafficBehaviourType.NTM || link.getLink().getBehaviourType() == TrafficBehaviourType.CORDON) { BoundedNode node = (BoundedNode) link.getLink().getStartNode(); BoundedNode nodeGraph = (BoundedNode) model.getNodeAreaGraphMap().get(node.getId()); nodeGraph.getCellBehaviour().setDemandToEnter(0); nodeGraph.getCellBehaviour().setArrivals(0); // set all fluxes from node to neighbour by destination to zero for (TripInfoByDestination tripInfoByDestination : nodeGraph.getCellBehaviour() .getTripInfoByDestinationMap().values()) { tripInfoByDestination.setFluxToNeighbour(0); tripInfoByDestination.setArrivedTrips(0); tripInfoByDestination.setDemandToDestination(0); } } else if (link.getLink().getBehaviourType() == TrafficBehaviourType.FLOW) { LinkCellTransmission ctmLink = (LinkCellTransmission) link.getLink(); // set the demand of trips that want to enter the first cell to zero // **** RELEVANT ctmLink.getCells().get(0).getCellBehaviourFlow().setDemandToEnter(0); for (FlowCell cell : ctmLink.getCells()) { for (TripInfoByDestination tripInfoByDestination : cell.getCellBehaviourFlow() .getTripInfoByDestinationMap().values()) { tripInfoByDestination.setFluxToNeighbour(0); } } } } } } /** * @param areaGraph SimpleDirectedWeightedGraph<NTMNode,LinkEdge<NTMLink>>; * @param ctmLink LinkCellTransmission; */ public static void simulateFlowLink(SimpleDirectedWeightedGraph> areaGraph, Map nodeAreaGraph, LinkCellTransmission ctmLink) { // Retrieve the cell transmission link // all cells (should) have identical characteristics FlowCell prevCell = null; BoundedNode neighbour = (BoundedNode) nodeAreaGraph.get(ctmLink.getEndNode().getId()); for (FlowCell cell : ctmLink.getCells()) { if (prevCell == null) { prevCell = cell; } else { double demandCell; double supplyCell; // determine the downstream cells (but the last): if (ctmLink.getCells().indexOf(cell) < ctmLink.getCells().size()) { supplyCell = cell.getCellBehaviourFlow().getSupply(); demandCell = prevCell.getCellBehaviourFlow().getDemand(); for (TripInfoByDestination tripInfoByDestination : cell.getCellBehaviourFlow().getTripInfoByDestinationMap() .values()) { BoundedNode destination = (BoundedNode) tripInfoByDestination.getDestination(); double demandToNextCell = prevCell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .getDemandToDestination(); if (demandToNextCell > 0) { // tripInfoByDestination.getDemandToNeighbour(); double flowToDestination = 0; if (demandCell > 0) { flowToDestination = Math.min(supplyCell / demandCell, 1.0) * demandToNextCell; } // **** RELEVANT cell.getCellBehaviourFlow().addAccumulatedCars(flowToDestination); cell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(flowToDestination); prevCell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(-flowToDestination); prevCell.getCellBehaviourFlow().addAccumulatedCars(-flowToDestination); } } } } double demandCell; double supplyCell; // the last cell of the cell transmission link transfers trips to the next Link/AreaNode if (ctmLink.getCells().indexOf(cell) == ctmLink.getCells().size() - 1) // at the last cell, the trips are forwarded to the next Node: could // be NTM, Flow or Cordon { for (TripInfoByDestination tripInfoByDestination : cell.getCellBehaviourFlow().getTripInfoByDestinationMap() .values()) { BoundedNode destination = (BoundedNode) tripInfoByDestination.getDestination(); double demandToNextCell = cell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination).getDemandToDestination(); if (demandToNextCell > 0) { TripInfoByDestination neighbourTripInfoByDestination; neighbourTripInfoByDestination = neighbour.getCellBehaviour().getTripInfoByDestinationMap().get(destination); Set nextNeighbours = neighbourTripInfoByDestination.getRouteFractionToNeighbours().keySet(); for (BoundedNode nextNeighbour : nextNeighbours) { double share = neighbourTripInfoByDestination.getRouteFractionToNeighbours().get(nextNeighbour); if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.NTM || nextNeighbour.getBehaviourType() == TrafficBehaviourType.CORDON) { supplyCell = nextNeighbour.getCellBehaviour().getSupply(); demandCell = nextNeighbour.getCellBehaviour().getDemandToEnter(); // tripInfoByDestination.getDemandToNeighbour(); double flowToDestination = 0; if (demandCell > 0) { flowToDestination = share * Math.min(supplyCell / demandCell, 1.0) * demandToNextCell; } // **** RELEVANT if (nextNeighbour != destination) { nextNeighbour.getCellBehaviour().addAccumulatedCars(flowToDestination); nextNeighbour.getCellBehaviour().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(flowToDestination); cell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(-flowToDestination); cell.getCellBehaviourFlow().addAccumulatedCars(-flowToDestination); } else { // trips have arrived at destination! if (demandToNextCell > 0) { destination.getCellBehaviour().addArrivals(flowToDestination); cell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(-flowToDestination); cell.getCellBehaviourFlow().addAccumulatedCars(-flowToDestination); } } } else if (nextNeighbour.getBehaviourType() == TrafficBehaviourType.FLOW) { // set the variables for the next loop (while...) LinkCellTransmission ctmLinkNext = (LinkCellTransmission) areaGraph.getEdge(neighbour, nextNeighbour).getLink(); // at the first cell after the NTM/Cordon Node, demand comes from // the FlowNode!! FlowCell firstCell = ctmLinkNext.getCells().get(0); // * tripInfoByDestination.getNeighbourAndRouteShare().get( // nextNeighbour); if (demandToNextCell >= 0) { // maximum to enter this cell supplyCell = firstCell.getCellBehaviourFlow().getSupply(); demandCell = firstCell.getCellBehaviourFlow().getDemandToEnter(); // tripInfoByDestination.getDemandToNeighbour(); double flowToDestination = 0; if (demandCell > 0) { flowToDestination = share * Math.min(supplyCell / demandCell, 1.0) * demandToNextCell; } // **** RELEVANT firstCell.getCellBehaviourFlow().addAccumulatedCars(flowToDestination); firstCell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(flowToDestination); cell.getCellBehaviourFlow().addAccumulatedCars(-flowToDestination); cell.getCellBehaviourFlow().getTripInfoByDestinationMap().get(destination) .addAccumulatedCarsToDestination(-flowToDestination); } } } } } } prevCell = cell; } } }