From 819b6342ae26ed99704c6b767dd573dc13d452fb Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Tue, 18 May 2021 16:51:36 -0400 Subject: [PATCH 01/10] Add python init functions --- packages/engine/stdlib/src/py/hstd/init.py | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 packages/engine/stdlib/src/py/hstd/init.py diff --git a/packages/engine/stdlib/src/py/hstd/init.py b/packages/engine/stdlib/src/py/hstd/init.py new file mode 100644 index 00000000000..ba536a8067a --- /dev/null +++ b/packages/engine/stdlib/src/py/hstd/init.py @@ -0,0 +1,97 @@ +""" +Initialization utility functions. +""" +import math +import random +from dataclasses import dataclass +from typing import Optional, List, Union, Callable, Mapping + +from .agent import AgentState + +AgentFunction = Callable[[], AgentState] +AgentTemplate = Union[AgentState, AgentFunction] + +@dataclass +class Topology: + x_bounds: List[float] + y_bounds: List[float] + z_bounds: Optional[List[float]] + +def create_agent(template: AgentTemplate) -> AgentState: + if (type(AgentTemplate) is AgentFunction): + return template() + else: + return template + +def scatter(count: int, topology: Topology, template: AgentTemplate) -> List[AgentState]: + 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(width) + x_bounds[0] + y = random.uniform(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]: + agents = [create_agent(template) for i in range(count)] + + return agents + +def grid(topology: Topology, template: AgentTemplate) -> List[AgentState]: + 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(count)] + + return agents + +def createLayout(layout: List[List[str]], templates: Mapping[str, AgentState], offset: List[float] = [0, 0, 0]) -> List[AgentState]: + height = len(layout) + agents = {} + + 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) + 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_list = [agent for sublist in agents.values() for agent in sublist] + + return agent_list + + + + + From a500597abda9cebc39436ecaf6abced54cbf84d6 Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Wed, 19 May 2021 11:27:23 -0400 Subject: [PATCH 02/10] Format and add neighbor library --- .../engine/stdlib/src/py/dev_requirements.txt | 6 +- packages/engine/stdlib/src/py/hstd/init.py | 31 +++-- .../engine/stdlib/src/py/hstd/neighbor.py | 126 ++++++++++++++++++ packages/engine/stdlib/src/py/hstd/rand.py | 0 .../engine/stdlib/src/py/hstd/test_init.py | 0 5 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 packages/engine/stdlib/src/py/hstd/neighbor.py create mode 100644 packages/engine/stdlib/src/py/hstd/rand.py create mode 100644 packages/engine/stdlib/src/py/hstd/test_init.py diff --git a/packages/engine/stdlib/src/py/dev_requirements.txt b/packages/engine/stdlib/src/py/dev_requirements.txt index 6ba5375bfe6..17156bdee73 100644 --- a/packages/engine/stdlib/src/py/dev_requirements.txt +++ b/packages/engine/stdlib/src/py/dev_requirements.txt @@ -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 \ No newline at end of file diff --git a/packages/engine/stdlib/src/py/hstd/init.py b/packages/engine/stdlib/src/py/hstd/init.py index ba536a8067a..9484c8b7fdd 100644 --- a/packages/engine/stdlib/src/py/hstd/init.py +++ b/packages/engine/stdlib/src/py/hstd/init.py @@ -11,19 +11,23 @@ AgentFunction = Callable[[], AgentState] AgentTemplate = Union[AgentState, AgentFunction] + @dataclass class Topology: x_bounds: List[float] y_bounds: List[float] z_bounds: Optional[List[float]] + def create_agent(template: AgentTemplate) -> AgentState: - if (type(AgentTemplate) is AgentFunction): + if type(AgentTemplate) is AgentFunction: return template() else: return template + def scatter(count: int, topology: Topology, template: AgentTemplate) -> List[AgentState]: + x_bounds = topology.x_bounds y_bounds = topology.y_bounds @@ -43,11 +47,13 @@ def assign_random_position() -> AgentState: return agents + def stack(count: int, template: AgentTemplate) -> List[AgentState]: agents = [create_agent(template) for i in range(count)] return agents + def grid(topology: Topology, template: AgentTemplate) -> List[AgentState]: x_bounds = topology.x_bounds y_bounds = topology.y_bounds @@ -59,7 +65,7 @@ def grid(topology: Topology, template: AgentTemplate) -> List[AgentState]: 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] @@ -69,29 +75,30 @@ def assign_grid_position(ind: int) -> AgentState: return agents -def createLayout(layout: List[List[str]], templates: Mapping[str, AgentState], offset: List[float] = [0, 0, 0]) -> List[AgentState]: + +def create_layout( + layout: List[List[str]], templates: Mapping[str, AgentState], offset: List[float] = [0, 0, 0] +) -> List[AgentState]: + height = len(layout) agents = {} 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): + if template_type in templates: + if template_type not in agents: agents[template_type] = [] - agent_name = (templates[template_type].agent_name or template_type) + len(agents[template_type]) + agent_name = (templates[template_type].agent_name or template_type) + 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_list = [agent for sublist in agents.values() for agent in sublist] return agent_list - - - - - diff --git a/packages/engine/stdlib/src/py/hstd/neighbor.py b/packages/engine/stdlib/src/py/hstd/neighbor.py new file mode 100644 index 00000000000..402674400cf --- /dev/null +++ b/packages/engine/stdlib/src/py/hstd/neighbor.py @@ -0,0 +1,126 @@ +""" +Neighbor utility functions. +""" +import math +import random +from dataclasses import dataclass +from typing import Optional, List, Union, Callable, Mapping +from functools import reduce +from .spatial import ( + manhattan_distance, + euclidean_squared_distance, + euclidean_distance, + chebyshev_distance, +) + +from .agent import AgentState + +pos_error = Exception("agent must have a position") +dir_error = Exception("agent must have a direction") + + +def neighbors_on_position(agent: AgentState, neighbors: List[AgentState]) -> List[AgentState]: + + if not agent["position"]: + raise pos_error + + def on_position(neighbor: AgentState) -> bool: + pos = agent["position"] + pos_equal = [pos[ind] == neighbor[ind] for ind in range(len(pos))] + + return reduce(lambda a, b: (bool(a and b)), pos_equal) + + return filter(on_position, neighbors) + + +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]: + + if not agent["position"]: + raise pos_error + + func = { + "manhattan": manhattan_distance, + "euclidean": euclidean_distance, + "euclidean_squared": euclidean_squared_distance, + "chebyshev": chebyshev_distance, + } + + def in_radius(neighbor: AgentState) -> bool: + pos = agent["position"] + # TODO implement way to ignore z axis + d = func[distance_function](neighbor["position"], pos) + + return (d <= max_radius) and (d >= min_radius) + + return filter(in_radius, neighbors) + + +def in_front_planar(agent: AgentState, neighbor: List[AgentState]) -> bool: + + a_pos = agent['position'] + n_pos = neighbor['position'] + a_dir = agent['direction'] + + [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] + 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: + + a_pos = agent['position'] + n_pos = neighbor['position'] + [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] + [ax, ay, az] = agent['direction'] + + cross_product = [dy*az - dz*ay, dx*az - dz*ax, dx*ay - dy*ax] + all_zero = reduce(lambda a, b: not bool(a or b), cross_product) + + # if cross_product is 0 + if not all_zero: + return False + + # check if same direction + same_dir = (dx > 0 and ax > 0) or (dy > 0 and ay > 0) or (dz > 0 and az > 0) + + return same_dir is front + + +def neighbors_in_front( + agent: AgentState, neighbors: List[AgentState], colinear: bool = False +) -> List[AgentState]: + + if not agent["position"]: + raise pos_error + if not agent["direction"]: + raise dir_error + + if colinear: + return filter(lambda n: is_linear(agent, n, True), neighbors) + else: + return filter(lambda n: in_front_planar(agent, n), neighbors) + + +def neighbors_behind( + agent: AgentState, neighbors: List[AgentState], colinear: bool = False +) -> List[AgentState]: + + if not agent["position"]: + raise pos_error + if not agent["direction"]: + raise dir_error + + if colinear: + return filter(lambda n: is_linear(agent, n, False), neighbors) + else: + return filter(lambda n: not in_front_planar(agent, n), neighbors) diff --git a/packages/engine/stdlib/src/py/hstd/rand.py b/packages/engine/stdlib/src/py/hstd/rand.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/engine/stdlib/src/py/hstd/test_init.py b/packages/engine/stdlib/src/py/hstd/test_init.py new file mode 100644 index 00000000000..e69de29bb2d From 17f535d851446cad91d6afb2b0e0ee1ab6aaaa52 Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Wed, 19 May 2021 14:20:07 -0400 Subject: [PATCH 03/10] Tests written for init functions --- packages/engine/stdlib/src/py/hstd/agent.py | 7 ++ packages/engine/stdlib/src/py/hstd/init.py | 11 +-- packages/engine/stdlib/src/py/hstd/spatial.py | 2 +- .../engine/stdlib/src/py/hstd/test_init.py | 69 +++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/engine/stdlib/src/py/hstd/agent.py b/packages/engine/stdlib/src/py/hstd/agent.py index 42505476c6d..d77c8c7ed54 100644 --- a/packages/engine/stdlib/src/py/hstd/agent.py +++ b/packages/engine/stdlib/src/py/hstd/agent.py @@ -13,9 +13,16 @@ def generate_agent_id(): @dataclass class AgentState: agent_id: str = field(default_factory=generate_agent_id) + agent_name: Optional[str] = None position: Optional[List[float]] = None direction: Optional[List[float]] = None + def __setitem__(self, key, value): + setattr(self, key, value) + + def __getitem__(self, key): + return getattr(self, key) + class AgentFieldError(Exception): def __init__(self, agent_id: str, field: str, msg: str = ""): diff --git a/packages/engine/stdlib/src/py/hstd/init.py b/packages/engine/stdlib/src/py/hstd/init.py index 9484c8b7fdd..06ae8806303 100644 --- a/packages/engine/stdlib/src/py/hstd/init.py +++ b/packages/engine/stdlib/src/py/hstd/init.py @@ -3,6 +3,7 @@ """ import math import random +from copy import deepcopy from dataclasses import dataclass from typing import Optional, List, Union, Callable, Mapping @@ -20,10 +21,10 @@ class Topology: def create_agent(template: AgentTemplate) -> AgentState: - if type(AgentTemplate) is AgentFunction: + if callable(template): return template() else: - return template + return deepcopy(template) def scatter(count: int, topology: Topology, template: AgentTemplate) -> List[AgentState]: @@ -35,11 +36,11 @@ def scatter(count: int, topology: Topology, template: AgentTemplate) -> List[Age height = y_bounds[1] - y_bounds[0] def assign_random_position() -> AgentState: - x = random.uniform(width) + x_bounds[0] - y = random.uniform(height) + y_bounds[0] + x = random.uniform(0, width) + x_bounds[0] + y = random.uniform(0, height) + y_bounds[0] agent = create_agent(template) - agent["position"] = [x, y] + agent['position'] = [x, y] return agent diff --git a/packages/engine/stdlib/src/py/hstd/spatial.py b/packages/engine/stdlib/src/py/hstd/spatial.py index 7e035bb5903..71d318682cb 100644 --- a/packages/engine/stdlib/src/py/hstd/spatial.py +++ b/packages/engine/stdlib/src/py/hstd/spatial.py @@ -13,7 +13,7 @@ class Topology: x_bounds: List[float] y_bounds: List[float] - z_bounds: Optional[List[float]] + z_bounds: Optional[List[float]] = 0 def manhattan_distance(p1: List[float], p2: List[float]) -> float: diff --git a/packages/engine/stdlib/src/py/hstd/test_init.py b/packages/engine/stdlib/src/py/hstd/test_init.py index e69de29bb2d..2a4410d9817 100644 --- a/packages/engine/stdlib/src/py/hstd/test_init.py +++ b/packages/engine/stdlib/src/py/hstd/test_init.py @@ -0,0 +1,69 @@ +import pytest + +from .agent import AgentState +from .spatial import Topology +from .init import scatter, grid, stack, create_layout + +init_topology = Topology([0, 2], [0, 2]) + +agent = AgentState(agent_name='test') +agent_function = lambda: AgentState(agent_name='test') + +num_agents = 4 + +scatter_agents = scatter(num_agents, init_topology, agent) +scatter_agents_function = scatter(num_agents, init_topology, agent_function) + +''' Test scatter function ''' +def test_scatter(): + assert len(scatter_agents) == num_agents + assert len(scatter_agents_function) == num_agents + + def subtest(a): + assert a['position'][0] >= init_topology.x_bounds[0] + assert a['position'][0] <= init_topology.x_bounds[1] + assert a['position'][1] >= init_topology.y_bounds[0] + assert a['position'][1] <= init_topology.y_bounds[1] + + assert a['agent_name'] == 'test' + + [subtest(agent) for agent in scatter_agents] + [subtest(agent) for agent in scatter_agents_function] + + +''' Test stack function ''' +stack_agents = stack(num_agents, agent) +stack_agents_function = stack(num_agents, agent_function) + +def test_stack(): + assert len(stack_agents) == num_agents + assert len(stack_agents_function) == num_agents + + def subtest(a): + assert a['agent_name'] == 'test' + + [subtest(agent) for agent in scatter_agents] + [subtest(agent) for agent in scatter_agents_function] + + +''' Test grid function ''' +grid_agents = grid(init_topology, agent) +grid_agents_function = grid(init_topology, agent) + +def test_grid(): + assert len(grid_agents) == num_agents + assert len(grid_agents_function) == num_agents + + def subtest(a): + assert a['position'][0] >= init_topology.x_bounds[0] + assert a['position'][0] <= init_topology.x_bounds[1] + assert a['position'][1] >= init_topology.y_bounds[0] + assert a['position'][1] <= init_topology.y_bounds[1] + + assert int(a['position'][0]) == a['position'][0] + assert int(a['position'][1]) == a['position'][1] + + assert a['agent_name'] == 'test' + + [subtest(agent) for agent in grid_agents] + [subtest(agent) for agent in grid_agents_function] \ No newline at end of file From 006c441c88549e0815299b05b6a8f36340f1f7aa Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Thu, 20 May 2021 07:22:11 -0400 Subject: [PATCH 04/10] Add rand functions and test --- packages/engine/stdlib/src/py/hstd/rand.py | 11 +++++++++++ packages/engine/stdlib/src/py/hstd/test_rand.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 packages/engine/stdlib/src/py/hstd/test_rand.py diff --git a/packages/engine/stdlib/src/py/hstd/rand.py b/packages/engine/stdlib/src/py/hstd/rand.py index e69de29bb2d..3f38d41979c 100644 --- a/packages/engine/stdlib/src/py/hstd/rand.py +++ b/packages/engine/stdlib/src/py/hstd/rand.py @@ -0,0 +1,11 @@ +""" +Random utility functions. +""" + +import random as rand + +def set_seed(s: str): + rand.seed(s) + +def random(): + return rand.random() diff --git a/packages/engine/stdlib/src/py/hstd/test_rand.py b/packages/engine/stdlib/src/py/hstd/test_rand.py new file mode 100644 index 00000000000..0332fcace61 --- /dev/null +++ b/packages/engine/stdlib/src/py/hstd/test_rand.py @@ -0,0 +1,14 @@ +import pytest + +from .rand import set_seed, random + +def test_random(): + n = random() + nn = random() + assert n != nn + + set_seed("test") + n = random() + set_seed("test") + nn = random() + assert n == pytest.approx(nn) \ No newline at end of file From 45fe8b64652da7c990b1fdbeab732caa8dff2a6d Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Thu, 20 May 2021 15:43:15 -0400 Subject: [PATCH 05/10] Finished all python tests, improved js neighborsInRadius function --- .../engine/stdlib/src/py/hstd/neighbor.py | 53 ++++++++-------- packages/engine/stdlib/src/py/hstd/spatial.py | 16 ++--- .../stdlib/src/py/hstd/test_neighbor.py | 62 +++++++++++++++++++ .../engine/stdlib/src/ts/neighbor.spec.ts | 12 ++-- packages/engine/stdlib/src/ts/neighbor.ts | 44 ++++++------- packages/engine/stdlib/src/ts/spatial.ts | 56 ++++++++++------- 6 files changed, 157 insertions(+), 86 deletions(-) create mode 100644 packages/engine/stdlib/src/py/hstd/test_neighbor.py diff --git a/packages/engine/stdlib/src/py/hstd/neighbor.py b/packages/engine/stdlib/src/py/hstd/neighbor.py index 402674400cf..77f9c753cf3 100644 --- a/packages/engine/stdlib/src/py/hstd/neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/neighbor.py @@ -1,10 +1,7 @@ """ Neighbor utility functions. """ -import math -import random -from dataclasses import dataclass -from typing import Optional, List, Union, Callable, Mapping +from typing import List from functools import reduce from .spatial import ( manhattan_distance, @@ -15,22 +12,22 @@ from .agent import AgentState -pos_error = Exception("agent must have a position") -dir_error = Exception("agent must have a direction") +pos_error = Exception('agent must have a position') +dir_error = Exception('agent must have a direction') def neighbors_on_position(agent: AgentState, neighbors: List[AgentState]) -> List[AgentState]: - if not agent["position"]: + if not agent['position']: raise pos_error def on_position(neighbor: AgentState) -> bool: - pos = agent["position"] + pos = agent['position'] pos_equal = [pos[ind] == neighbor[ind] for ind in range(len(pos))] return reduce(lambda a, b: (bool(a and b)), pos_equal) - return filter(on_position, neighbors) + return list(filter(on_position, neighbors)) def neighbors_in_radius( @@ -38,28 +35,30 @@ def neighbors_in_radius( neighbors: List[AgentState], max_radius: float = 1, min_radius: float = 0, - distance_function: str = "euclidean", + distance_function: str = 'euclidean', z_axis: bool = False, ) -> List[AgentState]: - if not agent["position"]: + if not agent['position']: raise pos_error func = { - "manhattan": manhattan_distance, - "euclidean": euclidean_distance, - "euclidean_squared": euclidean_squared_distance, - "chebyshev": chebyshev_distance, + 'manhattan': manhattan_distance, + 'euclidean': euclidean_distance, + 'euclidean_squared': euclidean_squared_distance, + 'chebyshev': chebyshev_distance, } def in_radius(neighbor: AgentState) -> bool: + if not neighbor['position']: + return False + pos = agent["position"] - # TODO implement way to ignore z axis - d = func[distance_function](neighbor["position"], pos) + d = func[distance_function](neighbor['position'], pos, z_axis) return (d <= max_radius) and (d >= min_radius) - return filter(in_radius, neighbors) + return list(filter(in_radius, neighbors)) def in_front_planar(agent: AgentState, neighbor: List[AgentState]) -> bool: @@ -82,16 +81,18 @@ def is_linear( n_pos = neighbor['position'] [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] [ax, ay, az] = agent['direction'] - + cross_product = [dy*az - dz*ay, dx*az - dz*ax, dx*ay - dy*ax] - all_zero = reduce(lambda a, b: not bool(a or b), cross_product) + all_zero = all([i == 0 for i in cross_product]) + # all_zero = reduce(lambda a, b: not bool(a or b), cross_product, False) - # if cross_product is 0 + # if cross_product is not 0 if not all_zero: return False + # check if same direction - same_dir = (dx > 0 and ax > 0) or (dy > 0 and ay > 0) or (dz > 0 and az > 0) + same_dir = (ax*dx > 0) or (ay*dy > 0) or (az*dz > 0) return same_dir is front @@ -106,9 +107,9 @@ def neighbors_in_front( raise dir_error if colinear: - return filter(lambda n: is_linear(agent, n, True), neighbors) + return list(filter(lambda n: is_linear(agent, n, True), neighbors)) else: - return filter(lambda n: in_front_planar(agent, n), neighbors) + return list(filter(lambda n: in_front_planar(agent, n), neighbors)) def neighbors_behind( @@ -121,6 +122,6 @@ def neighbors_behind( raise dir_error if colinear: - return filter(lambda n: is_linear(agent, n, False), neighbors) + return list(filter(lambda n: is_linear(agent, n, False), neighbors)) else: - return filter(lambda n: not in_front_planar(agent, n), neighbors) + return list(filter(lambda n: not in_front_planar(agent, n), neighbors)) diff --git a/packages/engine/stdlib/src/py/hstd/spatial.py b/packages/engine/stdlib/src/py/hstd/spatial.py index 71d318682cb..9476ef03d85 100644 --- a/packages/engine/stdlib/src/py/hstd/spatial.py +++ b/packages/engine/stdlib/src/py/hstd/spatial.py @@ -16,29 +16,29 @@ class Topology: z_bounds: Optional[List[float]] = 0 -def manhattan_distance(p1: List[float], p2: List[float]) -> float: +def manhattan_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> float: dx = abs(p1[0] - p2[0]) dy = abs(p1[1] - p2[1]) dz = abs(p1[2] - p2[2]) - return dx + dy + dz + return dx + dy + (dz if z_axis else 0) -def euclidean_squared_distance(p1: List[float], p2: List[float]) -> float: +def euclidean_squared_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> float: dx = p1[0] - p2[0] dy = p1[1] - p2[1] dz = p1[2] - p2[2] - return dx * dx + dy * dy + dz * dz + return dx * dx + dy * dy + (dz * dz if z_axis else 0) -def euclidean_distance(p1: List[float], p2: List[float]) -> float: - return math.sqrt(euclidean_squared_distance(p1, p2)) +def euclidean_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> float: + return math.sqrt(euclidean_squared_distance(p1, p2, z_axis)) -def chebyshev_distance(p1: List[float], p2: List[float]) -> float: +def chebyshev_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> float: dx = abs(p1[0] - p2[0]) dy = abs(p1[1] - p2[1]) dz = abs(p1[2] - p2[2]) - return max(dx, dy, dz) + return max(dx, dy, (dz if z_axis else 0)) def distance_between(a: AgentState, b: AgentState, distance="euclidean") -> Optional[float]: diff --git a/packages/engine/stdlib/src/py/hstd/test_neighbor.py b/packages/engine/stdlib/src/py/hstd/test_neighbor.py new file mode 100644 index 00000000000..432c6ec85bf --- /dev/null +++ b/packages/engine/stdlib/src/py/hstd/test_neighbor.py @@ -0,0 +1,62 @@ +import pytest + +from .agent import AgentState +from .neighbor import neighbors_on_position, neighbors_in_radius, neighbors_in_front, neighbors_behind + +na = AgentState(position=[1, 1, 0], direction=[1, 0, 0]) +nb = AgentState(position=[1, 2, 0], direction=[1, 1, 0]) +nc = AgentState(position=[-1, 1, 0]) +nd = AgentState(position=[1, 1, 0]) +ne = AgentState(position=[2, 3, 0]) +nf = AgentState(position=[3, 2, 0]) +ng = AgentState(position=[6, 6, -1], direction=[1, 0, 0]) +nh = AgentState(position=[6, 9, 0]) +ni = AgentState(position=[4, 9, 0]) +nj = AgentState(position=[3, 2, 2]) +nk = AgentState(position=[3, 1, 0]) +nl = AgentState(position=[1, 0, 0], direction=[1, 1, 1]) +nm = AgentState(position=[0, 1, 0]) +nn = AgentState(position=[0, -1, -1]) + +def same_position_test(): + same_pos = neighbors_on_position(na, [nb, nc, nd, ne, nf]) + assert same_pos == [nd] + +def test_max_radius(): + in_radius = neighbors_in_radius(ng, [na, nb, nc, nd, ne, nf, nh, ni], 3, distance_function='chebyshev') + assert in_radius == [nh, ni] + +def test_max_min_radius(): + in_radius_1 = neighbors_in_radius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 4, 3.5) + assert in_radius_1 == [ni] + + in_radius_2 = neighbors_in_radius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 7, 3.5, 'euclidean', True) + assert in_radius_2 == [nb, nf, ni, nj] + +def test_front(): + in_front_1 = neighbors_in_front(nb, [na, nc, ne, nf, ng, nh, nj]) + assert in_front_1 == [ne, nf, ng, nh, nj] + + in_front_2 = neighbors_in_front(na, [nb, nc, ne, nf, ng, nh, nj, nk, nl], True) + assert in_front_2 == [nk] + + in_front_3 = neighbors_in_front(nl, [na, nb, nc, ne, nf, ng, nh, nj, nk], True) + assert in_front_3 == [nj] + + in_front_4 = neighbors_in_front(nb, [na, nc, ne, nf, ng, nh, nj, nk], True) + assert in_front_4 == [ne] + +def test_behind(): + behind_1 = neighbors_behind(nb, [na, ne, ng, nh, nj, nm]) + assert behind_1 == [na, nm] + + behind_2 = neighbors_behind(nb, [na, ne, ng, nh, nj, nm], True) + assert behind_2 == [nm] + + behind_3 = neighbors_behind(na, [nb, nc, ne, ng, nh, nj, nm], True) + assert behind_3 == [nc, nm] + + behind_4 = neighbors_behind(nl, [na, nb, nc, ne, ng, nh, nj, nm, nn], True) + assert behind_4 == [nn] + + \ No newline at end of file diff --git a/packages/engine/stdlib/src/ts/neighbor.spec.ts b/packages/engine/stdlib/src/ts/neighbor.spec.ts index e122ce5c649..f4808ea0cca 100644 --- a/packages/engine/stdlib/src/ts/neighbor.spec.ts +++ b/packages/engine/stdlib/src/ts/neighbor.spec.ts @@ -2,6 +2,8 @@ import { neighborsBehind, neighborsInFront, neighborsInRadius, neighborsOnPosition } from "./neighbor"; +import { chebyshev_distance } from "./spatial" + /** Neighbor Function tests */ const na = { position: [1, 1, 0], direction: [1, 0, 0] }; const nb = { position: [1, 2, 0], direction: [1, 1, 0] }; @@ -25,7 +27,7 @@ test("find neighbors with same position", () => { }); test("find neighbors within a radius of 3", () => { - expect(neighborsInRadius(ng, [na, nb, nc, nd, ne, nf, nh, ni], 3)).toEqual([ + expect(neighborsInRadius(ng, [na, nb, nc, nd, ne, nf, nh, ni], 3, 0, 'chebyshev')).toEqual([ { position: [6, 9, 0] }, { position: [4, 9, 0] }, ]); @@ -33,12 +35,12 @@ test("find neighbors within a radius of 3", () => { test("find neighbors within a max radius of 4 and min radius of 3", () => { expect( - neighborsInRadius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 4, 3) - ).toEqual([{ position: [3, 2, 0] }, { position: [3, 2, 2] }]); + neighborsInRadius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 4, 3.5) + ).toEqual([ni]); expect( - neighborsInRadius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 4, 3, true) - ).toEqual([{ position: [3, 2, 2] }]); + neighborsInRadius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 7, 3.5, 'euclidean', true) + ).toEqual([nb, nf, ni, nj]); }); test("find neighbors in front of agent tests", () => { diff --git a/packages/engine/stdlib/src/ts/neighbor.ts b/packages/engine/stdlib/src/ts/neighbor.ts index 793414e24d2..259bbeb2501 100644 --- a/packages/engine/stdlib/src/ts/neighbor.ts +++ b/packages/engine/stdlib/src/ts/neighbor.ts @@ -1,5 +1,6 @@ /** Neighbor Functions */ import { PotentialAgent } from "./agent"; +import { manhattan_distance, euclidean_distance, euclidean_squared_distance, chebyshev_distance, Distance } from "./spatial"; const posError = new Error("agent must have a position"); const dirError = new Error("agent must have a direction"); @@ -43,34 +44,27 @@ export function neighborsOnPosition( export function neighborsInRadius( agent: PotentialAgent, neighbors: PotentialAgent[], - max_radius = 1, - min_radius = 0, - z_axis = false + max_radius: number = 1, + min_radius: number = 0, + distanceFunction: Distance = "euclidean", + z_axis: boolean = false ) { - return neighbors.filter((neighbor) => { - const aPos = agent.position; - const nPos = neighbor.position; + const aPos = agent.position; + if (!aPos) { throw posError; } - if (!aPos || !nPos) { - throw posError; - } + const dFunc: { [key in Distance]: Function } = { + "manhattan": manhattan_distance, + "euclidean": euclidean_distance, + "euclidean_sq": euclidean_squared_distance, + "chebyshev": chebyshev_distance, + }; - const notZ: number = z_axis ? 0 : 1; - - for (let i = 0; i < aPos.length - 1 * notZ; i++) { - const max = [aPos[i] + max_radius, aPos[i] - max_radius]; - const min = [aPos[i] + min_radius, aPos[i] - min_radius]; - if ( - !( - (nPos[i] <= max[0] && nPos[i] >= min[0]) || - (nPos[i] >= max[1] && nPos[i] <= min[1]) - ) - ) { - return false; - } - } + return neighbors.filter((neighbor) => { + const nPos = neighbor.position; + if (!nPos) { return false; } - return true; + const d = dFunc[distanceFunction](nPos, aPos, z_axis) + return (d <= max_radius) && (d >= min_radius) }); } @@ -85,7 +79,7 @@ export function neighborsInRadius( export function neighborsInFront( agent: PotentialAgent, neighbors: PotentialAgent[], - colinear = false + colinear: boolean = false ) { return neighbors.filter((neighbor) => { const aPos = agent.position; diff --git a/packages/engine/stdlib/src/ts/spatial.ts b/packages/engine/stdlib/src/ts/spatial.ts index 88e45139ea8..19ae4a2990f 100644 --- a/packages/engine/stdlib/src/ts/spatial.ts +++ b/packages/engine/stdlib/src/ts/spatial.ts @@ -8,6 +8,36 @@ export type Distance = "manhattan" | "euclidean_sq" | "chebyshev"; + +const { abs, pow, max, sqrt } = Math; + +export function manhattan_distance(a_pos: number[], b_pos: number[], z_axis: boolean = true) { + const dx = abs(a_pos[0] - b_pos[0]); + const dy = abs(a_pos[1] - b_pos[1]); + const dz = abs(a_pos[2] - b_pos[2]); + + return dx + dy + (z_axis ? dz : 0); +} + +export function euclidean_squared_distance(a_pos: number[], b_pos: number[], z_axis: boolean = true) { + const dx = pow(a_pos[0] - b_pos[0], 2); + const dy = pow(a_pos[1] - b_pos[1], 2); + const dz = pow(a_pos[2] - b_pos[2], 2); + + return dx + dy + (z_axis ? dz : 0); +} + +export function euclidean_distance(a_pos: number[], b_pos: number[], z_axis: boolean = true) { + return sqrt(euclidean_squared_distance(a_pos, b_pos, z_axis)); +} + +export function chebyshev_distance(a_pos: number[], b_pos: number[], z_axis: boolean = true) { + const dx = abs(a_pos[0] - b_pos[0]); + const dy = abs(a_pos[1] - b_pos[1]); + const dz = abs(a_pos[2] - b_pos[2]); + + return max(dx, dy, (z_axis ? dz : 0)); +} /** * Returns the specified distance between two agents. @@ -27,29 +57,11 @@ export function distanceBetween( [index in Distance]: (a_pos: number[], b_pos: number[]) => number; }; - const { abs, pow, max, sqrt } = Math; - const dFuncs: IdFuncs = { - manhattan: (a_pos: number[], b_pos: number[]) => - abs(a_pos[0] - b_pos[0]) + - abs(a_pos[1] - b_pos[1]) + - abs(a_pos[2] - b_pos[2]), - euclidean: (a_pos: number[], b_pos: number[]) => - sqrt( - pow(a_pos[0] - b_pos[0], 2) + - pow(a_pos[1] - b_pos[1], 2) + - pow(a_pos[2] - b_pos[2], 2) - ), - euclidean_sq: (a_pos: number[], b_pos: number[]) => - pow(a_pos[0] - b_pos[0], 2) + - pow(a_pos[1] - b_pos[1], 2) + - pow(a_pos[2] - b_pos[2], 2), - chebyshev: (a_pos: number[], b_pos: number[]) => - max( - abs(a_pos[0] - b_pos[0]), - abs(a_pos[1] - b_pos[1]), - abs(a_pos[2] - b_pos[2]) - ), + manhattan: manhattan_distance, + euclidean: euclidean_distance, + euclidean_sq: euclidean_squared_distance, + chebyshev: chebyshev_distance, }; const aPos = agentA.position; From 7e36d433141441b827ab498d538422f66baf57de Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Thu, 20 May 2021 15:48:22 -0400 Subject: [PATCH 06/10] Formatting --- packages/engine/stdlib/src/py/hstd/init.py | 2 +- .../engine/stdlib/src/py/hstd/neighbor.py | 49 +++++++++---------- packages/engine/stdlib/src/py/hstd/rand.py | 2 + .../engine/stdlib/src/py/hstd/test_init.py | 44 +++++++++-------- .../stdlib/src/py/hstd/test_neighbor.py | 22 +++++++-- .../engine/stdlib/src/py/hstd/test_rand.py | 3 +- .../engine/stdlib/src/ts/neighbor.spec.ts | 6 +-- packages/engine/stdlib/src/ts/neighbor.ts | 12 ++--- packages/engine/stdlib/src/ts/spatial.ts | 2 +- 9 files changed, 79 insertions(+), 63 deletions(-) diff --git a/packages/engine/stdlib/src/py/hstd/init.py b/packages/engine/stdlib/src/py/hstd/init.py index 06ae8806303..0ae2925e32a 100644 --- a/packages/engine/stdlib/src/py/hstd/init.py +++ b/packages/engine/stdlib/src/py/hstd/init.py @@ -40,7 +40,7 @@ def assign_random_position() -> AgentState: y = random.uniform(0, height) + y_bounds[0] agent = create_agent(template) - agent['position'] = [x, y] + agent["position"] = [x, y] return agent diff --git a/packages/engine/stdlib/src/py/hstd/neighbor.py b/packages/engine/stdlib/src/py/hstd/neighbor.py index 77f9c753cf3..7872a2bf67f 100644 --- a/packages/engine/stdlib/src/py/hstd/neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/neighbor.py @@ -12,17 +12,17 @@ from .agent import AgentState -pos_error = Exception('agent must have a position') -dir_error = Exception('agent must have a direction') +pos_error = Exception("agent must have a position") +dir_error = Exception("agent must have a direction") def neighbors_on_position(agent: AgentState, neighbors: List[AgentState]) -> List[AgentState]: - if not agent['position']: + if not agent["position"]: raise pos_error def on_position(neighbor: AgentState) -> bool: - pos = agent['position'] + pos = agent["position"] pos_equal = [pos[ind] == neighbor[ind] for ind in range(len(pos))] return reduce(lambda a, b: (bool(a and b)), pos_equal) @@ -35,26 +35,26 @@ def neighbors_in_radius( neighbors: List[AgentState], max_radius: float = 1, min_radius: float = 0, - distance_function: str = 'euclidean', + distance_function: str = "euclidean", z_axis: bool = False, ) -> List[AgentState]: - if not agent['position']: + if not agent["position"]: raise pos_error func = { - 'manhattan': manhattan_distance, - 'euclidean': euclidean_distance, - 'euclidean_squared': euclidean_squared_distance, - 'chebyshev': chebyshev_distance, + "manhattan": manhattan_distance, + "euclidean": euclidean_distance, + "euclidean_squared": euclidean_squared_distance, + "chebyshev": chebyshev_distance, } def in_radius(neighbor: AgentState) -> bool: - if not neighbor['position']: + if not neighbor["position"]: return False pos = agent["position"] - d = func[distance_function](neighbor['position'], pos, z_axis) + d = func[distance_function](neighbor["position"], pos, z_axis) return (d <= max_radius) and (d >= min_radius) @@ -63,9 +63,9 @@ def in_radius(neighbor: AgentState) -> bool: def in_front_planar(agent: AgentState, neighbor: List[AgentState]) -> bool: - a_pos = agent['position'] - n_pos = neighbor['position'] - a_dir = agent['direction'] + a_pos = agent["position"] + n_pos = neighbor["position"] + a_dir = agent["direction"] [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] D = a_dir[0] * dx + a_dir[1] * dy + a_dir[2] * dz @@ -73,16 +73,14 @@ def in_front_planar(agent: AgentState, neighbor: List[AgentState]) -> bool: return D > 0 -def is_linear( - agent: AgentState, neighbor: AgentState, front: bool -) -> bool: +def is_linear(agent: AgentState, neighbor: AgentState, front: bool) -> bool: - a_pos = agent['position'] - n_pos = neighbor['position'] + a_pos = agent["position"] + n_pos = neighbor["position"] [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] - [ax, ay, az] = agent['direction'] - - cross_product = [dy*az - dz*ay, dx*az - dz*ax, dx*ay - dy*ax] + [ax, ay, az] = agent["direction"] + + cross_product = [dy * az - dz * ay, dx * az - dz * ax, dx * ay - dy * ax] all_zero = all([i == 0 for i in cross_product]) # all_zero = reduce(lambda a, b: not bool(a or b), cross_product, False) @@ -90,10 +88,9 @@ def is_linear( if not all_zero: return False - # check if same direction - same_dir = (ax*dx > 0) or (ay*dy > 0) or (az*dz > 0) - + same_dir = (ax * dx > 0) or (ay * dy > 0) or (az * dz > 0) + return same_dir is front diff --git a/packages/engine/stdlib/src/py/hstd/rand.py b/packages/engine/stdlib/src/py/hstd/rand.py index 3f38d41979c..835b5c18e19 100644 --- a/packages/engine/stdlib/src/py/hstd/rand.py +++ b/packages/engine/stdlib/src/py/hstd/rand.py @@ -4,8 +4,10 @@ import random as rand + def set_seed(s: str): rand.seed(s) + def random(): return rand.random() diff --git a/packages/engine/stdlib/src/py/hstd/test_init.py b/packages/engine/stdlib/src/py/hstd/test_init.py index 2a4410d9817..ac93c0e2a7a 100644 --- a/packages/engine/stdlib/src/py/hstd/test_init.py +++ b/packages/engine/stdlib/src/py/hstd/test_init.py @@ -6,64 +6,68 @@ init_topology = Topology([0, 2], [0, 2]) -agent = AgentState(agent_name='test') -agent_function = lambda: AgentState(agent_name='test') +agent = AgentState(agent_name="test") +agent_function = lambda: AgentState(agent_name="test") num_agents = 4 scatter_agents = scatter(num_agents, init_topology, agent) scatter_agents_function = scatter(num_agents, init_topology, agent_function) -''' Test scatter function ''' +""" Test scatter function """ + + def test_scatter(): assert len(scatter_agents) == num_agents assert len(scatter_agents_function) == num_agents def subtest(a): - assert a['position'][0] >= init_topology.x_bounds[0] - assert a['position'][0] <= init_topology.x_bounds[1] - assert a['position'][1] >= init_topology.y_bounds[0] - assert a['position'][1] <= init_topology.y_bounds[1] + assert a["position"][0] >= init_topology.x_bounds[0] + assert a["position"][0] <= init_topology.x_bounds[1] + assert a["position"][1] >= init_topology.y_bounds[0] + assert a["position"][1] <= init_topology.y_bounds[1] - assert a['agent_name'] == 'test' + assert a["agent_name"] == "test" [subtest(agent) for agent in scatter_agents] [subtest(agent) for agent in scatter_agents_function] -''' Test stack function ''' +""" Test stack function """ stack_agents = stack(num_agents, agent) stack_agents_function = stack(num_agents, agent_function) + def test_stack(): assert len(stack_agents) == num_agents assert len(stack_agents_function) == num_agents def subtest(a): - assert a['agent_name'] == 'test' - + assert a["agent_name"] == "test" + [subtest(agent) for agent in scatter_agents] [subtest(agent) for agent in scatter_agents_function] -''' Test grid function ''' +""" Test grid function """ grid_agents = grid(init_topology, agent) grid_agents_function = grid(init_topology, agent) + def test_grid(): assert len(grid_agents) == num_agents assert len(grid_agents_function) == num_agents def subtest(a): - assert a['position'][0] >= init_topology.x_bounds[0] - assert a['position'][0] <= init_topology.x_bounds[1] - assert a['position'][1] >= init_topology.y_bounds[0] - assert a['position'][1] <= init_topology.y_bounds[1] + assert a["position"][0] >= init_topology.x_bounds[0] + assert a["position"][0] <= init_topology.x_bounds[1] + assert a["position"][1] >= init_topology.y_bounds[0] + assert a["position"][1] <= init_topology.y_bounds[1] - assert int(a['position'][0]) == a['position'][0] - assert int(a['position'][1]) == a['position'][1] + assert int(a["position"][0]) == a["position"][0] + assert int(a["position"][1]) == a["position"][1] - assert a['agent_name'] == 'test' + assert a["agent_name"] == "test" [subtest(agent) for agent in grid_agents] - [subtest(agent) for agent in grid_agents_function] \ No newline at end of file + [subtest(agent) for agent in grid_agents_function] diff --git a/packages/engine/stdlib/src/py/hstd/test_neighbor.py b/packages/engine/stdlib/src/py/hstd/test_neighbor.py index 432c6ec85bf..b2c868de276 100644 --- a/packages/engine/stdlib/src/py/hstd/test_neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/test_neighbor.py @@ -1,7 +1,12 @@ import pytest from .agent import AgentState -from .neighbor import neighbors_on_position, neighbors_in_radius, neighbors_in_front, neighbors_behind +from .neighbor import ( + neighbors_on_position, + neighbors_in_radius, + neighbors_in_front, + neighbors_behind, +) na = AgentState(position=[1, 1, 0], direction=[1, 0, 0]) nb = AgentState(position=[1, 2, 0], direction=[1, 1, 0]) @@ -18,21 +23,29 @@ nm = AgentState(position=[0, 1, 0]) nn = AgentState(position=[0, -1, -1]) + def same_position_test(): same_pos = neighbors_on_position(na, [nb, nc, nd, ne, nf]) assert same_pos == [nd] + def test_max_radius(): - in_radius = neighbors_in_radius(ng, [na, nb, nc, nd, ne, nf, nh, ni], 3, distance_function='chebyshev') + in_radius = neighbors_in_radius( + ng, [na, nb, nc, nd, ne, nf, nh, ni], 3, distance_function="chebyshev" + ) assert in_radius == [nh, ni] + def test_max_min_radius(): in_radius_1 = neighbors_in_radius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 4, 3.5) assert in_radius_1 == [ni] - in_radius_2 = neighbors_in_radius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 7, 3.5, 'euclidean', True) + in_radius_2 = neighbors_in_radius( + ng, [na, nb, nc, nd, nf, nh, ni, nj], 7, 3.5, "euclidean", True + ) assert in_radius_2 == [nb, nf, ni, nj] + def test_front(): in_front_1 = neighbors_in_front(nb, [na, nc, ne, nf, ng, nh, nj]) assert in_front_1 == [ne, nf, ng, nh, nj] @@ -46,6 +59,7 @@ def test_front(): in_front_4 = neighbors_in_front(nb, [na, nc, ne, nf, ng, nh, nj, nk], True) assert in_front_4 == [ne] + def test_behind(): behind_1 = neighbors_behind(nb, [na, ne, ng, nh, nj, nm]) assert behind_1 == [na, nm] @@ -58,5 +72,3 @@ def test_behind(): behind_4 = neighbors_behind(nl, [na, nb, nc, ne, ng, nh, nj, nm, nn], True) assert behind_4 == [nn] - - \ No newline at end of file diff --git a/packages/engine/stdlib/src/py/hstd/test_rand.py b/packages/engine/stdlib/src/py/hstd/test_rand.py index 0332fcace61..cec2873b492 100644 --- a/packages/engine/stdlib/src/py/hstd/test_rand.py +++ b/packages/engine/stdlib/src/py/hstd/test_rand.py @@ -2,6 +2,7 @@ from .rand import set_seed, random + def test_random(): n = random() nn = random() @@ -11,4 +12,4 @@ def test_random(): n = random() set_seed("test") nn = random() - assert n == pytest.approx(nn) \ No newline at end of file + assert n == pytest.approx(nn) diff --git a/packages/engine/stdlib/src/ts/neighbor.spec.ts b/packages/engine/stdlib/src/ts/neighbor.spec.ts index f4808ea0cca..5a8c9cb32d5 100644 --- a/packages/engine/stdlib/src/ts/neighbor.spec.ts +++ b/packages/engine/stdlib/src/ts/neighbor.spec.ts @@ -2,7 +2,7 @@ import { neighborsBehind, neighborsInFront, neighborsInRadius, neighborsOnPosition } from "./neighbor"; -import { chebyshev_distance } from "./spatial" +import { chebyshev_distance } from "./spatial"; /** Neighbor Function tests */ const na = { position: [1, 1, 0], direction: [1, 0, 0] }; @@ -27,7 +27,7 @@ test("find neighbors with same position", () => { }); test("find neighbors within a radius of 3", () => { - expect(neighborsInRadius(ng, [na, nb, nc, nd, ne, nf, nh, ni], 3, 0, 'chebyshev')).toEqual([ + expect(neighborsInRadius(ng, [na, nb, nc, nd, ne, nf, nh, ni], 3, 0, "chebyshev")).toEqual([ { position: [6, 9, 0] }, { position: [4, 9, 0] }, ]); @@ -39,7 +39,7 @@ test("find neighbors within a max radius of 4 and min radius of 3", () => { ).toEqual([ni]); expect( - neighborsInRadius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 7, 3.5, 'euclidean', true) + neighborsInRadius(ng, [na, nb, nc, nd, nf, nh, ni, nj], 7, 3.5, "euclidean", true) ).toEqual([nb, nf, ni, nj]); }); diff --git a/packages/engine/stdlib/src/ts/neighbor.ts b/packages/engine/stdlib/src/ts/neighbor.ts index 259bbeb2501..bd95f561ef7 100644 --- a/packages/engine/stdlib/src/ts/neighbor.ts +++ b/packages/engine/stdlib/src/ts/neighbor.ts @@ -53,18 +53,18 @@ export function neighborsInRadius( if (!aPos) { throw posError; } const dFunc: { [key in Distance]: Function } = { - "manhattan": manhattan_distance, - "euclidean": euclidean_distance, - "euclidean_sq": euclidean_squared_distance, - "chebyshev": chebyshev_distance, + manhattan: manhattan_distance, + euclidean: euclidean_distance, + euclidean_sq: euclidean_squared_distance, + chebyshev: chebyshev_distance, }; return neighbors.filter((neighbor) => { const nPos = neighbor.position; if (!nPos) { return false; } - const d = dFunc[distanceFunction](nPos, aPos, z_axis) - return (d <= max_radius) && (d >= min_radius) + const d = dFunc[distanceFunction](nPos, aPos, z_axis); + return (d <= max_radius) && (d >= min_radius); }); } diff --git a/packages/engine/stdlib/src/ts/spatial.ts b/packages/engine/stdlib/src/ts/spatial.ts index 19ae4a2990f..474e4385f68 100644 --- a/packages/engine/stdlib/src/ts/spatial.ts +++ b/packages/engine/stdlib/src/ts/spatial.ts @@ -8,7 +8,7 @@ export type Distance = "manhattan" | "euclidean_sq" | "chebyshev"; - + const { abs, pow, max, sqrt } = Math; export function manhattan_distance(a_pos: number[], b_pos: number[], z_axis: boolean = true) { From 864c366fbd41a18cf654c6c3aedaf8d2882808ad Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Thu, 20 May 2021 15:53:14 -0400 Subject: [PATCH 07/10] Typo fixes --- packages/engine/stdlib/src/py/hstd/spatial.py | 2 +- packages/engine/stdlib/src/ts/neighbor.spec.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/engine/stdlib/src/py/hstd/spatial.py b/packages/engine/stdlib/src/py/hstd/spatial.py index 9476ef03d85..a56f950be9b 100644 --- a/packages/engine/stdlib/src/py/hstd/spatial.py +++ b/packages/engine/stdlib/src/py/hstd/spatial.py @@ -13,7 +13,7 @@ class Topology: x_bounds: List[float] y_bounds: List[float] - z_bounds: Optional[List[float]] = 0 + z_bounds: Optional[List[float]] = [0, 0] def manhattan_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> float: diff --git a/packages/engine/stdlib/src/ts/neighbor.spec.ts b/packages/engine/stdlib/src/ts/neighbor.spec.ts index 5a8c9cb32d5..89cf5cb8164 100644 --- a/packages/engine/stdlib/src/ts/neighbor.spec.ts +++ b/packages/engine/stdlib/src/ts/neighbor.spec.ts @@ -2,7 +2,6 @@ import { neighborsBehind, neighborsInFront, neighborsInRadius, neighborsOnPosition } from "./neighbor"; -import { chebyshev_distance } from "./spatial"; /** Neighbor Function tests */ const na = { position: [1, 1, 0], direction: [1, 0, 0] }; From 243b407834cb34103bee948e203be0128f21240c Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Fri, 21 May 2021 10:13:06 -0400 Subject: [PATCH 08/10] Docstrings and fixes from review --- packages/engine/stdlib/src/py/hstd/context.py | 9 ++ packages/engine/stdlib/src/py/hstd/init.py | 54 ++++++--- .../engine/stdlib/src/py/hstd/neighbor.py | 108 ++++++++++-------- packages/engine/stdlib/src/py/hstd/rand.py | 2 + packages/engine/stdlib/src/py/hstd/spatial.py | 21 ++-- .../engine/stdlib/src/py/hstd/test_init.py | 32 ++---- .../stdlib/src/py/hstd/test_neighbor.py | 2 - .../engine/stdlib/src/py/hstd/test_rand.py | 1 + .../engine/stdlib/src/py/hstd/test_spatial.py | 7 +- .../engine/stdlib/src/ts/neighbor.spec.ts | 1 - packages/engine/stdlib/src/ts/neighbor.ts | 12 +- packages/engine/stdlib/src/ts/spatial.ts | 31 +++-- 12 files changed, 151 insertions(+), 129 deletions(-) create mode 100644 packages/engine/stdlib/src/py/hstd/context.py diff --git a/packages/engine/stdlib/src/py/hstd/context.py b/packages/engine/stdlib/src/py/hstd/context.py new file mode 100644 index 00000000000..fcb5d154c19 --- /dev/null +++ b/packages/engine/stdlib/src/py/hstd/context.py @@ -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]) diff --git a/packages/engine/stdlib/src/py/hstd/init.py b/packages/engine/stdlib/src/py/hstd/init.py index 0ae2925e32a..2ee9c1ef70d 100644 --- a/packages/engine/stdlib/src/py/hstd/init.py +++ b/packages/engine/stdlib/src/py/hstd/init.py @@ -4,22 +4,15 @@ import math import random from copy import deepcopy -from dataclasses import dataclass -from typing import Optional, List, Union, Callable, Mapping +from typing import Dict, List, Union, Callable, Mapping from .agent import AgentState +from .context import Topology AgentFunction = Callable[[], AgentState] AgentTemplate = Union[AgentState, AgentFunction] -@dataclass -class Topology: - x_bounds: List[float] - y_bounds: List[float] - z_bounds: Optional[List[float]] - - def create_agent(template: AgentTemplate) -> AgentState: if callable(template): return template() @@ -28,7 +21,15 @@ def create_agent(template: AgentTemplate) -> AgentState: def scatter(count: int, topology: Topology, template: AgentTemplate) -> List[AgentState]: - + """ + 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 @@ -50,12 +51,26 @@ def assign_random_position() -> AgentState: 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 @@ -72,7 +87,7 @@ def assign_grid_position(ind: int) -> AgentState: return agent - agents = [assign_grid_position(i) for i in range(count)] + agents = [assign_grid_position(i) for i in range(int(count))] return agents @@ -80,9 +95,18 @@ def assign_grid_position(ind: int) -> AgentState: 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 = {} + agents: Dict[str, List[AgentState]] = {} for pos_y, row in enumerate(layout): for pos_x, template_type in enumerate(row): @@ -90,15 +114,15 @@ def create_layout( if template_type not in agents: agents[template_type] = [] - agent_name = (templates[template_type].agent_name or template_type) + len( - 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() + agents[template_type].append(agent) agent_list = [agent for sublist in agents.values() for agent in sublist] diff --git a/packages/engine/stdlib/src/py/hstd/neighbor.py b/packages/engine/stdlib/src/py/hstd/neighbor.py index 7872a2bf67f..97094420273 100644 --- a/packages/engine/stdlib/src/py/hstd/neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/neighbor.py @@ -3,31 +3,19 @@ """ from typing import List from functools import reduce -from .spatial import ( - manhattan_distance, - euclidean_squared_distance, - euclidean_distance, - chebyshev_distance, -) -from .agent import AgentState - -pos_error = Exception("agent must have a position") -dir_error = Exception("agent must have a direction") +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 not agent["position"]: - raise pos_error - - def on_position(neighbor: AgentState) -> bool: - pos = agent["position"] - pos_equal = [pos[ind] == neighbor[ind] for ind in range(len(pos))] + AgentFieldError(agent.agent_id, "position", "cannot be None") - return reduce(lambda a, b: (bool(a and b)), pos_equal) - - return list(filter(on_position, neighbors)) + return [n for n in neighbors if n.position == agent.position] def neighbors_in_radius( @@ -38,54 +26,66 @@ def neighbors_in_radius( 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 not agent["position"]: - raise pos_error - - func = { - "manhattan": manhattan_distance, - "euclidean": euclidean_distance, - "euclidean_squared": euclidean_squared_distance, - "chebyshev": chebyshev_distance, - } + raise AgentFieldError(agent.agent_id, "position", "cannot be None") def in_radius(neighbor: AgentState) -> bool: if not neighbor["position"]: return False - pos = agent["position"] - d = func[distance_function](neighbor["position"], pos, z_axis) + d = distance_between(neighbor, agent, distance_function, z_axis) + if d is None: + return False return (d <= max_radius) and (d >= min_radius) - return list(filter(in_radius, neighbors)) + 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: List[AgentState]) -> bool: - a_pos = agent["position"] - n_pos = neighbor["position"] +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"] - [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] + [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: - - a_pos = agent["position"] - n_pos = neighbor["position"] - [dx, dy, dz] = [n_pos[ind] - a_pos[ind] for ind in range(3)] + """ + 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] - all_zero = all([i == 0 for i in cross_product]) - # all_zero = reduce(lambda a, b: not bool(a or b), cross_product, False) - # if cross_product is not 0 - if not all_zero: + if cross_product != [0, 0, 0]: return False # check if same direction @@ -97,28 +97,36 @@ def is_linear(agent: AgentState, neighbor: AgentState, front: bool) -> bool: 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 not agent["position"]: - raise pos_error + raise AgentFieldError(agent.agent_id, "position", "cannot be None") if not agent["direction"]: - raise dir_error + raise AgentFieldError(agent.agent_id, "direction", "cannot be None") if colinear: - return list(filter(lambda n: is_linear(agent, n, True), neighbors)) + return [n for n in neighbors if is_linear(agent, n, True)] else: - return list(filter(lambda n: in_front_planar(agent, n), neighbors)) + 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 not agent["position"]: - raise pos_error + raise AgentFieldError(agent.agent_id, "position", "cannot be None") if not agent["direction"]: - raise dir_error + raise AgentFieldError(agent.agent_id, "direction", "cannot be None") if colinear: - return list(filter(lambda n: is_linear(agent, n, False), neighbors)) + return [n for n in neighbors if is_linear(agent, n, False)] else: - return list(filter(lambda n: not in_front_planar(agent, n), neighbors)) + return [n for n in neighbors if not in_front_planar(agent, n)] diff --git a/packages/engine/stdlib/src/py/hstd/rand.py b/packages/engine/stdlib/src/py/hstd/rand.py index 835b5c18e19..ac388d18ac9 100644 --- a/packages/engine/stdlib/src/py/hstd/rand.py +++ b/packages/engine/stdlib/src/py/hstd/rand.py @@ -6,8 +6,10 @@ 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() diff --git a/packages/engine/stdlib/src/py/hstd/spatial.py b/packages/engine/stdlib/src/py/hstd/spatial.py index a56f950be9b..dc40110c7ea 100644 --- a/packages/engine/stdlib/src/py/hstd/spatial.py +++ b/packages/engine/stdlib/src/py/hstd/spatial.py @@ -3,17 +3,10 @@ """ import math import random -from dataclasses import dataclass from typing import Optional, List from .agent import AgentState, AgentFieldError - - -@dataclass -class Topology: - x_bounds: List[float] - y_bounds: List[float] - z_bounds: Optional[List[float]] = [0, 0] +from .context import Topology def manhattan_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> float: @@ -41,7 +34,9 @@ def chebyshev_distance(p1: List[float], p2: List[float], z_axis: bool = True) -> return max(dx, dy, (dz if z_axis else 0)) -def distance_between(a: AgentState, b: AgentState, distance="euclidean") -> Optional[float]: +def distance_between( + a: AgentState, b: AgentState, distance="euclidean", z_axis=True +) -> Optional[float]: """ Returns the specified distance between two agents. The parameter `distance` must be one of 'euclidean', 'euclidean_sq', 'manhattan' or 'chebyshev'. @@ -52,13 +47,13 @@ def distance_between(a: AgentState, b: AgentState, distance="euclidean") -> Opti raise AgentFieldError(b.agent_id, "position", "cannot be None") if distance == "euclidean": - return euclidean_distance(a.position, b.position) + return euclidean_distance(a.position, b.position, z_axis) elif distance == "euclidean_sq": - return euclidean_squared_distance(a.position, b.position) + return euclidean_squared_distance(a.position, b.position, z_axis) elif distance == "manhattan": - return manhattan_distance(a.position, b.position) + return manhattan_distance(a.position, b.position, z_axis) elif distance == "chebyshev": - return chebyshev_distance(a.position, b.position) + return chebyshev_distance(a.position, b.position, z_axis) raise ValueError( "distance must be one of 'euclidean', 'euclidean_sq', 'manhattan' or 'chebyshev'" diff --git a/packages/engine/stdlib/src/py/hstd/test_init.py b/packages/engine/stdlib/src/py/hstd/test_init.py index ac93c0e2a7a..888ca2f35f8 100644 --- a/packages/engine/stdlib/src/py/hstd/test_init.py +++ b/packages/engine/stdlib/src/py/hstd/test_init.py @@ -1,23 +1,19 @@ -import pytest - from .agent import AgentState from .spatial import Topology from .init import scatter, grid, stack, create_layout -init_topology = Topology([0, 2], [0, 2]) +init_topology = Topology([0, 2], [0, 2], []) agent = AgentState(agent_name="test") agent_function = lambda: AgentState(agent_name="test") num_agents = 4 -scatter_agents = scatter(num_agents, init_topology, agent) -scatter_agents_function = scatter(num_agents, init_topology, agent_function) - -""" Test scatter function """ - def test_scatter(): + scatter_agents = scatter(num_agents, init_topology, agent) + scatter_agents_function = scatter(num_agents, init_topology, agent_function) + assert len(scatter_agents) == num_agents assert len(scatter_agents_function) == num_agents @@ -33,28 +29,24 @@ def subtest(a): [subtest(agent) for agent in scatter_agents_function] -""" Test stack function """ -stack_agents = stack(num_agents, agent) -stack_agents_function = stack(num_agents, agent_function) - - def test_stack(): + stack_agents = stack(num_agents, agent) + stack_agents_function = stack(num_agents, agent_function) + assert len(stack_agents) == num_agents assert len(stack_agents_function) == num_agents def subtest(a): assert a["agent_name"] == "test" - [subtest(agent) for agent in scatter_agents] - [subtest(agent) for agent in scatter_agents_function] - - -""" Test grid function """ -grid_agents = grid(init_topology, agent) -grid_agents_function = grid(init_topology, agent) + [subtest(agent) for agent in stack_agents] + [subtest(agent) for agent in stack_agents_function] def test_grid(): + grid_agents = grid(init_topology, agent) + grid_agents_function = grid(init_topology, agent) + assert len(grid_agents) == num_agents assert len(grid_agents_function) == num_agents diff --git a/packages/engine/stdlib/src/py/hstd/test_neighbor.py b/packages/engine/stdlib/src/py/hstd/test_neighbor.py index b2c868de276..a3d48197115 100644 --- a/packages/engine/stdlib/src/py/hstd/test_neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/test_neighbor.py @@ -1,5 +1,3 @@ -import pytest - from .agent import AgentState from .neighbor import ( neighbors_on_position, diff --git a/packages/engine/stdlib/src/py/hstd/test_rand.py b/packages/engine/stdlib/src/py/hstd/test_rand.py index cec2873b492..2e7dedc5e97 100644 --- a/packages/engine/stdlib/src/py/hstd/test_rand.py +++ b/packages/engine/stdlib/src/py/hstd/test_rand.py @@ -1,3 +1,4 @@ +# type: ignore import pytest from .rand import set_seed, random diff --git a/packages/engine/stdlib/src/py/hstd/test_spatial.py b/packages/engine/stdlib/src/py/hstd/test_spatial.py index 33faf481b1e..39dc85b0ebc 100644 --- a/packages/engine/stdlib/src/py/hstd/test_spatial.py +++ b/packages/engine/stdlib/src/py/hstd/test_spatial.py @@ -1,3 +1,4 @@ +# type: ignore import pytest from .agent import AgentState @@ -36,8 +37,10 @@ def test_chebyshev_distance_between_tests(): def test_normalize_direction(): - assert normalize_vector(a.direction) == [0.7071067811865475, 0.7071067811865475] - assert normalize_vector(b.direction) == [0.31622776601683794, 0.9486832980505138] + if a.direction: + assert normalize_vector(a.direction) == [0.7071067811865475, 0.7071067811865475] + if b.direction: + assert normalize_vector(b.direction) == [0.31622776601683794, 0.9486832980505138] def test_random_position(): diff --git a/packages/engine/stdlib/src/ts/neighbor.spec.ts b/packages/engine/stdlib/src/ts/neighbor.spec.ts index 89cf5cb8164..bb0d84053cb 100644 --- a/packages/engine/stdlib/src/ts/neighbor.spec.ts +++ b/packages/engine/stdlib/src/ts/neighbor.spec.ts @@ -2,7 +2,6 @@ import { neighborsBehind, neighborsInFront, neighborsInRadius, neighborsOnPosition } from "./neighbor"; - /** Neighbor Function tests */ const na = { position: [1, 1, 0], direction: [1, 0, 0] }; const nb = { position: [1, 2, 0], direction: [1, 1, 0] }; diff --git a/packages/engine/stdlib/src/ts/neighbor.ts b/packages/engine/stdlib/src/ts/neighbor.ts index bd95f561ef7..f4b0b5a8c4c 100644 --- a/packages/engine/stdlib/src/ts/neighbor.ts +++ b/packages/engine/stdlib/src/ts/neighbor.ts @@ -1,6 +1,6 @@ /** Neighbor Functions */ import { PotentialAgent } from "./agent"; -import { manhattan_distance, euclidean_distance, euclidean_squared_distance, chebyshev_distance, Distance } from "./spatial"; +import { Distance, distanceBetween } from "./spatial"; const posError = new Error("agent must have a position"); const dirError = new Error("agent must have a direction"); @@ -39,6 +39,7 @@ export function neighborsOnPosition( * @param neighbors - context.neighbors() array, or an array of agents * @param max_radius - defaults to 1 * @param min_radius - defaults to 0 + * @param distanceFunction - defaults to "euclidean" * @param z_axis - defaults to false */ export function neighborsInRadius( @@ -52,18 +53,11 @@ export function neighborsInRadius( const aPos = agent.position; if (!aPos) { throw posError; } - const dFunc: { [key in Distance]: Function } = { - manhattan: manhattan_distance, - euclidean: euclidean_distance, - euclidean_sq: euclidean_squared_distance, - chebyshev: chebyshev_distance, - }; - return neighbors.filter((neighbor) => { const nPos = neighbor.position; if (!nPos) { return false; } - const d = dFunc[distanceFunction](nPos, aPos, z_axis); + const d = distanceBetween(neighbor, agent, distanceFunction, z_axis); return (d <= max_radius) && (d >= min_radius); }); } diff --git a/packages/engine/stdlib/src/ts/spatial.ts b/packages/engine/stdlib/src/ts/spatial.ts index 474e4385f68..0dc45b1d7a6 100644 --- a/packages/engine/stdlib/src/ts/spatial.ts +++ b/packages/engine/stdlib/src/ts/spatial.ts @@ -50,31 +50,28 @@ export function chebyshev_distance(a_pos: number[], b_pos: number[], z_axis: boo export function distanceBetween( agentA: PotentialAgent, agentB: PotentialAgent, - distance: Distance = "euclidean" + distance: Distance = "euclidean", + zAxis: boolean = true ) { - type IdFuncs = { - // eslint-disable-next-line no-unused-vars - [index in Distance]: (a_pos: number[], b_pos: number[]) => number; - }; - - const dFuncs: IdFuncs = { - manhattan: manhattan_distance, - euclidean: euclidean_distance, - euclidean_sq: euclidean_squared_distance, - chebyshev: chebyshev_distance, - }; - const aPos = agentA.position; const bPos = agentB.position; if (!aPos || !bPos) { throw posError; } - if (!dFuncs[distance]) { - throw new Error("distance must be one of 'euclidean', 'manhattan', 'euclidean_sq' or 'chebyshev'"); - } - return dFuncs[distance](aPos, bPos); + switch (distance) { + case "manhattan": + return manhattan_distance(aPos, bPos, zAxis); + case "euclidean": + return euclidean_distance(aPos, bPos, zAxis); + case "euclidean_sq": + return euclidean_squared_distance(aPos, bPos, zAxis); + case "chebyshev": + return chebyshev_distance(aPos, bPos, zAxis); + default: + throw new Error("distance must be one of 'euclidean', 'manhattan', 'euclidean_sq' or 'chebyshev'"); + } } /** From 71f3c68c9342196803c532f3e537b2f4ac325147 Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Fri, 21 May 2021 10:17:55 -0400 Subject: [PATCH 09/10] Formatted and finalized --- packages/engine/stdlib/src/py/hstd/init.py | 1 + packages/engine/stdlib/src/py/hstd/neighbor.py | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/engine/stdlib/src/py/hstd/init.py b/packages/engine/stdlib/src/py/hstd/init.py index 2ee9c1ef70d..55f73b0e631 100644 --- a/packages/engine/stdlib/src/py/hstd/init.py +++ b/packages/engine/stdlib/src/py/hstd/init.py @@ -9,6 +9,7 @@ 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] diff --git a/packages/engine/stdlib/src/py/hstd/neighbor.py b/packages/engine/stdlib/src/py/hstd/neighbor.py index 97094420273..8734a73f8e5 100644 --- a/packages/engine/stdlib/src/py/hstd/neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/neighbor.py @@ -12,7 +12,7 @@ def neighbors_on_position(agent: AgentState, neighbors: List[AgentState]) -> Lis """ Returns all `neighbors` whose position is identical to the `agent`. """ - if not agent["position"]: + if agent.position is None: AgentFieldError(agent.agent_id, "position", "cannot be None") return [n for n in neighbors if n.position == agent.position] @@ -39,11 +39,11 @@ def neighbors_in_radius( z_axis: include z-axis in distance calculations """ - if not agent["position"]: + if agent.position is None: raise AgentFieldError(agent.agent_id, "position", "cannot be None") def in_radius(neighbor: AgentState) -> bool: - if not neighbor["position"]: + if neighbor.position is None: return False d = distance_between(neighbor, agent, distance_function, z_axis) @@ -102,9 +102,9 @@ def neighbors_in_front( check that the neighbor lies along the agent's direction vector. """ - if not agent["position"]: + if agent.position is None: raise AgentFieldError(agent.agent_id, "position", "cannot be None") - if not agent["direction"]: + if agent.direction is None: raise AgentFieldError(agent.agent_id, "direction", "cannot be None") if colinear: @@ -121,9 +121,9 @@ def neighbors_behind( check that the neighbor lies along the agent's direction vector. """ - if not agent["position"]: + if agent.position is None: raise AgentFieldError(agent.agent_id, "position", "cannot be None") - if not agent["direction"]: + if agent.direction is None: raise AgentFieldError(agent.agent_id, "direction", "cannot be None") if colinear: From 0d61c59ef2f3bd21e1863165a49ae749344c5574 Mon Sep 17 00:00:00 2001 From: Nur Shlapobersky Date: Fri, 21 May 2021 10:56:10 -0400 Subject: [PATCH 10/10] last fixes --- packages/engine/stdlib/src/py/hstd/neighbor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/engine/stdlib/src/py/hstd/neighbor.py b/packages/engine/stdlib/src/py/hstd/neighbor.py index 8734a73f8e5..db64979e60b 100644 --- a/packages/engine/stdlib/src/py/hstd/neighbor.py +++ b/packages/engine/stdlib/src/py/hstd/neighbor.py @@ -2,7 +2,6 @@ Neighbor utility functions. """ from typing import List -from functools import reduce from .spatial import distance_between from .agent import AgentFieldError, AgentState @@ -13,7 +12,7 @@ def neighbors_on_position(agent: AgentState, neighbors: List[AgentState]) -> Lis Returns all `neighbors` whose position is identical to the `agent`. """ if agent.position is None: - AgentFieldError(agent.agent_id, "position", "cannot be None") + raise AgentFieldError(agent.agent_id, "position", "cannot be None") return [n for n in neighbors if n.position == agent.position]