-
Notifications
You must be signed in to change notification settings - Fork 118
Python Stdlib Parity #4
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
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
819b634
Add python init functions
nshlapo a500597
Format and add neighbor library
nshlapo 17f535d
Tests written for init functions
nshlapo 006c441
Add rand functions and test
nshlapo 45fe8b6
Finished all python tests, improved js neighborsInRadius function
nshlapo 7e36d43
Formatting
nshlapo 864c366
Typo fixes
nshlapo 243b407
Docstrings and fixes from review
nshlapo 71f3c68
Formatted and finalized
nshlapo 0d61c59
last fixes
nshlapo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| pytest == "6.2.2" | ||
| black == "20.8b1" | ||
| pylint == "2.7.2" | ||
| pytest == 6.2.2 | ||
| black == 20.8b1 | ||
| pylint == 2.7.2 | ||
| mypy==0.812 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| from dataclasses import dataclass, field | ||
| from typing import Optional, List | ||
|
|
||
|
|
||
| @dataclass | ||
| class Topology: | ||
| x_bounds: List[float] | ||
| y_bounds: List[float] | ||
| z_bounds: Optional[List[float]] = field(default_factory=lambda: [0.0, 0.0]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| """ | ||
| Initialization utility functions. | ||
| """ | ||
| import math | ||
| import random | ||
| from copy import deepcopy | ||
| from typing import Dict, List, Union, Callable, Mapping | ||
|
|
||
| from .agent import AgentState | ||
| from .context import Topology | ||
|
|
||
| # AgentTemplate can be an AgentState, or function which returns an AgentState | ||
| AgentFunction = Callable[[], AgentState] | ||
| AgentTemplate = Union[AgentState, AgentFunction] | ||
|
|
||
|
|
||
| def create_agent(template: AgentTemplate) -> AgentState: | ||
| if callable(template): | ||
| return template() | ||
| else: | ||
| return deepcopy(template) | ||
|
|
||
|
|
||
| def scatter(count: int, topology: Topology, template: AgentTemplate) -> List[AgentState]: | ||
|
nshlapo marked this conversation as resolved.
|
||
| """ | ||
| Generate `count` agents using the `template`, assigning them random positions within | ||
| the `topology` bounds. | ||
|
|
||
| Args: | ||
| count: the number of agents to generate. | ||
| topology: the `context.globals()["topology"]` value. | ||
| template: an agent definition, or a function which returns an agent definition. | ||
| """ | ||
| x_bounds = topology.x_bounds | ||
| y_bounds = topology.y_bounds | ||
|
|
||
| width = x_bounds[1] - x_bounds[0] | ||
| height = y_bounds[1] - y_bounds[0] | ||
|
|
||
| def assign_random_position() -> AgentState: | ||
| x = random.uniform(0, width) + x_bounds[0] | ||
| y = random.uniform(0, height) + y_bounds[0] | ||
|
|
||
| agent = create_agent(template) | ||
| agent["position"] = [x, y] | ||
|
|
||
| return agent | ||
|
|
||
| agents = [assign_random_position() for i in range(count)] | ||
|
|
||
| return agents | ||
|
|
||
|
|
||
| def stack(count: int, template: AgentTemplate) -> List[AgentState]: | ||
| """ | ||
| Generate `count` agents using the `template`. | ||
|
|
||
| Args: | ||
| count: the number of agents to generate. | ||
| template: an agent definition, or a function which returns an agent definition. | ||
| """ | ||
| agents = [create_agent(template) for i in range(count)] | ||
|
|
||
| return agents | ||
|
|
||
|
|
||
| def grid(topology: Topology, template: AgentTemplate) -> List[AgentState]: | ||
| """ | ||
| Generate agents on every integer location within the `topology` bounds. | ||
|
|
||
| Args: | ||
| topology: the `context.globals()["topology"]` value. | ||
| template: an agent definition, or a function which returns an agent definition. | ||
| """ | ||
| x_bounds = topology.x_bounds | ||
| y_bounds = topology.y_bounds | ||
|
|
||
| width = x_bounds[1] - x_bounds[0] | ||
| height = y_bounds[1] - y_bounds[0] | ||
| count = width * height | ||
|
|
||
| def assign_grid_position(ind: int) -> AgentState: | ||
| x = (ind % width) + x_bounds[0] | ||
| y = math.floor(ind / width) + y_bounds[0] | ||
|
|
||
| agent = create_agent(template) | ||
| agent["position"] = [x, y] | ||
|
|
||
| return agent | ||
|
|
||
| agents = [assign_grid_position(i) for i in range(int(count))] | ||
|
|
||
| return agents | ||
|
|
||
|
|
||
| def create_layout( | ||
| layout: List[List[str]], templates: Mapping[str, AgentState], offset: List[float] = [0, 0, 0] | ||
| ) -> List[AgentState]: | ||
| """ | ||
| Generate agents with positions based on a `layout`, and definitions | ||
| based on the `templates`. | ||
|
|
||
| Args: | ||
| layout: the locations of agents, typically uploaded as a csv dataset | ||
| templates: the definitions for each type of agent refernced in the layout | ||
| offset: optional offset specifying the position of the bottom right corner of the `layout` | ||
| """ | ||
|
|
||
| height = len(layout) | ||
| agents: Dict[str, List[AgentState]] = {} | ||
|
|
||
| for pos_y, row in enumerate(layout): | ||
| for pos_x, template_type in enumerate(row): | ||
| if template_type in templates: | ||
| if template_type not in agents: | ||
| agents[template_type] = [] | ||
|
|
||
| agent_name = (templates[template_type].agent_name or template_type) + str( | ||
| len(agents[template_type]) | ||
| ) | ||
|
|
||
| agent = templates[template_type] | ||
| agent["agent_name"] = agent_name | ||
| agent["position"] = [pos_x + offset[0], height - pos_y + offset[1], offset[2]] | ||
|
|
||
| agents[template_type].append(agent) | ||
|
|
||
| agent_list = [agent for sublist in agents.values() for agent in sublist] | ||
|
|
||
| return agent_list | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| """ | ||
| Neighbor utility functions. | ||
| """ | ||
| from typing import List | ||
|
|
||
| from .spatial import distance_between | ||
| from .agent import AgentFieldError, AgentState | ||
|
|
||
|
|
||
| def neighbors_on_position(agent: AgentState, neighbors: List[AgentState]) -> List[AgentState]: | ||
| """ | ||
| Returns all `neighbors` whose position is identical to the `agent`. | ||
| """ | ||
| if agent.position is None: | ||
| raise AgentFieldError(agent.agent_id, "position", "cannot be None") | ||
|
|
||
| return [n for n in neighbors if n.position == agent.position] | ||
|
|
||
|
|
||
| def neighbors_in_radius( | ||
| agent: AgentState, | ||
| neighbors: List[AgentState], | ||
| max_radius: float = 1, | ||
| min_radius: float = 0, | ||
| distance_function: str = "euclidean", | ||
| z_axis: bool = False, | ||
| ) -> List[AgentState]: | ||
| """ | ||
| Returns all neighbors within a certain vision radius of an agent. | ||
| Default is 2D (`z_axis` set to false). Set `z_axis` to true for 3D positions. | ||
|
|
||
| Args: | ||
| agent: central agent | ||
| neighbors: context.neighbors() array, or an array of agents | ||
| max_radius: minimum radius for valid neighbors | ||
| min_radius: maximum radius for valid neighbors | ||
| distance_function: type of distance function to use | ||
| z_axis: include z-axis in distance calculations | ||
| """ | ||
|
|
||
| if agent.position is None: | ||
| raise AgentFieldError(agent.agent_id, "position", "cannot be None") | ||
|
|
||
| def in_radius(neighbor: AgentState) -> bool: | ||
| if neighbor.position is None: | ||
| return False | ||
|
|
||
| d = distance_between(neighbor, agent, distance_function, z_axis) | ||
| if d is None: | ||
| return False | ||
|
|
||
| return (d <= max_radius) and (d >= min_radius) | ||
|
|
||
| return [n for n in neighbors if in_radius(n)] | ||
|
|
||
|
|
||
| def difference_vector(vec1: List[float], vec2: List[float]): | ||
| """ | ||
| Calculate the difference vector `vec2` - `vec1`. | ||
| """ | ||
| return [vec2[ind] - vec1[ind] for ind in range(len(vec1))] | ||
|
|
||
|
|
||
| def in_front_planar(agent: AgentState, neighbor: AgentState) -> bool: | ||
| """ | ||
| Return True if a neighbor is anywhere in front of the agent. | ||
| """ | ||
|
|
||
| a_dir = agent["direction"] | ||
|
eadanfahey marked this conversation as resolved.
|
||
|
|
||
| [dx, dy, dz] = difference_vector(agent["position"], neighbor["position"]) | ||
| D = a_dir[0] * dx + a_dir[1] * dy + a_dir[2] * dz | ||
|
|
||
| return D > 0 | ||
|
|
||
|
|
||
| def is_linear(agent: AgentState, neighbor: AgentState, front: bool) -> bool: | ||
| """ | ||
| Check if a neighbor lies along the direction vector of the agent, and is | ||
| in front of or behind the agent, based on `front`. | ||
| """ | ||
| [dx, dy, dz] = difference_vector(agent["position"], neighbor["position"]) | ||
| [ax, ay, az] = agent["direction"] | ||
|
|
||
| cross_product = [dy * az - dz * ay, dx * az - dz * ax, dx * ay - dy * ax] | ||
|
|
||
| if cross_product != [0, 0, 0]: | ||
| return False | ||
|
|
||
| # check if same direction | ||
| same_dir = (ax * dx > 0) or (ay * dy > 0) or (az * dz > 0) | ||
|
|
||
| return same_dir is front | ||
|
|
||
|
|
||
| def neighbors_in_front( | ||
| agent: AgentState, neighbors: List[AgentState], colinear: bool = False | ||
| ) -> List[AgentState]: | ||
| """ | ||
| Return all `neighbors` in front of the `agent`. If `colinear` is True | ||
| check that the neighbor lies along the agent's direction vector. | ||
| """ | ||
|
|
||
| if agent.position is None: | ||
| raise AgentFieldError(agent.agent_id, "position", "cannot be None") | ||
| if agent.direction is None: | ||
| raise AgentFieldError(agent.agent_id, "direction", "cannot be None") | ||
|
|
||
| if colinear: | ||
| return [n for n in neighbors if is_linear(agent, n, True)] | ||
| else: | ||
| return [n for n in neighbors if in_front_planar(agent, n)] | ||
|
|
||
|
|
||
| def neighbors_behind( | ||
| agent: AgentState, neighbors: List[AgentState], colinear: bool = False | ||
| ) -> List[AgentState]: | ||
| """ | ||
| Return all `neighbors` behind the `agent`. If `colinear` is True | ||
| check that the neighbor lies along the agent's direction vector. | ||
| """ | ||
|
|
||
| if agent.position is None: | ||
| raise AgentFieldError(agent.agent_id, "position", "cannot be None") | ||
| if agent.direction is None: | ||
| raise AgentFieldError(agent.agent_id, "direction", "cannot be None") | ||
|
|
||
| if colinear: | ||
|
nshlapo marked this conversation as resolved.
|
||
| return [n for n in neighbors if is_linear(agent, n, False)] | ||
| else: | ||
| return [n for n in neighbors if not in_front_planar(agent, n)] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| """ | ||
|
eadanfahey marked this conversation as resolved.
|
||
| Random utility functions. | ||
| """ | ||
|
|
||
| import random as rand | ||
|
|
||
|
|
||
| def set_seed(s: str): | ||
| """ Set the random seed for Python's random library """ | ||
| rand.seed(s) | ||
|
|
||
|
|
||
| def random(): | ||
| """ Returns a random number between 0 and 1 """ | ||
| return rand.random() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.