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