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