Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix local traffic #1628

Merged
merged 9 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 99 additions & 29 deletions smarts/core/local_traffic_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __init__(self):
self._reserved_areas: Dict[str, Polygon] = dict()
self._actors_created: int = 0
self._lane_bumpers_cache: Dict[
RoadMap.Lane, List[Tuple[float, VehicleState]]
RoadMap.Lane, List[Tuple[float, VehicleState, int]]
] = dict()
self._offsets_cache: Dict[str, Dict[str, float]] = dict()
# start with the default recovery flags...
Expand Down Expand Up @@ -255,6 +255,9 @@ def _relinquish_actor(self, actor_state: ActorState):
sim = self._sim()
assert sim
sim.provider_relinquishing_actor(self, actor_state)
self._logger.debug(
f"{actor_state} is no longer managed by local traffic provider"
)
if actor_state.actor_id in self._my_actors:
del self._my_actors[actor_state.actor_id]

Expand All @@ -275,24 +278,28 @@ def step(self, actions, dt: float, elapsed_sim_time: float) -> ProviderState:
for actor in self._my_actors.values():
actor.compute_next_state(dt)

dones = []
losts = []
dones = set()
losts = set()
removed = set()
remap_ids: Dict[str, str] = dict()
for actor_id, actor in self._my_actors.items():
actor.step(dt)
if actor.finished_route:
dones.append(actor.actor_id)
dones.add(actor.actor_id)
elif actor.off_route:
losts.append(actor)
losts.add(actor)
elif actor.teleporting:
# pybullet doesn't like it when a vehicle jumps from one side of the map to another,
# so we need to give teleporting vehicles a new id and thus a new chassis.
actor.bump_id()
remap_ids[actor_id] = actor.actor_id
for actor in losts:
for actor in losts - removed:
removed.add(actor.actor_id)
self._relinquish_actor(actor.state)
for actor_id in dones:
sim.provider_removing_actor(self, actor.state)
for actor_id in dones - removed:
actor = self._my_actors.get(actor_id)
if actor:
sim.provider_removing_actor(self, actor.state)
# The following is not really necessary due to the above calling teardown(),
# but it doesn't hurt...
if actor_id in self._my_actors:
Expand Down Expand Up @@ -349,6 +356,7 @@ def destroy(self):

def stop_managing(self, actor_id: str):
# called when agent hijacks this vehicle
self._logger.debug(f"{actor_id} is removed from local traffic management")
assert (
actor_id in self._my_actors
), f"stop_managing() called for non-tracked vehicle id '{actor_id}'"
Expand All @@ -373,7 +381,7 @@ def vehicle_collided(self, vehicle_id: str):
# Probably the most realistic thing we can do is leave the vehicle sitting in the road, blocking traffic!
# (... and then add a "rubber-neck mode" for all nearby vehicles?! ;)
# Let's do that for now, but we should also consider just removing the vehicle.
traffic_actor.stay_put()
# traffic_actor.stay_put()

def update_route_for_vehicle(self, vehicle_id: str, new_route: RoadMap.Route):
traffic_actor = self._my_actors.get(vehicle_id)
Expand Down Expand Up @@ -449,9 +457,7 @@ class _TrafficActor:

def __init__(self, flow: Dict[str, Any], owner: LocalTrafficProvider):
self._logger = logging.getLogger(self.__class__.__name__)

self._owner = weakref.ref(owner)
self._lane_bumpers_cache = owner._lane_bumpers_cache

