OdApplier.java

  1. package org.opentrafficsim.road.od;

  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.Comparator;
  5. import java.util.LinkedHashMap;
  6. import java.util.LinkedHashSet;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Map.Entry;
  10. import java.util.Set;
  11. import java.util.stream.Collectors;

  12. import org.djunits.unit.FrequencyUnit;
  13. import org.djunits.value.vdouble.scalar.Duration;
  14. import org.djunits.value.vdouble.scalar.Frequency;
  15. import org.djunits.value.vdouble.scalar.Length;
  16. import org.djunits.value.vdouble.scalar.Time;
  17. import org.djutils.exceptions.Throw;
  18. import org.opentrafficsim.base.parameters.ParameterException;
  19. import org.opentrafficsim.core.distributions.Generator;
  20. import org.opentrafficsim.core.distributions.ProbabilityException;
  21. import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
  22. import org.opentrafficsim.core.gtu.GtuException;
  23. import org.opentrafficsim.core.gtu.GtuType;
  24. import org.opentrafficsim.core.idgenerator.IdGenerator;
  25. import org.opentrafficsim.core.math.Draw;
  26. import org.opentrafficsim.core.network.Connector;
  27. import org.opentrafficsim.core.network.Link;
  28. import org.opentrafficsim.core.network.LinkType;
  29. import org.opentrafficsim.core.network.NetworkException;
  30. import org.opentrafficsim.core.network.Node;
  31. import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
  32. import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
  33. import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator;
  34. import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.RoomChecker;
  35. import org.opentrafficsim.road.gtu.generator.MarkovCorrelation;
  36. import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
  37. import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGenerator;
  38. import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGeneratorOd;
  39. import org.opentrafficsim.road.gtu.generator.headway.Arrivals;
  40. import org.opentrafficsim.road.gtu.generator.headway.ArrivalsHeadwayGenerator;
  41. import org.opentrafficsim.road.gtu.generator.headway.ArrivalsHeadwayGenerator.HeadwayDistribution;
  42. import org.opentrafficsim.road.gtu.generator.headway.DemandPattern;
  43. import org.opentrafficsim.road.network.RoadNetwork;
  44. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  45. import org.opentrafficsim.road.network.lane.Lane;
  46. import org.opentrafficsim.road.network.lane.LanePosition;
  47. import org.opentrafficsim.road.network.lane.object.detector.DetectorType;
  48. import org.opentrafficsim.road.network.lane.object.detector.LaneDetector;
  49. import org.opentrafficsim.road.network.lane.object.detector.SinkDetector;

  50. import nl.tudelft.simulation.dsol.SimRuntimeException;
  51. import nl.tudelft.simulation.jstats.streams.MersenneTwister;
  52. import nl.tudelft.simulation.jstats.streams.StreamInterface;

  53. /**
  54.  * Utility to create vehicle generators on a network from an OD.
  55.  * <p>
  56.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  57.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  58.  * </p>
  59.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  60.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  61.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  62.  */
  63. public final class OdApplier
  64. {

  65.     /**
  66.      * Utility class.
  67.      */
  68.     private OdApplier()
  69.     {
  70.         //
  71.     }

  72.     /**
  73.      * Applies the OD to the network by creating vehicle generators. The map returned contains objects created for vehicle
  74.      * generation. These are bundled in a {@code GeneratorObjects} and mapped to the vehicle generator id. Vehicle generator id
  75.      * is equal to the origin node id. For lane-based generators the id's are appended with an ordered number (e.g. A1), where
  76.      * the ordering is first by link id, and then right to left concerning the lateral lane position at the start of the lane.
  77.      * For node "A" this would for example be:<br>
  78.      * <table >
  79.      * <caption>&nbsp;</caption>
  80.      * <tr>
  81.      * <th>Generator id</th>
  82.      * <th>Link</th>
  83.      * <th>Lateral start offset</th>
  84.      * </tr>
  85.      * <tr>
  86.      * <th>A1</th>
  87.      * <th>AB</th>
  88.      * <th>-1.75m</th>
  89.      * </tr>
  90.      * <tr>
  91.      * <th>A2</th>
  92.      * <th>AB</th>
  93.      * <th>1.75m</th>
  94.      * </tr>
  95.      * <tr>
  96.      * <th>A3</th>
  97.      * <th>AC</th>
  98.      * <th>-3.5m</th>
  99.      * </tr>
  100.      * <tr>
  101.      * <th>A4</th>
  102.      * <th>AC</th>
  103.      * <th>0.0m</th>
  104.      * </tr>
  105.      * </table>
  106.      * If the GTU generation is lane-based (i.e. {@code Lane} in the {@code Categorization}) this method creates a
  107.      * {@code LaneBasedGtuGenerator} per lane. It will have a single source of demand data, specifying demand towards all
  108.      * relevant destinations, and with a unique {@code MarkovChain} for the GTU type if {@code MarkovCorrelation} is defined.
  109.      * For zone GTU generation one {@code LaneBasedGtuGenerator} is created, with one single source of demand data, specifying
  110.      * demand towards all destinations. A single {@code MarkovChain} may be used. Traffic is distributed over possible
  111.      * {@code Connectors} based on their link-weight, or the number of lanes of the connected links if no weight is given.
  112.      * @param network RoadNetwork; network
  113.      * @param od OdMatrix; OD matrix
  114.      * @param odOptions OdOptions; options for vehicle generation
  115.      * @param detectorType DetectorType; detector type.
  116.      * @return Map&lt;String, GeneratorObjects&gt; map of generator id's and created generator objects mainly for testing
  117.      * @throws ParameterException if a parameter is missing
  118.      * @throws SimRuntimeException if this method is called after simulation time 0
  119.      */
  120.     @SuppressWarnings("checkstyle:methodlength")
  121.     public static Map<String, GeneratorObjects> applyOd(final RoadNetwork network, final OdMatrix od, final OdOptions odOptions,
  122.             final DetectorType detectorType) throws ParameterException, SimRuntimeException
  123.     {
  124.         Throw.whenNull(network, "Network may not be null.");
  125.         Throw.whenNull(od, "OD matrix may not be null.");
  126.         Throw.whenNull(odOptions, "OD options may not be null.");
  127.         OtsSimulatorInterface simulator = network.getSimulator();
  128.         Throw.when(!simulator.getSimulatorTime().eq0(), SimRuntimeException.class,
  129.                 "Method OdApplier.applyOd() should be invoked at simulation time 0.");

  130.         // TODO sinks? white extension links?
  131.         for (Node destination : od.getDestinations())
  132.         {
  133.             createSensorsAtDestination(destination, simulator, detectorType);
  134.         }

  135.         // TODO clean up stream acquiring code after task OTS-315 has been completed
  136.         StreamInterface stream = getStream(simulator);

  137.         boolean laneBased = od.getCategorization().entails(Lane.class);
  138.         Map<String, GeneratorObjects> output = new LinkedHashMap<>();
  139.         for (Node origin : od.getOrigins())
  140.         {
  141.             Map<Lane, DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>> originNodePerLane = new LinkedHashMap<>();
  142.             DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> originNodeZone =
  143.                     buildDemandNodeTree(od, odOptions, stream, origin, originNodePerLane);
  144.             Map<DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>, Set<LanePosition>> initialPositions =
  145.                     new LinkedHashMap<>();
  146.             Map<CrossSectionLink, Double> linkWeights = new LinkedHashMap<>();
  147.             Map<CrossSectionLink, Node> viaNodes = new LinkedHashMap<>();
  148.             if (laneBased)
  149.             {
  150.                 gatherPositionsLaneBased(originNodePerLane, initialPositions);
  151.             }
  152.             else
  153.             {
  154.                 initialPositions.put(originNodeZone, gatherPositionsZone(origin, linkWeights, viaNodes));
  155.             }
  156.             if (linkWeights.isEmpty())
  157.             {
  158.                 linkWeights = null;
  159.                 viaNodes = null;
  160.             }
  161.             initialPositions = sortByValue(initialPositions); // sorts by lateral position at link start
  162.             createGenerators(network, odOptions, simulator, laneBased, stream, output, initialPositions, linkWeights, viaNodes);
  163.         }
  164.         return output;
  165.     }

  166.     /**
  167.      * Builds nested demand node structure (i.e. tree) for demand and GTU characteristics generation. If
  168.      * {@code MarkovCorrelation} is specified, in case of zone GTU generation, a single {@code MarkovChain} is used for the
  169.      * selection of GTU type and the relevant lane. In case of lane-based GTU generation, one {@code MarkovChain} is used for
  170.      * each lane, even when multiple {@code Category}'s contain the same lane. This method loops over all destinations for the
  171.      * given origin, and then over all categories. For lane-based GTU generation, at that level the appropriate origin node is
  172.      * taken from the input map, or it is created in to it, and the destination demand-node coupled to that for the looped
  173.      * destination is obtained or created, with possible {@code MarkovChain}. For zone GTU generation, the looping is a more
  174.      * straight-forward creation of nodes from origin, and for destination and category. The result of lane-based GTU generation
  175.      * is given in the input map, for zone GTU generation the single origin demand node is returned by the method.
  176.      * @param od OdMatrix; OD matrix.
  177.      * @param odOptions OdOptions; OD options.
  178.      * @param stream StreamInterface; random number stream.
  179.      * @param origin Node; origin node.
  180.      * @param originNodePerLane Map&lt;Lane, DemandNode&lt;Node, DemandNode&lt;Node, DemandNode&lt;Category, ?&gt;&gt;&gt;&gt;;
  181.      *            map of origin demand node per lane, populated for lane-based GTU generation.
  182.      * @return DemandNode&lt;Node, DemandNode&lt;Node, DemandNode&lt;Category, ?&gt;&gt;&gt;; demand node structure for the
  183.      *         entire generator in case of zone GTU generation.
  184.      */
  185.     private static DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> buildDemandNodeTree(final OdMatrix od,
  186.             final OdOptions odOptions, final StreamInterface stream, final Node origin,
  187.             final Map<Lane, DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>> originNodePerLane)
  188.     {
  189.         boolean laneBased = od.getCategorization().entails(Lane.class);
  190.         boolean markovian = od.getCategorization().entails(GtuType.class);
  191.         DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> demandNode = null; // for each generator, flexibly used
  192.         MarkovChain markovChain = null;
  193.         if (!laneBased)
  194.         {
  195.             demandNode = new DemandNode<>(origin, stream, null);
  196.             LinkType linkType = getLinkTypeFromNode(origin);
  197.             if (markovian)
  198.             {
  199.                 MarkovCorrelation<GtuType, Frequency> correlation = odOptions.get(OdOptions.MARKOV, null, origin, linkType);
  200.                 if (correlation != null)
  201.                 {
  202.                     Throw.when(!od.getCategorization().entails(GtuType.class), IllegalArgumentException.class,
  203.                             "Markov correlation can only be used on OD categorization entailing GTU type.");
  204.                     markovChain = new MarkovChain(correlation);
  205.                 }
  206.             }
  207.         }
  208.         for (Node destination : od.getDestinations())
  209.         {
  210.             Set<Category> categories = od.getCategories(origin, destination);
  211.             if (!categories.isEmpty())
  212.             {
  213.                 DemandNode<Node, DemandNode<Category, ?>> destinationNode = null;
  214.                 if (!laneBased)
  215.                 {
  216.                     destinationNode = new DemandNode<>(destination, stream, markovChain);
  217.                     demandNode.addChild(destinationNode);
  218.                 }
  219.                 for (Category category : categories)
  220.                 {
  221.                     if (laneBased)
  222.                     {
  223.                         // obtain or create root and destination nodes
  224.                         Lane lane = category.get(Lane.class);
  225.                         demandNode = originNodePerLane.get(lane);
  226.                         if (demandNode == null)
  227.                         {
  228.                             demandNode = new DemandNode<>(origin, stream, null);
  229.                             originNodePerLane.put(lane, demandNode);
  230.                         }
  231.                         destinationNode = demandNode.getChild(destination);
  232.                         if (destinationNode == null)
  233.                         {
  234.                             markovChain = null;
  235.                             if (markovian)
  236.                             {
  237.                                 MarkovCorrelation<GtuType, Frequency> correlation =
  238.                                         odOptions.get(OdOptions.MARKOV, lane, origin, lane.getLink().getType());
  239.                                 if (correlation != null)
  240.                                 {
  241.                                     Throw.when(!od.getCategorization().entails(GtuType.class), IllegalArgumentException.class,
  242.                                             "Markov correlation can only be used on OD categorization entailing GTU type.");
  243.                                     markovChain = new MarkovChain(correlation); // 1 for each generator per lane
  244.                                 }
  245.                             }
  246.                             destinationNode = new DemandNode<>(destination, stream, markovChain);
  247.                             demandNode.addChild(destinationNode);
  248.                         }
  249.                     }
  250.                     DemandNode<Category, ?> categoryNode =
  251.                             new DemandNode<>(category, od.getDemandPattern(origin, destination, category));
  252.                     if (markovian)
  253.                     {
  254.                         destinationNode.addLeaf(categoryNode, category.get(GtuType.class));
  255.                     }
  256.                     else
  257.                     {
  258.                         destinationNode.addChild(categoryNode);
  259.                     }
  260.                 }
  261.             }
  262.         }
  263.         return demandNode;
  264.     }

  265.     /**
  266.      * Returns a set of positions for GTU generation from each lane defined in demand. Stores the positions with the coupled
  267.      * demand node.
  268.      * @param originNodePerLane Map&lt;Lane, DemandNode&lt;Node, DemandNode&lt;Node, DemandNode&lt;Category, ?&gt;&gt;&gt;&gt;;
  269.      *            map with a demand node per lane.
  270.      * @param initialPositions Map&lt;DemandNode&lt;Node, DemandNode&lt;Node, DemandNode&lt;Category, ?&gt;&gt;&gt;,
  271.      *            Set&lt;LanePosition&gt;&gt;; map with positions per demand node.
  272.      */
  273.     private static void gatherPositionsLaneBased(
  274.             final Map<Lane, DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>> originNodePerLane,
  275.             final Map<DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>, Set<LanePosition>> initialPositions)
  276.     {
  277.         for (Lane lane : originNodePerLane.keySet())
  278.         {
  279.             DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> demandNode = originNodePerLane.get(lane);
  280.             Set<LanePosition> initialPosition = new LinkedHashSet<>();
  281.             initialPosition.add(lane.getLink().getStartNode().equals(demandNode.getObject())
  282.                     ? new LanePosition(lane, Length.ZERO) : new LanePosition(lane, lane.getLength()));
  283.             initialPositions.put(demandNode, initialPosition);
  284.         }
  285.     }

  286.     /**
  287.      * Returns a set of positions for GTU generation from a zone. All links connected to the origin node are considered. In case
  288.      * a link is a {@code Connector}, a link weight and via-node over that link are stored in the provided maps, for later use
  289.      * in constructing weighted generator positions. For each {@code CrossSectionLink} attached to the via-node, or to the first
  290.      * link if there was no {@code Connector}, positions are generated.
  291.      * @param origin Node; origin node for the zone.
  292.      * @param linkWeights Map&lt;CrossSectionLink, Double&gt;; link weight map to place link weights in.
  293.      * @param viaNodes Map&lt;CrossSectionLink, Node&gt;; via node map to place via nodes in.
  294.      * @return Set&lt;LanePosition&gt;; gathered lane positions.
  295.      */
  296.     private static Set<LanePosition> gatherPositionsZone(final Node origin, final Map<CrossSectionLink, Double> linkWeights,
  297.             final Map<CrossSectionLink, Node> viaNodes)
  298.     {
  299.         Set<LanePosition> positionSet = new LinkedHashSet<>();
  300.         for (Link link : origin.getLinks())
  301.         {
  302.             if (link instanceof Connector)
  303.             {
  304.                 Connector connector = (Connector) link;
  305.                 if (connector.getStartNode().equals(origin))
  306.                 {
  307.                     Node connectedNode = connector.getEndNode();
  308.                     // count number of served links
  309.                     int served = 0;
  310.                     for (Link connectedLink : connectedNode.getLinks())
  311.                     {
  312.                         if (connectedLink instanceof CrossSectionLink)
  313.                         {
  314.                             served++;
  315.                         }
  316.                     }
  317.                     for (Link connectedLink : connectedNode.getLinks())
  318.                     {
  319.                         if (connectedLink instanceof CrossSectionLink)
  320.                         {
  321.                             if (connector.getDemandWeight() > 0.0)
  322.                             {
  323.                                 // store weight under connected link, as this
  324.                                 linkWeights.put(((CrossSectionLink) connectedLink), connector.getDemandWeight() / served);
  325.                             }
  326.                             else
  327.                             {
  328.                                 // negative weight results in number of lanes being used
  329.                                 linkWeights.put(((CrossSectionLink) connectedLink), -1.0);
  330.                             }
  331.                             viaNodes.put((CrossSectionLink) connectedLink, connectedNode);
  332.                             setLanePosition((CrossSectionLink) connectedLink, connectedNode, positionSet);
  333.                         }
  334.                     }
  335.                 }
  336.             }
  337.             else if (link instanceof CrossSectionLink)
  338.             {
  339.                 setLanePosition((CrossSectionLink) link, origin, positionSet);
  340.             }
  341.         }
  342.         return positionSet;
  343.     }

  344.     /**
  345.      * Creates GTU generators. For lane-based GTU generation (i.e. {@code Lane} in the {@code Categorization}), the generators
  346.      * will obtain an ID with the node id plus a counter. For this the initial positions need to be sorted. The link weights and
  347.      * via nodes should be {@code null} for lane-based GTU generation. Furthermore, the lane is then used to obtain OD option
  348.      * values possibly specified at the lane level. Other than that, for either lane-based or zone GTU generation, a
  349.      * {@code LaneBasedGtuGenerator} is created for each initial position given.
  350.      * @param network RoadNetwork; network.
  351.      * @param odOptions OdOptions; od options.
  352.      * @param simulator OtsSimulatorInterface; simulator.
  353.      * @param laneBased boolean; lane in category.
  354.      * @param stream StreamInterface; random number stream.
  355.      * @param output Map&lt;String, GeneratorObjects&gt;; map that output elements will be stored in.
  356.      * @param initialPositions Map&lt;DemandNode&lt;Node, DemandNode&lt;Node, DemandNode&lt;Category, ?&gt;&gt;&gt;,
  357.      *            Set&lt;LanePosition&gt;&gt;; sorted initial positions.
  358.      * @param linkWeights Map&lt;CrossSectionLink, Double&gt;; weights per link, may be {@code null}.
  359.      * @param viaNodes Map&lt;CrossSectionLink, Node&gt;; nodes to select from for zone, may be {@code null}.
  360.      * @throws ParameterException if drawing from the inter-arrival generator fails
  361.      */
  362.     @SuppressWarnings("checkstyle:parameternumber")
  363.     private static void createGenerators(final RoadNetwork network, final OdOptions odOptions,
  364.             final OtsSimulatorInterface simulator, final boolean laneBased, final StreamInterface stream,
  365.             final Map<String, GeneratorObjects> output,
  366.             final Map<DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>, Set<LanePosition>> initialPositions,
  367.             final Map<CrossSectionLink, Double> linkWeights, final Map<CrossSectionLink, Node> viaNodes)
  368.             throws ParameterException
  369.     {
  370.         Map<Node, Integer> laneGeneratorCounterForUniqueId = new LinkedHashMap<>();
  371.         for (DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>> root : initialPositions.keySet())
  372.         {
  373.             Set<LanePosition> initialPosition = initialPositions.get(root);
  374.             // id
  375.             Node o = root.getObject();
  376.             String id = o.getId();
  377.             if (laneBased)
  378.             {
  379.                 Integer count = laneGeneratorCounterForUniqueId.get(o);
  380.                 if (count == null)
  381.                 {
  382.                     count = 0;
  383.                 }
  384.                 count++;
  385.                 id += count;
  386.                 laneGeneratorCounterForUniqueId.put(o, count);
  387.             }
  388.             // functional generation elements
  389.             Lane lane;
  390.             LinkType linkType;
  391.             if (laneBased)
  392.             {
  393.                 lane = initialPosition.iterator().next().lane();
  394.                 linkType = lane.getLink().getType();
  395.             }
  396.             else
  397.             {
  398.                 lane = null;
  399.                 linkType = getLinkTypeFromNode(o);
  400.             }
  401.             HeadwayDistribution randomization = odOptions.get(OdOptions.HEADWAY_DIST, lane, o, linkType);
  402.             ArrivalsHeadwayGenerator headwayGenerator = new ArrivalsHeadwayGenerator(root, simulator, stream, randomization);
  403.             LaneBasedGtuCharacteristicsGeneratorOd characteristicsGeneratorOd =
  404.                     odOptions.get(OdOptions.GTU_TYPE, lane, o, linkType);
  405.             LaneBasedGtuCharacteristicsGenerator characteristicsGenerator = new LaneBasedGtuCharacteristicsGenerator()
  406.             {
  407.                 /** {@inheritDoc} */
  408.                 @Override
  409.                 public LaneBasedGtuCharacteristics draw() throws ProbabilityException, ParameterException, GtuException
  410.                 {
  411.                     Time time = simulator.getSimulatorAbsTime();
  412.                     Node origin = root.getObject();
  413.                     DemandNode<Node, DemandNode<Category, ?>> destinationNode = root.draw(time);
  414.                     Node destination = destinationNode.getObject();
  415.                     Category category = destinationNode.draw(time).getObject();
  416.                     return characteristicsGeneratorOd.draw(origin, destination, category, stream);
  417.                 }
  418.             };

  419.             RoomChecker roomChecker = odOptions.get(OdOptions.ROOM_CHECKER, lane, o, linkType);
  420.             IdGenerator idGenerator = odOptions.get(OdOptions.GTU_ID, lane, o, linkType);
  421.             LaneBiases biases = odOptions.get(OdOptions.LANE_BIAS, lane, o, linkType);
  422.             // and finally, the generator
  423.             try
  424.             {
  425.                 LaneBasedGtuGenerator generator = new LaneBasedGtuGenerator(id, headwayGenerator, characteristicsGenerator,
  426.                         GeneratorPositions.create(initialPosition, stream, biases, linkWeights, viaNodes), network, simulator,
  427.                         roomChecker, idGenerator);
  428.                 generator.setNoLaneChangeDistance(odOptions.get(OdOptions.NO_LC_DIST, lane, o, linkType));
  429.                 generator.setInstantaneousLaneChange(odOptions.get(OdOptions.INSTANT_LC, lane, o, linkType));
  430.                 generator.setErrorHandler(odOptions.get(OdOptions.ERROR_HANDLER, lane, o, linkType));
  431.                 output.put(id, new GeneratorObjects(generator, headwayGenerator, characteristicsGenerator));
  432.             }
  433.             catch (SimRuntimeException exception)
  434.             {
  435.                 // should not happen, we check that time is 0
  436.                 simulator.getLogger().always().error(exception);
  437.                 throw new RuntimeException(exception);
  438.             }
  439.             catch (ProbabilityException exception)
  440.             {
  441.                 // should not happen, as we define probabilities in the headwayGenerator
  442.                 simulator.getLogger().always().error(exception);
  443.                 throw new RuntimeException(exception);
  444.             }
  445.             catch (NetworkException exception)
  446.             {
  447.                 // should not happen, as unique ids are guaranteed by UUID
  448.                 simulator.getLogger().always().error(exception);
  449.                 throw new RuntimeException(exception);
  450.             }
  451.         }
  452.     }

  453.     /**
  454.      * Obtains a stream for vehicle generation.
  455.      * @param simulator OtsSimulatorInterface; simulator.
  456.      * @return StreamInterface; stream for vehicle generation.
  457.      */
  458.     private static StreamInterface getStream(final OtsSimulatorInterface simulator)
  459.     {
  460.         StreamInterface stream = simulator.getModel().getStream("generation");
  461.         if (stream == null)
  462.         {
  463.             stream = simulator.getModel().getStream("default");
  464.             if (stream == null)
  465.             {
  466.                 System.out
  467.                         .println("Using locally created stream (not from the simulator) for vehicle generation, with seed 1.");
  468.                 stream = new MersenneTwister(1L);
  469.             }
  470.             else
  471.             {
  472.                 System.out.println("Using stream 'default' for vehicle generation.");
  473.             }
  474.         }
  475.         return stream;
  476.     }

  477.     /**
  478.      * Create destination sensors at all lanes connected to a destination node. This method considers connectors too.
  479.      * @param destination Node; destination node
  480.      * @param simulator OtsSimulatorInterface; simulator
  481.      * @param detectorType DetectorType; detector type.
  482.      */
  483.     private static void createSensorsAtDestination(final Node destination, final OtsSimulatorInterface simulator,
  484.             final DetectorType detectorType)
  485.     {
  486.         for (Link link : destination.getLinks())
  487.         {
  488.             if (link.isConnector() && !link.getStartNode().equals(destination))
  489.             {
  490.                 createSensorsAtDestinationNode(link.getStartNode(), simulator, detectorType);
  491.             }
  492.             else
  493.             {
  494.                 createSensorsAtDestinationNode(destination, simulator, detectorType);
  495.             }
  496.         }
  497.     }

  498.     /**
  499.      * Create sensors at all lanes connected to this node. This method does not handle connectors.
  500.      * @param destination Node; the destination node
  501.      * @param simulator OtsSimulatorInterface; simulator
  502.      * @param detectorType DetectorType; detector type.
  503.      */
  504.     private static void createSensorsAtDestinationNode(final Node destination, final OtsSimulatorInterface simulator,
  505.             final DetectorType detectorType)
  506.     {
  507.         for (Link link : destination.getLinks())
  508.         {
  509.             if (link instanceof CrossSectionLink)
  510.             {
  511.                 for (Lane lane : ((CrossSectionLink) link).getLanes())
  512.                 {
  513.                     try
  514.                     {
  515.                         // if the lane already contains a SinkDetector, skip creating a new one
  516.                         boolean destinationDetectorExists = false;
  517.                         for (LaneDetector detector : lane.getDetectors())
  518.                         {
  519.                             if (detector instanceof SinkDetector)
  520.                             {
  521.                                 destinationDetectorExists = true;
  522.                             }
  523.                         }
  524.                         if (!destinationDetectorExists)
  525.                         {
  526.                             new SinkDetector(lane, lane.getLength(), simulator, detectorType, SinkDetector.DESTINATION);
  527.                         }
  528.                     }
  529.                     catch (NetworkException exception)
  530.                     {
  531.                         // can not happen, we use Length.ZERO and lane.getLength()
  532.                         simulator.getLogger().always().error(exception);
  533.                         throw new RuntimeException(exception);
  534.                     }
  535.                 }
  536.             }
  537.         }
  538.     }

  539.     /**
  540.      * Returns the common ancestor {@code LinkType} of all links connected to the node, moving through connectors.
  541.      * @param node Node; origin node
  542.      * @return common ancestor {@code LinkType} of all links connected to the node, moving through connectors
  543.      */
  544.     private static LinkType getLinkTypeFromNode(final Node node)
  545.     {
  546.         return getLinkTypeFromNode0(node, false);
  547.     }

  548.     /**
  549.      * Returns the common ancestor {@code LinkType} of all links connected to the node, moving through connectors.
  550.      * @param node Node; origin node
  551.      * @param ignoreConnectors boolean; ignore connectors
  552.      * @return common ancestor {@code LinkType} of all links connected to the node, moving through connectors
  553.      */
  554.     private static LinkType getLinkTypeFromNode0(final Node node, final boolean ignoreConnectors)
  555.     {
  556.         LinkType linkType = null;
  557.         for (Link link : node.getLinks())
  558.         {
  559.             LinkType next = link.getType();
  560.             if (!ignoreConnectors && link.isConnector())
  561.             {
  562.                 Node otherNode = link.getStartNode().equals(node) ? link.getEndNode() : link.getStartNode();
  563.                 next = getLinkTypeFromNode0(otherNode, true);
  564.             }
  565.             if (next != null && !link.isConnector())
  566.             {
  567.                 if (linkType == null)
  568.                 {
  569.                     linkType = next;
  570.                 }
  571.                 else
  572.                 {
  573.                     linkType = linkType.commonAncestor(next);
  574.                     if (linkType == null)
  575.                     {
  576.                         // incompatible link types
  577.                         return null;
  578.                     }
  579.                 }
  580.             }
  581.         }
  582.         return linkType;
  583.     }

  584.     /**
  585.      * Returns a sorted map.
  586.      * @param map Map&lt;K, V&gt;; input map
  587.      * @param <K> key type (implemented for cleaner code only)
  588.      * @param <V> value type (implemented for cleaner code only)
  589.      * @return Map; sorted map
  590.      */
  591.     private static <K, V extends Set<LanePosition>> Map<K, V> sortByValue(final Map<K, V> map)
  592.     {
  593.         return map.entrySet().stream().sorted(new Comparator<Map.Entry<K, V>>()
  594.         {
  595.             @Override
  596.             public int compare(final Entry<K, V> o1, final Entry<K, V> o2)
  597.             {
  598.                 LanePosition lanePos1 = o1.getValue().iterator().next();
  599.                 String linkId1 = lanePos1.lane().getLink().getId();
  600.                 LanePosition lanePos2 = o2.getValue().iterator().next();
  601.                 String linkId2 = lanePos2.lane().getLink().getId();
  602.                 int c = linkId1.compareToIgnoreCase(linkId2);
  603.                 if (c == 0)
  604.                 {
  605.                     Length pos1 = Length.ZERO;
  606.                     Length lat1 = lanePos1.lane().getLateralCenterPosition(pos1);
  607.                     Length pos2 = Length.ZERO;
  608.                     Length lat2 = lanePos2.lane().getLateralCenterPosition(pos2);
  609.                     return lat1.compareTo(lat2);
  610.                 }
  611.                 return c;
  612.             }
  613.         }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
  614.     }

  615.     /**
  616.      * Adds {@code LanePosition}s to the input set, for {@code Lane}s on the given link, starting at the given {@code Node}.
  617.      * @param link CrossSectionLink; link with lanes to add positions for
  618.      * @param node Node; node on the side where positions should be placed
  619.      * @param positionSet Set&lt;LanePosition&gt;; set to add position to
  620.      */
  621.     private static void setLanePosition(final CrossSectionLink link, final Node node, final Set<LanePosition> positionSet)
  622.     {
  623.         for (Lane lane : link.getLanes())
  624.         {
  625.             // TODO should be GTU type dependent.
  626.             if (lane.getLink().getStartNode().equals(node))
  627.             {
  628.                 positionSet.add(new LanePosition(lane, Length.ZERO));
  629.             }
  630.         }
  631.     }

  632.     /**
  633.      * Node for demand tree. Based on two constructors there are 2 types of nodes:<br>
  634.      * <ul>
  635.      * <li>Branch nodes; with an object and a stream for randomly drawing a child node.</li>
  636.      * <li>Leaf nodes; with an object and demand data (time, frequency, interpolation).</li>
  637.      * </ul>
  638.      * To accomplish a branching of Node (origin) &gt; Node (destination) &gt; Category, the following generics types can be
  639.      * used:<br>
  640.      * <br>
  641.      * {@code DemandNode<Node, DemandNode<Node, DemandNode<Category, ?>>>}
  642.      * <p>
  643.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  644.      * <br>
  645.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  646.      * </p>
  647.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  648.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  649.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  650.      * @param <T> type of contained object
  651.      * @param <K> type of child nodes
  652.      */
  653.     private static class DemandNode<T, K extends DemandNode<?, ?>> implements Arrivals
  654.     {

  655.         /** Node object. */
  656.         private final T object;

  657.         /** Random stream to draw child node. */
  658.         private final StreamInterface stream;

  659.         /** Children. */
  660.         private final List<K> children = new ArrayList<>();

  661.         /** Demand data. */
  662.         private final DemandPattern demandPattern;

  663.         /** Unique GTU types of leaf nodes. */
  664.         private final List<GtuType> gtuTypes = new ArrayList<>();

  665.         /** Number of leaf nodes for the unique GTU types. */
  666.         private final List<Integer> gtuTypeCounts = new ArrayList<>();

  667.         /** GTU type of leaf nodes. */
  668.         private final Map<K, GtuType> gtuTypesPerChild = new LinkedHashMap<>();

  669.         /** Markov chain for GTU type selection. */
  670.         private final MarkovChain markov;

  671.         /**
  672.          * Constructor for branching node, with Markov selection.
  673.          * @param object T; node object
  674.          * @param stream StreamInterface; random stream to draw child node
  675.          * @param markov MarkovChain; Markov chain
  676.          */
  677.         DemandNode(final T object, final StreamInterface stream, final MarkovChain markov)
  678.         {
  679.             this.object = object;
  680.             this.stream = stream;
  681.             this.demandPattern = null;
  682.             this.markov = markov;
  683.         }

  684.         /**
  685.          * Constructor for leaf node, without Markov selection.
  686.          * @param object T; node object
  687.          * @param demandPattern DemandPattern; demand data
  688.          */
  689.         DemandNode(final T object, final DemandPattern demandPattern)
  690.         {
  691.             this.object = object;
  692.             this.stream = null;
  693.             this.demandPattern = demandPattern;
  694.             this.markov = null;
  695.         }

  696.         /**
  697.          * Adds child to a branching node.
  698.          * @param child K; child node
  699.          */
  700.         public void addChild(final K child)
  701.         {
  702.             this.children.add(child);
  703.         }

  704.         /**
  705.          * Adds child to a branching node.
  706.          * @param child K; child node
  707.          * @param gtuType GtuType; gtu type for Markov chain
  708.          */
  709.         public void addLeaf(final K child, final GtuType gtuType)
  710.         {
  711.             Throw.when(this.gtuTypes == null, IllegalStateException.class,
  712.                     "Adding leaf with GtuType in not possible on a non-Markov node.");
  713.             addChild(child);
  714.             this.gtuTypesPerChild.put(child, gtuType);
  715.             if (!this.gtuTypes.contains(gtuType))
  716.             {
  717.                 this.gtuTypes.add(gtuType);
  718.                 this.gtuTypeCounts.add(1);
  719.             }
  720.             else
  721.             {
  722.                 int index = this.gtuTypes.indexOf(gtuType);
  723.                 this.gtuTypeCounts.set(index, this.gtuTypeCounts.get(index) + 1);
  724.             }
  725.         }

  726.         /**
  727.          * Randomly draws a child node.
  728.          * @param time Time; simulation time
  729.          * @return K; randomly drawn child node
  730.          */
  731.         public K draw(final Time time)
  732.         {
  733.             Throw.when(this.children.isEmpty(), RuntimeException.class, "Calling draw on a leaf node in the demand tree.");
  734.             Map<K, Double> weightMap = new LinkedHashMap<>();
  735.             if (this.markov == null)
  736.             {
  737.                 // regular draw, loop children and collect their frequencies
  738.                 for (K child : this.children)
  739.                 {
  740.                     double f = child.getFrequency(time, true).si; // sliceStart = true is arbitrary
  741.                     weightMap.put(child, f);
  742.                 }
  743.             }
  744.             else
  745.             {
  746.                 // markov chain draw, the markov chain only selects a GTU type, not a child node
  747.                 GtuType[] gtuTypeArray = new GtuType[this.gtuTypes.size()];
  748.                 gtuTypeArray = this.gtuTypes.toArray(gtuTypeArray);
  749.                 Frequency[] steadyState = new Frequency[this.gtuTypes.size()];
  750.                 Arrays.fill(steadyState, Frequency.ZERO);
  751.                 Map<K, Frequency> frequencies = new LinkedHashMap<>(); // stored, saves us from calculating them twice
  752.                 for (K child : this.children)
  753.                 {
  754.                     GtuType gtuType = this.gtuTypesPerChild.get(child);
  755.                     int index = this.gtuTypes.indexOf(gtuType);
  756.                     Frequency f = child.getFrequency(time, true); // sliceStart = true is arbitrary
  757.                     frequencies.put(child, f);
  758.                     steadyState[index] = steadyState[index].plus(f);
  759.                 }
  760.                 GtuType nextGtuType = this.markov.draw(gtuTypeArray, steadyState, this.stream);
  761.                 // select only child nodes registered to the next GTU type
  762.                 for (K child : this.children)
  763.                 {
  764.                     if (this.gtuTypesPerChild.get(child).equals(nextGtuType))
  765.                     {
  766.                         double f = frequencies.get(child).si;
  767.                         weightMap.put(child, f);
  768.                     }
  769.                 }
  770.             }
  771.             return Draw.drawWeighted(weightMap, this.stream);
  772.         }

  773.         /**
  774.          * Returns the node object.
  775.          * @return T; node object
  776.          */
  777.         public T getObject()
  778.         {
  779.             return this.object;
  780.         }

  781.         /**
  782.          * Returns the child that pertains to specified object or {@code null} if no such child is present.
  783.          * @param obj Object; child object
  784.          * @return child that pertains to specified object or {@code null} if no such child is present
  785.          */
  786.         public K getChild(final Object obj)
  787.         {
  788.             for (K child : this.children)
  789.             {
  790.                 if (child.getObject().equals(obj))
  791.                 {
  792.                     return child;
  793.                 }
  794.             }
  795.             return null;
  796.         }

  797.         /** {@inheritDoc} */
  798.         @Override
  799.         public Frequency getFrequency(final Time time, final boolean sliceStart)
  800.         {
  801.             if (this.demandPattern != null)
  802.             {
  803.                 return this.demandPattern.getFrequency(time, sliceStart);
  804.             }
  805.             Frequency f = new Frequency(0.0, FrequencyUnit.PER_HOUR);
  806.             for (K child : this.children)
  807.             {
  808.                 f = f.plus(child.getFrequency(time, sliceStart));
  809.             }
  810.             return f;
  811.         }

  812.         /** {@inheritDoc} */
  813.         @Override
  814.         public Time nextTimeSlice(final Time time)
  815.         {
  816.             if (this.demandPattern != null)
  817.             {
  818.                 return this.demandPattern.nextTimeSlice(time);
  819.             }
  820.             Time out = null;
  821.             for (K child : this.children)
  822.             {
  823.                 Time childSlice = child.nextTimeSlice(time);
  824.                 out = out == null || (childSlice != null && childSlice.lt(out)) ? childSlice : out;
  825.             }
  826.             return out;
  827.         }

  828.         /** {@inheritDoc} */
  829.         @Override
  830.         public String toString()
  831.         {
  832.             return "DemandNode [object=" + this.object + ", stream=" + this.stream + ", children=" + this.children
  833.                     + ", demandPattern=" + this.demandPattern + ", gtuTypes=" + this.gtuTypes + ", gtuTypeCounts="
  834.                     + this.gtuTypeCounts + ", gtuTypesPerChild=" + this.gtuTypesPerChild + ", markov=" + this.markov + "]";
  835.         }

  836.     }

  837.     /**
  838.      * Wrapper class around a {@code MarkovCorrelation}, including the last type. One of these should be used for each vehicle
  839.      * generator.
  840.      */
  841.     private static class MarkovChain
  842.     {
  843.         /** Markov correlation for GTU type selection. */
  844.         private final MarkovCorrelation<GtuType, Frequency> markov;

  845.         /** Previously returned GTU type. */
  846.         private GtuType previousGtuType = null;

  847.         /**
  848.          * Constructor.
  849.          * @param markov MarkovCorrelation&lt;GtuType, Frequency&gt;; Markov correlation for GTU type selection
  850.          */
  851.         MarkovChain(final MarkovCorrelation<GtuType, Frequency> markov)
  852.         {
  853.             this.markov = markov;
  854.         }

  855.         /**
  856.          * Returns a next GTU type drawn using a Markov chain.
  857.          * @param gtuTypes GtuType[]; GtuTypes to consider
  858.          * @param intensities Frequency[]; frequency for each GTU type, i.e. the steady-state
  859.          * @param stream StreamInterface; stream for random numbers
  860.          * @return next GTU type drawn using a Markov chain
  861.          */
  862.         public GtuType draw(final GtuType[] gtuTypes, final Frequency[] intensities, final StreamInterface stream)
  863.         {
  864.             this.previousGtuType = this.markov.drawState(this.previousGtuType, gtuTypes, intensities, stream);
  865.             return this.previousGtuType;
  866.         }
  867.     }

  868.     /**
  869.      * Class to contain created generator objects.
  870.      * <p>
  871.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  872.      * <br>
  873.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  874.      * </p>
  875.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  876.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  877.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  878.      * @param generator LaneBasedGtuGenerator; main generator for GTU's
  879.      * @param headwayGenerator Generator&lt;Duration&gt;; generator of headways
  880.      * @param characteristicsGenerator LaneBasedGtuCharacteristicsGenerator; generator of GTU characteristics
  881.      */
  882.     public static record GeneratorObjects(LaneBasedGtuGenerator generator, Generator<Duration> headwayGenerator,
  883.             LaneBasedGtuCharacteristicsGenerator characteristicsGenerator)
  884.     {
  885.     }

  886. }