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