diff --git a/CHANGELOG.md b/CHANGELOG.md index cfe7029650..6ac15d158f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ Copy and pasting the git commit messages is __NOT__ enough. - Added engine `observation_workers` configuration which can be used to configure the number of parallel sensor workers: 0 runs the sensors on the local thread, >=1 runs using the multiprocessing backing. - Added engine `sensor_parallelization` configuration of sensor parallelization backing, options ("mp"|"ray"): "mp" python multiprocessing, "ray" ray worker backing. - Added engine `reset_retries` configuration engine retries before the simulator will raise an error on reset. -- Introduced new comfort metric in metric cost functions. +- Introduced new comfort cost function in metric module. +- Introduced new gap-between-vehicles cost function in metric module. ### Changed - The trap manager, `TrapManager`, is now a subclass of `ActorCaptureManager`. - Considering lane-change time ranges between 3s and 6s, assuming a speed of 13.89m/s, the via sensor lane acquisition range was increased from 40m to 80m, for better driving ability. @@ -48,6 +49,7 @@ Copy and pasting the git commit messages is __NOT__ enough. - In the metrics module, the records which is the raw metrics data and the scoring which is the formula to compute the final results are now separated to provided greater flexibility for applying metrics to different environments. - Benchmark listing may specify specialised metric formula for each benchmark. - Changed `benchmark_runner_v0.py` to only average records across scenarios that share the same environment. Records are not averaged across different environments, because the scoring formula may differ in different environments. +- Renamed GapBetweenVehicles cost to VehicleGap cost in metric module. ### Deprecated ### Fixed - Fixed issues related to waypoints in junctions on Argoverse maps. Waypoints will now be generated for all paths leading through the lane(s) the vehicle is on. diff --git a/docs/benchmarks/driving_smarts_2023_3.rst b/docs/benchmarks/driving_smarts_2023_3.rst index 5e5c5201a0..f97bf216af 100644 --- a/docs/benchmarks/driving_smarts_2023_3.rst +++ b/docs/benchmarks/driving_smarts_2023_3.rst @@ -196,7 +196,7 @@ Train $ cd /SMARTS/examples/rl/platoon $ python3.8 -m venv ./.venv $ source ./.venv/bin/activate - $ pip install --upgrade pip wheel + $ pip install --upgrade pip $ pip install -e ./../../../.[camera_obs,argoverse] $ pip install -e ./inference/ @@ -246,7 +246,7 @@ Evaluate $ cd /SMARTS $ python3.8 -m venv ./.venv $ source ./.venv/bin/activate - $ pip install --upgrade pip wheel + $ pip install --upgrade pip $ pip install -e .[camera_obs,argoverse] $ scl zoo install examples/rl/platoon/inference $ scl benchmark run driving_smarts_2023_3 examples.rl.platoon.inference:contrib-agent-v0 --auto-install diff --git a/examples/rl/platoon/train/reward.py b/examples/rl/platoon/train/reward.py index efb8d88ac5..d7150be12c 100644 --- a/examples/rl/platoon/train/reward.py +++ b/examples/rl/platoon/train/reward.py @@ -45,8 +45,6 @@ def step(self, action): agent_obs["events"]["collisions"] | agent_obs["events"]["off_road"] ): pass - elif agent_obs["events"]["agents_alive_done"]: - print(f"{agent_id}: Agents alive done triggered.") elif agent_obs["events"]["actors_alive_done"]: print(f"{agent_id}: Actors alive done triggered.") else: diff --git a/smarts/benchmark/driving_smarts/v2023/metric_formula_platoon.py b/smarts/benchmark/driving_smarts/v2023/metric_formula_platoon.py index e3e496eb33..d59b20c2b0 100644 --- a/smarts/benchmark/driving_smarts/v2023/metric_formula_platoon.py +++ b/smarts/benchmark/driving_smarts/v2023/metric_formula_platoon.py @@ -30,9 +30,9 @@ from smarts.env.gymnasium.wrappers.metric.params import ( Comfort, DistToObstacles, - GapBetweenVehicles, Params, Steps, + VehicleGap, ) from smarts.env.gymnasium.wrappers.metric.types import Record from smarts.env.gymnasium.wrappers.metric.utils import ( @@ -63,10 +63,10 @@ def params(self) -> Params: dist_to_obstacles=DistToObstacles( active=False, ), - gap_between_vehicles=GapBetweenVehicles( - active=False, - interest="Leader-007", - ), # TODO: Activate after implementing gap_between_vehicles cost function. + vehicle_gap=VehicleGap( + active=True, + actor="Leader-007", + ), steps=Steps( active=False, ), @@ -85,7 +85,7 @@ def score(self, records_sum: Dict[str, Dict[str, Record]]) -> Score: +-------------------+--------+-----------------------------------------------------------+ | DistToDestination | [0, 1] | Remaining distance to destination. The lower, the better. | +-------------------+--------+-----------------------------------------------------------+ - | GapBetweenVehicles| [0, 1] | Gap between vehicles in a platoon. The higher, the better.| + | VehicleGap | [0, 1] | Gap between vehicles in a convoy. The lower, the better. | +-------------------+--------+-----------------------------------------------------------+ | Humanness | [0, 1] | Humanness indicator. The higher, the better. | +-------------------+--------+-----------------------------------------------------------+ @@ -93,7 +93,7 @@ def score(self, records_sum: Dict[str, Dict[str, Record]]) -> Score: +-------------------+--------+-----------------------------------------------------------+ Returns: - Score: Contains "Overall", "DistToDestination", "GapBetweenVehicles", + Score: Contains "Overall", "DistToDestination", "VehicleGap", "Humanness", and "Rules" scores. """ @@ -123,10 +123,10 @@ def score(self, records_sum: Dict[str, Dict[str, Record]]) -> Score: dist_to_destination = costs_final.dist_to_destination humanness = _humanness(costs=costs_final) rules = _rules(costs=costs_final) - gap_between_vehicles = costs_final.gap_between_vehicles + vehicle_gap = costs_final.vehicle_gap overall = ( 0.50 * (1 - dist_to_destination) - + 0.25 * gap_between_vehicles + + 0.25 * (1 - vehicle_gap) + 0.20 * humanness + 0.05 * rules ) @@ -135,7 +135,7 @@ def score(self, records_sum: Dict[str, Dict[str, Record]]) -> Score: { "overall": overall, "dist_to_destination": dist_to_destination, - "gap_between_vehicles": gap_between_vehicles, + "vehicle_gap": vehicle_gap, "humanness": humanness, "rules": rules, } diff --git a/smarts/env/gymnasium/driving_smarts_2023_env.py b/smarts/env/gymnasium/driving_smarts_2023_env.py index badee0eb1c..d126c8c22b 100644 --- a/smarts/env/gymnasium/driving_smarts_2023_env.py +++ b/smarts/env/gymnasium/driving_smarts_2023_env.py @@ -162,7 +162,7 @@ def resolve_agent_interface(agent_interface: AgentInterface): ) max_episode_steps = 1000 waypoints_lookahead = 80 - neighborhood_radius = 50 + neighborhood_radius = 80 return AgentInterface( accelerometer=True, action=agent_interface.action, diff --git a/smarts/env/gymnasium/platoon_env.py b/smarts/env/gymnasium/platoon_env.py index 8fa70623f2..b5c600d148 100644 --- a/smarts/env/gymnasium/platoon_env.py +++ b/smarts/env/gymnasium/platoon_env.py @@ -169,7 +169,7 @@ def resolve_agent_interface(agent_interface: AgentInterface): ) max_episode_steps = 1000 waypoints_lookahead = 80 - neighborhood_radius = 50 + neighborhood_radius = 80 return AgentInterface( accelerometer=True, action=agent_interface.action, diff --git a/smarts/env/gymnasium/wrappers/metric/costs.py b/smarts/env/gymnasium/wrappers/metric/costs.py index 75a94bcea9..df78028805 100644 --- a/smarts/env/gymnasium/wrappers/metric/costs.py +++ b/smarts/env/gymnasium/wrappers/metric/costs.py @@ -31,15 +31,17 @@ from smarts.core.vehicle_index import VehicleIndex from smarts.env.gymnasium.wrappers.metric.params import Params from smarts.env.gymnasium.wrappers.metric.types import Costs -from smarts.env.gymnasium.wrappers.metric.utils import SlidingWindow +from smarts.env.gymnasium.wrappers.metric.utils import SlidingWindow, nearest_waypoint Done = NewType("Done", bool) -def _collisions() -> Callable[[RoadMap, Done, Observation], Costs]: +def _collisions() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: sum = 0 - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal sum sum = sum + len(obs.events.collisions) @@ -49,7 +51,7 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _comfort() -> Callable[[RoadMap, Done, Observation], Costs]: +def _comfort() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: jerk_linear_max = np.linalg.norm(np.array([0.9, 0.9, 0])) # Units: m/s^3 acc_linear_max = np.linalg.norm(np.array([2.0, 1.47, 0])) # Units: m/s^2 T_p = 30 # Penalty time steps = penalty time / delta time step = 3s / 0.1s = 30 @@ -57,7 +59,9 @@ def _comfort() -> Callable[[RoadMap, Done, Observation], Costs]: step = 0 dyn_window = SlidingWindow(size=T_p) - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal jerk_linear_max, acc_linear_max, T_p, T_u, step, dyn_window step = step + 1 @@ -86,13 +90,15 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: def _dist_to_destination( end_pos: Point = Point(0, 0, 0), dist_tot: float = 0 -) -> Callable[[RoadMap, Done, Observation], Costs]: +) -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: mean = 0 step = 0 end_pos = end_pos dist_tot = dist_tot - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal mean, step, end_pos, dist_tot if not done: @@ -114,7 +120,7 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: def _dist_to_obstacles( ignore: List[str], -) -> Callable[[RoadMap, Done, Observation], Costs]: +) -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: mean = 0 step = 0 rel_angle_th = np.pi * 40 / 180 @@ -123,7 +129,9 @@ def _dist_to_obstacles( safe_time = 3 # Safe driving distance expressed in time. Units:seconds. ignore = ignore - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal mean, step, rel_angle_th, rel_heading_th, w_dist, safe_time, ignore # Ego's position and heading with respect to the map's coordinate system. @@ -199,26 +207,7 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _gap_between_vehicles( - interest: str, -) -> Callable[[RoadMap, Done, Observation], Costs]: - mean = 0 - step = 0 - interest = interest - - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: - nonlocal mean, step, interest - - # TODO: Cost function is to be designed. - - j_gap = 0 - mean, step = running_mean(prev_mean=mean, prev_step=step, new_val=j_gap) - return Costs(gap_between_vehicles=0) - - return func - - -def _jerk_linear() -> Callable[[RoadMap, Done, Observation], Costs]: +def _jerk_linear() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: mean = 0 step = 0 jerk_linear_max = np.linalg.norm(np.array([0.9, 0.9, 0])) # Units: m/s^3 @@ -231,7 +220,9 @@ def _jerk_linear() -> Callable[[RoadMap, Done, Observation], Costs]: Neural Information Processing Systems, NeurIPS 2019, Vancouver, Canada. """ - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal mean, step, jerk_linear_max jerk_linear = np.linalg.norm(obs.ego_vehicle_state.linear_jerk) @@ -242,11 +233,13 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _lane_center_offset() -> Callable[[RoadMap, Done, Observation], Costs]: +def _lane_center_offset() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: mean = 0 step = 0 - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal mean, step if obs.events.off_road: @@ -276,10 +269,12 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _off_road() -> Callable[[RoadMap, Done, Observation], Costs]: +def _off_road() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: sum = 0 - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal sum sum = sum + obs.events.off_road @@ -289,11 +284,13 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _speed_limit() -> Callable[[RoadMap, Done, Observation], Costs]: +def _speed_limit() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: mean = 0 step = 0 - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal mean, step if obs.events.off_road: @@ -321,11 +318,13 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _steps(max_episode_steps: int) -> Callable[[RoadMap, Done, Observation], Costs]: +def _steps(max_episode_steps: int) -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: step = 0 max_episode_steps = max_episode_steps - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal step, max_episode_steps step = step + 1 @@ -351,11 +350,99 @@ def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: return func -def _wrong_way() -> Callable[[RoadMap, Done, Observation], Costs]: +def _vehicle_gap( + num_agents: int, + actor: str, +) -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: + mean = 0 + step = 0 + num_agents = num_agents + aoi = actor # Actor of interest, i.e., aoi + vehicle_length = 4 # Units: m. Car length=3.68 m. + safe_separation = 1 # Units: seconds. Minimum separation time between two vehicles. + waypoint_spacing = 1 # Units: m. Assumption: Waypoints are spaced 1m apart. + max_column_length = (num_agents + 1) * vehicle_length * 3.5 # Units: m. + # Column length is the length of roadway a convoy of vehicles occupy, + # measured from the lead vehicle (i.e., actor of interest) to the trail + # vehicle. Here, num_agents is incremented by 1 to provide leeway for 1 + # traffic vehicle within the convoy. Multiplied by 3.5 to account for + # vehicle separation distance while driving. + min_waypoints_length = int(np.ceil(max_column_length / waypoint_spacing)) + + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: + nonlocal mean, step, num_agents, aoi, vehicle_length, safe_separation, waypoint_spacing, max_column_length, min_waypoints_length + + if done == True: + return Costs(vehicle_gap=mean) + + column_length = min( + ( + num_agents * safe_separation * obs.ego_vehicle_state.speed + + num_agents + * vehicle_length + * 2 # Multiplied by 2 to provide leeway when speed = 0. + ), + max_column_length, + ) + + # Truncate all paths to be of the same length. + min_len = min(map(len, obs.waypoint_paths)) + assert min_len >= min_waypoints_length, ( + f"Expected waypoints length >= {min_waypoints_length}, but got " + f"waypoints length = {min_len}." + ) + trunc_waypoints = list(map(lambda x: x[:min_len], obs.waypoint_paths)) + waypoints = [list(map(lambda x: x.pos, path)) for path in trunc_waypoints] + waypoints = np.array(waypoints, dtype=np.float64) + waypoints = np.pad( + waypoints, ((0, 0), (0, 0), (0, 1)), mode="constant", constant_values=0 + ) + + # Find the nearest waypoint index to the actor of interest, if any. + lane_width = obs.waypoint_paths[0][0].lane_width + aoi_pos = vehicle_index.vehicle_position(aoi) + aoi_wp_ind, aoi_ind = nearest_waypoint( + matrix=waypoints, + points=np.array([aoi_pos]), + radius=lane_width, + ) + + if aoi_ind == None: + # Actor of interest not found. + j_gap = 1 + elif aoi_wp_ind[1] * waypoint_spacing > column_length: + # Ego is outside of the maximum column length. + j_gap = 1 + else: + # Find the nearest waypoint index to the ego. + ego_pos = obs.ego_vehicle_state.position + dist = np.linalg.norm(waypoints[:, 0, :] - ego_pos, axis=-1) + ego_wp_inds = np.where(dist == dist.min())[0] + + if aoi_wp_ind[0] in ego_wp_inds: + # Ego is in the same lane as the actor of interest. + j_gap = (aoi_wp_ind[1] * waypoint_spacing - vehicle_length) / ( + column_length - vehicle_length + ) + else: + # Ego is not in the same lane as the actor of interest. + j_gap = 1 + + mean, step = running_mean(prev_mean=mean, prev_step=step, new_val=j_gap) + return Costs(vehicle_gap=mean) + + return func + + +def _wrong_way() -> Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]: mean = 0 step = 0 - def func(road_map: RoadMap, done: Done, obs: Observation) -> Costs: + def func( + road_map: RoadMap, vehicle_index: VehicleIndex, done: Done, obs: Observation + ) -> Costs: nonlocal mean, step j_wrong_way = 0 if obs.events.wrong_way: @@ -373,22 +460,22 @@ class CostFuncsBase: running cost over time steps, for a given scenario.""" # fmt: off - collisions: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _collisions - comfort: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _comfort - dist_to_destination: Callable[[Point,float], Callable[[RoadMap, Done, Observation], Costs]] = _dist_to_destination - dist_to_obstacles: Callable[[List[str]], Callable[[RoadMap, Done, Observation], Costs]] = _dist_to_obstacles - gap_between_vehicles: Callable[[str], Callable[[RoadMap, Done, Observation], Costs]] = _gap_between_vehicles - jerk_linear: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _jerk_linear - lane_center_offset: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _lane_center_offset - off_road: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _off_road - speed_limit: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _speed_limit - steps: Callable[[int], Callable[[RoadMap, Done, Observation], Costs]] = _steps - wrong_way: Callable[[], Callable[[RoadMap, Done, Observation], Costs]] = _wrong_way + collisions: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _collisions + comfort: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _comfort + dist_to_destination: Callable[[Point,float], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _dist_to_destination + dist_to_obstacles: Callable[[List[str]], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _dist_to_obstacles + jerk_linear: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _jerk_linear + lane_center_offset: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _lane_center_offset + off_road: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _off_road + speed_limit: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _speed_limit + steps: Callable[[int], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _steps + vehicle_gap: Callable[[int, str], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _vehicle_gap + wrong_way: Callable[[], Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] = _wrong_way # fmt: on CostFuncs = NewType( - "CostFuncs", Dict[str, Callable[[RoadMap, Done, Observation], Costs]] + "CostFuncs", Dict[str, Callable[[RoadMap, VehicleIndex, Done, Observation], Costs]] ) diff --git a/smarts/env/gymnasium/wrappers/metric/metrics.py b/smarts/env/gymnasium/wrappers/metric/metrics.py index 231d3bd8ba..585feecd1b 100644 --- a/smarts/env/gymnasium/wrappers/metric/metrics.py +++ b/smarts/env/gymnasium/wrappers/metric/metrics.py @@ -123,7 +123,12 @@ def step(self, action: Dict[str, Any]): # Compute all cost functions. costs = Costs() for _, cost_func in self._cost_funcs[agent_name].items(): - new_costs = cost_func(self._road_map, Done(dones[agent_name]), base_obs) + new_costs = cost_func( + self._road_map, + self._vehicle_index, + Done(dones[agent_name]), + base_obs, + ) if dones[agent_name]: costs = add_dataclass(new_costs, costs) @@ -211,8 +216,9 @@ def reset(self, **kwargs): dist_to_obstacles={ "ignore": self._params.dist_to_obstacles.ignore, }, - gap_between_vehicles={ - "interest": self._params.gap_between_vehicles.interest, + vehicle_gap={ + "num_agents": len(self._cur_agents), + "actor": self._params.vehicle_gap.actor, }, steps={ "max_episode_steps": self.env.agent_interfaces[ diff --git a/smarts/env/gymnasium/wrappers/metric/params.py b/smarts/env/gymnasium/wrappers/metric/params.py index b61874b65a..cf6acd69a7 100644 --- a/smarts/env/gymnasium/wrappers/metric/params.py +++ b/smarts/env/gymnasium/wrappers/metric/params.py @@ -68,17 +68,6 @@ class DistToObstacles: """ -@dataclass(frozen=True) -class GapBetweenVehicles: - """Parameters for gap between vehicles cost function.""" - - active: bool = False - """If True, enables computation of coresponding cost function. Else, - disabled. - """ - interest: str = "Leader-007" - - @dataclass(frozen=True) class JerkLinear: """Parameters for jerk linear cost function.""" @@ -129,6 +118,18 @@ class Steps: """ +@dataclass(frozen=True) +class VehicleGap: + """Parameters for gap between vehicles cost function.""" + + active: bool = False + """If True, enables computation of coresponding cost function. Else, + disabled. + """ + actor: str = "Leader-007" + """Gap between ego and the specified `actor` is computed.""" + + @dataclass(frozen=True) class WrongWay: """Parameters for wrong way cost function.""" @@ -147,10 +148,10 @@ class Params: comfort: Comfort = Comfort() dist_to_destination: DistToDestination = DistToDestination() dist_to_obstacles: DistToObstacles = DistToObstacles() - gap_between_vehicles: GapBetweenVehicles = GapBetweenVehicles() jerk_linear: JerkLinear = JerkLinear() lane_center_offset: LaneCenterOffset = LaneCenterOffset() off_road: OffRoad = OffRoad() speed_limit: SpeedLimit = SpeedLimit() steps: Steps = Steps() + vehicle_gap: VehicleGap = VehicleGap() wrong_way: WrongWay = WrongWay() diff --git a/smarts/env/gymnasium/wrappers/metric/types.py b/smarts/env/gymnasium/wrappers/metric/types.py index dcc0df8f03..6ad22c17cd 100644 --- a/smarts/env/gymnasium/wrappers/metric/types.py +++ b/smarts/env/gymnasium/wrappers/metric/types.py @@ -31,12 +31,12 @@ class Costs: comfort: float = 0 dist_to_destination: float = 0 dist_to_obstacles: float = 0 - gap_between_vehicles: float = 0 jerk_linear: float = 0 lane_center_offset: float = 0 off_road: int = 0 speed_limit: float = 0 steps: float = 0 + vehicle_gap: float = 0 wrong_way: float = 0 diff --git a/smarts/env/gymnasium/wrappers/metric/utils.py b/smarts/env/gymnasium/wrappers/metric/utils.py index 93cdc1affe..685e67bcd6 100644 --- a/smarts/env/gymnasium/wrappers/metric/utils.py +++ b/smarts/env/gymnasium/wrappers/metric/utils.py @@ -22,7 +22,9 @@ from collections import deque from dataclasses import fields -from typing import Callable, TypeVar, Union +from typing import Callable, Optional, Tuple, TypeVar, Union + +import numpy as np from smarts.env.gymnasium.wrappers.metric.types import Costs, Counts @@ -97,6 +99,47 @@ def multiply(value: Union[int, float], multiplier: Union[int, float]) -> float: return float(value * multiplier) +def nearest_waypoint( + matrix: np.ndarray, points: np.ndarray, radius: float = 1 +) -> Tuple[Tuple[int, int], Optional[int]]: + """ + Returns + (i) the `matrix` index of the nearest waypoint to the ego, which has a nearby `point`. + (ii) the `points` index which is nearby the nearest waypoint to the ego. + + Nearby is defined as a point within `radius` of a waypoint. + + Args: + matrix (np.ndarray): Waypoints matrix. + points (np.ndarray): Points matrix. + radius (float, optional): Nearby radius. Defaults to 2. + + Returns: + Tuple[Tuple[int, int], Optional[int]] : `matrix` index of shape (a,b) and scalar `point` index. + """ + cur_point_index = ((np.intp(1e10), np.intp(1e10)), None) + + if points.shape == (0,): + return cur_point_index + + assert len(matrix.shape) == 3 + assert matrix.shape[2] == 3 + assert len(points.shape) == 2 + assert points.shape[1] == 3 + + points_expanded = np.expand_dims(points, (1, 2)) + diff = matrix - points_expanded + dist = np.linalg.norm(diff, axis=-1) + for ii in range(points.shape[0]): + index = np.argmin(dist[ii]) + index_unravel = np.unravel_index(index, dist[ii].shape) + min_dist = dist[ii][index_unravel] + if min_dist <= radius and index_unravel[1] < cur_point_index[0][1]: + cur_point_index = (index_unravel, ii) + + return cur_point_index + + class SlidingWindow: """A sliding window which moves to the right by accepting new elements. The maximum value within the sliding window can be queried at anytime by calling diff --git a/smarts/env/utils/observation_conversion.py b/smarts/env/utils/observation_conversion.py index 3b103a1ea4..04bab7a762 100644 --- a/smarts/env/utils/observation_conversion.py +++ b/smarts/env/utils/observation_conversion.py @@ -34,7 +34,7 @@ from smarts.core.road_map import Waypoint _LIDAR_SHP = 300 -_NEIGHBOR_SHP = 10 +_NEIGHBOR_SHP = 50 _WAYPOINT_SHP = (12, 80) _SIGNALS_SHP = (3,) _POSITION_SHP = (3,)