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 >= 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> = 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> = 0, 1 vs. 2 for <i>bias</i> = 1, 1 vs. 4 for
765 * <i>bias</i> = 2, 1 vs. 8 for <i>bias</i> = 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> = 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> = abs(<i>latBiasLane</i> - <i>laneNumFromRight</i>). Here,
774 * <i>latBiasLane</i> = 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 }