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