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