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