View Javadoc
1   package org.opentrafficsim.road.gtu.generator.od;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Comparator;
6   import java.util.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Map.Entry;
11  import java.util.Set;
12  import java.util.stream.Collectors;
13  
14  import org.djunits.unit.FrequencyUnit;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Frequency;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.djutils.exceptions.Throw;
20  import org.opentrafficsim.base.parameters.ParameterException;
21  import org.opentrafficsim.core.distributions.Generator;
22  import org.opentrafficsim.core.distributions.ProbabilityException;
23  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
24  import org.opentrafficsim.core.gtu.GTUDirectionality;
25  import org.opentrafficsim.core.gtu.GTUException;
26  import org.opentrafficsim.core.gtu.GTUType;
27  import org.opentrafficsim.core.idgenerator.IdGenerator;
28  import org.opentrafficsim.core.math.Draw;
29  import org.opentrafficsim.core.network.Link;
30  import org.opentrafficsim.core.network.LinkType;
31  import org.opentrafficsim.core.network.NetworkException;
32  import org.opentrafficsim.core.network.Node;
33  import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
34  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
35  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
36  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator.RoomChecker;
37  import org.opentrafficsim.road.gtu.generator.MarkovCorrelation;
38  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristics;
39  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristicsGenerator;
40  import org.opentrafficsim.road.gtu.generator.headway.Arrivals;
41  import org.opentrafficsim.road.gtu.generator.headway.ArrivalsHeadwayGenerator;
42  import org.opentrafficsim.road.gtu.generator.headway.ArrivalsHeadwayGenerator.HeadwayDistribution;
43  import org.opentrafficsim.road.gtu.generator.headway.DemandPattern;
44  import org.opentrafficsim.road.gtu.strategical.od.Categorization;
45  import org.opentrafficsim.road.gtu.strategical.od.Category;
46  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
47  import org.opentrafficsim.road.network.OTSRoadNetwork;
48  import org.opentrafficsim.road.network.lane.CrossSectionLink;
49  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
50  import org.opentrafficsim.road.network.lane.Lane;
51  import org.opentrafficsim.road.network.lane.object.sensor.DestinationSensor;
52  import org.opentrafficsim.road.network.lane.object.sensor.Sensor;
53  
54  import nl.tudelft.simulation.dsol.SimRuntimeException;
55  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
56  import nl.tudelft.simulation.jstats.streams.StreamInterface;
57  
58  /**
59   * Utility to create vehicle generators on a network from an OD.
60   * <p>
61   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
62   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
63   * <p>
64   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 30 nov. 2017 <br>
65   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
66   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
67   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
68   */
69  public final class ODApplier
70  {
71  
72      /**
73       * Utility class.
74       */
75      private ODApplier()
76      {
77          //
78      }
79  
80      /**
81       * Applies the OD to the network by creating vehicle generators. The map returned contains objects created for vehicle
82       * generation. These are bundled in a {@code GeneratorObjects} and mapped to the vehicle generator id. Vehicle generator id
83       * is equal to the origin node id. For lane-based generators the id's are appended with an ordered number (e.g. A1), where
84       * the ordering is first by link id, and then right to left concerning the lateral lane position at the start of the lane.
85       * For node "A" this would for example be:<br>
86       * <table >
87       * <caption>&nbsp;</caption>
88       * <tr>
89       * <th>Generator id</th>
90       * <th>Link</th>
91       * <th>Lateral start offset</th>
92       * </tr>
93       * <tr>
94       * <th>A1</th>
95       * <th>AB</th>
96       * <th>-1.75m</th>
97       * </tr>
98       * <tr>
99       * <th>A2</th>
100      * <th>AB</th>
101      * <th>1.75m</th>
102      * </tr>
103      * <tr>
104      * <th>A3</th>
105      * <th>AC</th>
106      * <th>-3.5m</th>
107      * </tr>
108      * <tr>
109      * <th>A4</th>
110      * <th>AC</th>
111      * <th>0.0m</th>
112      * </tr>
113      * </table>
114      * @param network OTSRoadNetwork; network
115      * @param od ODMatrix; OD matrix
116      * @param odOptions ODOptions; options for vehicle generation
117      * @return Map&lt;String, GeneratorObjects&gt; map of generator id's and created generator objects mainly for testing
118      * @throws ParameterException if a parameter is missing
119      * @throws SimRuntimeException if this method is called after simulation time 0
120      */
121     @SuppressWarnings("checkstyle:methodlength")
122     public static Map<String, GeneratorObjects> applyOD(final OTSRoadNetwork network, final ODMatrix od,
123             final ODOptions odOptions) throws ParameterException, SimRuntimeException
124     {
125         Throw.whenNull(network, "Network may not be null.");
126         Throw.whenNull(od, "OD matrix may not be null.");
127         Throw.whenNull(odOptions, "OD options may not be null.");
128         OTSSimulatorInterface simulator = network.getSimulator();
129         Throw.when(!simulator.getSimulatorTime().eq0(), SimRuntimeException.class,
130             "Method ODApplier.applyOD() should be invoked at simulation time 0.");
131 
132         // TODO sinks? white extension links?
133         for (Node destination : od.getDestinations())
134         {
135             createSensorsAtDestination(destination, simulator);
136         }
137 
138         final Categorization categorization = od.getCategorization();
139         final boolean laneBased = categorization.entails(Lane.class);
140         boolean markovian = od.getCategorization().entails(GTUType.class);
141 
142         // TODO clean up stream acquiring code after task OTS-315 has been completed
143         StreamInterface stream = simulator.getModel().getStream("generation");
144         if (stream == null)
145         {
146             stream = simulator.getModel().getStream("default");
147             if (stream == null)
148             {
149                 System.out.println(
150                     "Using locally created stream (not from the simulator) for vehicle generation, with seed 1.");
151                 stream = new MersenneTwister(1L);
152             }
153             else
154             {
155                 System.out.println("Using stream 'default' for vehicle generation.");
156             }
157         }
158 
159         Map<String, GeneratorObjects> output = new LinkedHashMap<>();
160         for (Node origin : od.getOrigins())
161         {
162             // Step 1: create DemandNode trees, starting with a root for each vehicle generator
163             DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> rootNode = null; // root node for each generator
164             /**
165              * Depending on whether the categorization is lane based or not, we either have 1 root per origin, or we have 1 root
166              * per lane (i.e. 1 generator at an origin putting traffic on multiple lanes, or N generators per origin, each
167              * generating traffic on 1 lane). In order to know to which root node the sub nodes belong in a loop, we store root
168              * nodes by lane. Effectively, the map functions as an artificial branching of demand before the origin node, only
169              * used if the categorization contains lanes. For non-lane based demand, the root node and destination node created
170              * in the outer loop can simply be used.
171              */
172             Map<Lane, DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>> originNodePerLane = new LinkedHashMap<>();
173             MarkovChain markovChain = null;
174             if (!laneBased)
175             {
176                 rootNode = new DemandNode<>(origin, stream, null);
177                 LinkType linkType = getLinkTypeFromNode(origin);
178                 if (markovian)
179                 {
180                     MarkovCorrelation<GTUType, Frequency> correlation = odOptions.get(ODOptions.MARKOV, null, origin,
181                         linkType);
182                     if (correlation != null)
183                     {
184                         Throw.when(!od.getCategorization().entails(GTUType.class), IllegalArgumentException.class,
185                             "Markov correlation can only be used on OD categorization entailing GTU type.");
186                         markovChain = new MarkovChain(correlation);
187                     }
188                 }
189             }
190             for (Node destination : od.getDestinations())
191             {
192                 Set<Category> categories = od.getCategories(origin, destination);
193                 if (!categories.isEmpty())
194                 {
195                     DemandNode<Node, DemandNode<Category, ?>> destinationNode = null;
196                     if (!laneBased)
197                     {
198                         destinationNode = new DemandNode<>(destination, stream, markovChain);
199                         rootNode.addChild(destinationNode);
200                     }
201                     for (Category category : categories)
202                     {
203                         if (laneBased)
204                         {
205                             // obtain or create root and destination nodes
206                             Lane lane = category.get(Lane.class);
207                             rootNode = originNodePerLane.get(lane);
208                             if (rootNode == null)
209                             {
210                                 rootNode = new DemandNode<>(origin, stream, null);
211                                 originNodePerLane.put(lane, rootNode);
212                             }
213                             destinationNode = rootNode.getChild(destination);
214                             if (destinationNode == null)
215                             {
216                                 markovChain = null;
217                                 if (markovian)
218                                 {
219                                     MarkovCorrelation<GTUType, Frequency> correlation = odOptions.get(ODOptions.MARKOV, lane,
220                                         origin, lane.getParentLink().getLinkType());
221                                     if (correlation != null)
222                                     {
223                                         Throw.when(!od.getCategorization().entails(GTUType.class),
224                                             IllegalArgumentException.class,
225                                             "Markov correlation can only be used on OD categorization entailing GTU type.");
226                                         markovChain = new MarkovChain(correlation); // 1 for each generator
227                                     }
228                                 }
229                                 destinationNode = new DemandNode<>(destination, stream, markovChain);
230                                 rootNode.addChild(destinationNode);
231                             }
232                         }
233                         DemandNode<Category, ?> categoryNode = new DemandNode<>(category, od.getDemandPattern(origin,
234                             destination, category));
235                         if (markovian)
236                         {
237                             destinationNode.addLeaf(categoryNode, category.get(GTUType.class));
238                         }
239                         else
240                         {
241                             destinationNode.addChild(categoryNode);
242                         }
243                     }
244                 }
245             }
246 
247             // Step 2: gather DirectedLanePositions for each generator pertaining to each DemandNode<...>
248             Map<DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>, Set<DirectedLanePosition>> initialPositions =
249                 new LinkedHashMap<>();
250             Map<CrossSectionLink, Double> linkWeights = null;
251             Map<CrossSectionLink, Node> viaNodes = null;
252             if (laneBased)
253             {
254                 for (Lane lane : originNodePerLane.keySet())
255                 {
256                     DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> demandNode = originNodePerLane.get(lane);
257                     Set<DirectedLanePosition> initialPosition = new LinkedHashSet<>();
258                     try
259                     {
260                         initialPosition.add(lane.getParentLink().getStartNode().equals(demandNode.getObject())
261                             ? new DirectedLanePosition(lane, Length.ZERO, GTUDirectionality.DIR_PLUS)
262                             : new DirectedLanePosition(lane, lane.getLength(), GTUDirectionality.DIR_MINUS));
263                     }
264                     catch (GTUException ge)
265                     {
266                         throw new RuntimeException(ge);
267                     }
268                     initialPositions.put(demandNode, initialPosition);
269                 }
270             }
271             else
272             {
273                 Set<DirectedLanePosition> positionSet = new LinkedHashSet<>();
274                 for (Link link : origin.getLinks())
275                 {
276                     if (link.getLinkType().isConnector())
277                     {
278                         if (link.getStartNode().equals(origin))
279                         {
280                             Node connectedNode = link.getEndNode();
281                             // count number of served links
282                             int served = 0;
283                             for (Link connectedLink : connectedNode.getLinks())
284                             {
285                                 if (connectedLink instanceof CrossSectionLink && !connectedLink.getLinkType().isConnector())
286                                 {
287                                     served++;
288                                 }
289                             }
290                             for (Link connectedLink : connectedNode.getLinks())
291                             {
292                                 if (connectedLink instanceof CrossSectionLink)
293                                 {
294                                     if (link instanceof CrossSectionLink && ((CrossSectionLink) link)
295                                         .getDemandWeight() != null)
296                                     {
297                                         if (linkWeights == null)
298                                         {
299                                             linkWeights = new LinkedHashMap<>();
300                                             viaNodes = new LinkedHashMap<>();
301                                         }
302                                         // store weight under connected link, as this
303                                         linkWeights.put(((CrossSectionLink) connectedLink), ((CrossSectionLink) link)
304                                             .getDemandWeight() / served);
305                                         viaNodes.put((CrossSectionLink) connectedLink, connectedNode);
306                                     }
307                                     setDirectedLanePosition((CrossSectionLink) connectedLink, connectedNode, positionSet);
308                                 }
309                             }
310                         }
311                     }
312                     else if (link instanceof CrossSectionLink)
313                     {
314                         setDirectedLanePosition((CrossSectionLink) link, origin, positionSet);
315                     }
316                 }
317                 initialPositions.put(rootNode, positionSet);
318             }
319 
320             // Step 3: create generator(s)
321             initialPositions = sortByValue(initialPositions); // sorts by lateral position at link start
322             Map<Node, Integer> originGeneratorCounts = new LinkedHashMap<>();
323             for (DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> root : initialPositions.keySet())
324             {
325                 Set<DirectedLanePosition> initialPosition = initialPositions.get(root);
326                 // id
327                 Node o = root.getObject();
328                 String id = o.getId();
329                 if (laneBased)
330                 {
331                     Integer count = originGeneratorCounts.get(o);
332                     if (count == null)
333                     {
334                         count = 0;
335                     }
336                     count++;
337                     id += count;
338                     originGeneratorCounts.put(o, count);
339                 }
340                 // functional generation elements
341                 Lane lane;
342                 LinkType linkType;
343                 if (laneBased)
344                 {
345                     lane = initialPosition.iterator().next().getLane();
346                     linkType = lane.getParentLink().getLinkType();
347                 }
348                 else
349                 {
350                     lane = null;
351                     linkType = getLinkTypeFromNode(o);
352                 }
353                 HeadwayDistribution randomization = odOptions.get(ODOptions.HEADWAY_DIST, lane, o, linkType);
354                 ArrivalsHeadwayGenerator headwayGenerator = new ArrivalsHeadwayGenerator(root, simulator, stream,
355                     randomization);
356                 GTUCharacteristicsGeneratorODWrapper characteristicsGenerator = new GTUCharacteristicsGeneratorODWrapper(
357                     root, simulator, odOptions.get(ODOptions.GTU_TYPE, lane, o, linkType), stream);
358                 RoomChecker roomChecker = odOptions.get(ODOptions.ROOM_CHECKER, lane, o, linkType);
359                 IdGenerator idGenerator = odOptions.get(ODOptions.GTU_ID, lane, o, linkType);
360                 LaneBiases biases = odOptions.get(ODOptions.getLaneBiasOption(network), lane, o, linkType);
361                 // and finally, the generator
362                 try
363                 {
364                     LaneBasedGTUGenerator generator = new LaneBasedGTUGenerator(id, headwayGenerator,
365                         characteristicsGenerator, GeneratorPositions.create(initialPosition, stream, biases, linkWeights,
366                             viaNodes), network, simulator, roomChecker, idGenerator);
367                     generator.setNoLaneChangeDistance(odOptions.get(ODOptions.NO_LC_DIST, lane, o, linkType));
368                     generator.setInstantaneousLaneChange(odOptions.get(ODOptions.INSTANT_LC, lane, o, linkType));
369                     generator.setErrorHandler(odOptions.get(ODOptions.ERROR_HANDLER, lane, o, linkType));
370                     output.put(id, new GeneratorObjects(generator, headwayGenerator, characteristicsGenerator));
371                 }
372                 catch (SimRuntimeException exception)
373                 {
374                     // should not happen, we check that time is 0
375                     simulator.getLogger().always().error(exception);
376                     throw new RuntimeException(exception);
377                 }
378                 catch (ProbabilityException exception)
379                 {
380                     // should not happen, as we define probabilities in the headwayGenerator
381                     simulator.getLogger().always().error(exception);
382                     throw new RuntimeException(exception);
383                 }
384             }
385         }
386         return output;
387     }
388 
389     /**
390      * Create destination sensors at all lanes connected to a destination node. This method considers connectors too.
391      * @param destination Node; destination node
392      * @param simulator OTSSimulatorInterface; simulator
393      */
394     private static void createSensorsAtDestination(final Node destination, final OTSSimulatorInterface simulator)
395     {
396         for (Link link : destination.getLinks())
397         {
398             if (link.getLinkType().isConnector() && !link.getStartNode().equals(destination))
399             {
400                 createSensorsAtDestinationNode(link.getStartNode(), simulator);
401             }
402             else
403             {
404                 createSensorsAtDestinationNode(destination, simulator);
405             }
406         }
407     }
408 
409     /**
410      * Create sensors at all lanes connected to this node. This method does not handle connectors.
411      * @param destination Node; the destination node
412      * @param simulator OTSSimulatorInterface; simulator
413      */
414     private static void createSensorsAtDestinationNode(final Node destination, final OTSSimulatorInterface simulator)
415     {
416         for (Link link : destination.getLinks())
417         {
418             if (link instanceof CrossSectionLink)
419             {
420                 for (Lane lane : ((CrossSectionLink) link).getLanes())
421                 {
422                     try
423                     {
424                         // if the lane already contains a SinkSensor, skip creating a new one
425                         boolean destinationSensorExists = false;
426                         for (Sensor sensor : lane.getSensors())
427                         {
428                             if (sensor instanceof DestinationSensor)
429                             {
430                                 destinationSensorExists = true;
431                             }
432                         }
433                         if (!destinationSensorExists)
434                         {
435                             if (link.getEndNode().equals(destination))
436                             {
437                                 new DestinationSensor(lane, lane.getLength(), GTUDirectionality.DIR_PLUS, simulator);
438                             }
439                             else if (link.getStartNode().equals(destination))
440                             {
441                                 new DestinationSensor(lane, Length.ZERO, GTUDirectionality.DIR_MINUS, simulator);
442                             }
443                         }
444                     }
445                     catch (NetworkException exception)
446                     {
447                         // can not happen, we use Length.ZERO and lane.getLength()
448                         simulator.getLogger().always().error(exception);
449                         throw new RuntimeException(exception);
450                     }
451                 }
452             }
453         }
454     }
455 
456     /**
457      * Returns the common ancestor {@code LinkType} of all links connected to the node, moving through connectors.
458      * @param node Node; origin node
459      * @return common ancestor {@code LinkType} of all links connected to the node, moving through connectors
460      */
461     private static LinkType getLinkTypeFromNode(final Node node)
462     {
463         return getLinkTypeFromNode0(node, false);
464     }
465 
466     /**
467      * Returns the common ancestor {@code LinkType} of all links connected to the node, moving through connectors.
468      * @param node Node; origin node
469      * @param ignoreConnectors boolean; ignore connectors
470      * @return common ancestor {@code LinkType} of all links connected to the node, moving through connectors
471      */
472     private static LinkType getLinkTypeFromNode0(final Node node, final boolean ignoreConnectors)
473     {
474         LinkType linkType = null;
475         for (Link link : node.getLinks())
476         {
477             LinkType next = link.getLinkType();
478             if (!ignoreConnectors && next.isConnector())
479             {
480                 Node otherNode = link.getStartNode().equals(node) ? link.getEndNode() : link.getStartNode();
481                 next = getLinkTypeFromNode0(otherNode, true);
482             }
483             if (next != null && !next.isConnector())
484             {
485                 if (linkType == null)
486                 {
487                     linkType = next;
488                 }
489                 else
490                 {
491                     linkType = linkType.commonAncestor(next);
492                     if (linkType == null)
493                     {
494                         // incompatible link types
495                         return null;
496                     }
497                 }
498             }
499         }
500         return linkType;
501     }
502 
503     /**
504      * Returns a sorted map.
505      * @param map Map&lt;K, V&gt;; input map
506      * @param <K> key type (implemented for cleaner code only)
507      * @param <V> value type (implemented for cleaner code only)
508      * @return Map; sorted map
509      */
510     private static <K, V extends Set<DirectedLanePosition>> Map<K, V> sortByValue(final Map<K, V> map)
511     {
512         return map.entrySet().stream().sorted(new Comparator<Map.Entry<K, V>>()
513         {
514             @Override
515             public int compare(final Entry<K, V> o1, final Entry<K, V> o2)
516             {
517                 DirectedLanePosition lanePos1 = o1.getValue().iterator().next();
518                 String linkId1 = lanePos1.getLane().getParentLink().getId();
519                 DirectedLanePosition lanePos2 = o2.getValue().iterator().next();
520                 String linkId2 = lanePos2.getLane().getParentLink().getId();
521                 int c = linkId1.compareToIgnoreCase(linkId2);
522                 if (c == 0)
523                 {
524                     Length pos1 = lanePos1.getGtuDirection().isPlus() ? Length.ZERO : lanePos1.getLane().getLength();
525                     Length lat1 = lanePos1.getLane().getLateralCenterPosition(pos1);
526                     Length pos2 = lanePos2.getGtuDirection().isPlus() ? Length.ZERO : lanePos2.getLane().getLength();
527                     Length lat2 = lanePos2.getLane().getLateralCenterPosition(pos2);
528                     return lat1.compareTo(lat2);
529                 }
530                 return c;
531             }
532         }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
533     }
534 
535     /**
536      * Adds {@code DirectedLanePosition}s to the input set, for {@code Lane}s on the given link, starting at the given
537      * {@code Node}.
538      * @param link CrossSectionLink; link with lanes to add positions for
539      * @param node Node; node on the side where positions should be placed
540      * @param positionSet Set&lt;DirectedLanePosition&gt;; set to add position to
541      */
542     private static void setDirectedLanePosition(final CrossSectionLink link, final Node node,
543             final Set<DirectedLanePosition> positionSet)
544     {
545         for (Lane lane : link.getLanes())
546         {
547             // TODO should be GTU type dependent.
548             if (!lane.getParentLink().getStartNode().equals(node))
549             {
550                 return;
551                 // TODO handle lanes that ARE drivable contrary to the design direction of the link
552             }
553             try
554             {
555                 positionSet.add(lane.getParentLink().getStartNode().equals(node) ? new DirectedLanePosition(lane,
556                     Length.ZERO, GTUDirectionality.DIR_PLUS) : new DirectedLanePosition(lane, lane.getLength(),
557                         GTUDirectionality.DIR_MINUS));
558             }
559             catch (GTUException ge)
560             {
561                 link.getSimulator().getLogger().always().error(ge);
562                 throw new RuntimeException(ge);
563             }
564         }
565     }
566 
567     /**
568      * Node for demand tree. Based on two constructors there are 2 types of nodes:<br>
569      * <ul>
570      * <li>Branch nodes; with an object and a stream for randomly drawing a child node.</li>
571      * <li>Leaf nodes; with an object and demand data (time, frequency, interpolation).</li>
572      * </ul>
573      * To accomplish a branching of Node (origin) &gt; Node (destination) &gt; Category, the following generics types can be
574      * used:<br>
575      * <br>
576      * {@code DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>}
577      * <p>
578      * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
579      * <br>
580      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
581      * <p>
582      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 1 dec. 2017 <br>
583      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
584      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
585      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
586      * @param <T> type of contained object
587      * @param <K> type of child nodes
588      */
589     private static class DemandNode<T, K extends DemandNode<?, ?>> implements Arrivals
590     {
591 
592         /** Node object. */
593         private final T object;
594 
595         /** Random stream to draw child node. */
596         private final StreamInterface stream;
597 
598         /** Children. */
599         private final List<K> children = new ArrayList<>();
600 
601         /** Demand data. */
602         private final DemandPattern demandPattern;
603 
604         /** Unique GTU types of leaf nodes. */
605         private final List<GTUType> gtuTypes = new ArrayList<>();
606 
607         /** Number of leaf nodes for the unique GTU types. */
608         private final List<Integer> gtuTypeCounts = new ArrayList<>();
609 
610         /** GTU type of leaf nodes. */
611         private final Map<K, GTUType> gtuTypesPerChild = new LinkedHashMap<>();
612 
613         /** Markov chain for GTU type selection. */
614         private final MarkovChain markov;
615 
616         /**
617          * Constructor for branching node, with Markov selection.
618          * @param object T; node object
619          * @param stream StreamInterface; random stream to draw child node
620          * @param markov MarkovChain; Markov chain
621          */
622         DemandNode(final T object, final StreamInterface stream, final MarkovChain markov)
623         {
624             this.object = object;
625             this.stream = stream;
626             this.demandPattern = null;
627             this.markov = markov;
628         }
629 
630         /**
631          * Constructor for leaf node, without Markov selection.
632          * @param object T; node object
633          * @param demandPattern DemandPattern; demand data
634          */
635         DemandNode(final T object, final DemandPattern demandPattern)
636         {
637             this.object = object;
638             this.stream = null;
639             this.demandPattern = demandPattern;
640             this.markov = null;
641         }
642 
643         /**
644          * Adds child to a branching node.
645          * @param child K; child node
646          */
647         public void addChild(final K child)
648         {
649             this.children.add(child);
650         }
651 
652         /**
653          * Adds child to a branching node.
654          * @param child K; child node
655          * @param gtuType GTUType; gtu type for Markov chain
656          */
657         public void addLeaf(final K child, final GTUType gtuType)
658         {
659             Throw.when(this.gtuTypes == null, IllegalStateException.class,
660                 "Adding leaf with GTUType in not possible on a non-Markov node.");
661             addChild(child);
662             this.gtuTypesPerChild.put(child, gtuType);
663             if (!this.gtuTypes.contains(gtuType))
664             {
665                 this.gtuTypes.add(gtuType);
666                 this.gtuTypeCounts.add(1);
667             }
668             else
669             {
670                 int index = this.gtuTypes.indexOf(gtuType);
671                 this.gtuTypeCounts.set(index, this.gtuTypeCounts.get(index) + 1);
672             }
673         }
674 
675         /**
676          * Randomly draws a child node.
677          * @param time Time; simulation time
678          * @return K; randomly drawn child node
679          */
680         public K draw(final Time time)
681         {
682             Throw.when(this.children.isEmpty(), RuntimeException.class, "Calling draw on a leaf node in the demand tree.");
683             Map<K, Double> weightMap = new LinkedHashMap<>();
684             if (this.markov == null)
685             {
686                 // regular draw, loop children and collect their frequencies
687                 for (K child : this.children)
688                 {
689                     double f = child.getFrequency(time, true).si; // sliceStart = true is arbitrary
690                     weightMap.put(child, f);
691                 }
692             }
693             else
694             {
695                 // markov chain draw, the markov chain only selects a GTU type, not a child node
696                 GTUType[] gtuTypeArray = new GTUType[this.gtuTypes.size()];
697                 gtuTypeArray = this.gtuTypes.toArray(gtuTypeArray);
698                 Frequency[] steadyState = new Frequency[this.gtuTypes.size()];
699                 Arrays.fill(steadyState, Frequency.ZERO);
700                 Map<K, Frequency> frequencies = new LinkedHashMap<>(); // stored, saves us from calculating them twice
701                 for (K child : this.children)
702                 {
703                     GTUType gtuType = this.gtuTypesPerChild.get(child);
704                     int index = this.gtuTypes.indexOf(gtuType);
705                     Frequency f = child.getFrequency(time, true); // sliceStart = true is arbitrary
706                     frequencies.put(child, f);
707                     steadyState[index] = steadyState[index].plus(f);
708                 }
709                 GTUType nextGtuType = this.markov.draw(gtuTypeArray, steadyState, this.stream);
710                 // select only child nodes registered to the next GTU type
711                 for (K child : this.children)
712                 {
713                     if (this.gtuTypesPerChild.get(child).equals(nextGtuType))
714                     {
715                         double f = frequencies.get(child).si;
716                         weightMap.put(child, f);
717                     }
718                 }
719             }
720             return Draw.drawWeighted(weightMap, this.stream);
721         }
722 
723         /**
724          * Returns the node object.
725          * @return T; node object
726          */
727         public T getObject()
728         {
729             return this.object;
730         }
731 
732         /**
733          * Returns the child that pertains to specified object or {@code null} if no such child is present.
734          * @param obj Object; child object
735          * @return child that pertains to specified object or {@code null} if no such child is present
736          */
737         public K getChild(final Object obj)
738         {
739             for (K child : this.children)
740             {
741                 if (child.getObject().equals(obj))
742                 {
743                     return child;
744                 }
745             }
746             return null;
747         }
748 
749         /** {@inheritDoc} */
750         @Override
751         public Frequency getFrequency(final Time time, final boolean sliceStart)
752         {
753             if (this.demandPattern != null)
754             {
755                 return this.demandPattern.getFrequency(time, sliceStart);
756             }
757             Frequency f = new Frequency(0.0, FrequencyUnit.PER_HOUR);
758             for (K child : this.children)
759             {
760                 f = f.plus(child.getFrequency(time, sliceStart));
761             }
762             return f;
763         }
764 
765         /** {@inheritDoc} */
766         @Override
767         public Time nextTimeSlice(final Time time)
768         {
769             if (this.demandPattern != null)
770             {
771                 return this.demandPattern.nextTimeSlice(time);
772             }
773             Time out = null;
774             for (K child : this.children)
775             {
776                 Time childSlice = child.nextTimeSlice(time);
777                 out = out == null || (childSlice != null && childSlice.lt(out)) ? childSlice : out;
778             }
779             return out;
780         }
781 
782         /** {@inheritDoc} */
783         @Override
784         public String toString()
785         {
786             return "DemandNode [object=" + this.object + ", stream=" + this.stream + ", children=" + this.children
787                 + ", demandPattern=" + this.demandPattern + ", gtuTypes=" + this.gtuTypes + ", gtuTypeCounts="
788                 + this.gtuTypeCounts + ", gtuTypesPerChild=" + this.gtuTypesPerChild + ", markov=" + this.markov + "]";
789         }
790 
791     }
792 
793     /**
794      * Wrapper class around a {@code MarkovCorrelation}, including the last type. One of these should be used for each vehicle
795      * generator.
796      */
797     private static class MarkovChain
798     {
799         /** Markov correlation for GTU type selection. */
800         private final MarkovCorrelation<GTUType, Frequency> markov;
801 
802         /** Previously returned GTU type. */
803         private GTUType previousGtuType = null;
804 
805         /**
806          * Constructor.
807          * @param markov MarkovCorrelation&lt;GTUType, Frequency&gt;; Markov correlation for GTU type selection
808          */
809         MarkovChain(final MarkovCorrelation<GTUType, Frequency> markov)
810         {
811             this.markov = markov;
812         }
813 
814         /**
815          * Returns a next GTU type drawn using a Markov chain.
816          * @param gtuTypes GTUType[]; GTUTypes to consider
817          * @param intensities Frequency[]; frequency for each GTU type, i.e. the steady-state
818          * @param stream StreamInterface; stream for random numbers
819          * @return next GTU type drawn using a Markov chain
820          */
821         public GTUType draw(final GTUType[] gtuTypes, final Frequency[] intensities, final StreamInterface stream)
822         {
823             this.previousGtuType = this.markov.drawState(this.previousGtuType, gtuTypes, intensities, stream);
824             return this.previousGtuType;
825         }
826     }
827 
828     /**
829      * Characteristics generation based on OD demand.
830      * <p>
831      * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
832      * <br>
833      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
834      * <p>
835      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 7 dec. 2017 <br>
836      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
837      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
838      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
839      */
840     private static class GTUCharacteristicsGeneratorODWrapper implements LaneBasedGTUCharacteristicsGenerator
841     {
842 
843         /** Root node with origin. */
844         private final DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> root;
845 
846         /** Simulator. */
847         private final OTSSimulatorInterface simulator;
848 
849         /** Characteristics generator based on OD information. */
850         private final GTUCharacteristicsGeneratorOD characteristicsGenerator;
851 
852         /** Stream for random numbers. */
853         private final StreamInterface randomStream;
854 
855         /**
856          * @param root DemandNode&lt;Node, DemandNode&lt;Node, DemandNode&lt;Category, ?&gt;&gt;&gt;; root node with origin
857          * @param simulator OTSSimulatorInterface; simulator
858          * @param characteristicsGenerator GTUCharacteristicsGeneratorOD; characteristics generator based on OD information
859          * @param randomStream StreamInterface; stream for random numbers
860          */
861         GTUCharacteristicsGeneratorODWrapper(final DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> root,
862                 final OTSSimulatorInterface simulator,
863                 final GTUCharacteristicsGeneratorOD characteristicsGenerator, final StreamInterface randomStream)
864         {
865             this.root = root;
866             this.simulator = simulator;
867             this.characteristicsGenerator = characteristicsGenerator;
868             this.randomStream = randomStream;
869         }
870 
871         /** {@inheritDoc} */
872         @Override
873         public LaneBasedGTUCharacteristics draw() throws ProbabilityException, ParameterException, GTUException
874         {
875             // obtain node objects
876             Time time = this.simulator.getSimulatorAbsTime();
877             Node origin = this.root.getObject();
878             DemandNode<Node, DemandNode<Category, ?>> destinationNode = this.root.draw(time);
879             Node destination = destinationNode.getObject();
880             Category category = destinationNode.draw(time).getObject();
881             // forward to lower-level generator
882             // XXX typically calls DefaultGTUCharacteristicsGeneratorOD.draw(...)
883             return this.characteristicsGenerator.draw(origin, destination, category, this.randomStream);
884         }
885 
886         /** {@inheritDoc} */
887         @Override
888         public String toString()
889         {
890             return "GTUCharacteristicsGeneratorODWrapper [root=" + this.root + ", simulator=" + this.simulator
891                 + ", characteristicsGenerator=" + this.characteristicsGenerator + ", randomStream=" + this.randomStream
892                 + "]";
893         }
894 
895     }
896 
897     /**
898      * Class to contain created generator objects.
899      * <p>
900      * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
901      * <br>
902      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
903      * <p>
904      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 12 dec. 2017 <br>
905      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
906      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
907      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
908      */
909     public static class GeneratorObjects
910     {
911 
912         /** Main generator for GTU's. */
913         private final LaneBasedGTUGenerator generator;
914 
915         /** Generator of headways. */
916         private final Generator<Duration> headwayGenerator;
917 
918         /** Generator of GTU characteristics. */
919         private final LaneBasedGTUCharacteristicsGenerator characteristicsGenerator;
920 
921         /**
922          * @param generator LaneBasedGTUGenerator; main generator for GTU's
923          * @param headwayGenerator Generator&lt;Duration&gt;; generator of headways
924          * @param characteristicsGenerator LaneBasedGTUCharacteristicsGenerator; generator of GTU characteristics
925          */
926         public GeneratorObjects(final LaneBasedGTUGenerator generator, final Generator<Duration> headwayGenerator,
927                 final LaneBasedGTUCharacteristicsGenerator characteristicsGenerator)
928         {
929             this.generator = generator;
930             this.headwayGenerator = headwayGenerator;
931             this.characteristicsGenerator = characteristicsGenerator;
932         }
933 
934         /**
935          * Returns the main generator for GTU's.
936          * @return LaneBasedGTUGenerator; main generator for GTU's
937          */
938         public LaneBasedGTUGenerator getGenerator()
939         {
940             return this.generator;
941         }
942 
943         /**
944          * Returns the generator of headways.
945          * @return Generator&lt;Duration&gt; generator of headways
946          */
947         public Generator<Duration> getHeadwayGenerator()
948         {
949             return this.headwayGenerator;
950         }
951 
952         /**
953          * Returns the generator of GTU characteristics.
954          * @return LaneBasedGTUCharacteristicsGenerator; generator of GTU characteristics
955          */
956         public LaneBasedGTUCharacteristicsGenerator getCharachteristicsGenerator()
957         {
958             return this.characteristicsGenerator;
959         }
960 
961     }
962 
963 }