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