def test_obj_exists_handles_out_of_grid_positions(data: st.DataObject) -> None: """ Make sure the correct error is raised. """ raised_index_error = False raised_value_error = False index_error = None value_error = None env = data.draw(bst.envs()) obj_type_id = data.draw(bst.obj_type_ids(env=env)) pos_indices = st.integers(min_value=-100, max_value=100) pos_strategy = st.tuples(pos_indices, pos_indices) pos: Tuple[int, int] = data.draw(pos_strategy) # type: ignore try: existence = env._obj_exists(obj_type_id, pos) except IndexError as err: raised_index_error = True index_error = err except ValueError as err: raised_value_error = True value_error = err # If pos in in the grid, should raise no errors. if 0 <= pos[0] < env.width and 0 <= pos[1] < env.height: try: assert not raised_index_error except AssertionError: raise index_error # type: ignore # If negative, should catch and raise a ValueError. elif pos[0] < 0 or pos[1] < 0: assert raised_value_error # Otherwise, currently raises an IndexError. # TODO: Add an error message for this? else: assert raised_index_error
def test_move_correctly_modifies_agent_state(data: st.DataObject) -> None: """ Makes sure they actually move or STAY. """ # TODO: Handle out-of-bounds errors. # TODO: Consider making the environment toroidal. env = data.draw(bst.envs()) env.reset() old_locations: Dict[int, Tuple[int, int]] = {} for agent_id, agent in env.agents.items(): old_locations[agent_id] = agent.pos tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) executed_dict = env._move(tuple_action_dict) # TODO: Consider making ``env.LEFT``, etc tuples which can be added to existing # positions rather than just integers. for agent_id, action in executed_dict.items(): agent = env.agents[agent_id] move = action[0] old_pos = old_locations[agent_id] if move == env.UP or move == env.DOWN: assert agent.pos[0] == old_pos[0] if move == env.LEFT or move == env.RIGHT: assert agent.pos[1] == old_pos[1] if move == env.UP: assert agent.pos[1] == old_pos[1] + 1 if move == env.DOWN: assert agent.pos[1] == old_pos[1] - 1 if move == env.RIGHT: assert agent.pos[0] == old_pos[0] + 1 if move == env.LEFT: assert agent.pos[0] == old_pos[0] - 1 if move == env.STAY: assert agent.pos == old_pos
class EnvironmentMachine(RuleBasedStateMachine): """ Finite-state machine for testing ``Env`` multi agent environment. """ @timing @given(env=bst.envs()) def __init__(self, env: Env): super(EnvironmentMachine, self).__init__() self.env = env @initialize() @timing def reset(self) -> None: env = self.env obs = env.reset() for agent_id, agent_obs in obs.items(): # Calculate correct number of each object type. correct_obj_nums = { obj_type: 0 for obj_type in env.obj_type_ids.values() } for dx, dy in product(range(-env.sight_len, env.sight_len + 1), repeat=2): x = env.agents[agent_id].pos[0] + dx y = env.agents[agent_id].pos[1] + dy if (x, y) not in product(range(env.width), range(env.height)): continue for obj_type in env.obj_type_ids.values(): correct_obj_nums[obj_type] += int(env.grid[x][y][obj_type]) # Calculate number of each object type in returned observations. observed_obj_nums = { obj_type: 0 for obj_type in env.obj_type_ids.values() } for dx, dy in product(range(-env.sight_len, env.sight_len + 1), repeat=2): for obj_type in env.obj_type_ids.values(): observed_obj_nums[obj_type] += int( agent_obs[obj_type][dx][dy]) assert correct_obj_nums == observed_obj_nums @rule() @timing @given(data=st.data()) def update_pos(self, data: st.DataObject) -> None: pos = data.draw(bst.positions(env=self.env)) move = data.draw(bst.moves(env=self.env)) new_pos = self.env._update_pos(pos, move) if pos[0] != new_pos[0]: assert pos[1] == new_pos[1] assert abs(pos[0] - new_pos[0]) == 1 if pos[1] != new_pos[1]: assert pos[0] == new_pos[0] assert abs(pos[1] - new_pos[1]) == 1 @rule() def dummy(self) -> None: assert True
def test_consume_removes_nothing_else(data: st.DataObject) -> None: """ Otherwise, food should remain in place. """ env = data.draw(bst.envs()) env.reset() food_obj_type_id = env.obj_type_ids["food"] agent_food_positions: Dict[int, Tuple[int, int]] = {} for agent_id, agent in env.agents.items(): if env.grid[agent.pos + (food_obj_type_id,)] == 1: agent_food_positions[agent_id] = agent.pos tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) eating_positions: List[Tuple[int, int]] = [] for agent_id, pos in agent_food_positions.items(): if tuple_action_dict[agent_id][1] == env.EAT: eating_positions.append(pos) food_positions: Set[Tuple[int, int]] = set() for x in range(env.width): for y in range(env.height): if env.grid[(x, y) + (food_obj_type_id,)] == 1: food_positions.add((x, y)) persistent_food_positions = food_positions - set(eating_positions) env._consume(tuple_action_dict) for pos in persistent_food_positions: assert env.grid[pos + (food_obj_type_id,)] == 1
def test_env_plant_does_nothing_when_zero_prob(data: st.DataObject) -> None: env: Env = data.draw(bst.envs()) env.adaptive_food = False env.food_regen_prob = 0.0 old_num_foods = env.num_foods env._plant() assert env.num_foods == old_num_foods
def test_get_obs_has_correct_shape(data: st.DataObject) -> None: """ Make sure that a returned observation has the correct shape. """ env = data.draw(bst.envs()) env.reset() pos = data.draw(bst.positions(env=env)) ob = env._get_obs(pos) assert ob.shape == env.observation_space.shape
def test_mate_makes_num_agents_nondecreasing(data: st.DataObject) -> None: """ Makes sure ``len(agents)`` is nondecreasing. """ env = data.draw(bst.envs()) env.reset() old_num_agents = len(env.agents) tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) env._mate(tuple_action_dict) assert old_num_agents <= len(env.agents)
def test_mate_adds_children_to_agents(data: st.DataObject) -> None: """ Makes sure child ids get added to ``env.agents``. """ env = data.draw(bst.envs()) env.reset() tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) child_ids = env._mate(tuple_action_dict) for child_id in child_ids: assert child_id in env.agents
def test_get_obs_has_no_out_of_range_elements(data: st.DataObject) -> None: """ Make sure that a returned observation only contains 0 or 1. """ env = data.draw(bst.envs()) env.reset() pos = data.draw(bst.positions(env=env)) ob = env._get_obs(pos) for elem in np.nditer(ob): assert elem in (0.0, 1.0)
def test_mate_children_are_new(data: st.DataObject) -> None: """ Makes sure children are new. """ env = data.draw(bst.envs()) env.reset() old_agent_memory_addresses = [id(agent) for agent in env.agents.values()] tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) child_ids = env._mate(tuple_action_dict) for child_id in child_ids: assert id(env.agents[child_id]) not in old_agent_memory_addresses
def test_move_holds_other_actions_invariant(data: st.DataObject) -> None: """ Makes sure the returned action dict only modifies move subaction space. """ env = data.draw(bst.envs()) env.reset() tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) executed_dict = env._move(tuple_action_dict) pairs = zip(list(tuple_action_dict.values()), list(executed_dict.values())) for attempted_action, executed_action in pairs: assert attempted_action[1:] == executed_action[1:]
def test_move_only_changes_to_stay(data: st.DataObject) -> None: """ Makes sure the returned action dict only changes to STAY if at all. """ env = data.draw(bst.envs()) env.reset() tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) executed_dict = env._move(tuple_action_dict) pairs = zip(list(tuple_action_dict.values()), list(executed_dict.values())) for attempted_action, executed_action in pairs: if attempted_action[0] != executed_action[0]: assert executed_action[0] == env.STAY
def test_obj_exists_marks_grid_squares_correctly(data: st.DataObject) -> None: """ Make sure emptiness of grid matches function output. """ env = data.draw(bst.envs()) env.reset() obj_type_id = data.draw(bst.obj_type_ids(env=env)) for x in range(env.width): for y in range(env.height): if env.grid[x][y][obj_type_id] == 0: assert not env._obj_exists(obj_type_id, (x, y)) else: assert env._obj_exists(obj_type_id, (x, y))
def test_obj_exists_has_correct_num_agents(data: st.DataObject) -> None: """ Make sure iterating over grid with obj_exists sees all agents. """ env = data.draw(bst.envs()) env.reset() obj_type_id = env.obj_type_ids["agent"] num_agents = 0 for x in range(env.width): for y in range(env.height): if env._obj_exists(obj_type_id, (x, y)): num_agents += 1 assert num_agents == len(env.agents)
def test_env_place_no_double_place_homo(data: st.DataObject) -> None: """ Tests that env gets angry if you try to double up h**o objs. """ env = data.draw(strategies.envs()) pos = data.draw(strategies.positions(env=env)) env.reset() homo_obj_type_ids = set( env.obj_type_ids.values()) - env.heterogeneous_obj_type_ids obj_type_id = data.draw(st.sampled_from(list(homo_obj_type_ids))) if not env._obj_exists(obj_type_id, pos): env._place(obj_type_id, pos) with pytest.raises(ValueError): env._place(obj_type_id, pos)
def test_consume_makes_agent_health_nondecreasing(data: st.DataObject) -> None: """ Tests that agent.health in the correct direction. """ env = data.draw(bst.envs()) env.reset() tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) old_healths: Dict[int, float] = {} for agent_id, agent in env.agents.items(): old_healths[agent_id] = agent.health env._consume(tuple_action_dict) for agent_id, agent in env.agents.items(): assert old_healths[agent_id] <= agent.health
def test_get_obs_has_correct_objects(data: st.DataObject) -> None: """ Make sure that a returned observation is accurate w.r.t. ``env.grid``. """ env = data.draw(bst.envs()) env.reset() pos = data.draw(bst.positions(env=env)) ob = env._get_obs(pos) for i in range(ob.shape[1]): for j in range(ob.shape[2]): ob_pos = (pos[0] + i - env.sight_len, pos[1] + j - env.sight_len) if 0 <= ob_pos[0] < env.width and 0 <= ob_pos[1] < env.height: ob_square = ob[:, i, j] env_square = env.grid[ob_pos] assert np.all(ob_square == env_square) else: assert np.all(ob[:, i, j] == np.zeros((env.num_obj_types,)))
def test_mate_executes_action(data: st.DataObject) -> None: """ Tests children are created when they're suppsed to. """ env = data.draw(bst.envs()) assume(env.height * env.width >= 3) # Generate two adjacent positions. mom_pos = data.draw(bst.positions(env=env)) open_positions = env._get_adj_positions(mom_pos) dad_pos = data.draw(st.sampled_from(open_positions)) # Create a mom and dad. mom = Agent( config=env.config, num_actions=env.num_actions, pos=mom_pos, initial_health=1.0, ) dad = Agent( config=env.config, num_actions=env.num_actions, pos=dad_pos, initial_health=1.0, ) mom.is_mature = True dad.is_mature = True mom.mating_cooldown = 0 dad.mating_cooldown = 0 mom_id = env._new_agent_id() dad_id = env._new_agent_id() env.agents[mom_id] = mom env.agents[dad_id] = dad env._place(env.obj_type_ids["agent"], mom_pos, mom_id) env._place(env.obj_type_ids["agent"], dad_pos, dad_id) # Construct subactions. mom_move = data.draw(bst.moves(env=env)) dad_move = data.draw(bst.moves(env=env)) mom_consumption = data.draw(bst.consumptions(env=env)) dad_consumption = data.draw(bst.consumptions(env=env)) mom_action = (mom_move, mom_consumption, env.MATE) dad_action = (dad_move, dad_consumption, env.MATE) action_dict = {mom_id: mom_action, dad_id: dad_action} child_ids = env._mate(action_dict) assert len(child_ids) == 1 child = env.agents[child_ids.pop()] def adjacent(pos1: Tuple[int, int], pos2: Tuple[int, int]) -> bool: """ Decide whether or not two positions are orthogonally adjacent. """ return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1]) == 1 assert len(env.agents) == 3 assert adjacent(child.pos, mom.pos) or adjacent(child.pos, dad.pos)
def test_consume_decreases_num_foods_correctly(data: st.DataObject) -> None: """ The ``env.num_foods`` attribute is decremented properly. """ env = data.draw(bst.envs()) env.reset() food_obj_type_id = env.obj_type_ids["food"] agent_food_positions: Dict[int, Tuple[int, int]] = {} for agent_id, agent in env.agents.items(): if env.grid[agent.pos + (food_obj_type_id,)] == 1: agent_food_positions[agent_id] = agent.pos tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) eating_positions: List[Tuple[int, int]] = [] for agent_id, pos in agent_food_positions.items(): if tuple_action_dict[agent_id][1] == env.EAT: eating_positions.append(pos) old_num_foods = env.num_foods env._consume(tuple_action_dict) assert old_num_foods - env.num_foods == len(eating_positions)
def test_obj_exists_detects_bad_id_map(data: st.DataObject) -> None: """ Make sure error is raised when ``id_map`` disagrees with ``grid``. """ env = data.draw(bst.envs()) obj_type_id = env.obj_type_ids["agent"] pos = data.draw(bst.positions(env=env)) x = pos[0] y = pos[1] grid_idx = pos + (obj_type_id, ) singleton_id_set = set([data.draw(st.integers(min_value=0, max_value=3))]) env.id_map[x][y][obj_type_id] = singleton_id_set raised_value_error = False try: _ = env._obj_exists(obj_type_id, pos) except ValueError: raised_value_error = True if env.grid[grid_idx] == 0: assert raised_value_error else: assert not raised_value_error
def test_consume_removes_food_when_appropriate(data: st.DataObject) -> None: """ If the action is ``EAT`` and there's food, should disappear. """ env = data.draw(bst.envs()) env.reset() food_obj_type_id = env.obj_type_ids["food"] agent_food_positions: Dict[int, Tuple[int, int]] = {} for agent_id, agent in env.agents.items(): if env.grid[agent.pos + (food_obj_type_id,)] == 1: agent_food_positions[agent_id] = agent.pos tuple_action_dict = data.draw(bst.tuple_action_dicts(env=env)) eating_positions: List[Tuple[int, int]] = [] for agent_id, pos in agent_food_positions.items(): if tuple_action_dict[agent_id][1] == env.EAT: eating_positions.append(pos) env._consume(tuple_action_dict) for pos in eating_positions: assert env.grid[pos + (food_obj_type_id,)] == 0
def test_obj_exists_handles_invalid_obj_type_ids(data: st.DataObject) -> None: """ Make sure the correct error is raised. """ env = data.draw(bst.envs()) obj_type_id = data.draw(st.integers(min_value=-10, max_value=10)) pos = data.draw(bst.positions(env=env)) raised_value_error = False try: existence = env._obj_exists(obj_type_id, pos) except ValueError as err: raised_value_error = True value_error = err # If the obj_type_id is invalid, should raise ValueError. if obj_type_id not in env.obj_type_names: assert raised_value_error # Otherwise, should raise no error. else: try: assert not raised_value_error except AssertionError: raise value_error
def test_env_plant_fills_grid_when_one_prob(data: st.DataObject) -> None: env: Env = data.draw(bst.envs()) env.adaptive_food = False env.food_regen_prob = 1.0 env._plant() assert env.num_foods == env.width * env.height
def test_env_plant_doesnt_remove_food(data: st.DataObject) -> None: env: Env = data.draw(bst.envs()) old_num_foods = env.num_foods env._plant() assert env.num_foods >= old_num_foods
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Test that ``Env.fill()`` works correctly. """ import itertools from hypothesis import given, settings from hypothesis import HealthCheck as hc from bees.env import Env from bees.tests import strategies # pylint: disable=no-value-for-parameter @given(strategies.envs()) def test_env_fill_places_correct_number_of_agents(env: Env) -> None: """ Tests that we find a place for each agent in ``self.agents``. """ env.fill() num_grid_agents = 0 for row in env.grid: for square in row: if square[env.obj_type_ids["agent"]] == 1: num_grid_agents += 1 assert num_grid_agents == len(env.agents) @settings(suppress_health_check=[hc.too_slow]) @given(strategies.envs()) def test_env_fill_sets_all_agent_positions_correctly(env: Env) -> None: """ Tests that ``agent.pos`` is set correctly. """ env.fill()