View Javadoc
1   package org.opentrafficsim.road.gtu.generator;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
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.Objects;
11  import java.util.Set;
12  
13  import org.djunits.unit.SpeedUnit;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.djunits.value.vdouble.scalar.Speed;
16  import org.djutils.exceptions.Throw;
17  import org.opentrafficsim.core.gtu.GtuException;
18  import org.opentrafficsim.core.gtu.GtuType;
19  import org.opentrafficsim.core.math.Draw;
20  import org.opentrafficsim.core.network.Link;
21  import org.opentrafficsim.core.network.NetworkException;
22  import org.opentrafficsim.core.network.Node;
23  import org.opentrafficsim.core.network.route.Route;
24  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.RoadPosition.BySpeed;
25  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.RoadPosition.ByValue;
26  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
27  import org.opentrafficsim.road.network.lane.CrossSectionLink;
28  import org.opentrafficsim.road.network.lane.Lane;
29  import org.opentrafficsim.road.network.lane.LanePosition;
30  
31  import nl.tudelft.simulation.jstats.streams.StreamInterface;
32  
33  /**
34   * Helper class for vehicle generation which can draw the next GTU position to try to place a GTU. If the GTU can not be placed,
35   * it should be included in a queue. This class requires the number of unplaced GTU's per lane, in order to appropriately divide
36   * traffic over the lanes.
37   * <p>
38   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
39   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
40   * </p>
41   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
42   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
43   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
44   */
45  public interface GeneratorPositions
46  {
47  
48      /**
49       * Draw a new position to generate a GTU.
50       * @param gtuType GTU type.
51       * @param characteristics characteristics of the generated GTU.
52       * @param unplaced number of unplaced GTUs per lane, counting from the right and starting at 1.
53       * @return new position to generate a GTU.
54       * @throws GtuException when the underlying structure is inconsistent for drawing
55       */
56      GeneratorLanePosition draw(GtuType gtuType, LaneBasedGtuCharacteristics characteristics,
57              Map<CrossSectionLink, Map<Integer, Integer>> unplaced) throws GtuException;
58  
59      /**
60       * Returns all underlying positions.
61       * @return all underlying positions.
62       */
63      Set<GeneratorLanePosition> getAllPositions();
64  
65      /**
66       * Create a GeneratorPositions object to draw positions from. The given positions are grouped per link. Lanes are drawn
67       * without bias. Each link receives a weight equal to the number of lanes.
68       * @param positions all considered positions, each lane is considered separately
69       * @param stream stream for random numbers
70       * @return object to draw positions from
71       */
72      static GeneratorPositions create(final Set<LanePosition> positions, final StreamInterface stream)
73      {
74          return create(positions, stream, null, null, null);
75      }
76  
77      /**
78       * Create a GeneratorPositions object to draw positions from. The given positions are grouped per link. Each link receives a
79       * weight equal to the number of lanes.
80       * @param positions all considered positions, each lane is considered separately
81       * @param stream stream for random numbers
82       * @param biases lane biases for GTU types
83       * @return object to draw positions from
84       */
85      static GeneratorPositions create(final Set<LanePosition> positions, final StreamInterface stream, final LaneBiases biases)
86      {
87          return create(positions, stream, biases, null, null);
88      }
89  
90      /**
91       * Create a GeneratorPositions object to draw positions from. The given positions are grouped per link. Lanes are drawn
92       * without bias.
93       * @param positions all considered positions, each lane is considered separately
94       * @param stream stream for random numbers
95       * @param linkWeights weight per link direction
96       * @param viaNodes nodes connectors feed to for each link where GTU's will be generated
97       * @return object to draw positions from
98       */
99      static GeneratorPositions create(final Set<LanePosition> positions, final StreamInterface stream,
100             final Map<CrossSectionLink, Double> linkWeights, final Map<CrossSectionLink, Node> viaNodes)
101     {
102         return create(positions, stream, null, linkWeights, viaNodes);
103     }
104 
105     /**
106      * Create a GeneratorPositions object to draw positions from. The given positions are grouped per link.
107      * @param positions all considered positions, each lane is considered separately
108      * @param stream stream for random numbers
109      * @param laneBiases lane biases for GTU types
110      * @param linkWeights weight per link
111      * @param viaNodes nodes connectors feed to for each link where GTU's will be generated
112      * @return object to draw positions from
113      */
114     static GeneratorPositions create(final Set<LanePosition> positions, final StreamInterface stream,
115             final LaneBiases laneBiases, final Map<CrossSectionLink, Double> linkWeights,
116             final Map<CrossSectionLink, Node> viaNodes)
117     {
118 
119         // group directions per link
120         Map<Link, Set<LanePosition>> linkSplit = new LinkedHashMap<>();
121         for (LanePosition position : positions)
122         {
123             linkSplit.computeIfAbsent(position.lane().getLink(), (link) -> new LinkedHashSet<>()).add(position);
124         }
125 
126         // create list of GeneratorLinkPositions
127         List<GeneratorLinkPosition> linkPositions = new ArrayList<>();
128         Set<GeneratorLanePosition> allLanePositions = new LinkedHashSet<>();
129         for (Link splitLink : linkSplit.keySet())
130         {
131             List<Lane> lanes = ((CrossSectionLink) splitLink).getLanes();
132             // let's sort the lanes by lateral position
133             Collections.sort(lanes, new Comparator<Lane>()
134             {
135                 @Override
136                 public int compare(final Lane lane1, final Lane lane2)
137                 {
138                     Length lat1 = lane1.getOffsetAtBegin();
139                     Length lat2 = lane2.getOffsetAtBegin();
140                     return lat1.compareTo(lat2);
141                 }
142             });
143             // create list of GeneratorLanePositions
144             List<GeneratorLanePosition> lanePositions = new ArrayList<>();
145             for (LanePosition lanePosition : linkSplit.get(splitLink))
146             {
147                 lanePositions.add(new GeneratorLanePosition(lanes.indexOf(lanePosition.lane()) + 1, lanePosition,
148                         (CrossSectionLink) splitLink));
149             }
150             allLanePositions.addAll(lanePositions);
151             // create the GeneratorLinkPosition
152             if (linkWeights == null)
153             {
154                 linkPositions.add(new GeneratorLinkPosition(lanePositions, splitLink, stream, laneBiases));
155             }
156             else
157             {
158                 Double weight = linkWeights.get(splitLink);
159                 Throw.whenNull(weight, "Using link weights for GTU generation, but no weight for link %s is defined.",
160                         splitLink);
161                 linkPositions.add(new GeneratorLinkPosition(lanePositions, splitLink, stream, laneBiases, weight,
162                         viaNodes.get(splitLink)));
163             }
164         }
165 
166         // create the GeneratorZonePosition
167         GeneratorZonePosition position = new GeneratorZonePosition(linkPositions);
168         return new GeneratorPositions()
169         {
170             @Override
171             public GeneratorLanePosition draw(final GtuType gtuType, final LaneBasedGtuCharacteristics characteristics,
172                     final Map<CrossSectionLink, Map<Integer, Integer>> unplaced) throws GtuException
173             {
174                 GeneratorLinkPosition linkPosition =
175                         position.draw(gtuType, stream, characteristics.getDestination(), characteristics.getRoute());
176                 Speed desiredSpeed = characteristics.getStrategicalPlannerFactory().peekDesiredSpeed(gtuType,
177                         linkPosition.speedLimit(gtuType), characteristics.getMaximumSpeed());
178                 return linkPosition.draw(gtuType, unplaced.get(linkPosition.getLink()), desiredSpeed);
179             }
180 
181             @Override
182             public Set<GeneratorLanePosition> getAllPositions()
183             {
184                 return allLanePositions;
185             }
186         };
187     }
188 
189     /**
190      * Class representing a vehicle generation lane, providing elementary information for randomly drawing links and lanes.
191      * <p>
192      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
193      * <br>
194      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
195      * </p>
196      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
197      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
198      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
199      */
200     final class GeneratorLanePosition
201     {
202 
203         /** Lane number, where 1 is the right-most lane. */
204         private final int laneNumber;
205 
206         /** Position. */
207         private final LanePosition position;
208 
209         /** Link. */
210         private final CrossSectionLink link;
211 
212         /**
213          * Constructor.
214          * @param laneNumber lane number, where 1 is the right-most lane
215          * @param position position set, representing a single GTU position on the network
216          * @param link link
217          */
218         GeneratorLanePosition(final int laneNumber, final LanePosition position, final CrossSectionLink link)
219         {
220             this.laneNumber = laneNumber;
221             this.position = position;
222             this.link = link;
223         }
224 
225         /**
226          * Returns the lane number, where 1 is the right-most lane.
227          * @return lane number, where 1 is the right-most lane
228          */
229         int getLaneNumber()
230         {
231             return this.laneNumber;
232         }
233 
234         /**
235          * Returns whether this lane is accessible to the GTU type.
236          * @param gtuType gtu type
237          * @return whether this lane is accessible to the GTU type
238          */
239         boolean allows(final GtuType gtuType)
240         {
241             return this.position.lane().getType().isCompatible(gtuType);
242         }
243 
244         /**
245          * Returns the contained position set, representing a single GTU position on the network.
246          * @return contained position set, representing a single GTU position on the network
247          */
248         LanePosition getPosition()
249         {
250             return this.position;
251         }
252 
253         /**
254          * Returns the link.
255          * @return link
256          */
257         CrossSectionLink getLink()
258         {
259             return this.link;
260         }
261 
262         @Override
263         public int hashCode()
264         {
265             return Objects.hash(this.laneNumber, this.link, this.position);
266         }
267 
268         @Override
269         public boolean equals(final Object obj)
270         {
271             if (this == obj)
272             {
273                 return true;
274             }
275             if (obj == null)
276             {
277                 return false;
278             }
279             if (getClass() != obj.getClass())
280             {
281                 return false;
282             }
283             GeneratorLanePosition other = (GeneratorLanePosition) obj;
284             return this.laneNumber == other.laneNumber && Objects.equals(this.link, other.link)
285                     && Objects.equals(this.position, other.position);
286         }
287 
288         @Override
289         public String toString()
290         {
291             return "GeneratorLanePosition [laneNumber=" + this.laneNumber + ", position=" + this.position + ", link="
292                     + this.link + "]";
293         }
294 
295     }
296 
297     /**
298      * Class representing a vehicle generation link to provide individual generation positions.
299      * <p>
300      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
301      * <br>
302      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
303      * </p>
304      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
305      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
306      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
307      */
308     final class GeneratorLinkPosition
309     {
310 
311         /** Contained lanes. */
312         private final List<GeneratorLanePosition> positions;
313 
314         /** The link. */
315         private final Link link;
316 
317         /** Random stream. */
318         private final StreamInterface stream;
319 
320         /** Lane bias. */
321         private final LaneBiases laneBiases;
322 
323         /** Weight for drawing this link. */
324         private final double weight;
325 
326         /** Node by which a connector connects, may be {@code null}. */
327         private final Node viaNode;
328 
329         /**
330          * Constructor.
331          * @param positions contained lanes
332          * @param link the link
333          * @param stream stream
334          * @param laneBiases lane biases
335          */
336         GeneratorLinkPosition(final List<GeneratorLanePosition> positions, final Link link, final StreamInterface stream,
337                 final LaneBiases laneBiases)
338         {
339             this.positions = positions;
340             this.link = link;
341             this.stream = stream;
342             this.laneBiases = laneBiases;
343             this.weight = -1;
344             this.viaNode = null;
345         }
346 
347         /**
348          * Constructor.
349          * @param positions contained lanes
350          * @param link the link
351          * @param stream stream
352          * @param laneBiases lane biases
353          * @param weight weight for drawing this link
354          * @param viaNode node by which a connector connects
355          */
356         GeneratorLinkPosition(final List<GeneratorLanePosition> positions, final Link link, final StreamInterface stream,
357                 final LaneBiases laneBiases, final double weight, final Node viaNode)
358         {
359             this.positions = positions;
360             this.link = link;
361             this.stream = stream;
362             this.laneBiases = laneBiases;
363             this.weight = weight;
364             this.viaNode = viaNode;
365         }
366 
367         /**
368          * Return the link.
369          * @return link
370          */
371         Link getLink()
372         {
373             return this.link;
374         }
375 
376         /**
377          * Returns the weight for this link. This is either a predefined weight, or the number of lanes for the GTU type.
378          * @param gtuType GTU type
379          * @return weight for this link
380          */
381         double getWeight(final GtuType gtuType)
382         {
383             if (this.weight < 0.0)
384             {
385                 return getNumberOfLanes(gtuType);
386             }
387             return this.weight;
388         }
389 
390         /**
391          * Returns the node by which a connector connects.
392          * @return the node by which a connector connects
393          */
394         Node getViaNode()
395         {
396             return this.viaNode;
397         }
398 
399         /**
400          * Returns the number of accessible lanes for the GTU type.
401          * @param gtuType GTU type
402          * @return number of accessible lanes for the GTU type
403          */
404         int getNumberOfLanes(final GtuType gtuType)
405         {
406             int numberOfLanes = 0;
407             for (GeneratorLanePosition lanePosition : this.positions)
408             {
409                 if (lanePosition.allows(gtuType))
410                 {
411                     numberOfLanes++;
412                 }
413             }
414             return numberOfLanes;
415         }
416 
417         /**
418          * Draws a specific GeneratorLanePosition utilizing lane biases of GTU types.
419          * @param gtuType GTU type
420          * @param unplaced number of unplaced GTUs per lane. The lane number should match with
421          *            {@code GeneratorLanePosition.getLaneNumber()}, where 1 is the right-most lane. Missing lanes are assumed
422          *            to have no queue.
423          * @param desiredSpeed desired speed, possibly used to determine the biased road position
424          * @return specific GeneratorLanePosition utilizing lane biases of GTU types
425          */
426         GeneratorLanePosition draw(final GtuType gtuType, final Map<Integer, Integer> unplaced, final Speed desiredSpeed)
427         {
428             Map<GeneratorLanePosition, Double> map = new LinkedHashMap<>();
429             for (int i = 0; i < this.positions.size(); i++)
430             {
431                 GeneratorLanePosition lanePosition = this.positions.get(i);
432                 if (lanePosition.allows(gtuType))
433                 {
434                     GtuType type = gtuType;
435                     boolean found = false;
436                     while (this.laneBiases != null && !found && type != null)
437                     {
438                         if (this.laneBiases.contains(type))
439                         {
440                             found = true;
441                             int laneNum = lanePosition.getLaneNumber();
442                             int unplacedTemplates = unplaced == null ? 0 : unplaced.getOrDefault(laneNum, 0);
443                             double w = this.laneBiases.getBias(type).calculateWeight(laneNum, getNumberOfLanes(gtuType),
444                                     unplacedTemplates, desiredSpeed);
445                             map.put(lanePosition, w);
446                         }
447                         type = type.getParent();
448                     }
449                     if (!found)
450                     {
451                         map.put(lanePosition, 1.0);
452                     }
453                 }
454             }
455             if (0 == map.size())
456             {
457                 System.err.println("This really, really can't work...");
458             }
459             return Draw.drawWeighted(map, this.stream);
460         }
461 
462         @Override
463         public String toString()
464         {
465             return "GeneratorLinkPosition [positions=" + this.positions + "]";
466         }
467 
468         /**
469          * @param gtuType GTU type
470          * @return speed limit
471          */
472         public Speed speedLimit(final GtuType gtuType)
473         {
474             Speed speedLimit = null;
475             for (GeneratorLanePosition pos : this.positions)
476             {
477                 try
478                 {
479                     Speed limit = pos.getPosition().lane().getSpeedLimit(gtuType);
480                     if (speedLimit == null || limit.lt(speedLimit))
481                     {
482                         speedLimit = limit;
483                     }
484                 }
485                 catch (NetworkException exception)
486                 {
487                     // ignore
488                 }
489             }
490             Throw.when(speedLimit == null, IllegalStateException.class, "No speed limit could be determined for GtuType %s.",
491                     gtuType);
492             return speedLimit;
493         }
494 
495     }
496 
497     /**
498      * Class representing a vehicle generation zone to provide individual generation positions.
499      * <p>
500      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
501      * <br>
502      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
503      * </p>
504      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
505      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
506      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
507      */
508     final class GeneratorZonePosition
509     {
510 
511         /** Contained links. */
512         private final List<GeneratorLinkPosition> positions;
513 
514         /**
515          * Constructor.
516          * @param positions contained links
517          */
518         GeneratorZonePosition(final List<GeneratorLinkPosition> positions)
519         {
520             this.positions = positions;
521         }
522 
523         /**
524          * Draws a GeneratorLinkPosition using number of accessible lanes for the GtuType as weight, and a GeneratorLanePosition
525          * from that.
526          * @param gtuType GTU type
527          * @param stream stream for random numbers
528          * @param destination destination node
529          * @param route route, may be {@code null}
530          * @return draws a LinkPosition using number of accessible lanes for the GtuType as weight, and a GeneratorLanePosition
531          *         from that
532          */
533         GeneratorLinkPosition draw(final GtuType gtuType, final StreamInterface stream, final Node destination,
534                 final Route route)
535         {
536             Map<GeneratorLinkPosition, Double> map = new LinkedHashMap<>();
537             for (int i = 0; i < this.positions.size(); i++)
538             {
539                 GeneratorLinkPosition glp = this.positions.get(i);
540                 Link link = glp.getLink();
541                 if (route != null)
542                 {
543                     int from = route.indexOf(link.getStartNode());
544                     int to = route.indexOf(link.getEndNode());
545                     if (from > -1 && to > -1 && to - from == 1)
546                     {
547                         map.put(glp, glp.getWeight(gtuType));
548                     }
549                 }
550                 else
551                 {
552                     // let's check whether any route is possible over this link
553                     if (glp.getViaNode() != null)
554                     {
555                         Route r;
556                         try
557                         {
558                             r = glp.getViaNode().getNetwork().getShortestRouteBetween(gtuType, glp.getViaNode(), destination);
559                         }
560                         catch (NetworkException exception)
561                         {
562                             r = null;
563                         }
564                         if (r != null)
565                         {
566                             map.put(glp, glp.getWeight(gtuType));
567                         }
568                     }
569                     else
570                     {
571                         map.put(glp, glp.getWeight(gtuType));
572                     }
573                 }
574             }
575             return Draw.drawWeighted(map, stream);
576         }
577 
578         @Override
579         public String toString()
580         {
581             return "GeneratorZonePosition [positions=" + this.positions + "]";
582         }
583 
584     }
585 
586     /**
587      * Set of lane biases per GTU type.
588      * <p>
589      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
590      * <br>
591      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
592      * </p>
593      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
594      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
595      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
596      */
597     final class LaneBiases
598     {
599 
600         /** Biases per GTU type. */
601         private final Map<GtuType, LaneBias> biases = new LinkedHashMap<>();
602 
603         /**
604          * Adds a GTU bias for randomly drawing a lane.
605          * @param gtuType gtu type
606          * @param bias bias
607          * @return lane biases for method chaining
608          */
609         public LaneBiases addBias(final GtuType gtuType, final LaneBias bias)
610         {
611             Throw.whenNull(gtuType, "GTU type may not be null.");
612             Throw.whenNull(bias, "Bias may not be null.");
613             this.biases.put(gtuType, bias);
614             return this;
615         }
616 
617         /**
618          * Whether a bias is defined for the given type.
619          * @param gtuType GTU type
620          * @return whether a bias is defined for the given type
621          */
622         public boolean contains(final GtuType gtuType)
623         {
624             return this.biases.containsKey(gtuType);
625         }
626 
627         /**
628          * Returns the bias of given GTU type, or {@code Bias.None} if none defined for the GTU type.
629          * @param gtuType GTU type
630          * @return bias of the GTU type
631          */
632         public LaneBias getBias(final GtuType gtuType)
633         {
634             return this.biases.getOrDefault(gtuType, LaneBias.NONE);
635         }
636 
637         @Override
638         public String toString()
639         {
640             return "LaneBiases [" + this.biases + "]";
641         }
642 
643     }
644 
645     /**
646      * Vehicle generation lateral bias. Includes a lane maximum, e.g. trucks only on 2 right-hand lanes.
647      * <p>
648      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
649      * <br>
650      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
651      * </p>
652      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
653      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
654      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
655      */
656     final class LaneBias
657     {
658 
659         /** No bias. */
660         public static final LaneBias NONE = new LaneBias(new ByValue(0.0), 0.0, Integer.MAX_VALUE);
661 
662         /** Weak left-hand bias, 2nd left lane contains 50% relative to left most lane, in free traffic. */
663         public static final LaneBias WEAK_LEFT = new LaneBias(new ByValue(1.0), 1.0, Integer.MAX_VALUE);
664 
665         /** Left-hand bias, 2nd left lane contains 25% relative to left most lane, in free traffic. */
666         public static final LaneBias LEFT = new LaneBias(new ByValue(1.0), 2.0, Integer.MAX_VALUE);
667 
668         /** Strong left-hand bias, 2nd left lane contains 3.125% relative to left most lane, in free traffic. */
669         public static final LaneBias STRONG_LEFT = new LaneBias(new ByValue(1.0), 5.0, Integer.MAX_VALUE);
670 
671         /** Weak middle bias, 2nd left lane contains 50% relative to left most lane, in free traffic. */
672         public static final LaneBias WEAK_MIDDLE = new LaneBias(new ByValue(0.5), 1.0, Integer.MAX_VALUE);
673 
674         /** Middle bias, 2nd left lane contains 25% relative to left most lane, in free traffic. */
675         public static final LaneBias MIDDLE = new LaneBias(new ByValue(0.5), 2.0, Integer.MAX_VALUE);
676 
677         /** Strong middle bias, 2nd left lane contains 3.125% relative to left most lane, in free traffic. */
678         public static final LaneBias STRONG_MIDDLE = new LaneBias(new ByValue(0.5), 5.0, Integer.MAX_VALUE);
679 
680         /** Weak right-hand bias, 2nd right lane contains 50% relative to right most lane, in free traffic. */
681         public static final LaneBias WEAK_RIGHT = new LaneBias(new ByValue(0.0), 1.0, Integer.MAX_VALUE);
682 
683         /** Right-hand bias, 2nd right lane contains 25% relative to right most lane, in free traffic. */
684         public static final LaneBias RIGHT = new LaneBias(new ByValue(0.0), 2.0, Integer.MAX_VALUE);
685 
686         /** Strong right-hand bias, 2nd right lane contains 3.125% relative to right most lane, in free traffic. */
687         public static final LaneBias STRONG_RIGHT = new LaneBias(new ByValue(0.0), 5.0, Integer.MAX_VALUE);
688 
689         /** Strong right-hand bias, limited to a maximum of 2 lanes. */
690         public static final LaneBias TRUCK_RIGHT = new LaneBias(new ByValue(0.0), 5.0, 2);
691 
692         /**
693          * Returns a bias by speed with normal extent.
694          * @param leftSpeed desired speed for full left bias
695          * @param rightSpeed desired speed for full right bias
696          * @return bias by speed with normal extent
697          */
698         public static LaneBias bySpeed(final Speed leftSpeed, final Speed rightSpeed)
699         {
700             return new LaneBias(new BySpeed(leftSpeed, rightSpeed), 2.0, Integer.MAX_VALUE);
701         }
702 
703         /**
704          * Returns a bias by speed with normal extent. Convenience km/h input.
705          * @param leftSpeedKm desired speed for full left bias
706          * @param rightSpeedKm desired speed for full right bias
707          * @return bias by speed with normal extent
708          */
709         public static LaneBias bySpeed(final double leftSpeedKm, final double rightSpeedKm)
710         {
711             return bySpeed(new Speed(leftSpeedKm, SpeedUnit.KM_PER_HOUR), new Speed(rightSpeedKm, SpeedUnit.KM_PER_HOUR));
712         }
713 
714         /** Provider of position on the road (0 = full left, 1 = full right). */
715         private final RoadPosition roadPosition;
716 
717         /** Bias extent. */
718         private final double bias;
719 
720         /** Number of lanes to consider in either direction, including the preferred lane. */
721         private final double stickyLanes;
722 
723         /**
724          * Constructor.
725          * @param roadPosition lateral position on the road (0 = right, 0.5 = middle, 1 = left)
726          * @param bias bias extent, lower values create more spread traffic, 0.0 causes no lane preference
727          * @param stickyLanes number of lanes to consider in either direction, including the preferred lane
728          */
729         public LaneBias(final RoadPosition roadPosition, final double bias, final double stickyLanes)
730         {
731             Throw.when(bias < 0.0, IllegalArgumentException.class, "Bias should be positive or 0.");
732             Throw.when(stickyLanes < 1.0, IllegalArgumentException.class, "Sticky lanes should be 1.0 or larger.");
733             this.roadPosition = roadPosition;
734             this.bias = bias;
735             this.stickyLanes = stickyLanes;
736         }
737 
738         /**
739          * Returns a random draw weight for given lane. The weight is calculated as:
740          * 
741          * <pre>
742          * weight = { 0,                               d &gt;= number of sticky lanes
743          *          { 1 / ((d + 1)^bias * (m + 1)),    otherwise
744          * 
745          * where,
746          *      d:      lane deviation from lateral bias position
747          *      bias:   bias extent
748          *      m:      number of unplaced GTU's
749          * </pre>
750          * 
751          * The formula makes sure that all lanes have equal weight for <i>bias</i> &#61; 0, given an equal number of unplaced
752          * GTU's <i>m</i>. The bias can be seen to result in this: for each GTU on the 2nd lane, there are 2^(<i>bias</i> - 1)
753          * GTU's on the 1st lane. In numbers: 1 vs. 1 for <i>bias</i> &#61; 0, 1 vs. 2 for <i>bias</i> &#61; 1, 1 vs. 4 for
754          * <i>bias</i> &#61; 2, 1 vs. 8 for <i>bias</i> &#61; 3, etc.<br>
755          * <br>
756          * Division by <i>m</i> + 1 makes sure traffic distributes over the lanes in case of spillback, or otherwise too high
757          * demand on a particular lane. The weight for lanes with more unplaced GTU's simply reduces. This effect balances out
758          * with the bias, meaning that for a strong bias, GTU's are still likely to be generated on the biased lanes. Given a
759          * relatively strong bias of <i>bias</i> &#61; 5, the weight for the 1st and 2nd lane becomes equal if the 2nd lane has
760          * no unplaced GTU's, while the 1st lane has 31 unplaced GTU's.<br>
761          * <br>
762          * Lane deviation <i>d</i> is calculated as <i>d</i> &#61; abs(<i>latBiasLane</i> - <i>laneNumFromRight</i>). Here,
763          * <i>latBiasLane</i> &#61; 1 + <i>roadPosition</i>*(<i>numberOfLanes</i> - 1), i.e. ranging from 1 to 4 on a 4-lane
764          * road. For lanes that are beyond the number of sticky lanes, the weight is always 0.<br>
765          * <br>
766          * @param laneNumFromRight number of lane counted from right to left
767          * @param numberOfLanes total number of lanes
768          * @param numberOfUnplacedGTUs number of GTU's in the generation queue
769          * @param desiredSpeed desired speed, possibly used to determine the biased road position
770          * @return random draw weight for given lane
771          */
772         public double calculateWeight(final int laneNumFromRight, final int numberOfLanes, final int numberOfUnplacedGTUs,
773                 final Speed desiredSpeed)
774         {
775             double d = Math.abs((1.0 + this.roadPosition.getValue(desiredSpeed) * (numberOfLanes - 1.0)) - laneNumFromRight);
776             if (d >= this.stickyLanes)
777             {
778                 return 0.0;
779             }
780             return 1.0 / (Math.pow(d + 1.0, this.bias) * (numberOfUnplacedGTUs + 1.0));
781         }
782 
783         @Override
784         public int hashCode()
785         {
786             final int prime = 31;
787             int result = 1;
788             long temp;
789             temp = Double.doubleToLongBits(this.bias);
790             result = prime * result + (int) (temp ^ (temp >>> 32));
791             result = prime * result + ((this.roadPosition == null) ? 0 : this.roadPosition.hashCode());
792             temp = Double.doubleToLongBits(this.stickyLanes);
793             result = prime * result + (int) (temp ^ (temp >>> 32));
794             return result;
795         }
796 
797         @Override
798         public boolean equals(final Object obj)
799         {
800             if (this == obj)
801             {
802                 return true;
803             }
804             if (obj == null)
805             {
806                 return false;
807             }
808             if (getClass() != obj.getClass())
809             {
810                 return false;
811             }
812             LaneBias other = (LaneBias) obj;
813             if (Double.doubleToLongBits(this.bias) != Double.doubleToLongBits(other.bias))
814             {
815                 return false;
816             }
817             if (this.roadPosition == null)
818             {
819                 if (other.roadPosition != null)
820                 {
821                     return false;
822                 }
823             }
824             else if (!this.roadPosition.equals(other.roadPosition))
825             {
826                 return false;
827             }
828             if (Double.doubleToLongBits(this.stickyLanes) != Double.doubleToLongBits(other.stickyLanes))
829             {
830                 return false;
831             }
832             return true;
833         }
834 
835         @Override
836         public String toString()
837         {
838             return "Bias [roadPosition=" + this.roadPosition + ", bias=" + this.bias + ", stickyLanes=" + this.stickyLanes
839                     + "]";
840         }
841 
842     }
843 
844     /**
845      * Interface for preferred road position for a lane bias.
846      * <p>
847      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
848      * <br>
849      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
850      * </p>
851      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
852      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
853      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
854      */
855     public interface RoadPosition
856     {
857 
858         /**
859          * Returns the road position (0.0 = right, 1.0 = left).
860          * @param desiredSpeed desired speed at the generator
861          * @return road position (0.0 = right, 1.0 = left)
862          */
863         double getValue(Speed desiredSpeed);
864 
865         /**
866          * Fixed road position.
867          * <p>
868          * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
869          * reserved. <br>
870          * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
871          * <p>
872          * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
873          * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
874          * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
875          */
876         class ByValue implements RoadPosition
877         {
878 
879             /** Road position. */
880             private double value;
881 
882             /**
883              * Constructor.
884              * @param value road position
885              */
886             public ByValue(final double value)
887             {
888                 Throw.when(value < 0.0 || value > 1.0, IllegalArgumentException.class,
889                         "Road position value should be in the range [0...1].");
890                 this.value = value;
891             }
892 
893             @Override
894             public double getValue(final Speed desiredSpeed)
895             {
896                 return this.value;
897             }
898 
899             @Override
900             public int hashCode()
901             {
902                 final int prime = 31;
903                 int result = 1;
904                 long temp;
905                 temp = Double.doubleToLongBits(this.value);
906                 result = prime * result + (int) (temp ^ (temp >>> 32));
907                 return result;
908             }
909 
910             @Override
911             public boolean equals(final Object obj)
912             {
913                 if (this == obj)
914                 {
915                     return true;
916                 }
917                 if (obj == null)
918                 {
919                     return false;
920                 }
921                 if (getClass() != obj.getClass())
922                 {
923                     return false;
924                 }
925                 ByValue other = (ByValue) obj;
926                 if (Double.doubleToLongBits(this.value) != Double.doubleToLongBits(other.value))
927                 {
928                     return false;
929                 }
930                 return true;
931             }
932 
933         }
934 
935         /**
936          * Road position based on desired speed.
937          * <p>
938          * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
939          * reserved. <br>
940          * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
941          * <p>
942          * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
943          * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
944          * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
945          */
946         class BySpeed implements RoadPosition
947         {
948 
949             /** Desired speed at left side of the road. */
950             private Speed leftSpeed;
951 
952             /** Desired speed at the right side of the road. */
953             private Speed rightSpeed;
954 
955             /**
956              * Constructor.
957              * @param leftSpeed desired speed at left side of the road
958              * @param rightSpeed desired speed at right side of the road
959              */
960             public BySpeed(final Speed leftSpeed, final Speed rightSpeed)
961             {
962                 Throw.when(leftSpeed.eq(rightSpeed), IllegalArgumentException.class,
963                         "Left speed and right speed may not be equal. Use LaneBias.NONE.");
964                 this.leftSpeed = leftSpeed;
965                 this.rightSpeed = rightSpeed;
966             }
967 
968             @Override
969             public double getValue(final Speed desiredSpeed)
970             {
971                 Throw.whenNull(desiredSpeed, "Peeked desired speed from a strategical planner factory is null, "
972                         + "while a lane bias depends on desired speed.");
973                 double value = (desiredSpeed.si - this.rightSpeed.si) / (this.leftSpeed.si - this.rightSpeed.si);
974                 return value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);
975             }
976 
977             @Override
978             public int hashCode()
979             {
980                 final int prime = 31;
981                 int result = 1;
982                 result = prime * result + ((this.leftSpeed == null) ? 0 : this.leftSpeed.hashCode());
983                 result = prime * result + ((this.rightSpeed == null) ? 0 : this.rightSpeed.hashCode());
984                 return result;
985             }
986 
987             @Override
988             public boolean equals(final Object obj)
989             {
990                 if (this == obj)
991                 {
992                     return true;
993                 }
994                 if (obj == null)
995                 {
996                     return false;
997                 }
998                 if (getClass() != obj.getClass())
999                 {
1000                     return false;
1001                 }
1002                 BySpeed other = (BySpeed) obj;
1003                 if (this.leftSpeed == null)
1004                 {
1005                     if (other.leftSpeed != null)
1006                     {
1007                         return false;
1008                     }
1009                 }
1010                 else if (!this.leftSpeed.equals(other.leftSpeed))
1011                 {
1012                     return false;
1013                 }
1014                 if (this.rightSpeed == null)
1015                 {
1016                     if (other.rightSpeed != null)
1017                     {
1018                         return false;
1019                     }
1020                 }
1021                 else if (!this.rightSpeed.equals(other.rightSpeed))
1022                 {
1023                     return false;
1024                 }
1025                 return true;
1026             }
1027 
1028         }
1029 
1030     }
1031 
1032 }