Everything is an object now
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import abc
|
||||
import time
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import List, Union, Iterable
|
||||
from typing import List, Union, Iterable, Dict
|
||||
|
||||
import gym
|
||||
import numpy as np
|
||||
@@ -14,8 +15,8 @@ from environments.factory.base.shadow_casting import Map
|
||||
from environments.factory.renderer import Renderer, RenderEntity
|
||||
from environments.helpers import Constants as c, Constants
|
||||
from environments import helpers as h
|
||||
from environments.factory.base.objects import Slice, Agent, Tile, Action
|
||||
from environments.factory.base.registers import StateSlices, Actions, Entities, Agents, Doors, FloorTiles
|
||||
from environments.factory.base.objects import Agent, Tile, Action
|
||||
from environments.factory.base.registers import Actions, Entities, Agents, Doors, FloorTiles, WallTiles
|
||||
from environments.utility_classes import MovementProperties
|
||||
|
||||
REC_TAC = 'rec'
|
||||
@@ -30,9 +31,13 @@ class BaseFactory(gym.Env):
|
||||
|
||||
@property
|
||||
def observation_space(self):
|
||||
slices = self._slices.n_observable_slices
|
||||
level_shape = (self.pomdp_r * 2 + 1, self.pomdp_r * 2 + 1) if self.pomdp_r else self._level_shape
|
||||
space = spaces.Box(low=0, high=1, shape=(slices, *level_shape), dtype=np.float32)
|
||||
if r := self.pomdp_r:
|
||||
z = self._obs_cube.shape[0]
|
||||
xy = r*2 + 1
|
||||
level_shape = (z, xy, xy)
|
||||
else:
|
||||
level_shape = self._obs_cube.shape
|
||||
space = spaces.Box(low=0, high=1, shape=level_shape, dtype=np.float32)
|
||||
return space
|
||||
|
||||
@property
|
||||
@@ -51,8 +56,8 @@ class BaseFactory(gym.Env):
|
||||
|
||||
def __init__(self, level_name='simple', n_agents=1, max_steps=int(5e2), pomdp_r: Union[None, int] = 0,
|
||||
movement_properties: MovementProperties = MovementProperties(), parse_doors=False,
|
||||
combin_agent_slices_in_obs: bool = False, frames_to_stack=0, record_episodes=False,
|
||||
omit_agent_slice_in_obs=False, done_at_collision=False, cast_shadows=True,
|
||||
combin_agent_obs: bool = False, frames_to_stack=0, record_episodes=False,
|
||||
omit_agent_in_obs=False, done_at_collision=False, cast_shadows=True,
|
||||
verbose=False, doors_have_area=True, env_seed=time.time_ns(), **kwargs):
|
||||
assert frames_to_stack != 1 and frames_to_stack >= 0, "'frames_to_stack' cannot be negative or 1."
|
||||
|
||||
@@ -69,8 +74,8 @@ class BaseFactory(gym.Env):
|
||||
|
||||
self.max_steps = max_steps
|
||||
self.pomdp_r = pomdp_r
|
||||
self.combin_agent_slices_in_obs = combin_agent_slices_in_obs
|
||||
self.omit_agent_slice_in_obs = omit_agent_slice_in_obs
|
||||
self.combin_agent_obs = combin_agent_obs
|
||||
self.omit_agent_in_obs = omit_agent_in_obs
|
||||
self.cast_shadows = cast_shadows
|
||||
self.frames_to_stack = frames_to_stack
|
||||
|
||||
@@ -87,86 +92,74 @@ class BaseFactory(gym.Env):
|
||||
# Reset
|
||||
self.reset()
|
||||
|
||||
def _init_state_slices(self) -> StateSlices:
|
||||
state_slices = StateSlices()
|
||||
|
||||
def _base_init_env(self):
|
||||
# Objects
|
||||
entities = {}
|
||||
# Level
|
||||
level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.level_name}.txt'
|
||||
parsed_level = h.parse_level(level_filepath)
|
||||
level = [Slice(c.LEVEL, h.one_hot_level(parsed_level), is_blocking_light=True)]
|
||||
self._level_shape = level[0].shape
|
||||
level_array = h.one_hot_level(parsed_level)
|
||||
self._level_shape = level_array.shape
|
||||
|
||||
# Walls
|
||||
walls = WallTiles.from_argwhere_coordinates(
|
||||
np.argwhere(level_array == c.OCCUPIED_CELL.value),
|
||||
self._level_shape
|
||||
)
|
||||
entities.update({c.WALLS: walls})
|
||||
|
||||
# Floor
|
||||
floor = FloorTiles.from_argwhere_coordinates(
|
||||
np.argwhere(level_array == c.FREE_CELL.value),
|
||||
self._level_shape
|
||||
)
|
||||
entities.update({c.FLOOR: floor})
|
||||
|
||||
# NOPOS
|
||||
self.NO_POS_TILE = Tile(c.NO_POS, c.NO_POS.value)
|
||||
|
||||
# Doors
|
||||
parsed_doors = h.one_hot_level(parsed_level, c.DOOR)
|
||||
if parsed_doors.any():
|
||||
doors = [Slice(c.DOORS, parsed_doors, is_blocking_light=True)]
|
||||
else:
|
||||
doors = []
|
||||
if np.any(parsed_doors):
|
||||
door_tiles = [floor.by_pos(pos) for pos in np.argwhere(parsed_doors == c.OCCUPIED_CELL.value)]
|
||||
doors = Doors.from_tiles(door_tiles, self._level_shape, context=floor, is_blocking_light=True)
|
||||
entities.update({c.DOORS: doors})
|
||||
|
||||
# Agents
|
||||
agents = []
|
||||
agent_names = [f'{c.AGENT.value}#{i}' for i in range(self.n_agents)]
|
||||
agents = Agents.from_tiles(floor.empty_tiles[:self.n_agents], self._level_shape)
|
||||
entities.update({c.AGENT: agents})
|
||||
|
||||
if self.combin_agent_slices_in_obs and self.omit_agent_slice_in_obs:
|
||||
if self.n_agents == 1:
|
||||
observables = [False]
|
||||
else:
|
||||
observables = [True] + ([False] * (self.n_agents - 1))
|
||||
elif self.combin_agent_slices_in_obs and not self.omit_agent_slice_in_obs:
|
||||
observables = [True] + ([False] * (self.n_agents - 1))
|
||||
elif not self.combin_agent_slices_in_obs and self.omit_agent_slice_in_obs:
|
||||
observables = [False] + ([True] * (self.n_agents - 1))
|
||||
elif not self.combin_agent_slices_in_obs and not self.omit_agent_slice_in_obs:
|
||||
observables = [True] * self.n_agents
|
||||
else:
|
||||
raise RuntimeError('This should not happen!')
|
||||
|
||||
for observable, agent_name in zip(observables, agent_names):
|
||||
agents.append(Slice(agent_name, np.zeros_like(level[0].slice, dtype=np.float32), is_observable=observable))
|
||||
state_slices.register_additional_items(level+doors+agents+self.additional_slices)
|
||||
return state_slices
|
||||
|
||||
def _init_obs_cube(self) -> np.ndarray:
|
||||
x, y = self._slices.by_enum(c.LEVEL).shape
|
||||
state = np.zeros((len(self._slices), x, y), dtype=np.float32)
|
||||
state[0] = self._slices.by_enum(c.LEVEL).slice
|
||||
if r := self.pomdp_r:
|
||||
self._padded_obs_cube = np.full((len(self._slices), x + r*2, y + r*2), c.FREE_CELL.value, dtype=np.float32)
|
||||
self._padded_obs_cube[0] = c.OCCUPIED_CELL.value
|
||||
self._padded_obs_cube[:, r:r+x, r:r+y] = state
|
||||
if self.combin_agent_slices_in_obs and self.n_agents > 1:
|
||||
self._combined_obs_cube = np.zeros(self.observation_space.shape, dtype=np.float32)
|
||||
return state
|
||||
|
||||
def _init_entities(self):
|
||||
# Tile Init
|
||||
self._tiles = FloorTiles.from_argwhere_coordinates(self._slices.by_enum(c.LEVEL).free_tiles)
|
||||
|
||||
# Door Init
|
||||
if self.parse_doors:
|
||||
tiles = [self._tiles.by_pos(x) for x in self._slices.by_enum(c.DOORS).occupied_tiles]
|
||||
self._doors = Doors.from_tiles(tiles, context=self._tiles, has_area=self.doors_have_area)
|
||||
|
||||
# Agent Init on random positions
|
||||
self._agents = Agents.from_tiles(self._base_rng.choice(self._tiles, self.n_agents))
|
||||
entities = Entities()
|
||||
entities.register_additional_items([self._agents])
|
||||
|
||||
if self.parse_doors:
|
||||
entities.register_additional_items([self._doors])
|
||||
# All entities
|
||||
self._entities = Entities()
|
||||
self._entities.register_additional_items(entities)
|
||||
|
||||
# Additional Entitites from SubEnvs
|
||||
if additional_entities := self.additional_entities:
|
||||
entities.register_additional_items(additional_entities)
|
||||
self._entities.register_additional_items(additional_entities)
|
||||
|
||||
return entities
|
||||
# Return
|
||||
return self._entities
|
||||
|
||||
def _init_obs_cube(self):
|
||||
arrays = self._entities.arrays
|
||||
|
||||
if self.omit_agent_in_obs and self.n_agents == 1:
|
||||
del arrays[c.AGENT]
|
||||
obs_cube_z = sum([a.shape[0] if not self._entities[key].is_per_agent else 1 for key, a in arrays.items()])
|
||||
self._obs_cube = np.zeros((obs_cube_z, *self._level_shape), dtype=np.float32)
|
||||
|
||||
# Optionally Pad this obs cube for pomdp cases
|
||||
if r := self.pomdp_r:
|
||||
x, y = self._level_shape
|
||||
self._padded_obs_cube = np.full((obs_cube_z, x + r*2, y + r*2), c.SHADOWED_CELL.value, dtype=np.float32)
|
||||
# self._padded_obs_cube[0] = c.OCCUPIED_CELL.value
|
||||
self._padded_obs_cube[:, r:r+x, r:r+y] = self._obs_cube
|
||||
|
||||
def reset(self) -> (np.ndarray, int, bool, dict):
|
||||
self._slices = self._init_state_slices()
|
||||
self._obs_cube = self._init_obs_cube()
|
||||
self._entitites = self._init_entities()
|
||||
_ = self._base_init_env()
|
||||
self._init_obs_cube()
|
||||
self.do_additional_reset()
|
||||
self._flush_state()
|
||||
|
||||
self._steps = 0
|
||||
|
||||
obs = self._get_observations()
|
||||
@@ -182,7 +175,7 @@ class BaseFactory(gym.Env):
|
||||
self.hook_pre_step()
|
||||
|
||||
# Move this in a seperate function?
|
||||
for action, agent in zip(actions, self._agents):
|
||||
for action, agent in zip(actions, self._entities[c.AGENT]):
|
||||
agent.clear_temp_sate()
|
||||
action_obj = self._actions[action]
|
||||
if self._actions.is_moving_action(action_obj):
|
||||
@@ -200,9 +193,6 @@ class BaseFactory(gym.Env):
|
||||
# In-between step Hook for later use
|
||||
info = self.do_additional_step()
|
||||
|
||||
# Write to observation cube
|
||||
self._flush_state()
|
||||
|
||||
tiles_with_collisions = self.get_all_tiles_with_collisions()
|
||||
for tile in tiles_with_collisions:
|
||||
guests = tile.guests_that_can_collide
|
||||
@@ -216,7 +206,7 @@ class BaseFactory(gym.Env):
|
||||
|
||||
# Step the door close intervall
|
||||
if self.parse_doors:
|
||||
self._doors.tick_doors()
|
||||
self._entities[c.DOORS].tick_doors()
|
||||
|
||||
# Finalize
|
||||
reward, reward_info = self.calculate_reward()
|
||||
@@ -237,9 +227,9 @@ class BaseFactory(gym.Env):
|
||||
def _handle_door_interaction(self, agent):
|
||||
# Check if agent really is standing on a door:
|
||||
if self.doors_have_area:
|
||||
door = self._doors.get_near_position(agent.pos)
|
||||
door = self._entities[c.DOORS].get_near_position(agent.pos)
|
||||
else:
|
||||
door = self._doors.by_pos(agent.pos)
|
||||
door = self._entities[c.DOORS].by_pos(agent.pos)
|
||||
if door is not None:
|
||||
door.use()
|
||||
return c.VALID.value
|
||||
@@ -247,36 +237,44 @@ class BaseFactory(gym.Env):
|
||||
else:
|
||||
return c.NOT_VALID.value
|
||||
|
||||
def _flush_state(self):
|
||||
self._obs_cube[np.arange(len(self._slices)) != self._slices.get_idx(c.LEVEL)] = c.FREE_CELL.value
|
||||
if self.parse_doors:
|
||||
for door in self._doors:
|
||||
if door.is_open and self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] != c.OPEN_DOOR.value:
|
||||
self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] = c.OPEN_DOOR.value
|
||||
elif door.is_closed and self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] != c.CLOSED_DOOR.value:
|
||||
self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] = c.CLOSED_DOOR.value
|
||||
for agent in self._agents:
|
||||
self._obs_cube[self._slices.get_idx_by_name(agent.name)][agent.pos] = c.OCCUPIED_CELL.value
|
||||
if agent.last_pos != c.NO_POS:
|
||||
self._obs_cube[self._slices.get_idx_by_name(agent.name)][agent.last_pos] = c.FREE_CELL.value
|
||||
|
||||
def _get_observations(self) -> np.ndarray:
|
||||
if self.n_agents == 1:
|
||||
obs = self._build_per_agent_obs(self._agents[0])
|
||||
obs = self._build_per_agent_obs(self._entities[c.AGENT][0])
|
||||
elif self.n_agents >= 2:
|
||||
obs = np.stack([self._build_per_agent_obs(agent) for agent in self._agents])
|
||||
obs = np.stack([self._build_per_agent_obs(agent) for agent in self._entities[c.AGENT]])
|
||||
else:
|
||||
raise ValueError('n_agents cannot be smaller than 1!!')
|
||||
return obs
|
||||
|
||||
def _build_per_agent_obs(self, agent: Agent) -> np.ndarray:
|
||||
first_agent_slice = self._slices.AGENTSTARTIDX
|
||||
plain_arrays = self._entities.arrays
|
||||
if self.omit_agent_in_obs and self.n_agents == 1:
|
||||
del plain_arrays[c.AGENT]
|
||||
|
||||
running_idx, shadowing_idxs, can_be_shadowed_idxs = 0, [], []
|
||||
|
||||
for key, array in plain_arrays.items():
|
||||
if self._entities[key].is_per_agent:
|
||||
per_agent_idx = self._entities[key].get_idx_by_name(agent.name)
|
||||
z = 1
|
||||
self._obs_cube[running_idx: z] = array[per_agent_idx]
|
||||
else:
|
||||
z = array.shape[0]
|
||||
self._obs_cube[running_idx: z] = array
|
||||
# Define which OBS SLices cast a Shadow
|
||||
if self._entities[key].is_blocking_light:
|
||||
for i in range(z):
|
||||
shadowing_idxs.append(running_idx + i)
|
||||
# Define which OBS SLices are effected by shadows
|
||||
if self._entities[key].can_be_shadowed:
|
||||
for i in range(z):
|
||||
can_be_shadowed_idxs.append(running_idx + i)
|
||||
running_idx += z
|
||||
|
||||
if r := self.pomdp_r:
|
||||
x, y = self._level_shape
|
||||
self._padded_obs_cube[:, r:r + x, r:r + y] = self._obs_cube
|
||||
global_x, global_y = agent.pos
|
||||
global_x += r
|
||||
global_y += r
|
||||
global_x, global_y = map(sum, zip(agent.pos, (r, r)))
|
||||
x0, x1 = max(0, global_x - self.pomdp_r), global_x + self.pomdp_r + 1
|
||||
y0, y1 = max(0, global_y - self.pomdp_r), global_y + self.pomdp_r + 1
|
||||
obs = self._padded_obs_cube[:, x0:x1, y0:y1]
|
||||
@@ -284,10 +282,9 @@ class BaseFactory(gym.Env):
|
||||
obs = self._obs_cube
|
||||
|
||||
if self.cast_shadows:
|
||||
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx, obs_slice
|
||||
in enumerate(self._slices) if obs_slice.is_blocking_light]
|
||||
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx in shadowing_idxs]
|
||||
door_shadowing = False
|
||||
if door := self._doors.by_pos(agent.pos):
|
||||
if door := self._entities[c.DOORS].by_pos(agent.pos):
|
||||
if door.is_closed:
|
||||
for group in door.connectivity_subgroups:
|
||||
if agent.last_pos not in group:
|
||||
@@ -298,8 +295,9 @@ class BaseFactory(gym.Env):
|
||||
xs, ys = zip(*blocking)
|
||||
else:
|
||||
xs, ys = zip(*group)
|
||||
# noinspection PyTypeChecker
|
||||
obs_block_light[self._slices.get_idx(c.LEVEL)][xs, ys] = False
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
obs_block_light[0][xs, ys] = False
|
||||
|
||||
light_block_map = Map((np.prod(obs_block_light, axis=0) != True).astype(int))
|
||||
if self.pomdp_r:
|
||||
@@ -310,28 +308,18 @@ class BaseFactory(gym.Env):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
light_block_map[xs, ys] = 0
|
||||
agent.temp_light_map = light_block_map
|
||||
for obs_idx in range(obs.shape[0]):
|
||||
if self._slices[obs_idx].can_be_shadowed:
|
||||
obs[obs_idx] = (obs[obs_idx] * light_block_map) - (
|
||||
(1 - light_block_map) * obs[self._slices.get_idx(c.LEVEL)]
|
||||
)
|
||||
for obs_idx in can_be_shadowed_idxs:
|
||||
obs[obs_idx] = (obs[obs_idx] * light_block_map) - (
|
||||
(1 - light_block_map) * obs[0]
|
||||
)
|
||||
|
||||
if self.combin_agent_slices_in_obs and self.n_agents > 1:
|
||||
agent_obs = np.sum(obs[[key for key, l_slice in self._slices.items() if c.AGENT.name in l_slice.name and
|
||||
(not self.omit_agent_slice_in_obs and l_slice.name != agent.name)]],
|
||||
axis=0, keepdims=True)
|
||||
obs = np.concatenate((obs[:first_agent_slice], agent_obs, obs[first_agent_slice+self.n_agents:]))
|
||||
return obs
|
||||
else:
|
||||
if self.omit_agent_slice_in_obs:
|
||||
obs_new = obs[[key for key, val in self._slices.items() if val.name != agent.name]]
|
||||
return obs_new
|
||||
else:
|
||||
return obs
|
||||
return obs
|
||||
|
||||
def get_all_tiles_with_collisions(self) -> List[Tile]:
|
||||
tiles_with_collisions = list()
|
||||
for tile in self._tiles:
|
||||
for tile in self._entities[c.FLOOR]:
|
||||
if tile.is_occupied():
|
||||
guests = [guest for guest in tile.guests if guest.can_collide]
|
||||
if len(guests) >= 2:
|
||||
@@ -353,7 +341,7 @@ class BaseFactory(gym.Env):
|
||||
x_new = agent.x + x_diff
|
||||
y_new = agent.y + y_diff
|
||||
|
||||
new_tile = self._tiles.by_pos((x_new, y_new))
|
||||
new_tile = self._entities[c.FLOOR].by_pos((x_new, y_new))
|
||||
if new_tile:
|
||||
valid = c.VALID
|
||||
else:
|
||||
@@ -362,13 +350,13 @@ class BaseFactory(gym.Env):
|
||||
return tile, valid
|
||||
|
||||
if self.parse_doors and agent.last_pos != c.NO_POS:
|
||||
if door := self._doors.by_pos(new_tile.pos):
|
||||
if door := self._entities[c.DOORS].by_pos(new_tile.pos):
|
||||
if door.can_collide:
|
||||
return agent.tile, c.NOT_VALID
|
||||
else: # door.is_closed:
|
||||
pass
|
||||
|
||||
if door := self._doors.by_pos(agent.pos):
|
||||
if door := self._entities[c.DOORS].by_pos(agent.pos):
|
||||
if door.is_open:
|
||||
pass
|
||||
else: # door.is_closed:
|
||||
@@ -388,7 +376,7 @@ class BaseFactory(gym.Env):
|
||||
info_dict = dict()
|
||||
reward = 0
|
||||
|
||||
for agent in self._agents:
|
||||
for agent in self._entities[c.AGENT]:
|
||||
if self._actions.is_moving_action(agent.temp_action):
|
||||
if agent.temp_valid:
|
||||
# info_dict.update(movement=1)
|
||||
@@ -427,16 +415,15 @@ class BaseFactory(gym.Env):
|
||||
height, width = self._obs_cube.shape[1:]
|
||||
self._renderer = Renderer(width, height, view_radius=self.pomdp_r, fps=5)
|
||||
|
||||
walls = [RenderEntity('wall', pos)
|
||||
for pos in np.argwhere(self._slices.by_enum(c.LEVEL).slice == c.OCCUPIED_CELL.value)]
|
||||
walls = [RenderEntity('wall', wall.pos) for wall in self._entities[c.WALLS]]
|
||||
|
||||
agents = []
|
||||
for i, agent in enumerate(self._agents):
|
||||
for i, agent in enumerate(self._entities[c.AGENT]):
|
||||
name, state = h.asset_str(agent)
|
||||
agents.append(RenderEntity(name, agent.pos, 1, 'none', state, i + 1, agent.temp_light_map))
|
||||
doors = []
|
||||
if self.parse_doors:
|
||||
for i, door in enumerate(self._doors):
|
||||
for i, door in enumerate(self._entities[c.DOORS]):
|
||||
name, state = 'door_open' if door.is_open else 'door_closed', 'blank'
|
||||
doors.append(RenderEntity(name, door.pos, 1, 'none', state, i + 1))
|
||||
additional_assets = self.render_additional_assets()
|
||||
@@ -454,7 +441,9 @@ class BaseFactory(gym.Env):
|
||||
|
||||
def _summarize_state(self):
|
||||
summary = {f'{REC_TAC}_step': self._steps}
|
||||
for entity in self._entitites:
|
||||
|
||||
self._entities[c.WALLS].summarize_state()
|
||||
for entity in self._entities:
|
||||
if hasattr(entity, 'summarize_state'):
|
||||
summary.update({f'{REC_TAC}_{entity.name}': entity.summarize_state()})
|
||||
return summary
|
||||
@@ -475,24 +464,14 @@ class BaseFactory(gym.Env):
|
||||
return []
|
||||
|
||||
@property
|
||||
def additional_entities(self) -> Union[Entities, List[Entities]]:
|
||||
def additional_entities(self) -> Dict[(Enum, Entities)]:
|
||||
"""
|
||||
When heriting from this Base Class, you musst implement this methode!!!
|
||||
|
||||
:return: A single Entites collection or a list of such.
|
||||
:rtype: Union[Entities, List[Entities]]
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def additional_slices(self) -> Union[Slice, List[Slice]]:
|
||||
"""
|
||||
When heriting from this Base Class, you musst implement this methode!!!
|
||||
|
||||
:return: A list of Slice-objects.
|
||||
:rtype: List[Slice]
|
||||
"""
|
||||
return []
|
||||
return {}
|
||||
|
||||
# Functions which provide additions to functions of the base class
|
||||
# Always call super!!!!!!
|
||||
|
||||
Reference in New Issue
Block a user