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