From 05bd6a0bfe0099a8f26cddcf8c525259047c3166 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Wed, 17 Jul 2024 13:48:37 -0700 Subject: [PATCH 1/7] Updated all large area tools to use agent properties by default while maintaining backwards compatibility. --- examples/large_map_example.py | 9 +++++--- invertedai/large/_quadtree.py | 12 +++++----- invertedai/large/common.py | 42 ++++++++++++++++++++++++++-------- invertedai/large/drive.py | 33 +++++++++++++++++++------- invertedai/large/initialize.py | 42 +++++++++++++++++----------------- invertedai/utils.py | 31 ++++++++++++++++--------- 6 files changed, 110 insertions(+), 59 deletions(-) diff --git a/examples/large_map_example.py b/examples/large_map_example.py index f3cdeb9..dc6e73e 100644 --- a/examples/large_map_example.py +++ b/examples/large_map_example.py @@ -1,4 +1,7 @@ import invertedai as iai +from invertedai.large.common import Region +from invertedai.common import AgentAttributes +from invertedai.utils import get_default_agent_properties import argparse from tqdm import tqdm @@ -51,7 +54,7 @@ def main(args): ) scene_plotter.initialize_recording( agent_states=response.agent_states, - agent_attributes=response.agent_attributes, + agent_properties=response.agent_properties, traffic_light_states=response.traffic_lights_states ) @@ -59,12 +62,12 @@ def main(args): print(f"Number of agents in simulation: {total_num_agents}") print(f"Begin stepping through simulation.") - agent_attributes = response.agent_attributes + agent_properties = response.agent_properties for _ in tqdm(range(args.sim_length)): response = iai.large_drive( location = args.location, agent_states = response.agent_states, - agent_attributes = agent_attributes, + agent_properties = agent_properties, recurrent_states = response.recurrent_states, light_recurrent_states = response.light_recurrent_states, random_seed = drive_seed, diff --git a/invertedai/large/_quadtree.py b/invertedai/large/_quadtree.py index 9475387..15f2f09 100644 --- a/invertedai/large/_quadtree.py +++ b/invertedai/large/_quadtree.py @@ -2,7 +2,7 @@ import invertedai as iai from invertedai.large.common import Region -from invertedai.common import Point, AgentState, AgentAttributes, RecurrentState +from invertedai.common import Point, AgentState, AgentProperties, RecurrentState BUFFER_FOV = 35 QUADTREE_SIZE_BUFFER = 1 @@ -14,22 +14,22 @@ class QuadTreeAgentInfo(BaseModel): See Also -------- AgentState - AgentAttributes + AgentProperties RecurrentState """ agent_state: AgentState - agent_attributes: AgentAttributes + agent_properties: AgentProperties recurrent_state: RecurrentState agent_id: int def tolist(self): - return [self.agent_state, self.agent_attributes, self.recurrent_state, self.agent_id] + return [self.agent_state, self.agent_properties, self.recurrent_state, self.agent_id] @classmethod def fromlist(cls, l): - agent_state, agent_attributes, recurrent_state, agent_id = l - return cls(agent_state=agent_state, agent_attributes=agent_attributes, recurrent_state=recurrent_state, agent_id=agent_id) + agent_state, agent_properties, recurrent_state, agent_id = l + return cls(agent_state=agent_state, agent_properties=agent_properties, recurrent_state=recurrent_state, agent_id=agent_id) class QuadTree: def __init__( diff --git a/invertedai/large/common.py b/invertedai/large/common.py index 70343e6..d64566e 100644 --- a/invertedai/large/common.py +++ b/invertedai/large/common.py @@ -1,7 +1,7 @@ -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Union from pydantic import BaseModel -from invertedai.common import AgentAttributes, AgentState, RecurrentState, Point +from invertedai.common import AgentAttributes, AgentProperties, AgentState, RecurrentState, Point class Region(BaseModel): """ @@ -9,13 +9,13 @@ class Region(BaseModel): See Also -------- - AgentAttributes + AgentProperties """ center: Point #: The center of the region if such a concept is relevant (e.g. center of a square, center of a rectangle) size: float #: Side length of the region for the default interpretation of a region as a square agent_states: Optional[List[AgentState]] = [] #: A list of existing agents within the region - agent_attributes: Optional[List[AgentAttributes]] = [] #: The attributes of agents that exist within the region or that will be initialized within the region + agent_properties: Optional[List[AgentProperties]] = [] #: The static parameters of agents that exist within the region or that will be initialized within the region recurrent_states: Optional[List[RecurrentState]] = [] #: Recurrent states of the agents eixsting within the region @classmethod @@ -24,7 +24,7 @@ def create_square_region( center: Point, size: Optional[float] = 100, agent_states: Optional[List[AgentState]] = [], - agent_attributes: Optional[List[AgentAttributes]] = [], + agent_properties: Optional[List[Union[AgentAttributes,AgentProperties]]] = [], recurrent_states: Optional[List[RecurrentState]] = [] ): cls.center = center @@ -32,11 +32,20 @@ def create_square_region( for agent in agent_states: assert cls.is_inside(cls,agent.center), f"Existing agent states at position {agent.center} must be located within the region." + + agent_properties_new = [] + for properties in agent_properties: + properties_new = properties + if isinstance(properties,AgentAttributes): + properties_new = cls.convert_attributes_to_properties(cls,properties_new) + agent_properties_new.append(properties_new) + agent_properties = agent_properties_new + return cls( center=center, size=size, agent_states=agent_states, - agent_attributes=agent_attributes, + agent_properties=agent_properties, recurrent_states=recurrent_states ) @@ -50,25 +59,27 @@ def copy( center=region.center, size=region.size, agent_states=region.agent_states, - agent_attributes=region.agent_attributes, + agent_properties=region.agent_properties, recurrent_states=region.recurrent_states ) def clear_agents(self): self.agent_states = [] - self.agent_attributes = [] + self.agent_properties = [] self.recurrent_states = [] def insert_all_agent_details( self, agent_state: AgentState, - agent_attributes: AgentAttributes, + agent_properties: Union[AgentAttributes,AgentProperties], recurrent_state: RecurrentState ): + if isinstance(agent_properties,AgentAttributes): + agent_properties = self.convert_attributes_to_properties(agent_properties) self.agent_states.append(agent_state) - self.agent_attributes.append(agent_attributes) + self.agent_properties.append(agent_properties) self.recurrent_states.append(recurrent_state) def is_inside( @@ -85,3 +96,14 @@ def is_inside( x, y = point.x, point.y region_x, region_y = self.center.x, self.center.y return region_x - self.size/2 <= x and x <= region_x + self.size/2 and region_y - self.size/2 <= y and y <= region_y + self.size/2 + + def convert_attributes_to_properties(self,attributes): + properties = AgentProperties( + length=attributes.length, + width=attributes.width, + rear_axis_offset=attributes.rear_axis_offset, + agent_type=attributes.agent_type, + waypoint=attributes.waypoint + ) + + return properties diff --git a/invertedai/large/drive.py b/invertedai/large/drive.py index af925b1..62c0292 100644 --- a/invertedai/large/drive.py +++ b/invertedai/large/drive.py @@ -1,11 +1,11 @@ -from typing import Tuple, Optional, List +from typing import Tuple, Optional, List, Union from pydantic import BaseModel, validate_call from math import ceil import asyncio import invertedai as iai from invertedai.large.common import Region -from invertedai.common import Point, AgentState, AgentAttributes, RecurrentState, TrafficLightStatesDict, LightRecurrentState +from invertedai.common import Point, AgentState, AgentAttributes, AgentProperties, RecurrentState, TrafficLightStatesDict, LightRecurrentState from invertedai.api.drive import DriveResponse from invertedai.error import InvertedAIError, InvalidRequestError from ._quadtree import QuadTreeAgentInfo, QuadTree, _flatten_and_sort, QUADTREE_SIZE_BUFFER @@ -20,7 +20,7 @@ async def async_drive_all(async_input_params): def large_drive( location: str, agent_states: List[AgentState], - agent_attributes: List[AgentAttributes], + agent_properties: List[Union[AgentAttributes,AgentProperties]], recurrent_states: List[RecurrentState], traffic_lights_states: Optional[TrafficLightStatesDict] = None, light_recurrent_states: Optional[List[LightRecurrentState]] = None, @@ -45,7 +45,7 @@ def large_drive( agent_states: Please refer to the documentation of :func:`drive` for information on this parameter. - agent_attributes: + agent_properties: Please refer to the documentation of :func:`drive` for information on this parameter. recurrent_states: @@ -86,8 +86,25 @@ def large_drive( if single_call_agent_limit > DRIVE_MAXIMUM_NUM_AGENTS: single_call_agent_limit = DRIVE_MAXIMUM_NUM_AGENTS iai.logger.warning(f"Single Call Agent Limit cannot be more than {DRIVE_MAXIMUM_NUM_AGENTS}, limiting this value to {DRIVE_MAXIMUM_NUM_AGENTS} and proceeding.") - if not (len(agent_states) == len(agent_attributes) == len(recurrent_states)): + if not (len(agent_states) == len(agent_properties) == len(recurrent_states)): raise InvalidRequestError(message="Input lists are not of equal size.") + if not len(agent_states) > 0: + raise InvalidRequestError(message="Valid call must contain at least 1 agent.") + + # Convert any AgentAttributes to AgentProperties for backwards compatibility + agent_properties_new = [] + for properties in agent_properties: + properties_new = properties + if isinstance(properties,AgentAttributes): + properties_new = AgentProperties( + length=properties.length, + width=properties.width, + rear_axis_offset=properties.rear_axis_offset, + agent_type=properties.agent_type, + waypoint=properties.waypoint + ) + agent_properties_new.append(properties_new) + agent_properties = agent_properties_new # Generate quadtree agent_x = [agent.center.x for agent in agent_states] @@ -103,7 +120,7 @@ def large_drive( size=region_size ), ) - for i, (agent, attrs, recurr_state) in enumerate(zip(agent_states,agent_attributes,recurrent_states)): + for i, (agent, attrs, recurr_state) in enumerate(zip(agent_states,agent_properties,recurrent_states)): agent_info = QuadTreeAgentInfo.fromlist([agent, attrs, recurr_state, i]) is_inserted = quadtree.insert(agent_info) @@ -128,8 +145,8 @@ def large_drive( agent_id_order.extend(region_agents_ids) input_params = { "location":location, - "agent_attributes":region.agent_attributes+region_buffer.agent_attributes, "agent_states":region.agent_states+region_buffer.agent_states, + "agent_properties":region.agent_properties+region_buffer.agent_properties, "recurrent_states":region.recurrent_states+region_buffer.recurrent_states, "light_recurrent_states":light_recurrent_states, "traffic_lights_states":traffic_lights_states, @@ -164,7 +181,7 @@ def large_drive( response = iai.drive( location = location, agent_states = agent_states, - agent_attributes = agent_attributes, + agent_properties = agent_properties, recurrent_states = recurrent_states, traffic_lights_states = traffic_lights_states, light_recurrent_states = light_recurrent_states, diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index cd6711c..12bc799 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -10,7 +10,7 @@ from invertedai.large.common import Region from invertedai.api.initialize import InitializeResponse from invertedai.common import TrafficLightStatesDict, Point -from invertedai.utils import get_default_agent_attributes +from invertedai.utils import get_default_agent_properties from invertedai.error import InvertedAIError AGENT_SCOPE_FOV_BUFFER = 20 @@ -156,7 +156,7 @@ def get_number_of_agents_per_region_by_drivable_area( """ This function takes in a list of regions, calculates the driveable area for each of them using :func:`location_info`, then creates a new Region object with copied location and shape data and - inserts a number of default AgentAttributes objects into it porportional to its drivable surface + inserts a number of default AgentProperties objects into it porportional to its drivable surface area relative to the other regions. Regions with no or a relatively small amount of drivable surfaces will be assigned zero agents. @@ -220,22 +220,22 @@ def get_number_of_agents_per_region_by_drivable_area( random_indexes = choices(list(range(len(new_regions))), weights=all_region_weights, k=total_num_agents) for ind in random_indexes: - new_regions[ind].agent_attributes.extend(get_default_agent_attributes({"car":1})) + new_regions[ind].agent_properties.extend(get_default_agent_properties({"car":1})) return new_regions def _get_all_existing_agents_from_regions(regions,exclude_index=None): agent_states = [] - agent_attributes = [] + agent_properties = [] recurrent_states = [] for ind, region in enumerate(regions): if not ind == exclude_index: region_agent_states = region.agent_states agent_states.extend(region_agent_states) - agent_attributes.extend(region.agent_attributes[:len(region_agent_states)]) + agent_properties.extend(region.agent_properties[:len(region_agent_states)]) recurrent_states.extend(region.recurrent_states) - return agent_states, agent_attributes, recurrent_states + return agent_states, agent_properties, recurrent_states @validate_call def large_initialize( @@ -254,7 +254,7 @@ def large_initialize( response is combined into a single response which is returned. While looping over all regions, if there are agents in other regions that are near enough to the region of interest, they will be passed as conditional to :func:`initialize`. :func:`initialize` - will not be called if no agent_states or agent_attributes are specified in the region. + will not be called if no agent_states or agent_properties are specified in the region. As well, predefined agents may be passed via the regions and will be considered as conditional. A boolean flag can be used to control failure behaviour if :func:`initialize` is unable to produce viable vehicle placements if the initialization should continue or @@ -267,7 +267,7 @@ def large_initialize( regions: List of regions that contains information about the center, size, and agent_states and - agent_attributes formatted to align with :func:`initialize`. Please refer to the + agent_properties formatted to align with :func:`initialize`. Please refer to the documentation for :func:`initialize` for more details. The Region objects are not modified, rather they are used @@ -298,7 +298,7 @@ def large_initialize( """ agent_states_sampled = [] - agent_attributes_sampled = [] + agent_properties_sampled = [] agent_rs_sampled = [] def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: @@ -320,35 +320,35 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: region_center = region.center region_size = region.size - existing_agent_states, existing_agent_attributes, _ = _get_all_existing_agents_from_regions(regions,i) + existing_agent_states, existing_agent_properties, _ = _get_all_existing_agents_from_regions(regions,i) # Acquire agents that exist in other regions that must be passed as conditional to avoid collisions out_of_region_conditional_agents = list(filter( lambda x: inside_fov(center=region_center, agent_scope_fov=region_size+AGENT_SCOPE_FOV_BUFFER, point=x[0].center), - zip(existing_agent_states,existing_agent_attributes) + zip(existing_agent_states,existing_agent_properties) )) out_of_region_conditional_agent_states = [x[0] for x in out_of_region_conditional_agents] - out_of_region_conditional_agent_attributes = [x[1] for x in out_of_region_conditional_agents] + out_of_region_conditional_agent_properties = [x[1] for x in out_of_region_conditional_agents] region_conditional_agent_states = [] if region.agent_states is None else region.agent_states num_region_conditional_agents = len(region_conditional_agent_states) - region_conditional_agent_attributes = [] if region.agent_attributes is None else region.agent_attributes[:num_region_conditional_agents] - region_unsampled_agent_attributes = [] if region.agent_attributes is None else region.agent_attributes[num_region_conditional_agents:] + region_conditional_agent_properties = [] if region.agent_properties is None else region.agent_properties[:num_region_conditional_agents] + region_unsampled_agent_properties = [] if region.agent_properties is None else region.agent_properties[num_region_conditional_agents:] all_agent_states = out_of_region_conditional_agent_states + region_conditional_agent_states - all_agent_attributes = out_of_region_conditional_agent_attributes + region_conditional_agent_attributes + region_unsampled_agent_attributes + all_agent_properties = out_of_region_conditional_agent_properties + region_conditional_agent_properties + region_unsampled_agent_properties num_out_of_region_conditional_agents = len(out_of_region_conditional_agent_states) regions[i].clear_agents() response = None - if len(all_agent_attributes) > 0: + if len(all_agent_properties) > 0: for attempt in range(num_attempts): try: response = iai.initialize( location=location, states_history=None if len(all_agent_states) == 0 else [all_agent_states], - agent_attributes=all_agent_attributes, + agent_properties=all_agent_properties, get_infractions=get_infractions, traffic_light_state_history=traffic_light_state_history, location_of_interest=(region_center.x, region_center.y), @@ -374,7 +374,7 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: response = iai.initialize( location=location, states_history=[all_agent_states], - agent_attributes=all_agent_attributes[:num_out_of_region_conditional_agents+num_region_conditional_agents], + agent_properties=all_agent_properties[:num_out_of_region_conditional_agents+num_region_conditional_agents], get_infractions=get_infractions, traffic_light_state_history=traffic_light_state_history, location_of_interest=(region_center.x, region_center.y), @@ -386,7 +386,7 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: # Filter out conditional agents from other regions for state, attrs, r_state in zip( response.agent_states[num_out_of_region_conditional_agents:], - response.agent_attributes[num_out_of_region_conditional_agents:], + response.agent_properties[num_out_of_region_conditional_agents:], response.recurrent_states[num_out_of_region_conditional_agents:] ): regions[i].insert_all_agent_details(state,attrs,r_state) @@ -398,14 +398,14 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: continue - all_agent_states, all_agent_attributes, all_recurrent_states = _get_all_existing_agents_from_regions(regions) + all_agent_states, all_agent_properties, all_recurrent_states = _get_all_existing_agents_from_regions(regions) if len(all_responses) > 0: # Get non-region-specific values such as api_model_version and traffic_light_states from an existing response response = all_responses[0] # Set agent information with all agent information from every region response.agent_states = all_agent_states - response.agent_attributes = all_agent_attributes + response.agent_properties = all_agent_properties response.recurrent_states = all_recurrent_states else: raise InvertedAIError(message=f"Unable to initialize all given regions. Please check the input parameters.") diff --git a/invertedai/utils.py b/invertedai/utils.py index f8bcbc7..72a8194 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -10,7 +10,7 @@ import time -from typing import Dict, Optional, List, Tuple +from typing import Dict, Optional, List, Tuple, Union from tqdm.contrib import tmap from itertools import product from copy import deepcopy @@ -29,7 +29,7 @@ import invertedai.api import invertedai.api.config from invertedai import error -from invertedai.common import AgentState, AgentAttributes, StaticMapActor, TrafficLightStatesDict, Point, RecurrentState +from invertedai.common import AgentState, AgentAttributes, AgentProperties, StaticMapActor, TrafficLightStatesDict, Point, RecurrentState from invertedai.future import to_thread from invertedai.error import InvertedAIError from invertedai.api.initialize import InitializeResponse @@ -443,17 +443,26 @@ def _interpret_response_line(self, result): return data @validate_call -def get_default_agent_attributes(agent_count_dict: Dict[str,int]) -> List[AgentAttributes]: +def get_default_agent_properties( + agent_count_dict: Dict[str,int], + attribute_type: Optional[str] = "properties" +) -> List[Union[AgentAttributes,AgentProperties]]: # Function that outputs a list a AgentAttributes with minimal default settings. # Mainly meant to be used to pad a list of AgentAttributes to send as input to # initialize(). This list is created by reading a dictionary containing the # desired agent types with the agent count for each type respectively. + # If desired to use deprecate AgentAttributes instead of AgentProperties, input + # string "attributes" instead of default "properties" agent_attributes_list = [] for agent_type, agent_count in agent_count_dict.items(): for _ in range(agent_count): - agent_attributes_list.append(AgentAttributes.fromlist([agent_type])) + if attribute_type == "properties": + agent_properties = AgentProperties(agent_type=agent_type) + agent_attributes_list.append(agent_properties) + else: + agent_attributes_list.append(AgentAttributes.fromlist([agent_type])) return agent_attributes_list @@ -687,7 +696,7 @@ def __init__( dpi=100, ): self.conditional_agents = None - self.agent_attributes = None + self.agent_properties = None self.traffic_lights_history = None self.agent_states_history = None self.open_drive = open_drive @@ -736,13 +745,13 @@ def __init__( def initialize_recording( self, agent_states, - agent_attributes, + agent_properties, traffic_light_states=None, conditional_agents=None, ): self.agent_states_history = [agent_states] self.traffic_lights_history = [traffic_light_states] - self.agent_attributes = agent_attributes + self.agent_properties = agent_properties if conditional_agents is not None: self.conditional_agents = conditional_agents else: @@ -751,7 +760,7 @@ def initialize_recording( def reset_recording(self): self.agent_states_history = [] self.traffic_lights_history = [] - self.agent_attributes = None + self.agent_properties = None self.conditional_agents = [] def record_step(self, agent_states, traffic_light_states=None): @@ -761,7 +770,7 @@ def record_step(self, agent_states, traffic_light_states=None): def plot_scene( self, agent_states, - agent_attributes, + agent_properties, traffic_light_states=None, conditional_agents=None, ax=None, @@ -771,7 +780,7 @@ def plot_scene( ): self.initialize_recording( agent_states, - agent_attributes, + agent_properties, traffic_light_states=traffic_light_states, conditional_agents=conditional_agents, ) @@ -878,7 +887,7 @@ def _initialize_plot( def _update_frame_to(self, frame_idx): for i, (agent, agent_attribute) in enumerate( - zip(self.agent_states_history[frame_idx], self.agent_attributes) + zip(self.agent_states_history[frame_idx], self.agent_properties) ): self._update_agent(i, agent, agent_attribute) From 4389f9fd948076e6fd8155150b3bb9b31a57cd77 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 19 Jul 2024 14:53:34 -0700 Subject: [PATCH 2/7] Added fix to large initialize that prevents long-range agent spawning collisions. --- invertedai/large/initialize.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index 12bc799..e55b0cb 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -13,7 +13,7 @@ from invertedai.utils import get_default_agent_properties from invertedai.error import InvertedAIError -AGENT_SCOPE_FOV_BUFFER = 20 +AGENT_SCOPE_FOV_BUFFER = 60 ATTEMPT_PER_NUM_REGIONS = 15 @validate_call @@ -220,7 +220,7 @@ def get_number_of_agents_per_region_by_drivable_area( random_indexes = choices(list(range(len(new_regions))), weights=all_region_weights, k=total_num_agents) for ind in random_indexes: - new_regions[ind].agent_properties.extend(get_default_agent_properties({"car":1})) + new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({"car":1}) return new_regions @@ -231,9 +231,9 @@ def _get_all_existing_agents_from_regions(regions,exclude_index=None): for ind, region in enumerate(regions): if not ind == exclude_index: region_agent_states = region.agent_states - agent_states.extend(region_agent_states) - agent_properties.extend(region.agent_properties[:len(region_agent_states)]) - recurrent_states.extend(region.recurrent_states) + agent_states = agent_states + region_agent_states + agent_properties = agent_properties + region.agent_properties[:len(region_agent_states)] + recurrent_states = recurrent_states + region.recurrent_states return agent_states, agent_properties, recurrent_states @@ -389,6 +389,10 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: response.agent_properties[num_out_of_region_conditional_agents:], response.recurrent_states[num_out_of_region_conditional_agents:] ): + if not return_exact_agents: + if not inside_fov(center=region_center, agent_scope_fov=region_size, point=state.center): + continue + regions[i].insert_all_agent_details(state,attrs,r_state) if traffic_light_state_history is None and response.traffic_lights_states is not None: From 2151434067061c2747a46df00b02dd0a868744e9 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 26 Jul 2024 11:11:00 -0700 Subject: [PATCH 3/7] Added function to large initialize to enable pre-existing agents to regions. --- invertedai/large/initialize.py | 49 +++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index e55b0cb..3d93bf4 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -5,11 +5,12 @@ from tqdm.contrib import tenumerate import numpy as np from random import choices, seed +from math import sqrt import invertedai as iai from invertedai.large.common import Region from invertedai.api.initialize import InitializeResponse -from invertedai.common import TrafficLightStatesDict, Point +from invertedai.common import TrafficLightStatesDict, Point, AgentProperties, AgentStates from invertedai.utils import get_default_agent_properties from invertedai.error import InvertedAIError @@ -145,6 +146,39 @@ def get_neighbors(center): return regions +@validate_call +def insert_agents_into_nearest_region( + regions: List[Region], + agent_properties: List[AgentProperties], + agent_states: List[AgentStates] +) -> List[Region]: + """ + Helper function to place pre-existing agents into a group of regions. If agents exist + within the bounds of multiple regions, it is placed within the region to which whose + center it is closest. Agents will be placed "into" the region that is closest even if + it is not within the bounds of the region. + + Arguments + ---------- + regions: + A list of Regions with bounds and centre defined for which agents are associated. + + agent_states: + Please refer to the documentation of :func:`drive` for information on this parameter. + + agent_properties: + Please refer to the documentation of :func:`drive` for information on this parameter. + """ + + for state, prop in zip (agent_states,agent_properties): + region_distances = [] + for region in regions: + region_distances.append(sqrt((state.center.x-region.center.x)**2 + (state.center.y-region.center.y)**2)) + + closest_region_index = region_distances.index(min(region_distances)) + regions[closest_region_index].agent_properties.append(prop) + + @validate_call def get_number_of_agents_per_region_by_drivable_area( location: str, @@ -158,7 +192,8 @@ def get_number_of_agents_per_region_by_drivable_area( :func:`location_info`, then creates a new Region object with copied location and shape data and inserts a number of default AgentProperties objects into it porportional to its drivable surface area relative to the other regions. Regions with no or a relatively small amount of drivable - surfaces will be assigned zero agents. + surfaces will be assigned zero agents. If a region is at its capacity (e.g. due to pre-existing + agents), no more agents will be added to it. Arguments ---------- @@ -219,8 +254,16 @@ def get_number_of_agents_per_region_by_drivable_area( all_region_weights[i] = drivable_ratio/total_drivable_area_ratio random_indexes = choices(list(range(len(new_regions))), weights=all_region_weights, k=total_num_agents) + number_sampled_agents = {} + for ind in random_indexes: + if ind not in number_sampled_agents: + number_sampled_agents[ind] = 1 + else: + number_sampled_agents[ind] += 1 + for ind in random_indexes: - new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({"car":1}) + if len(new_regions[ind].agent_properties) < number_sampled_agents[ind]: + new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({"car":1}) return new_regions From 5d138ceedeebc3eb9d09d58b871642fa6bfc4544 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 26 Jul 2024 11:11:54 -0700 Subject: [PATCH 4/7] Fixed return issue. --- invertedai/large/initialize.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index 3d93bf4..b41f3f1 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -178,6 +178,8 @@ def insert_agents_into_nearest_region( closest_region_index = region_distances.index(min(region_distances)) regions[closest_region_index].agent_properties.append(prop) + return regions + @validate_call def get_number_of_agents_per_region_by_drivable_area( From a9d91c8316dcabd3f397b60135e5b0f281df7ff3 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 26 Jul 2024 11:18:30 -0700 Subject: [PATCH 5/7] Added function to init --- invertedai/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invertedai/__init__.py b/invertedai/__init__.py index aa06dd4..386a59b 100644 --- a/invertedai/__init__.py +++ b/invertedai/__init__.py @@ -10,7 +10,7 @@ from invertedai.api.blame import blame, async_blame from invertedai.cosimulation import BasicCosimulation from invertedai.utils import Jupyter_Render, IAILogger, Session -from invertedai.large.initialize import get_regions_in_grid, get_number_of_agents_per_region_by_drivable_area, get_regions_default, large_initialize +from invertedai.large.initialize import get_regions_in_grid, get_number_of_agents_per_region_by_drivable_area, insert_agents_into_nearest_region, get_regions_default, large_initialize from invertedai.large.drive import large_drive dev = strtobool(os.environ.get("IAI_DEV", "false")) From 871715f076bab93e3326440c516542696c53c20a Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 26 Jul 2024 13:06:05 -0700 Subject: [PATCH 6/7] Updated data type. --- invertedai/large/initialize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index b41f3f1..19e6d8d 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -10,7 +10,7 @@ import invertedai as iai from invertedai.large.common import Region from invertedai.api.initialize import InitializeResponse -from invertedai.common import TrafficLightStatesDict, Point, AgentProperties, AgentStates +from invertedai.common import TrafficLightStatesDict, Point, AgentProperties, AgentState from invertedai.utils import get_default_agent_properties from invertedai.error import InvertedAIError @@ -150,7 +150,7 @@ def get_neighbors(center): def insert_agents_into_nearest_region( regions: List[Region], agent_properties: List[AgentProperties], - agent_states: List[AgentStates] + agent_states: List[AgentState] ) -> List[Region]: """ Helper function to place pre-existing agents into a group of regions. If agents exist From 77e78748eb727a22647fbbb0ab7a922d8ac5918d Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Wed, 31 Jul 2024 17:34:34 -0700 Subject: [PATCH 7/7] Added function for converting attribtues to properties and implemented it. --- invertedai/large/common.py | 16 +++------------- invertedai/large/drive.py | 9 ++------- invertedai/utils.py | 34 ++++++++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/invertedai/large/common.py b/invertedai/large/common.py index d64566e..7a629ce 100644 --- a/invertedai/large/common.py +++ b/invertedai/large/common.py @@ -2,6 +2,7 @@ from pydantic import BaseModel from invertedai.common import AgentAttributes, AgentProperties, AgentState, RecurrentState, Point +from invertedai.utils import convert_attributes_to_properties class Region(BaseModel): """ @@ -37,7 +38,7 @@ def create_square_region( for properties in agent_properties: properties_new = properties if isinstance(properties,AgentAttributes): - properties_new = cls.convert_attributes_to_properties(cls,properties_new) + properties_new = convert_attributes_to_properties(properties_new) agent_properties_new.append(properties_new) agent_properties = agent_properties_new @@ -76,7 +77,7 @@ def insert_all_agent_details( recurrent_state: RecurrentState ): if isinstance(agent_properties,AgentAttributes): - agent_properties = self.convert_attributes_to_properties(agent_properties) + agent_properties = convert_attributes_to_properties(agent_properties) self.agent_states.append(agent_state) self.agent_properties.append(agent_properties) @@ -96,14 +97,3 @@ def is_inside( x, y = point.x, point.y region_x, region_y = self.center.x, self.center.y return region_x - self.size/2 <= x and x <= region_x + self.size/2 and region_y - self.size/2 <= y and y <= region_y + self.size/2 - - def convert_attributes_to_properties(self,attributes): - properties = AgentProperties( - length=attributes.length, - width=attributes.width, - rear_axis_offset=attributes.rear_axis_offset, - agent_type=attributes.agent_type, - waypoint=attributes.waypoint - ) - - return properties diff --git a/invertedai/large/drive.py b/invertedai/large/drive.py index 62c0292..810aebb 100644 --- a/invertedai/large/drive.py +++ b/invertedai/large/drive.py @@ -7,6 +7,7 @@ from invertedai.large.common import Region from invertedai.common import Point, AgentState, AgentAttributes, AgentProperties, RecurrentState, TrafficLightStatesDict, LightRecurrentState from invertedai.api.drive import DriveResponse +from invertedai.utils import convert_attributes_to_properties from invertedai.error import InvertedAIError, InvalidRequestError from ._quadtree import QuadTreeAgentInfo, QuadTree, _flatten_and_sort, QUADTREE_SIZE_BUFFER @@ -96,13 +97,7 @@ def large_drive( for properties in agent_properties: properties_new = properties if isinstance(properties,AgentAttributes): - properties_new = AgentProperties( - length=properties.length, - width=properties.width, - rear_axis_offset=properties.rear_axis_offset, - agent_type=properties.agent_type, - waypoint=properties.waypoint - ) + properties_new = convert_attributes_to_properties(properties) agent_properties_new.append(properties_new) agent_properties = agent_properties_new diff --git a/invertedai/utils.py b/invertedai/utils.py index 72a8194..83d50af 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -445,20 +445,22 @@ def _interpret_response_line(self, result): @validate_call def get_default_agent_properties( agent_count_dict: Dict[str,int], - attribute_type: Optional[str] = "properties" + use_agent_properties: Optional[bool] = True ) -> List[Union[AgentAttributes,AgentProperties]]: - # Function that outputs a list a AgentAttributes with minimal default settings. - # Mainly meant to be used to pad a list of AgentAttributes to send as input to - # initialize(). This list is created by reading a dictionary containing the - # desired agent types with the agent count for each type respectively. - # If desired to use deprecate AgentAttributes instead of AgentProperties, input - # string "attributes" instead of default "properties" + """ + Function that outputs a list a AgentAttributes with minimal default settings. + Mainly meant to be used to pad a list of AgentAttributes to send as input to + initialize(). This list is created by reading a dictionary containing the + desired agent types with the agent count for each type respectively. + If desired to use deprecate AgentAttributes instead of AgentProperties, set the + use_agent_properties flag to False. + """ agent_attributes_list = [] for agent_type, agent_count in agent_count_dict.items(): for _ in range(agent_count): - if attribute_type == "properties": + if use_agent_properties: agent_properties = AgentProperties(agent_type=agent_type) agent_attributes_list.append(agent_properties) else: @@ -466,6 +468,22 @@ def get_default_agent_properties( return agent_attributes_list +@validate_call +def convert_attributes_to_properties(attributes: AgentAttributes) -> AgentProperties: + """ + Convert deprecated AgentAttributes data type to AgentProperties. + """ + + properties = AgentProperties( + length=attributes.length, + width=attributes.width, + rear_axis_offset=attributes.rear_axis_offset, + agent_type=attributes.agent_type, + waypoint=attributes.waypoint + ) + + return properties + @validate_call def iai_conditional_initialize( location: str,