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