self._state = None
self._flow: Dict[str, Any] = flow
Expand Down Expand Up @@ -492,6 +498,8 @@ def __init__(self, flow: Dict[str, Any], owner: LocalTrafficProvider):
self._cutting_in = False
self._in_front_after_cutin_secs = 0
self._cutin_hold_secs = float(self._vtype.get("lcHoldPeriod", 3.0))
self._forward_after_added = 0
self._after_added_hold_secs = self._cutin_hold_secs
self._target_cutin_gap = 2.5 * self._min_space_cush
self._aggressiveness = float(self._vtype.get("lcAssertive", 1.0))
if self._aggressiveness <= 0:
Expand Down Expand Up @@ -766,6 +774,7 @@ def __init__(
gap: float,
lane_coord: RefLinePoint,
agent_gap: Optional[float],
ahead_id: Optional[str],
):
self.lane = lane
self.time_left = time_left
Expand All @@ -775,6 +784,7 @@ def __init__(
self.gap = gap # just the gap ahead (in meters)
self.lane_coord = lane_coord
self.agent_gap = agent_gap
self.ahead_id = ahead_id

@property
def drive_time(self) -> float:
Expand Down Expand Up @@ -854,6 +864,8 @@ def exit_time(self, speed: float, to_index: int, acc: float = 0.0) -> float:
def _find_vehicle_ahead_on_route(
self, lane: RoadMap.Lane, dte: float, rind: int
) -> Tuple[float, Optional[VehicleState]]:
owner = self._owner()
assert owner
nv_ahead_dist = math.inf
nv_ahead_vs = None
rind += 1
Expand All @@ -862,7 +874,7 @@ def _find_vehicle_ahead_on_route(
len_to_end = self._route.distance_from(rt_oln)
if len_to_end is None:
continue
lbc = self._lane_bumpers_cache.get(ogl)
lbc = owner._lane_bumpers_cache.get(ogl)
if lbc:
fi = 0
while fi < len(lbc):
Expand All @@ -886,7 +898,9 @@ def _find_vehicle_ahead_on_route(
def _find_vehicle_ahead(
self, lane: RoadMap.Lane, my_offset: float, search_start: float
) -> Tuple[float, Optional[VehicleState]]:
lbc = self._lane_bumpers_cache.get(lane)
owner = self._owner()
assert owner
lbc = owner._lane_bumpers_cache.get(lane)
if lbc:
lane_spot = bisect_right(lbc, (search_start, self._state, 3))
# if we're at an angle to the lane, it's possible for the
Expand All @@ -908,7 +922,9 @@ def _find_vehicle_ahead(
def _find_vehicle_behind(
self, lane: RoadMap.Lane, my_offset: float, search_start: float
) -> Tuple[float, Optional[VehicleState]]:
lbc = self._lane_bumpers_cache.get(lane)
owner = self._owner()
assert owner
lbc = owner._lane_bumpers_cache.get(lane)
if lbc:
lane_spot = bisect_left(lbc, (search_start, self._state, -1))
# if we're at an angle to the lane, it's possible for the
Expand All @@ -924,7 +940,9 @@ def _find_vehicle_behind(
return 0, bv_vs

def find_last(ll: RoadMap.Lane) -> Tuple[float, Optional[VehicleState]]:
plbc = self._lane_bumpers_cache.get(ll)
owner = self._owner()
assert owner
plbc = owner._lane_bumpers_cache.get(ll)
if not plbc:
return math.inf, None
for bv_offset, bv_vs, _ in reversed(plbc):
Expand Down Expand Up @@ -972,7 +990,7 @@ def _compute_lane_window(self, lane: RoadMap.Lane):
rt_ln = RoadMap.Route.RouteLane(lane, self._route_ind)
path_len = self._route.distance_from(rt_ln) or lane.length
path_len -= my_offset
lane_time_left = _safe_division(path_len, self.speed, default=0)
lane_time_left = _safe_division(path_len, self.speed)

half_len = 0.5 * self._state.dimensions.length
front_bumper = my_offset + half_len
Expand Down Expand Up @@ -1010,6 +1028,7 @@ def _compute_lane_window(self, lane: RoadMap.Lane):
ahead_dist,
lane_coord,
behind_dist if bv_vs and bv_vs.role == ActorRole.EgoAgent else None,
nv_vs.actor_id if nv_vs else None,
)

def _compute_lane_windows(self):
Expand Down Expand Up @@ -1075,32 +1094,69 @@ def _pick_lane(self, dt: float):
self._lane_win = self._lane_windows[my_idx]
# Try to find the best among available lanes...
best_lw = self._lane_windows[my_idx]
for l in range(len(self._lane_windows)):
idx = (my_idx + l) % len(self._lane_windows)

# Check self, then right, then left.
lanes_to_right = list(range(0, my_idx))[::-1]
lanes_to_left = list(
range(min(my_idx, len(self._lane_windows)), len(self._lane_windows))
)
cut_in_is_real_lane = self._cutting_into and self._cutting_into.index < len(
self._lane_windows
)
checks = lanes_to_right + lanes_to_left

# hold lane for some time if added recently
if self._forward_after_added < self._after_added_hold_secs:
self._forward_after_added += dt
# skip checks
checks = []

## TODO: Determine how blocked lane changes should be addressed
## Idea is to keep lane if blocked on right, slow down if blocked on left
# if cut_in_is_real_lane and self._target_lane_win.gap < self._min_space_cush:
# # blocked on the right so pick a closer lane until cutin lane is available
# if self._cutting_into.index < my_idx:
# best_lw = self._lane_windows[min(my_idx, self._cutting_into.index + 1)]
# self._cutting_into = best_lw.lane
# # skip lane checks
# checks = []
# # if blocked on the left, do nothing for now and wait
# elif self._cutting_into.index > my_idx:
# pass

for idx in checks:
lw = self._lane_windows[idx]
# skip lanes I can't drive in (e.g., bike lanes on waymo maps)
if not lw.lane.is_drivable:
continue
# if I can't safely reach the lane, don't consider it
change_time = 0
if l > 0:
if abs(idx - my_idx) > 1:
change_time, can_cross = self._crossing_time_into(idx)
if not can_cross:
continue
min_time_cush = float(self._vtype.get("tau", 1.0))
neighbour_lane_bias = (
0.1 * change_time * (1 if abs(self._lane.index - idx) == 1 else 0)
)
will_rearend = lw.ttc + neighbour_lane_bias < min_time_cush
# if my route destination is available, prefer that
if (
lw.lane == self._dest_lane
and lw.lane_coord.s + lw.gap >= self._dest_offset
):
# TAI: speed up or slow down as appropriate if _crossing_time_into() was False
best_lw = lw
if not self._dogmatic:
if not will_rearend and not self._dogmatic:
break
cut_in_is_real_lane = self._cutting_into and self._cutting_into.index < len(
self._lane_windows
)
# if I'm in the process of changing lanes, continue (unless it's no longer safe)
if (
self._cutting_into
and self._cutting_into.index < len(self._lane_windows)
cut_in_is_real_lane
and self._crossing_time_into(self._cutting_into.index)[1]
and not will_rearend
):
best_lw = self._lane_windows[self._cutting_into.index]
if self._cutting_into != self._lane:
Expand Down Expand Up @@ -1133,18 +1189,30 @@ def _pick_lane(self, dt: float):
self._cutting_into = lw.lane
self._cutting_in = True
continue
longer_drive_time = lw.drive_time > best_lw.drive_time
equal_drive_time = lw.drive_time == best_lw.drive_time
is_destination_lane = lw.lane == self._dest_lane
highest_ttre = lw.ttre >= best_lw.ttre
right_of_best_lw = idx < best_lw.lane.index
# otherwise, keep track of the remaining options and eventually
# pick the lane with the longest available driving time on my route
# or, in the case of ties, the right-most lane (assuming I'm not
# cutting anyone off to get there).
if lw.drive_time > best_lw.drive_time or (
lw.drive_time == best_lw.drive_time
and (
(lw.lane == self._dest_lane and self._offset < self._dest_offset)
or (lw.ttre >= best_lw.ttre and idx < best_lw.lane.index)
if (
longer_drive_time
or (
equal_drive_time
and (
(is_destination_lane and self._offset < self._dest_offset)
or (highest_ttre and right_of_best_lw)
)
and not will_rearend
)
or will_rearend
and lw.ttc > best_lw.ttc
):
best_lw = lw

# keep track of the fact I'm changing lanes for next step
# so I don't swerve back and forth indecesively
if best_lw.lane != self._lane and not self._cutting_into:
Expand Down Expand Up @@ -1320,6 +1388,8 @@ def _higher_priority(
bearing: float,
foe: RoadMap.Lane,
) -> bool:
owner = self._owner()
assert owner
# take into account TLS (don't yield to TL-stopped vehicles)
# XXX: we currently only determine this for actors we're controlling
owner = self._owner()
Expand All @@ -1343,7 +1413,7 @@ def _higher_priority(
if dist_to_junction <= 0 and self._lane == junction:

def _in_lane(lane):
for _, vs, _ in self._lane_bumpers_cache.get(lane, []):
for _, vs, _ in owner._lane_bumpers_cache.get(lane, []):
if vs.actor_id == self.actor_id:
return True
return False
Expand Down Expand Up @@ -1425,7 +1495,7 @@ def _handle_junctions(self, dt: float, window: int = 5, max_range: float = 100.0
if check_lane == self._lane:
continue
handled = set()
lbc = self._lane_bumpers_cache.get(check_lane, [])
lbc = owner._lane_bumpers_cache.get(check_lane, [])
for offset, fv, bumper in lbc:
if fv.actor_id == self.actor_id:
continue
Expand Down
14 changes: 6 additions & 8 deletions smarts/scenarios/straight/3lane_cut_in/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,10 @@
normal = TrafficActor(
name="car",
sigma=1,
speed=Distribution(sigma=0.3, mean=1.5),
speed=Distribution(sigma=0.1, mean=1.5),
min_gap=Distribution(sigma=0, mean=1),
lane_changing_model=SmartsLaneChangingModel(
cutin_prob=1,
assertive=10,
dogmatic=True,
cutin_prob=1, assertive=10, dogmatic=True, slow_down_after=0.5
),
)

Expand All @@ -57,7 +55,7 @@

# Traffic combinations = 3C2 + 3C3 = 3 + 1 = 4
# Repeated traffic combinations = 4 * 100 = 400
min_flows = 2
min_flows = 3
max_flows = 3
route_comb = [
com
Expand All @@ -76,7 +74,7 @@
end=("gneE3", end_lane, "max"),
),
# Random flow rate, between x and y vehicles per minute.
rate=60 * random.uniform(15, 25),
rate=60 * random.uniform(6, 14),
# Random flow start time, between x and y seconds.
begin=random.uniform(0, 5),
# For an episode with maximum_episode_steps=3000 and step
Expand All @@ -92,11 +90,11 @@
)


route = Route(begin=("gneE3", 0, 10), end=("gneE3", 0, "max"))
route = Route(begin=("gneE3", 1, 10), end=("gneE3", 1, "max"))
ego_missions = [
Mission(
route=route,
start_time=15, # Delayed start, to ensure road has prior traffic.
start_time=20, # Delayed start, to ensure road has prior traffic.
)
]

Expand Down