-
Notifications
You must be signed in to change notification settings - Fork 1
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
Large area simulation rework #203
Large area simulation rework #203
Conversation
… more general use data types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial comments
invertedai/large/common.py
Outdated
agent_attributes: Optional[List[AgentAttributes]] = None # The attributes of agents that exist within the region or that will be initialized within the region | ||
recurrent_states: Optional[List[RecurrentState]] = None # Recurrent states of the agents contained within the region | ||
region_type: str = 'square' # Geometric category of the region. As of now, only 'square' is supported. | ||
vertexes: List[Point] # An ordered list of x-y coordinates of the region defined clockwise around the perimeter of the region |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are referring to vertex
, i believe the plural form is vertices
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vertices is indeed used but Vertexes is also used and more grammatically correct.
invertedai/large/drive.py
Outdated
|
||
BUFFER_FOV = 35 | ||
|
||
class Particle(BaseModel): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure we want to keep using the word Particle
here ? TreeNode or Node make more sense to me here. Or we can simply just call it Agent in this local namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a carry over from the old area drive, however particle is the most commonly used term for "things that are placed within a quad tree". However Agent would indeed be more effective and descriptive.
invertedai/large/common.py
Outdated
|
||
return vertexes | ||
|
||
def check_point_in_region(self,point): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to have a doc string for this method explaining how things work in general, but you should reformat it to have it enclosed instead of using the comments.
And i don't think you need those additional comments explain how things work line by line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function was removed since it is not needed at this time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's some feedback for the parts I managed to read tonight. I'll add more tomorrow. Generally, the structure is looking good and this is what we want, but there are a lot of things that can be simplified and renamed to something more obvious.
If we're confident that the old versions of those functions are not used, I think it's fine to delete them. If we thought they might be used, deprecating them for some time would be in order.
One name change to consider is to use large_initialize
and large_drive
instead of region_initialize
and region_drive
. Those are more in line with what those functions accomplish rather than how they do it.
invertedai/large/common.py
Outdated
|
||
class Region(BaseModel): | ||
""" | ||
A data structure to contain information about a region in a map to be used in large simulations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A data structure to contain information about a region in a map to be used in large simulations. | |
A region in a map used to divide a large simulation into smaller parts. |
It is self-evidence that this is a data structure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/common.py
Outdated
AgentAttributes | ||
""" | ||
|
||
center: Optional[Point] = None # The center of the region if such a concept is relevant (e.g. center of a square, center of a rectangle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
center: Optional[Point] = None # The center of the region if such a concept is relevant (e.g. center of a square, center of a rectangle) | |
center: Optional[Point] = None #: The center of the region if such a concept is relevant (e.g. center of a square, center of a rectangle) |
Use #:
for each field to include those comments in the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/common.py
Outdated
""" | ||
|
||
center: Optional[Point] = None # The center of the region if such a concept is relevant (e.g. center of a square, center of a rectangle) | ||
fov: Optional[float] = None # Side length of the region for the default interpretation of a region as a square |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size
or length
would be a better name, as the region definition doesn't necessarily have anything to do with viewing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/common.py
Outdated
agent_states: Optional[List[AgentState]] = None # A list of existing agents within the region | ||
agent_attributes: Optional[List[AgentAttributes]] = None # The attributes of agents that exist within the region or that will be initialized within the region | ||
recurrent_states: Optional[List[RecurrentState]] = None # Recurrent states of the agents contained within the region |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a semantic difference between None
and []
here? I'm also wondering if we should make it a List[Tuple[AgentAttributes, AgentState, Optional[RecurrentState]]]
instead, but I'm not convinced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these Regions were only used for driving then I would agree with the Tuple but since it's used for initialize as well which can be quite wonky, I'd say lets keep state, attributes, and recurrent states separate.
invertedai/large/initialize.py
Outdated
AGENT_SCOPE_FOV_BUFFER = 20 | ||
|
||
@validate_call | ||
def define_regions_grid( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a docstring explaining how the grid is produced and what is a typical use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
queue, centers = [map_center], [] | ||
|
||
while queue: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BFS is way of an overkill here. You can compute the grid of a rectangular area in closed form.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sake of time, I can update this later since this won't impact the user interface and we know this code works so we can quietly make that change later while users are testing the features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it can be done later.
invertedai/large/initialize.py
Outdated
max_agent_density: Optional[int] = 10, | ||
scaling_factor: Optional[float] = 1.0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These argument names, as well as the name of the function itself, are not at all intuitive. Presumably what this function does is compute agent numbers for each region given some parameters, so the naming should reflect that. Neither density
nor scaling
have obvious meanings here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've renamed them but when could you provide some feedback on if the new names are insufficient, too wordy, and if so, could you elaborate on what you are looking for?
invertedai/large/initialize.py
Outdated
center_road_area_dict = {} | ||
max_drivable_area_ratio = 0 | ||
|
||
iterable_regions = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iterable_regions = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleted.
invertedai/large/initialize.py
Outdated
# Convert to grayscale using Luminosity Method: gray = 0.114*B + 0.587*G + 0.299*R | ||
# Image should be in BGR color pixel format | ||
birdview_grayscale = np.matmul(birdview.reshape(total_num_pixels,3),np.array([[0.114],[0.587],[0.299]])) | ||
number_of_black_pix = np.sum(birdview_grayscale == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Convert to grayscale using Luminosity Method: gray = 0.114*B + 0.587*G + 0.299*R | |
# Image should be in BGR color pixel format | |
birdview_grayscale = np.matmul(birdview.reshape(total_num_pixels,3),np.array([[0.114],[0.587],[0.299]])) | |
number_of_black_pix = np.sum(birdview_grayscale == 0) | |
number_of_black_pix = np.sum((birdview.sum(axis=-1) == 0).int()) |
This is a very elaborate conversion only to compare things to zero at the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm yeah I guess at this point we know we don't need to do anything more fancy with a grayscale image. I made this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally I see 3 types of comments:
- definition to arguments and fields
- explanation of key steps within a long workflow
- explanation of why things are called/implemented as such
For this repo, I believe type 1 and 2 are reasonable, but we should remove/rephrase those that are type 3. I marked them below.
invertedai/large/drive.py
Outdated
self.southWest.get_leaf_nodes() + self.southEast.get_leaf_nodes() | ||
|
||
def _flatten_and_sort(nested_list,index_list): | ||
# Cannot unpack iterables during list comprehension so use this helper function instead |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
agent_x[i] = agent.center.x | ||
agent_y[i] = agent.center.y | ||
max_x, min_x, max_y, min_y = max(agent_x), min(agent_x), max(agent_y), min(agent_y) | ||
region_fov = ceil(max(max_x,max_y) - min(min_x,min_y)) #Round up the starting FOV to an integer to reduce floating point issues |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider removing the comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
if len(region.agent_states) == 0: | ||
# Region is empty, do not call DRIVE | ||
continue | ||
|
||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if len(region.agent_states) == 0: | |
# Region is empty, do not call DRIVE | |
continue | |
else: | |
if len(region.agent_states) > 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a more careful review of initialize
. I'll review drive
as well, but posting this already in the interest of expediency.
invertedai/large/initialize.py
Outdated
response.agent_states = all_agent_states | ||
response.agent_attributes = all_agent_attributes | ||
response.recurrent_states = all_recurrent_states |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little tricky, but for the traffic lights to work correctly, you need to return the recurrent light states from the response that originally set them. When you provide light states as input, the recurrent light states returned are garbage. @rf-ivtdai is adding this explanation to the documentation, but you'll want to fix the returned value here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I've made a change that should account for this.
invertedai/large/initialize.py
Outdated
traffic_light_state_history=traffic_light_state_history, | ||
location_of_interest=(region_center.x, region_center.y), | ||
random_seed=random_seed, | ||
get_birdview=get_birdview, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's wasteful to request a birdview on every call, if you're only using the last one. I would remove this option altogether, but if you keep it, maybe there's a way to only request one birdview?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with leaving this out for now because it's not-trivial how to interpret this in the "large" version of initialize and drive and there are indeed other visualization methods.
invertedai/large/initialize.py
Outdated
except InvertedAIError as e: | ||
iai.logger.warning(e) | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little odd to just ignore this. This breaks the usual contract of INITIALIZE, which is that you get exactly as many agents as you request, or it's a failure. Let's discuss in person.
|
||
queue, centers = [map_center], [] | ||
|
||
while queue: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it can be done later.
invertedai/large/initialize.py
Outdated
region_center = region.center | ||
region_size = region.size | ||
|
||
existing_agent_states, existing_agent_attributes, _ = _get_all_existing_agents_from_regions(regions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can pull this out of the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regions are populated with agents one at a time. Therefore, each loop all agents currently in the simulation (initialized, preddefined, whatever) need to be accessed so that ones that are relevant to the current region of interest can be included as conditional. An alternative would be to have 3 running lists for states, attributes, and recurrent states but I went with this method for the sake of saving memory space and code cleanliness at the cost of a small increase in computation time due to looping through every region.
invertedai/large/initialize.py
Outdated
response = iai.initialize( | ||
location=location, | ||
states_history=None if len(all_agent_states) == 0 else [all_agent_states], | ||
agent_attributes=[] if len(all_agent_attributes) == 0 else all_agent_attributes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agent_attributes=[] if len(all_agent_attributes) == 0 else all_agent_attributes, | |
agent_attributes=all_agent_attributes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
invertedai/large/initialize.py
Outdated
conditional_agent_attributes = [x[1] for x in conditional_agents] | ||
|
||
region_predefined_agent_states = [] if region.agent_states is None else region.agent_states | ||
all_agent_states = conditional_agent_states + region_predefined_agent_states |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't those already captured in conditional_agent_states
? It looks like you're including them twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference here is that conditional_agent_states
contains states from regions that have already been initialized and predefined agents from other regions whereas region_predefined_agent_states
contains predefined agents for the current region of interest. Any of these agents could be relevant as "conditional" for calling initialize
on the current region.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've renamed conditional_agent_states
to out_of_region_conditional_agent_states
and used this naming scheme wherever else relevant.
invertedai/large/initialize.py
Outdated
# Filter out agents that are not inside the ROI to avoid collision with other agents not passed as conditional | ||
# SLACK is for removing the agents that are very close to the boundary and they may collide with agents not | ||
# labelled as conditional | ||
valid_agents = list(filter( | ||
lambda x: inside_fov(center=region_center, agent_scope_fov=region_size - SLACK, point=x[0].center), | ||
zip(response_agent_states_sampled, response_agent_attributes_sampled, response_recurrent_states_sampled) | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another instance where we remove some agents that the user is expecting to see. Perhaps this is fine, but we need to be clear in the documentation about what we return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually upon further inspection, this can be removed because of the filter earlier in the loop that only takes in agents as conditional if they are within a certain scope so no collisions should happen at the edge anyways.
invertedai/large/initialize.py
Outdated
regions[i].agent_states = response.agent_states[predefined_agents_slice] + valid_agent_state | ||
regions[i].agent_attributes = response.agent_attributes[predefined_agents_slice] + valid_agent_attrs | ||
regions[i].recurrent_states = response.recurrent_states[predefined_agents_slice] + valid_agent_rs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying Region objects in place is another thing that we need to be careful about in the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed so that it uses the member function region[i].insert_all_agent_details
instead of modifying the fields directly.
invertedai/large/initialize.py
Outdated
valid_agent_attrs = [x[1] for x in valid_agents] | ||
valid_agent_rs = [x[2] for x in valid_agents] | ||
|
||
predefined_agents_slice = slice(len(conditional_agent_states),len(conditional_agent_states)+len(region_predefined_agent_states)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure this is correct? I don't know what exactly was the intention here, but this seems suspicious to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My current understanding of initialize
is that the order of agents matters, insomuch as if N
is the number of conditional agents each with an AgentState in the the states_history
argument, then initialize only samples M-N agents where M is the number of AgentAttributes in the agent_attributes
argument. That is fine and dandy however the trickiness here is that there are two types of "conditional" agents here: conditional agents predefined within the region of interest and conditional agents that exist outside of the region of interest. The former need to be kept within the region whereas the former, at this point in the loop have served their purpose and can be ignored. So this slice is trying to collect just the conditional agents predefined within this region.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, now with the deletion of that "valid states" filter, this is moot and the slice can just be len(out_of_region_conditional_agent_states)
onwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a review for drive
. I'll do one more read of the whole thing.
invertedai/large/drive.py
Outdated
get_infractions: bool = False, | ||
random_seed: Optional[int] = None, | ||
api_model_version: Optional[str] = None, | ||
capacity: Optional[int] = 100, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capacity: Optional[int] = 100, | |
single_call_agent_limit: Optional[int] = None, |
Setting the default to None
will make for a seamless transition if we increase the agent limit in DRIVE in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense, change is implemented.
invertedai/large/drive.py
Outdated
random_seed: Optional[int] = None, | ||
api_model_version: Optional[str] = None, | ||
capacity: Optional[int] = 100, | ||
is_async: Optional[bool] = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_async: Optional[bool] = True | |
async_api_calls: bool = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
invertedai/large/drive.py
Outdated
:func:`drive` | ||
""" | ||
|
||
num_agents = len(agent_states) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe check that all the input lists are the same length? I believe that's required here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
agent_x = [None]*num_agents | ||
agent_y = [None]*num_agents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agent_x = [None]*num_agents | |
agent_y = [None]*num_agents | |
agent_x = [None for _ in range(num_agents)] | |
agent_y = [None for _ in range(num_agents)] |
What you did works, but I recommend this as a habit. Otherwise you end up getting surprising bugs when you change None
to a mutable object, and you get N references instead of N separate objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
for i, agent in enumerate(agent_states): | ||
agent_x[i] = agent.center.x | ||
agent_y[i] = agent.center.y |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be the idiomatic Python equivalent.
for i, agent in enumerate(agent_states): | |
agent_x[i] = agent.center.x | |
agent_y[i] = agent.center.y | |
agent_x = [agent.center.x for agent in agent_states] | |
agent_y = [agent.center.y for agent in agent_states] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
invertedai/large/drive.py
Outdated
api_model_version = '' if len(all_responses) == 0 else all_responses[0].api_model_version, | ||
birdview = None if len(all_responses) == 0 or not get_birdview else all_responses[0].birdview, | ||
traffic_lights_states = traffic_lights_states if len(all_responses) == 0 else all_responses[0].traffic_lights_states, | ||
light_recurrent_states = light_recurrent_states if len(all_responses) == 0 else all_responses[0].light_recurrent_states |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under what circumstances would we ever get no responses? Only if there are no input agents? In that case, I think we'd want behavior consistent with that drive
does with no agents - easiest to achieve by making a single call to drive
and returning the response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I've made that change as well as adding an if statement to check whether it should just call regular drive (i.e. if there is only 1 leaf node).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the user happens to input no agent information, the regular call to drive() will handle that.
invertedai/large/drive.py
Outdated
all_responses = asyncio.run(async_drive_all(async_input_params)) | ||
|
||
response = DriveResponse( | ||
agent_states = _flatten_and_sort([response.agent_states[:len(region.agent_attributes)] for response, region in zip(all_responses,non_empty_regions)],agent_id_order), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor repeated parts, len(region.agent_attributes)
and zip(all_responses,non_empty_regions)
into named variables for better readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put the zipped lists into a single variable and added a "get number of agents" function to the quad tree leaf node to make it more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually scratch that, if you attempt to refactor the zip part into a single variable, I think there is some pass-by-reference shenanigans where that zipped variable is being affected by the _flatten_and_sort
function and that's why I had to repeat it before.
invertedai/large/drive.py
Outdated
flat_list = [] | ||
for sublist in nested_list: | ||
flat_list.extend(sublist) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flat_list = [] | |
for sublist in nested_list: | |
flat_list.extend(sublist) | |
[x for sublist in nested_list for x in sublist] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
sorted_list = [] | ||
for ind in range(len(index_list)): | ||
agent_index = index_list.index(ind) | ||
sorted_list.append(flat_list[agent_index]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorted_list = [] | |
for ind in range(len(index_list)): | |
agent_index = index_list.index(ind) | |
sorted_list.append(flat_list[agent_index]) | |
sorted_list = [x[1] for x in sorted(zip(index_list, flat_list))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
if is_async: | ||
all_responses = asyncio.run(async_drive_all(async_input_params)) | ||
|
||
response = DriveResponse( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole _flatten_and_sort
business is quite convoluted and could use refactoring, but we can leave that for later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is an annoying circumstance for sure. There is no connection between the location of the agent and it's index in the input parameter. This is a situation where an explicit ID for each agent would be useful but I understand it wouldn't make sense to change one of our basic data classes just to solve this.
I think a middle ground is that this flatten and sort method can be a member function of the quadtree class instead to make it a bit cleaner remaining functionally the same.
…gents instead of density
… return all requested agents.
Outstanding issues:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, it looks like we have the major things covered. Other than for some final tidying up, we should be good to go. Switching to an iterative QuadTree implementation would be my recommendation for getting rid of the recursion depth errors, but that can wait for later. More importantly, we need some test to ensure those new functions won't get broken by subsequent commits, but that can also be done in a separate PR.
invertedai/large/common.py
Outdated
center: Optional[Point] = None #: The center of the region if such a concept is relevant (e.g. center of a square, center of a rectangle) | ||
size: Optional[float] = None #: Side length of the region for the default interpretation of a region as a square |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these optional? It would seem that both are needed to define a region.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes a oversight when this was changed from the more general Region, this has been updated.
invertedai/large/common.py
Outdated
size: Optional[float] = None #: 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 | ||
recurrent_states: Optional[List[RecurrentState]] = [] #: Recurrent states of the agents contained within the region |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
recurrent_states: Optional[List[RecurrentState]] = [] #: Recurrent states of the agents contained within the region | |
recurrent_states: Optional[List[RecurrentState]] = [] #: Recurrent states of the existing agents within the region |
Only if I understand correctly that this is supposed to have the same length as agent_states
if given.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, if recurrent_states is not empty, it should be the same length as agent_states AND agent_attributes. The two uses cases that I can identify for this object are for passing as arguments to DRIVE and to INITIALIZE which have slightly different needs so it's hard to enforce a standard across the board, especially because INITIALIZE has many different operating modes (i.e. equal number states and attributes, more attributes than states, no states at all).
Do you think it'd be worthwhile to split these regions up? Or have some sort of data validation? E.g. if agent details are to be added, it should check that it is a valid configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I would perform some validation.
invertedai/large/common.py
Outdated
recurrent_states: Optional[List[RecurrentState]] = [] #: Recurrent states of the agents contained within the region | ||
|
||
@classmethod | ||
def init_square_region( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def init_square_region( | |
def create_square_region( |
init
may be confused with initialize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/common.py
Outdated
""" | ||
Helper function to clear all agent data within the Region but keep geometric information. | ||
|
||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
""" | |
Helper function to clear all agent data within the Region but keep geometric information. | |
""" |
I don't think this sentence add any information beyond what the name already says, so I would remove it to reduce the maintenance burden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/common.py
Outdated
""" | ||
Helper function to insert the details relevant to a single agent. | ||
|
||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
""" | |
Helper function to insert the details relevant to a single agent. | |
""" |
I don't think this one is even correct. Case in point that it's best to avoid docstrings that don't provide any extra information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
total_num_agents: Optional[int] = 10, | ||
random_seed: Optional[int] = None, | ||
display_progress_bar: Optional[bool] = True | ||
) -> List[Region]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're returning a list of regions. Make sure to specify whether those are the input Region objects, and if not whether the input objects are modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
agent_attributes=agent_attributes, | ||
recurrent_states=recurrent_states | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A copy
method would be a good idea for future proofing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
A utility function to drive more than the normal capacity of agents in a call to :func:`drive`. | ||
The agents are inserted into a quadtree structure and :func:`drive` is then called on each | ||
region represented by a leaf node of the quadtree. Agents near this region are included in the | ||
:func:`drive` calls to ensure the agents behave properly locally. The quadtree is constructed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:func:`drive` calls to ensure the agents behave properly locally. The quadtree is constructed | |
:func:`drive` calls to ensure the agents see all their neighbours. The quadtree is constructed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
invertedai/large/drive.py
Outdated
agent_states: | ||
Current states of all agents. | ||
The state must include x: [float], y: [float] coordinate in meters | ||
orientation: [float] in radians with 0 pointing along x and pi/2 pointing along y and | ||
speed: [float] in m/s. | ||
|
||
agent_attributes: | ||
Static attributes of all agents. | ||
List of agent attributes. Each agent requires, length: [float] | ||
width: [float] and rear_axis_offset: [float] all in meters. agent_type: [str], | ||
currently supports 'car' and 'pedestrian'. | ||
waypoint: optional [Point], the target waypoint of the agent. | ||
|
||
recurrent_states: | ||
Recurrent states for all agents, obtained from the previous call to | ||
:func:`drive` or :func:`initialize` or the large equivalents. | ||
|
||
traffic_lights_states: | ||
If the location contains traffic lights within the supported area, | ||
their current state should be provided here. Any traffic light for which no | ||
state is provided will have a state generated by iai. | ||
|
||
light_recurrent_states: | ||
Light recurrent states for all agents, obtained from the previous call to | ||
:func:`drive` or :func:`initialize` or the large equivalents. | ||
Specifies the state and time remaining for each light group in the map. | ||
If manual control of individual traffic lights is desired, modify the relevant state(s) | ||
in traffic_lights_states, then pass in light_recurrent_states as usual. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider pointing the reader to the documentation for drive instead. That has its downsides, but makes it less likely that this description gets out of sync from the one in drive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea, I will do the same with large_initialize
wherever possible because yeah it might be annoying but it is much more convenient for maintainability and less prone to mistakes and confusion.
invertedai/large/drive.py
Outdated
The number of agents allowed in a region before it must subdivide. Currently this | ||
value represents the capacity of a quadtree leaf node that will subdivide if the | ||
number of vehicles in the region passes this threshold. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The number of agents allowed in a region before it must subdivide. Currently this | |
value represents the capacity of a quadtree leaf node that will subdivide if the | |
number of vehicles in the region passes this threshold. | |
The number of agents allowed in a region before it must subdivide. Currently this | |
value represents the capacity of a quadtree leaf node that will subdivide if the | |
number of vehicles in the region passes this threshold. In any case, | |
this will be limited to the maximum currently supported by :func:`drive`. |
Assuming we clip this value at 100, which I think we should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Outstanding addition to include:
|
Before we merge, please make sure all the tests have passed. Right now we have been getting this error from the log:
|
Absolutely. I ran those tests locally and they passed without issue so I think therein lies the cause for the failure. I will come back and solve this issue before merging. |
Moved versions of initialize and drive designed for simulating on large maps to a different submodule called "large" in invertedai.
Developed a new basic datatype called Region which contains a center, fov, and agent information such as state, attributes, and recurrent state as well as helper functions. This data type was designed with some thought towards the future in case basic assumptions about what a "region" can be changed (e.g. bigger or smaller than 100x100m square, non-square regions).
Due to the lack of need for the pygame visualization and design constraint of being stateless, region_drive has become simpler and less reliant on dependencies.
Region initialize is now separated from HOW the regions are produced. Default helper functions have been provided for this but can be easily swapped out by the user.
The core functionality is largely the same as it was before, algorithmically speaking.
Questions for reviewers: