Item and Dirt Factory Working again

This commit is contained in:
Steffen Illium 2021-12-23 13:19:31 +01:00
parent b43f595207
commit 78bf19f7f4
11 changed files with 257 additions and 321 deletions

View File

@ -17,7 +17,7 @@ class QLearner(BaseLearner):
self.q_net = q_net self.q_net = q_net
self.target_q_net = target_q_net self.target_q_net = target_q_net
self.target_q_net.eval() self.target_q_net.eval()
#soft_update(self.q_net, self.target_q_net, tau=1.0) #soft_update(cls.q_net, cls.target_q_net, tau=1.0)
self.buffer = BaseBuffer(buffer_size) self.buffer = BaseBuffer(buffer_size)
self.target_update = target_update self.target_update = target_update
self.eps = eps_start self.eps = eps_start

View File

@ -1,8 +1,6 @@
import abc import abc
import enum
import time import time
from collections import defaultdict from collections import defaultdict
from enum import Enum
from itertools import chain from itertools import chain
from pathlib import Path from pathlib import Path
from typing import List, Union, Iterable, Dict from typing import List, Union, Iterable, Dict
@ -13,8 +11,8 @@ from gym import spaces
from gym.wrappers import FrameStack from gym.wrappers import FrameStack
from environments.factory.base.shadow_casting import Map from environments.factory.base.shadow_casting import Map
from environments.helpers import Constants as c, Constants
from environments import helpers as h from environments import helpers as h
from environments.helpers import Constants as c
from environments.factory.base.objects import Agent, Tile, Action from environments.factory.base.objects import Agent, Tile, Action
from environments.factory.base.registers import Actions, Entities, Agents, Doors, FloorTiles, WallTiles, PlaceHolders, \ from environments.factory.base.registers import Actions, Entities, Agents, Doors, FloorTiles, WallTiles, PlaceHolders, \
GlobalPositions GlobalPositions
@ -53,10 +51,9 @@ class BaseFactory(gym.Env):
_, named_obs = self._build_observations() _, named_obs = self._build_observations()
if self.n_agents > 1: if self.n_agents > 1:
# Only return the first named obs space, as their structure at the moment is same. # Only return the first named obs space, as their structure at the moment is same.
return [{key.name: val for key, val in named_ob.items()} for named_ob in named_obs.values()][0] return named_obs[list(named_obs.keys())[0]]
else: else:
return {key.name: val for key, val in named_obs.items()} return named_obs
@property @property
def pomdp_diameter(self): def pomdp_diameter(self):
@ -143,27 +140,27 @@ class BaseFactory(gym.Env):
# Walls # Walls
walls = WallTiles.from_argwhere_coordinates( walls = WallTiles.from_argwhere_coordinates(
np.argwhere(level_array == c.OCCUPIED_CELL.value), np.argwhere(level_array == c.OCCUPIED_CELL),
self._level_shape self._level_shape
) )
self._entities.register_additional_items({c.WALLS: walls}) self._entities.register_additional_items({c.WALLS: walls})
# Floor # Floor
floor = FloorTiles.from_argwhere_coordinates( floor = FloorTiles.from_argwhere_coordinates(
np.argwhere(level_array == c.FREE_CELL.value), np.argwhere(level_array == c.FREE_CELL),
self._level_shape self._level_shape
) )
self._entities.register_additional_items({c.FLOOR: floor}) self._entities.register_additional_items({c.FLOOR: floor})
# NOPOS # NOPOS
self._NO_POS_TILE = Tile(c.NO_POS.value, None) self._NO_POS_TILE = Tile(c.NO_POS, None)
# Doors # Doors
if self.parse_doors: if self.parse_doors:
parsed_doors = h.one_hot_level(self._parsed_level, c.DOOR) parsed_doors = h.one_hot_level(self._parsed_level, c.DOOR)
parsed_doors = np.pad(parsed_doors, self.obs_prop.pomdp_r, 'constant', constant_values=0) parsed_doors = np.pad(parsed_doors, self.obs_prop.pomdp_r, 'constant', constant_values=0)
if np.any(parsed_doors): if np.any(parsed_doors):
door_tiles = [floor.by_pos(tuple(pos)) for pos in np.argwhere(parsed_doors == c.OCCUPIED_CELL.value)] door_tiles = [floor.by_pos(tuple(pos)) for pos in np.argwhere(parsed_doors == c.OCCUPIED_CELL)]
doors = Doors.from_tiles(door_tiles, self._level_shape, doors = Doors.from_tiles(door_tiles, self._level_shape,
entity_kwargs=dict(context=floor) entity_kwargs=dict(context=floor)
) )
@ -209,7 +206,7 @@ class BaseFactory(gym.Env):
if self.obs_prop.show_global_position_info: if self.obs_prop.show_global_position_info:
global_positions = GlobalPositions(self._level_shape) global_positions = GlobalPositions(self._level_shape)
obs_shape_2d = self._level_shape if not self._pomdp_r else ((self.pomdp_diameter,) * 2) obs_shape_2d = self._level_shape if not self._pomdp_r else ((self.pomdp_diameter,) * 2)
global_positions.spawn_GlobalPositionObjects(obs_shape_2d, self[c.AGENT]) global_positions.spawn_global_position_objects(obs_shape_2d, self[c.AGENT])
self._entities.register_additional_items({c.GLOBAL_POSITION: global_positions}) self._entities.register_additional_items({c.GLOBAL_POSITION: global_positions})
# Return # Return
@ -239,8 +236,8 @@ class BaseFactory(gym.Env):
for action, agent in zip(actions, self[c.AGENT]): for action, agent in zip(actions, self[c.AGENT]):
agent.clear_temp_state() agent.clear_temp_state()
action_obj = self._actions[int(action)] action_obj = self._actions[int(action)]
# self.print(f'Action #{action} has been resolved to: {action_obj}') # cls.print(f'Action #{action} has been resolved to: {action_obj}')
if h.MovingAction.is_member(action_obj): if h.EnvActions.is_move(action_obj):
valid = self._move_or_colide(agent, action_obj) valid = self._move_or_colide(agent, action_obj)
elif h.EnvActions.NOOP == agent.temp_action: elif h.EnvActions.NOOP == agent.temp_action:
valid = c.VALID valid = c.VALID
@ -338,12 +335,12 @@ class BaseFactory(gym.Env):
obs_dict[c.AGENT_PLACEHOLDER] = placeholder_obs obs_dict[c.AGENT_PLACEHOLDER] = placeholder_obs
obs_dict[c.DOORS] = door_obs obs_dict[c.DOORS] = door_obs
obs_dict.update(add_obs_dict) obs_dict.update(add_obs_dict)
observations = np.vstack(list(obs_dict.values())) obsn = np.vstack(list(obs_dict.values()))
if self.obs_prop.pomdp_r: if self.obs_prop.pomdp_r:
observations = self._do_pomdp_cutout(agent, observations) obsn = self._do_pomdp_cutout(agent, obsn)
raw_obs = self._additional_raw_observations(agent) raw_obs = self._additional_per_agent_raw_observations(agent)
observations = np.vstack((observations, *list(raw_obs.values()))) obsn = np.vstack((obsn, *list(raw_obs.values())))
keys = list(chain(obs_dict.keys(), raw_obs.keys())) keys = list(chain(obs_dict.keys(), raw_obs.keys()))
idxs = np.cumsum([x.shape[0] for x in chain(obs_dict.values(), raw_obs.values())]) - 1 idxs = np.cumsum([x.shape[0] for x in chain(obs_dict.values(), raw_obs.values())]) - 1
@ -365,7 +362,7 @@ class BaseFactory(gym.Env):
print(e) print(e)
raise e raise e
if self.obs_prop.cast_shadows: if self.obs_prop.cast_shadows:
obs_block_light = observations[light_block_obs] != c.OCCUPIED_CELL.value obs_block_light = obsn[light_block_obs] != c.OCCUPIED_CELL
door_shadowing = False door_shadowing = False
if self.parse_doors: if self.parse_doors:
if doors := self[c.DOORS]: if doors := self[c.DOORS]:
@ -395,11 +392,11 @@ class BaseFactory(gym.Env):
light_block_map[xs, ys] = 0 light_block_map[xs, ys] = 0
agent.temp_light_map = light_block_map.copy() agent.temp_light_map = light_block_map.copy()
observations[shadowed_obs] = ((observations[shadowed_obs] * light_block_map) + 0.) - (1 - light_block_map) obsn[shadowed_obs] = ((obsn[shadowed_obs] * light_block_map) + 0.) - (1 - light_block_map)
else: else:
pass pass
per_agent_obsn[agent.name] = observations per_agent_obsn[agent.name] = obsn
if self.n_agents == 1: if self.n_agents == 1:
agent_name = self[c.AGENT][0].name agent_name = self[c.AGENT][0].name
@ -450,7 +447,7 @@ class BaseFactory(gym.Env):
tiles_with_collisions.append(tile) tiles_with_collisions.append(tile)
return tiles_with_collisions return tiles_with_collisions
def _move_or_colide(self, agent: Agent, action: Action) -> Constants: def _move_or_colide(self, agent: Agent, action: Action) -> bool:
new_tile, valid = self._check_agent_move(agent, action) new_tile, valid = self._check_agent_move(agent, action)
if valid: if valid:
# Does not collide width level boundaries # Does not collide width level boundaries
@ -624,7 +621,7 @@ class BaseFactory(gym.Env):
return [] return []
@property @property
def additional_entities(self) -> Dict[(Enum, Entities)]: def additional_entities(self) -> Dict[(str, Entities)]:
""" """
When heriting from this Base Class, you musst implement this methode!!! When heriting from this Base Class, you musst implement this methode!!!
@ -652,11 +649,11 @@ class BaseFactory(gym.Env):
return False return False
@abc.abstractmethod @abc.abstractmethod
def _additional_observations(self) -> Dict[Constants, np.typing.ArrayLike]: def _additional_observations(self) -> Dict[str, np.typing.ArrayLike]:
return {} return {}
@abc.abstractmethod @abc.abstractmethod
def _additional_raw_observations(self, agent) -> Dict[Constants, np.typing.ArrayLike]: def _additional_per_agent_raw_observations(self, agent) -> Dict[str, np.typing.ArrayLike]:
additional_raw_observations = {} additional_raw_observations = {}
if self.obs_prop.show_global_position_info: if self.obs_prop.show_global_position_info:
additional_raw_observations.update({c.GLOBAL_POSITION: self[c.GLOBAL_POSITION].by_entity(agent).as_array()}) additional_raw_observations.update({c.GLOBAL_POSITION: self[c.GLOBAL_POSITION].by_entity(agent).as_array()})

View File

@ -1,5 +1,4 @@
from collections import defaultdict from collections import defaultdict
from enum import Enum
from typing import Union from typing import Union
import networkx as nx import networkx as nx
@ -29,24 +28,18 @@ class Object:
@property @property
def identifier(self): def identifier(self):
if self._enum_ident is not None: if self._str_ident is not None:
return self._enum_ident
elif self._str_ident is not None:
return self._str_ident return self._str_ident
else: else:
return self._name return self._name
def __init__(self, str_ident: Union[str, None] = None, enum_ident: Union[Enum, None] = None, def __init__(self, str_ident: Union[str, None] = None, is_blocking_light=False, **kwargs):
is_blocking_light=False, **kwargs):
self._str_ident = str_ident self._str_ident = str_ident
self._enum_ident = enum_ident
if self._enum_ident is not None and self._str_ident is None: if self._str_ident is not None:
self._name = f'{self.__class__.__name__}[{self._enum_ident.name}]'
elif self._str_ident is not None and self._enum_ident is None:
self._name = f'{self.__class__.__name__}[{self._str_ident}]' self._name = f'{self.__class__.__name__}[{self._str_ident}]'
elif self._str_ident is None and self._enum_ident is None: elif self._str_ident is None:
self._name = f'{self.__class__.__name__}#{Object._u_idx[self.__class__.__name__]}' self._name = f'{self.__class__.__name__}#{Object._u_idx[self.__class__.__name__]}'
Object._u_idx[self.__class__.__name__] += 1 Object._u_idx[self.__class__.__name__] += 1
else: else:
@ -60,16 +53,7 @@ class Object:
return f'{self.name}' return f'{self.name}'
def __eq__(self, other) -> bool: def __eq__(self, other) -> bool:
if self._enum_ident is not None: return other == self.identifier
if isinstance(other, Enum):
return other == self._enum_ident
elif isinstance(other, Object):
return other._enum_ident == self._enum_ident
else:
raise ValueError('Must be evaluated against an Enunm Identifier or Object with such.')
else:
assert isinstance(other, Object), ' This Object can only be compared to other Objects.'
return other.name == self.name
class EnvObject(Object): class EnvObject(Object):
@ -80,14 +64,17 @@ class EnvObject(Object):
@property @property
def encoding(self): def encoding(self):
return c.OCCUPIED_CELL.value return c.OCCUPIED_CELL
def __init__(self, register, **kwargs): def __init__(self, register, **kwargs):
super(EnvObject, self).__init__(**kwargs) super(EnvObject, self).__init__(**kwargs)
self._register = register self._register = register
def change_register(self, register):
self._register = register
class BoundingMixin:
class BoundingMixin(Object):
@property @property
def bound_entity(self): def bound_entity(self):
@ -163,7 +150,7 @@ class MoveableEntity(Entity):
if self._last_tile: if self._last_tile:
return self._last_tile.pos return self._last_tile.pos
else: else:
return c.NO_POS.value return c.NO_POS
@property @property
def direction_of_view(self): def direction_of_view(self):
@ -218,30 +205,27 @@ class PlaceHolder(Object):
return "PlaceHolder" return "PlaceHolder"
class GlobalPosition(EnvObject): class GlobalPosition(EnvObject, BoundingMixin):
def belongs_to_entity(self, entity): @property
return self._agent == entity def encoding(self):
if self._normalized:
return tuple(np.diff(self._bound_entity.pos, self._level_shape))
else:
return self.bound_entity.pos
def __init__(self, level_shape, *args, normalized: bool = True, **kwargs):
super(GlobalPosition, self).__init__(self, *args, **kwargs)
def __init__(self, level_shape, obs_shape, agent, normalized: bool = True):
super(GlobalPosition, self).__init__(self)
self._obs_shape = (1, *obs_shape) if len(obs_shape) == 2 else obs_shape
self._agent = agent
self._level_shape = level_shape self._level_shape = level_shape
self._normalized = normalized self._normalized = normalized
def as_array(self):
pos_array = np.zeros(self._obs_shape)
for xy in range(1):
pos_array[0, 0, xy] = self._agent.pos[xy] / self._level_shape[xy]
return pos_array
class Tile(EnvObject): class Tile(EnvObject):
@property @property
def encoding(self): def encoding(self):
return c.FREE_CELL.value return c.FREE_CELL
@property @property
def guests_that_can_collide(self): def guests_that_can_collide(self):
@ -302,7 +286,7 @@ class Wall(Tile):
@property @property
def encoding(self): def encoding(self):
return c.OCCUPIED_CELL.value return c.OCCUPIED_CELL
pass pass
@ -319,7 +303,7 @@ class Door(Entity):
@property @property
def encoding(self): def encoding(self):
# This is important as it shadow is checked by occupation value # This is important as it shadow is checked by occupation value
return c.OCCUPIED_CELL.value if self.is_closed else 2 return c.OCCUPIED_CELL if self.is_closed else 2
@property @property
def str_state(self): def str_state(self):
@ -403,7 +387,7 @@ class Agent(MoveableEntity):
# noinspection PyAttributeOutsideInit # noinspection PyAttributeOutsideInit
def clear_temp_state(self): def clear_temp_state(self):
# for attr in self.__dict__: # for attr in cls.__dict__:
# if attr.startswith('temp'): # if attr.startswith('temp'):
self.temp_collisions = [] self.temp_collisions = []
self.temp_valid = None self.temp_valid = None

View File

@ -4,6 +4,7 @@ from abc import ABC
from typing import List, Union, Dict, Tuple from typing import List, Union, Dict, Tuple
import numpy as np import numpy as np
import six
from environments.factory.base.objects import Entity, Tile, Agent, Door, Action, Wall, PlaceHolder, GlobalPosition, \ from environments.factory.base.objects import Entity, Tile, Agent, Door, Action, Wall, PlaceHolder, GlobalPosition, \
Object, EnvObject Object, EnvObject
@ -56,7 +57,7 @@ class ObjectRegister:
def _get_index(self, item): def _get_index(self, item):
try: try:
return next(i for i, v in enumerate(self._register.values()) if v == item) return next(i for i, v in enumerate(self._register.values()) if v == item)
except (StopIteration, AssertionError): except StopIteration:
return None return None
def __getitem__(self, item): def __getitem__(self, item):
@ -73,24 +74,30 @@ class ObjectRegister:
return None return None
def __repr__(self): def __repr__(self):
return f'{self.__class__.__name__}({self._register})' return f'{self.__class__.__name__}[{self._register}]'
class EnvObjectRegister(ObjectRegister): class EnvObjectRegister(ObjectRegister):
_accepted_objects = EnvObject _accepted_objects = EnvObject
def __init__(self, obs_shape: (int, int), *args, **kwargs): @property
def encodings(self):
return [x.encoding for x in self]
def __init__(self, obs_shape: (int, int), *args, individual_slices: bool = False, **kwargs):
super(EnvObjectRegister, self).__init__(*args, **kwargs) super(EnvObjectRegister, self).__init__(*args, **kwargs)
self._shape = obs_shape self._shape = obs_shape
self._array = None self._array = None
self.hide_from_obs_builder = False self._individual_slices = individual_slices
self._lazy_eval_transforms = [] self._lazy_eval_transforms = []
def register_item(self, other: EnvObject): def register_item(self, other: EnvObject):
super(EnvObjectRegister, self).register_item(other) super(EnvObjectRegister, self).register_item(other)
if self._array is None: if self._array is None:
self._array = np.zeros((1, *self._shape)) self._array = np.zeros((1, *self._shape))
if self._individual_slices:
self._array = np.vstack((self._array, np.zeros((1, *self._shape))))
self.notify_change_to_value(other) self.notify_change_to_value(other)
def as_array(self): def as_array(self):
@ -105,7 +112,7 @@ class EnvObjectRegister(ObjectRegister):
return [val.summarize_state(n_steps=n_steps) for val in self.values()] return [val.summarize_state(n_steps=n_steps) for val in self.values()]
def notify_change_to_free(self, env_object: EnvObject): def notify_change_to_free(self, env_object: EnvObject):
self._array_change_notifyer(env_object, value=c.FREE_CELL.value) self._array_change_notifyer(env_object, value=c.FREE_CELL)
def notify_change_to_value(self, env_object: EnvObject): def notify_change_to_value(self, env_object: EnvObject):
self._array_change_notifyer(env_object) self._array_change_notifyer(env_object)
@ -114,9 +121,28 @@ class EnvObjectRegister(ObjectRegister):
pos = self._get_index(env_object) pos = self._get_index(env_object)
value = value if value is not None else env_object.encoding value = value if value is not None else env_object.encoding
self._lazy_eval_transforms.append((pos, value)) self._lazy_eval_transforms.append((pos, value))
if self._individual_slices:
idx = (self._get_index(env_object) * np.prod(self._shape[1:]), value)
self._lazy_eval_transforms.append((idx, value))
else:
self._lazy_eval_transforms.append((pos, value))
def _refresh_arrays(self):
poss, values = zip(*[(idx, x.encoding) for idx,x in enumerate(self.values())])
for pos, value in zip(poss, values):
self._lazy_eval_transforms.append((pos, value))
def __delitem__(self, name): def __delitem__(self, name):
self.notify_change_to_free(self._register[name]) idx, obj = next((i, obj) for i, obj in enumerate(self) if obj.name == name)
if self._individual_slices:
self._array = np.delete(self._array, idx, axis=0)
else:
self.notify_change_to_free(self._register[name])
# Dirty Hack to check if not beeing subclassed. In that case we need to refresh the array since positions
# in the observation array are result of enumeration. They can overide each other.
# Todo: Find a better solution
if not issubclass(self.__class__, EntityRegister) and issubclass(self.__class__, EnvObjectRegister):
self._refresh_arrays()
del self._register[name] del self._register[name]
def delete_env_object(self, env_object: EnvObject): def delete_env_object(self, env_object: EnvObject):
@ -153,26 +179,19 @@ class EntityRegister(EnvObjectRegister, ABC):
def tiles(self): def tiles(self):
return [entity.tile for entity in self] return [entity.tile for entity in self]
@property
def encodings(self):
return [x.encoding for x in self]
def __init__(self, level_shape, *args, def __init__(self, level_shape, *args,
is_blocking_light: bool = False, is_blocking_light: bool = False,
can_be_shadowed: bool = True, can_be_shadowed: bool = True,
individual_slices: bool = False, **kwargs): **kwargs):
super(EntityRegister, self).__init__(level_shape, *args, **kwargs) super(EntityRegister, self).__init__(level_shape, *args, **kwargs)
self._lazy_eval_transforms = [] self._lazy_eval_transforms = []
self.can_be_shadowed = can_be_shadowed self.can_be_shadowed = can_be_shadowed
self.individual_slices = individual_slices
self.is_blocking_light = is_blocking_light self.is_blocking_light = is_blocking_light
def __delitem__(self, name): def __delitem__(self, name):
idx, obj = next((i, obj) for i, obj in enumerate(self) if obj.name == name) idx, obj = next((i, obj) for i, obj in enumerate(self) if obj.name == name)
obj.tile.leave(obj) obj.tile.leave(obj)
super(EntityRegister, self).__delitem__(name) super(EntityRegister, self).__delitem__(name)
if self.individual_slices:
self._array = np.delete(self._array, idx, axis=0)
def as_array(self): def as_array(self):
if self._lazy_eval_transforms: if self._lazy_eval_transforms:
@ -188,7 +207,7 @@ class EntityRegister(EnvObjectRegister, ABC):
pos = pos if pos is not None else entity.pos pos = pos if pos is not None else entity.pos
value = value if value is not None else entity.encoding value = value if value is not None else entity.encoding
x, y = pos x, y = pos
if self.individual_slices: if self._individual_slices:
idx = (self._get_index(entity), x, y) idx = (self._get_index(entity), x, y)
else: else:
idx = (0, x, y) idx = (0, x, y)
@ -203,19 +222,12 @@ class EntityRegister(EnvObjectRegister, ABC):
class BoundRegisterMixin(EnvObjectRegister, ABC): class BoundRegisterMixin(EnvObjectRegister, ABC):
@classmethod def __init__(self, entity_to_be_bound, *args, **kwargs):
def from_entities_to_bind(self, entitites): super().__init__(*args, **kwargs)
def from_values(cls, values: Union[str, numbers.Number, List[Union[str, numbers.Number]]], self._bound_entity = entity_to_be_bound
*args, object_kwargs=None, **kwargs):
# objects_name = cls._accepted_objects.__name__ def belongs_to_entity(self, entity):
if isinstance(values, (str, numbers.Number)): return self._bound_entity == entity
values = [values]
register_obj = cls(*args, **kwargs)
objects = [cls._accepted_objects(register_obj, str_ident=i, fill_value=value,
**object_kwargs if object_kwargs is not None else {})
for i, value in enumerate(values)]
register_obj.register_additional_items(objects)
return register_obj
class MovingEntityObjectRegister(EntityRegister, ABC): class MovingEntityObjectRegister(EntityRegister, ABC):
@ -225,9 +237,9 @@ class MovingEntityObjectRegister(EntityRegister, ABC):
def notify_change_to_value(self, entity): def notify_change_to_value(self, entity):
super(MovingEntityObjectRegister, self).notify_change_to_value(entity) super(MovingEntityObjectRegister, self).notify_change_to_value(entity)
if entity.last_pos != c.NO_POS.value: if entity.last_pos != c.NO_POS:
try: try:
self._array_change_notifyer(entity, entity.last_pos, value=c.FREE_CELL.value) self._array_change_notifyer(entity, entity.last_pos, value=c.FREE_CELL)
except AttributeError: except AttributeError:
pass pass
@ -238,20 +250,26 @@ class MovingEntityObjectRegister(EntityRegister, ABC):
class GlobalPositions(EnvObjectRegister): class GlobalPositions(EnvObjectRegister):
_accepted_objects = GlobalPosition _accepted_objects = GlobalPosition
is_blocking_light = False is_blocking_light = False
can_be_shadowed = False can_be_shadowed = False
hide_from_obs_builder = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(GlobalPositions, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs) super(GlobalPositions, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs)
def as_array(self): def as_array(self):
# Todo make this lazy? # FIXME DEBUG!!! make this lazy?
return np.stack([gp.as_array() for inv_idx, gp in enumerate(self)]) return np.stack([gp.as_array() for inv_idx, gp in enumerate(self)])
def spawn_GlobalPositionObjects(self, obs_shape, agents): def as_array_by_entity(self, entity):
global_positions = [self._accepted_objects(self._shape, obs_shape, agent) # FIXME DEBUG!!! make this lazy?
return np.stack([gp.as_array() for inv_idx, gp in enumerate(self)])
def spawn_global_position_objects(self, agents):
# Todo, change to 'from xy'-form
global_positions = [self._accepted_objects(self._shape, agent)
for _, agent in enumerate(agents)] for _, agent in enumerate(agents)]
# noinspection PyTypeChecker # noinspection PyTypeChecker
self.register_additional_items(global_positions) self.register_additional_items(global_positions)
@ -276,7 +294,7 @@ class PlaceHolders(EnvObjectRegister):
_accepted_objects = PlaceHolder _accepted_objects = PlaceHolder
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
assert not 'individual_slices' in kwargs, 'Keyword - "individual_slices": "True" and must not be altered' assert 'individual_slices' not in kwargs, 'Keyword - "individual_slices": "True" and must not be altered'
kwargs.update(individual_slices=False) kwargs.update(individual_slices=False)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -316,10 +334,6 @@ class Entities(ObjectRegister):
def arrays(self): def arrays(self):
return {key: val.as_array() for key, val in self.items()} return {key: val.as_array() for key, val in self.items()}
@property
def obs_arrays(self):
return {key: val.as_array() for key, val in self.items() if not val.hide_from_obs_builder}
@property @property
def names(self): def names(self):
return list(self._register.keys()) return list(self._register.keys())
@ -347,24 +361,20 @@ class Entities(ObjectRegister):
class WallTiles(EntityRegister): class WallTiles(EntityRegister):
_accepted_objects = Wall _accepted_objects = Wall
_light_blocking = True _light_blocking = True
hide_from_obs_builder = True
def as_array(self): def as_array(self):
if not np.any(self._array): if not np.any(self._array):
# Which is Faster? # Which is Faster?
# indices = [x.pos for x in self] # indices = [x.pos for x in cls]
# np.put(self._array, [np.ravel_multi_index((0, *x), self._array.shape) for x in indices], self.encodings) # np.put(cls._array, [np.ravel_multi_index((0, *x), cls._array.shape) for x in indices], cls.encodings)
x, y = zip(*[x.pos for x in self]) x, y = zip(*[x.pos for x in self])
self._array[0, x, y] = self.encoding self._array[0, x, y] = self._value
return self._array return self._array
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(WallTiles, self).__init__(*args, is_blocking_light=self._light_blocking, individual_slices=False, super(WallTiles, self).__init__(*args, is_blocking_light=self._light_blocking, individual_slices=False,
**kwargs) **kwargs)
self._value = c.OCCUPIED_CELL
@property
def encoding(self):
return c.OCCUPIED_CELL.value
@classmethod @classmethod
def from_argwhere_coordinates(cls, argwhere_coordinates, *args, **kwargs): def from_argwhere_coordinates(cls, argwhere_coordinates, *args, **kwargs):
@ -393,10 +403,7 @@ class FloorTiles(WallTiles):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FloorTiles, self).__init__(*args, **kwargs) super(FloorTiles, self).__init__(*args, **kwargs)
self._value = c.FREE_CELL
@property
def encoding(self):
return c.FREE_CELL.value
@property @property
def occupied_tiles(self): def occupied_tiles(self):
@ -422,23 +429,8 @@ class FloorTiles(WallTiles):
class Agents(MovingEntityObjectRegister): class Agents(MovingEntityObjectRegister):
_accepted_objects = Agent _accepted_objects = Agent
def __init__(self, *args, hide_from_obs_builder=False, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.hide_from_obs_builder = hide_from_obs_builder
@DeprecationWarning
def Xas_array(self):
# Super Safe Version
# self._array[:] = c.FREE_CELL.value
indices = list(zip(range(len(self)), *zip(*[x.last_pos for x in self])))
np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], c.FREE_CELL.value)
indices = list(zip(range(len(self)), *zip(*[x.pos for x in self])))
np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], self.encodings)
if self.individual_slices:
return self._array
else:
return self._array.sum(axis=0, keepdims=True)
@property @property
def positions(self): def positions(self):
@ -484,17 +476,18 @@ class Actions(ObjectRegister):
self.can_use_doors = can_use_doors self.can_use_doors = can_use_doors
super(Actions, self).__init__() super(Actions, self).__init__()
# Move this to Baseclass, Env init?
if self.allow_square_movement: if self.allow_square_movement:
self.register_additional_items([self._accepted_objects(enum_ident=direction) self.register_additional_items([self._accepted_objects(str_ident=direction)
for direction in h.MovingAction.square()]) for direction in h.EnvActions.square_move()])
if self.allow_diagonal_movement: if self.allow_diagonal_movement:
self.register_additional_items([self._accepted_objects(enum_ident=direction) self.register_additional_items([self._accepted_objects(str_ident=direction)
for direction in h.MovingAction.diagonal()]) for direction in h.EnvActions.diagonal_move()])
self._movement_actions = self._register.copy() self._movement_actions = self._register.copy()
if self.can_use_doors: if self.can_use_doors:
self.register_additional_items([self._accepted_objects(enum_ident=h.EnvActions.USE_DOOR)]) self.register_additional_items([self._accepted_objects(str_ident=h.EnvActions.USE_DOOR)])
if self.allow_no_op: if self.allow_no_op:
self.register_additional_items([self._accepted_objects(enum_ident=h.EnvActions.NOOP)]) self.register_additional_items([self._accepted_objects(str_ident=h.EnvActions.NOOP)])
def is_moving_action(self, action: Union[int]): def is_moving_action(self, action: Union[int]):
return action in self.movement_actions.values() return action in self.movement_actions.values()
@ -504,7 +497,7 @@ class Zones(ObjectRegister):
@property @property
def accounting_zones(self): def accounting_zones(self):
return [self[idx] for idx, name in self.items() if name != c.DANGER_ZONE.value] return [self[idx] for idx, name in self.items() if name != c.DANGER_ZONE]
def __init__(self, parsed_level): def __init__(self, parsed_level):
raise NotImplementedError('This needs a Rework') raise NotImplementedError('This needs a Rework')
@ -513,9 +506,9 @@ class Zones(ObjectRegister):
self._accounting_zones = list() self._accounting_zones = list()
self._danger_zones = list() self._danger_zones = list()
for symbol in np.unique(parsed_level): for symbol in np.unique(parsed_level):
if symbol == c.WALL.value: if symbol == c.WALL:
continue continue
elif symbol == c.DANGER_ZONE.value: elif symbol == c.DANGER_ZONE:
self + symbol self + symbol
slices.append(h.one_hot_level(parsed_level, symbol)) slices.append(h.one_hot_level(parsed_level, symbol))
self._danger_zones.append(symbol) self._danger_zones.append(symbol)

View File

@ -16,14 +16,14 @@ class Map(object):
def __init__(self, map_array: np.typing.ArrayLike, diamond_slope: float = 0.9): def __init__(self, map_array: np.typing.ArrayLike, diamond_slope: float = 0.9):
self.data = map_array self.data = map_array
self.width, self.height = map_array.shape self.width, self.height = map_array.shape
self.light = np.full_like(self.data, c.FREE_CELL.value) self.light = np.full_like(self.data, c.FREE_CELL)
self.flag = c.FREE_CELL.value self.flag = c.FREE_CELL
self.d_slope = diamond_slope self.d_slope = diamond_slope
def blocked(self, x, y): def blocked(self, x, y):
return (x < 0 or y < 0 return (x < 0 or y < 0
or x >= self.width or y >= self.height or x >= self.width or y >= self.height
or self.data[x, y] == c.OCCUPIED_CELL.value) or self.data[x, y] == c.OCCUPIED_CELL)
def lit(self, x, y): def lit(self, x, y):
return self.light[x, y] == self.flag return self.light[x, y] == self.flag
@ -46,14 +46,14 @@ class Map(object):
# Translate the dx, dy coordinates into map coordinates: # Translate the dx, dy coordinates into map coordinates:
X, Y = cx + dx * xx + dy * xy, cy + dx * yx + dy * yy X, Y = cx + dx * xx + dy * xy, cy + dx * yx + dy * yy
# l_slope and r_slope store the slopes of the left and right # l_slope and r_slope store the slopes of the left and right
# extremities of the square we're considering: # extremities of the square_move we're considering:
l_slope, r_slope = (dx-self.d_slope)/(dy+self.d_slope), (dx+self.d_slope)/(dy-self.d_slope) l_slope, r_slope = (dx-self.d_slope)/(dy+self.d_slope), (dx+self.d_slope)/(dy-self.d_slope)
if start < r_slope: if start < r_slope:
continue continue
elif end > l_slope: elif end > l_slope:
break break
else: else:
# Our light beam is touching this square; light it: # Our light beam is touching this square_move; light it:
if dx*dx + dy*dy < radius_squared: if dx*dx + dy*dy < radius_squared:
self.set_lit(X, Y) self.set_lit(X, Y)
if blocked: if blocked:
@ -66,12 +66,12 @@ class Map(object):
start = new_start start = new_start
else: else:
if self.blocked(X, Y) and j < radius: if self.blocked(X, Y) and j < radius:
# This is a blocking square, start a child scan: # This is a blocking square_move, start a child scan:
blocked = True blocked = True
self._cast_light(cx, cy, j+1, start, l_slope, self._cast_light(cx, cy, j+1, start, l_slope,
radius, xx, xy, yx, yy, id+1) radius, xx, xy, yx, yy, id+1)
new_start = r_slope new_start = r_slope
# Row is scanned; do next row unless last square was blocked: # Row is scanned; do next row unless last square_move was blocked:
if blocked: if blocked:
break break

View File

@ -65,7 +65,6 @@ class BatteriesRegister(EnvObjectRegister):
_accepted_objects = Battery _accepted_objects = Battery
is_blocking_light = False is_blocking_light = False
can_be_shadowed = False can_be_shadowed = False
hide_from_obs_builder = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BatteriesRegister, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs) super(BatteriesRegister, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs)
@ -98,7 +97,7 @@ class BatteriesRegister(EnvObjectRegister):
def summarize_states(self, n_steps=None): def summarize_states(self, n_steps=None):
# as dict with additional nesting # as dict with additional nesting
# return dict(items=super(Inventories, self).summarize_states()) # return dict(items=super(Inventories, cls).summarize_states())
return super(BatteriesRegister, self).summarize_states(n_steps=n_steps) return super(BatteriesRegister, self).summarize_states(n_steps=n_steps)
@ -156,8 +155,8 @@ class BatteryFactory(BaseFactory):
self.btry_prop = btry_prop self.btry_prop = btry_prop
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def _additional_raw_observations(self, agent) -> Dict[Constants, np.typing.ArrayLike]: def _additional_per_agent_raw_observations(self, agent) -> Dict[Constants, np.typing.ArrayLike]:
additional_raw_observations = super()._additional_raw_observations(agent) additional_raw_observations = super()._additional_per_agent_raw_observations(agent)
additional_raw_observations.update({c.BATTERIES: self[c.BATTERIES].by_entity(agent).as_array()}) additional_raw_observations.update({c.BATTERIES: self[c.BATTERIES].by_entity(agent).as_array()})
return additional_raw_observations return additional_raw_observations

View File

@ -14,6 +14,8 @@ from environments.factory.base.registers import Entities, EntityRegister
from environments.factory.base.renderer import RenderEntity from environments.factory.base.renderer import RenderEntity
DESTINATION = 1 DESTINATION = 1
DESTINATION_DONE = 0.5 DESTINATION_DONE = 0.5
@ -70,8 +72,8 @@ class Destinations(EntityRegister):
def as_array(self): def as_array(self):
self._array[:] = c.FREE_CELL.value self._array[:] = c.FREE_CELL.value
# ToDo: Switch to new Style Array Put # ToDo: Switch to new Style Array Put
# indices = list(zip(range(len(self)), *zip(*[x.pos for x in self]))) # indices = list(zip(range(len(cls)), *zip(*[x.pos for x in cls])))
# np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], self.encodings) # np.put(cls._array, [np.ravel_multi_index(x, cls._array.shape) for x in indices], cls.encodings)
for item in self: for item in self:
if item.pos != c.NO_POS.value: if item.pos != c.NO_POS.value:
self._array[0, item.x, item.y] = item.encoding self._array[0, item.x, item.y] = item.encoding

View File

@ -5,24 +5,31 @@ import random
import numpy as np import numpy as np
from algorithms.TSP_dirt_agent import TSPDirtAgent # from algorithms.TSP_dirt_agent import TSPDirtAgent
from environments.helpers import Constants as c, Constants from environments.helpers import Constants as BaseConstants
from environments import helpers as h from environments.helpers import EnvActions as BaseActions
from environments.factory.base.base_factory import BaseFactory from environments.factory.base.base_factory import BaseFactory
from environments.factory.base.objects import Agent, Action, Entity, Tile from environments.factory.base.objects import Agent, Action, Entity, Tile
from environments.factory.base.registers import Entities, MovingEntityObjectRegister, EntityRegister from environments.factory.base.registers import Entities, EntityRegister
from environments.factory.base.renderer import RenderEntity from environments.factory.base.renderer import RenderEntity
from environments.utility_classes import ObservationProperties from environments.utility_classes import ObservationProperties
CLEAN_UP_ACTION = h.EnvActions.CLEAN_UP
class Constants(BaseConstants):
DIRT = 'Dirt'
class EnvActions(BaseActions):
CLEAN_UP = 'clean_up'
class DirtProperties(NamedTuple): class DirtProperties(NamedTuple):
initial_dirt_ratio: float = 0.3 # On INIT, on max how much tiles does the dirt spawn in percent. initial_dirt_ratio: float = 0.3 # On INIT, on max how many tiles does the dirt spawn in percent.
initial_dirt_spawn_r_var: float = 0.05 # How much does the dirt spawn amount vary? initial_dirt_spawn_r_var: float = 0.05 # How much does the dirt spawn amount vary?
clean_amount: float = 1 # How much does the robot clean with one actions. clean_amount: float = 1 # How much does the robot clean with one actions.
max_spawn_ratio: float = 0.20 # On max how much tiles does the dirt spawn in percent. max_spawn_ratio: float = 0.20 # On max how many tiles does the dirt spawn in percent.
max_spawn_amount: float = 0.3 # How much dirt does spawn per tile at max. max_spawn_amount: float = 0.3 # How much dirt does spawn per tile at max.
spawn_frequency: int = 0 # Spawn Frequency in Steps. spawn_frequency: int = 0 # Spawn Frequency in Steps.
max_local_amount: int = 2 # Max dirt amount per tile. max_local_amount: int = 2 # Max dirt amount per tile.
@ -77,7 +84,7 @@ class DirtRegister(EntityRegister):
super(DirtRegister, self).__init__(*args) super(DirtRegister, self).__init__(*args)
self._dirt_properties: DirtProperties = dirt_properties self._dirt_properties: DirtProperties = dirt_properties
def spawn_dirt(self, then_dirty_tiles) -> c: def spawn_dirt(self, then_dirty_tiles) -> bool:
if isinstance(then_dirty_tiles, Tile): if isinstance(then_dirty_tiles, Tile):
then_dirty_tiles = [then_dirty_tiles] then_dirty_tiles = [then_dirty_tiles]
for tile in then_dirty_tiles: for tile in then_dirty_tiles:
@ -108,6 +115,9 @@ def entropy(x):
return -(x * np.log(x + 1e-8)).sum() return -(x * np.log(x + 1e-8)).sum()
c = Constants
# noinspection PyAttributeOutsideInit, PyAbstractClass # noinspection PyAttributeOutsideInit, PyAbstractClass
class DirtFactory(BaseFactory): class DirtFactory(BaseFactory):
@ -115,7 +125,7 @@ class DirtFactory(BaseFactory):
def additional_actions(self) -> Union[Action, List[Action]]: def additional_actions(self) -> Union[Action, List[Action]]:
super_actions = super().additional_actions super_actions = super().additional_actions
if self.dirt_prop.agent_can_interact: if self.dirt_prop.agent_can_interact:
super_actions.append(Action(enum_ident=CLEAN_UP_ACTION)) super_actions.append(Action(str_ident=EnvActions.CLEAN_UP))
return super_actions return super_actions
@property @property
@ -194,7 +204,7 @@ class DirtFactory(BaseFactory):
def do_additional_actions(self, agent: Agent, action: Action) -> Union[None, c]: def do_additional_actions(self, agent: Agent, action: Action) -> Union[None, c]:
valid = super().do_additional_actions(agent, action) valid = super().do_additional_actions(agent, action)
if valid is None: if valid is None:
if action == CLEAN_UP_ACTION: if action == EnvActions.CLEAN_UP:
if self.dirt_prop.agent_can_interact: if self.dirt_prop.agent_can_interact:
valid = self.clean_up(agent) valid = self.clean_up(agent)
return valid return valid
@ -215,7 +225,7 @@ class DirtFactory(BaseFactory):
done = self.dirt_prop.done_when_clean and (len(self[c.DIRT]) == 0) done = self.dirt_prop.done_when_clean and (len(self[c.DIRT]) == 0)
return super_done or done return super_done or done
def _additional_observations(self) -> Dict[Constants, np.typing.ArrayLike]: def _additional_observations(self) -> Dict[str, np.typing.ArrayLike]:
additional_observations = super()._additional_observations() additional_observations = super()._additional_observations()
additional_observations.update({c.DIRT: self[c.DIRT].as_array()}) additional_observations.update({c.DIRT: self[c.DIRT].as_array()})
return additional_observations return additional_observations
@ -227,14 +237,14 @@ class DirtFactory(BaseFactory):
dirty_tile_count = len(dirt) dirty_tile_count = len(dirt)
# if dirty_tile_count: # if dirty_tile_count:
# dirt_distribution_score = entropy(softmax(np.asarray(dirt)) / dirty_tile_count) # dirt_distribution_score = entropy(softmax(np.asarray(dirt)) / dirty_tile_count)
#else: # else:
# dirt_distribution_score = 0 # dirt_distribution_score = 0
info_dict.update(dirt_amount=current_dirt_amount) info_dict.update(dirt_amount=current_dirt_amount)
info_dict.update(dirty_tile_count=dirty_tile_count) info_dict.update(dirty_tile_count=dirty_tile_count)
# info_dict.update(dirt_distribution_score=dirt_distribution_score) # info_dict.update(dirt_distribution_score=dirt_distribution_score)
if agent.temp_action == CLEAN_UP_ACTION: if agent.temp_action == EnvActions.CLEAN_UP:
if agent.temp_valid: if agent.temp_valid:
# Reward if pickup succeds, # Reward if pickup succeds,
# 0.5 on every pickup # 0.5 on every pickup
@ -257,7 +267,7 @@ class DirtFactory(BaseFactory):
if __name__ == '__main__': if __name__ == '__main__':
from environments.utility_classes import AgentRenderOptions as ARO from environments.utility_classes import AgentRenderOptions as aro
render = True render = True
dirt_props = DirtProperties( dirt_props = DirtProperties(
@ -273,7 +283,7 @@ if __name__ == '__main__':
agent_can_interact=True agent_can_interact=True
) )
obs_props = ObservationProperties(render_agents=ARO.COMBINED, omit_agent_self=True, obs_props = ObservationProperties(render_agents=aro.COMBINED, omit_agent_self=True,
pomdp_r=2, additional_agent_placeholder=None, cast_shadows=True) pomdp_r=2, additional_agent_placeholder=None, cast_shadows=True)
move_props = {'allow_square_movement': True, move_props = {'allow_square_movement': True,

View File

@ -1,22 +1,30 @@
import time import time
from collections import deque, UserList from collections import deque
from enum import Enum
from typing import List, Union, NamedTuple, Dict from typing import List, Union, NamedTuple, Dict
import numpy as np import numpy as np
import random import random
from environments.factory.base.base_factory import BaseFactory from environments.factory.base.base_factory import BaseFactory
from environments.helpers import Constants as c, Constants from environments.helpers import Constants as BaseConstants
from environments.helpers import EnvActions as BaseActions
from environments import helpers as h from environments import helpers as h
from environments.factory.base.objects import Agent, Entity, Action, Tile, MoveableEntity from environments.factory.base.objects import Agent, Entity, Action, Tile
from environments.factory.base.registers import Entities, EntityRegister, EnvObjectRegister, MovingEntityObjectRegister, \ from environments.factory.base.registers import Entities, EntityRegister, BoundRegisterMixin, ObjectRegister
BoundRegisterMixin
from environments.factory.base.renderer import RenderEntity from environments.factory.base.renderer import RenderEntity
NO_ITEM = 0 class Constants(BaseConstants):
ITEM_DROP_OFF = 1 NO_ITEM = 0
ITEM_DROP_OFF = 1
# Item Env
ITEM = 'Item'
INVENTORY = 'Inventory'
DROP_OFF = 'Drop_Off'
class EnvActions(BaseActions):
ITEM_ACTION = 'item_action'
class Item(Entity): class Item(Entity):
@ -41,13 +49,9 @@ class Item(Entity):
def set_auto_despawn(self, auto_despawn): def set_auto_despawn(self, auto_despawn):
self._auto_despawn = auto_despawn self._auto_despawn = auto_despawn
def despawn(self): def set_tile_to(self, no_pos_tile):
# Todo: Move this to base class? assert self._register.__class__.__name__ != ItemRegister.__class__
curr_tile = self.tile self._tile = no_pos_tile
curr_tile.leave(self)
self._tile = None
self._register.notify_change_to_value(self)
return True
class ItemRegister(EntityRegister): class ItemRegister(EntityRegister):
@ -64,58 +68,38 @@ class ItemRegister(EntityRegister):
del self[item] del self[item]
class Inventory(EntityRegister, BoundRegisterMixin): class Inventory(BoundRegisterMixin):
@property
def is_blocking_light(self):
return False
@property @property
def name(self): def name(self):
return f'{self.__class__.__name__}({self.agent.name})' return f'{self.__class__.__name__}({self._bound_entity.name})'
def __init__(self, obs_shape: (int, int), agent: Agent, capacity: int): def __init__(self, agent: Agent, capacity: int, *args, **kwargs):
super(Inventory, self).__init__() super(Inventory, self).__init__(agent, *args, is_blocking_light=False, can_be_shadowed=False, **kwargs)
self.agent = agent self.capacity = capacity
self._obs_shape = obs_shape
self._array = np.zeros((1, *self._obs_shape))
self.capacity = min(capacity, self._array.size)
def as_array(self): def as_array(self):
self._array[:] = c.FREE_CELL.value if self._array is None:
# ToDo: Make this Lazy self._array = np.zeros((1, *self._shape))
for item_idx, item in enumerate(self): return super(Inventory, self).as_array()
x_diff, y_diff = divmod(item_idx, self._array.shape[1])
self._array[0, int(x_diff), int(y_diff)] = item.encoding
return self._array
def __repr__(self): def summarize_states(self, **kwargs):
return f'{self.__class__.__name__}[{self.agent.name}]({self.data})'
def append(self, item) -> None:
if len(self) < self.capacity:
super(Inventory, self).append(item)
else:
raise RuntimeError('Inventory is full')
def belongs_to_entity(self, entity):
return self.agent == entity
def summarize_state(self, **kwargs):
attr_dict = {key: str(val) for key, val in self.__dict__.items() if not key.startswith('_') and key != 'data'} attr_dict = {key: str(val) for key, val in self.__dict__.items() if not key.startswith('_') and key != 'data'}
attr_dict.update(dict(items={val.name: val.summarize_state(**kwargs) for val in self})) attr_dict.update(dict(items={key: val.summarize_state(**kwargs) for key, val in self.items()}))
attr_dict.update(dict(name=self.name)) attr_dict.update(dict(name=self.name))
return attr_dict return attr_dict
def pop(self):
item_to_pop = self[0]
self.delete_env_object(item_to_pop)
return item_to_pop
class Inventories(EnvObjectRegister):
class Inventories(ObjectRegister):
_accepted_objects = Inventory _accepted_objects = Inventory
is_blocking_light = False is_blocking_light = False
can_be_shadowed = False can_be_shadowed = False
hide_from_obs_builder = True
def __init__(self, obs_shape, *args, **kwargs): def __init__(self, obs_shape, *args, **kwargs):
super(Inventories, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs) super(Inventories, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs)
@ -125,7 +109,7 @@ class Inventories(EnvObjectRegister):
return np.stack([inventory.as_array() for inv_idx, inventory in enumerate(self)]) return np.stack([inventory.as_array() for inv_idx, inventory in enumerate(self)])
def spawn_inventories(self, agents, capacity): def spawn_inventories(self, agents, capacity):
inventories = [self._accepted_objects(self._obs_shape, agent, capacity) inventories = [self._accepted_objects(agent, capacity, self._obs_shape)
for _, agent in enumerate(agents)] for _, agent in enumerate(agents)]
self.register_additional_items(inventories) self.register_additional_items(inventories)
@ -141,10 +125,8 @@ class Inventories(EnvObjectRegister):
except StopIteration: except StopIteration:
return None return None
def summarize_states(self, n_steps=None): def summarize_states(self, **kwargs):
# as dict with additional nesting return {key: val.summarize_states(**kwargs) for key, val in self.items()}
# return dict(items=super(Inventories, self).summarize_states())
return super(Inventories, self).summarize_states(n_steps=n_steps)
class DropOffLocation(Entity): class DropOffLocation(Entity):
@ -155,7 +137,7 @@ class DropOffLocation(Entity):
@property @property
def encoding(self): def encoding(self):
return ITEM_DROP_OFF return Constants.ITEM_DROP_OFF
def __init__(self, *args, storage_size_until_full: int = 5, auto_item_despawn_interval: int = 5, **kwargs): def __init__(self, *args, storage_size_until_full: int = 5, auto_item_despawn_interval: int = 5, **kwargs):
super(DropOffLocation, self).__init__(*args, **kwargs) super(DropOffLocation, self).__init__(*args, **kwargs)
@ -184,24 +166,17 @@ class DropOffLocations(EntityRegister):
_accepted_objects = DropOffLocation _accepted_objects = DropOffLocation
@DeprecationWarning
def Xas_array(self):
# Todo: Which is faster?
# indices = list(zip(range(len(self)), *zip(*[x.pos for x in self])))
# np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], self.encodings)
self._array[:] = c.FREE_CELL.value
indices = list(zip([0, ] * len(self), *zip(*[x.pos for x in self])))
np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], self.encodings)
return self._array
class ItemProperties(NamedTuple): class ItemProperties(NamedTuple):
n_items: int = 5 # How many items are there at the same time n_items: int = 5 # How many items are there at the same time
spawn_frequency: int = 10 # Spawn Frequency in Steps spawn_frequency: int = 10 # Spawn Frequency in Steps
n_drop_off_locations: int = 5 # How many DropOff locations are there at the same time n_drop_off_locations: int = 5 # How many DropOff locations are there at the same time
max_dropoff_storage_size: int = 0 # How many items are needed until the drop off is full max_dropoff_storage_size: int = 0 # How many items are needed until the dropoff is full
max_agent_inventory_capacity: int = 5 # How many items are needed until the agent inventory is full max_agent_inventory_capacity: int = 5 # How many items are needed until the agent inventory is full
agent_can_interact: bool = True # Whether agents have the possibility to interact with the domain items
c = Constants
a = EnvActions
# noinspection PyAttributeOutsideInit, PyAbstractClass # noinspection PyAttributeOutsideInit, PyAbstractClass
@ -220,11 +195,11 @@ class ItemFactory(BaseFactory):
def additional_actions(self) -> Union[Action, List[Action]]: def additional_actions(self) -> Union[Action, List[Action]]:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
super_actions = super().additional_actions super_actions = super().additional_actions
super_actions.append(Action(enum_ident=h.EnvActions.ITEM_ACTION)) super_actions.append(Action(str_ident=a.ITEM_ACTION))
return super_actions return super_actions
@property @property
def additional_entities(self) -> Dict[(Enum, Entities)]: def additional_entities(self) -> Dict[(str, Entities)]:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
super_entities = super().additional_entities super_entities = super().additional_entities
@ -238,19 +213,18 @@ class ItemFactory(BaseFactory):
empty_tiles = self[c.FLOOR].empty_tiles[:self.item_prop.n_items] empty_tiles = self[c.FLOOR].empty_tiles[:self.item_prop.n_items]
item_register.spawn_items(empty_tiles) item_register.spawn_items(empty_tiles)
inventories = Inventories(self._level_shape if not self._pomdp_r else ((self.pomdp_diameter,) * 2), inventories = Inventories(self._obs_shape, self._level_shape)
self._level_shape)
inventories.spawn_inventories(self[c.AGENT], self.item_prop.max_agent_inventory_capacity) inventories.spawn_inventories(self[c.AGENT], self.item_prop.max_agent_inventory_capacity)
super_entities.update({c.DROP_OFF: drop_offs, c.ITEM: item_register, c.INVENTORY: inventories}) super_entities.update({c.DROP_OFF: drop_offs, c.ITEM: item_register, c.INVENTORY: inventories})
return super_entities return super_entities
def _additional_raw_observations(self, agent) -> Dict[Constants, np.typing.ArrayLike]: def _additional_per_agent_raw_observations(self, agent) -> Dict[str, np.typing.ArrayLike]:
additional_raw_observations = super()._additional_raw_observations(agent) additional_raw_observations = super()._additional_per_agent_raw_observations(agent)
additional_raw_observations.update({c.INVENTORY: self[c.INVENTORY].by_entity(agent).as_array()}) additional_raw_observations.update({c.INVENTORY: self[c.INVENTORY].by_entity(agent).as_array()})
return additional_raw_observations return additional_raw_observations
def _additional_observations(self) -> Dict[Constants, np.typing.ArrayLike]: def _additional_observations(self) -> Dict[str, np.typing.ArrayLike]:
additional_observations = super()._additional_observations() additional_observations = super()._additional_observations()
additional_observations.update({c.ITEM: self[c.ITEM].as_array()}) additional_observations.update({c.ITEM: self[c.ITEM].as_array()})
additional_observations.update({c.DROP_OFF: self[c.DROP_OFF].as_array()}) additional_observations.update({c.DROP_OFF: self[c.DROP_OFF].as_array()})
@ -260,14 +234,16 @@ class ItemFactory(BaseFactory):
inventory = self[c.INVENTORY].by_entity(agent) inventory = self[c.INVENTORY].by_entity(agent)
if drop_off := self[c.DROP_OFF].by_pos(agent.pos): if drop_off := self[c.DROP_OFF].by_pos(agent.pos):
if inventory: if inventory:
valid = drop_off.place_item(inventory.pop(0)) valid = drop_off.place_item(inventory.pop())
return valid return valid
else: else:
return c.NOT_VALID return c.NOT_VALID
elif item := self[c.ITEM].by_pos(agent.pos): elif item := self[c.ITEM].by_pos(agent.pos):
try: try:
inventory.append(item) inventory.register_item(item)
item.despawn() item.change_register(inventory)
self[c.ITEM].delete_env_object(item)
item.set_tile_to(self._NO_POS_TILE)
return c.VALID return c.VALID
except RuntimeError: except RuntimeError:
return c.NOT_VALID return c.NOT_VALID
@ -278,12 +254,9 @@ class ItemFactory(BaseFactory):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
valid = super().do_additional_actions(agent, action) valid = super().do_additional_actions(agent, action)
if valid is None: if valid is None:
if action == h.EnvActions.ITEM_ACTION: if action == a.ITEM_ACTION:
if self.item_prop.agent_can_interact: valid = self.do_item_action(agent)
valid = self.do_item_action(agent) return valid
return valid
else:
return c.NOT_VALID
else: else:
return None return None
else: else:
@ -324,7 +297,7 @@ class ItemFactory(BaseFactory):
def calculate_additional_reward(self, agent: Agent) -> (int, dict): def calculate_additional_reward(self, agent: Agent) -> (int, dict):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
reward, info_dict = super().calculate_additional_reward(agent) reward, info_dict = super().calculate_additional_reward(agent)
if h.EnvActions.ITEM_ACTION == agent.temp_action: if a.ITEM_ACTION == agent.temp_action:
if agent.temp_valid: if agent.temp_valid:
if drop_off := self[c.DROP_OFF].by_pos(agent.pos): if drop_off := self[c.DROP_OFF].by_pos(agent.pos):
info_dict.update({f'{agent.name}_item_drop_off': 1}) info_dict.update({f'{agent.name}_item_drop_off': 1})
@ -352,21 +325,21 @@ class ItemFactory(BaseFactory):
def render_additional_assets(self, mode='human'): def render_additional_assets(self, mode='human'):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
additional_assets = super().render_additional_assets() additional_assets = super().render_additional_assets()
items = [RenderEntity(c.ITEM.value, item.tile.pos) for item in self[c.ITEM]] items = [RenderEntity(c.ITEM, item.tile.pos) for item in self[c.ITEM] if item.tile != self._NO_POS_TILE]
additional_assets.extend(items) additional_assets.extend(items)
drop_offs = [RenderEntity(c.DROP_OFF.value, drop_off.tile.pos) for drop_off in self[c.DROP_OFF]] drop_offs = [RenderEntity(c.DROP_OFF, drop_off.tile.pos) for drop_off in self[c.DROP_OFF]]
additional_assets.extend(drop_offs) additional_assets.extend(drop_offs)
return additional_assets return additional_assets
if __name__ == '__main__': if __name__ == '__main__':
from environments.utility_classes import AgentRenderOptions as ARO, ObservationProperties from environments.utility_classes import AgentRenderOptions as aro, ObservationProperties
render = True render = True
item_probs = ItemProperties() item_probs = ItemProperties(n_items=30)
obs_props = ObservationProperties(render_agents=ARO.SEPERATE, omit_agent_self=True, pomdp_r=2) obs_props = ObservationProperties(render_agents=aro.SEPERATE, omit_agent_self=True, pomdp_r=2)
move_props = {'allow_square_movement': True, move_props = {'allow_square_movement': True,
'allow_diagonal_movement': True, 'allow_diagonal_movement': True,

View File

@ -1,7 +1,5 @@
import itertools import itertools
from collections import defaultdict from collections import defaultdict
from enum import Enum
from pathlib import Path
from typing import Tuple, Union, Dict, List from typing import Tuple, Union, Dict, List
import networkx as nx import networkx as nx
@ -20,7 +18,7 @@ IGNORED_DF_COLUMNS = ['Episode', 'Run', 'train_step', 'step', 'index', 'dirt_amo
# Constants # Constants
class Constants(Enum): class Constants:
WALL = '#' WALL = '#'
WALLS = 'Walls' WALLS = 'Walls'
FLOOR = 'Floor' FLOOR = 'Floor'
@ -44,14 +42,6 @@ class Constants(Enum):
VALID = 'valid' VALID = 'valid'
NOT_VALID = 'not_valid' NOT_VALID = 'not_valid'
# Dirt Env
DIRT = 'Dirt'
# Item Env
ITEM = 'Item'
INVENTORY = 'Inventory'
DROP_OFF = 'Drop_Off'
# Battery Env # Battery Env
CHARGE_POD = 'Charge_Pod' CHARGE_POD = 'Charge_Pod'
BATTERIES = 'BATTERIES' BATTERIES = 'BATTERIES'
@ -60,14 +50,9 @@ class Constants(Enum):
DESTINATION = 'Destination' DESTINATION = 'Destination'
REACHEDDESTINATION = 'ReachedDestination' REACHEDDESTINATION = 'ReachedDestination'
def __bool__(self):
if 'not_' in self.value:
return False
else:
return bool(self.value)
class EnvActions:
class MovingAction(Enum): # Movements
NORTH = 'north' NORTH = 'north'
EAST = 'east' EAST = 'east'
SOUTH = 'south' SOUTH = 'south'
@ -77,29 +62,31 @@ class MovingAction(Enum):
SOUTHWEST = 'south_west' SOUTHWEST = 'south_west'
NORTHWEST = 'north_west' NORTHWEST = 'north_west'
@classmethod # Other
def is_member(cls, other): NOOP = 'no_op'
return any([other == direction for direction in cls])
@classmethod
def square(cls):
return [cls.NORTH, cls.EAST, cls.SOUTH, cls.WEST]
@classmethod
def diagonal(cls):
return [cls.NORTHEAST, cls.SOUTHEAST, cls.SOUTHWEST, cls.NORTHWEST]
class EnvActions(Enum):
NOOP = 'no_op'
USE_DOOR = 'use_door' USE_DOOR = 'use_door'
CLEAN_UP = 'clean_up'
ITEM_ACTION = 'item_action'
CHARGE = 'charge' CHARGE = 'charge'
WAIT_ON_DEST = 'wait' WAIT_ON_DEST = 'wait'
@classmethod
def is_move(cls, other):
return any([other == direction for direction in cls.movement_actions()])
m = MovingAction @classmethod
def square_move(cls):
return [cls.NORTH, cls.EAST, cls.SOUTH, cls.WEST]
@classmethod
def diagonal_move(cls):
return [cls.NORTHEAST, cls.SOUTHEAST, cls.SOUTHWEST, cls.NORTHWEST]
@classmethod
def movement_actions(cls):
return list(itertools.chain(cls.square_move(), cls.diagonal_move()))
m = EnvActions
c = Constants c = Constants
ACTIONMAP = defaultdict(lambda: (0, 0), {m.NORTH: (-1, 0), m.NORTHEAST: (-1, +1), ACTIONMAP = defaultdict(lambda: (0, 0), {m.NORTH: (-1, 0), m.NORTHEAST: (-1, +1),
@ -171,13 +158,10 @@ def parse_level(path):
return level return level
def one_hot_level(level, wall_char: Union[c, str] = c.WALL): def one_hot_level(level, wall_char: str = c.WALL):
grid = np.array(level) grid = np.array(level)
binary_grid = np.zeros(grid.shape, dtype=np.int8) binary_grid = np.zeros(grid.shape, dtype=np.int8)
if wall_char in c: binary_grid[grid == wall_char] = c.OCCUPIED_CELL
binary_grid[grid == wall_char.value] = c.OCCUPIED_CELL.value
else:
binary_grid[grid == wall_char] = c.OCCUPIED_CELL.value
return binary_grid return binary_grid
@ -198,19 +182,19 @@ def check_position(slice_to_check_against: ArrayLike, position_to_check: Tuple[i
def asset_str(agent): def asset_str(agent):
# What does this abonimation do? # What does this abonimation do?
# if any([x is None for x in [self._slices[j] for j in agent.collisions]]): # if any([x is None for x in [cls._slices[j] for j in agent.collisions]]):
# print('error') # print('error')
col_names = [x.name for x in agent.temp_collisions] col_names = [x.name for x in agent.temp_collisions]
if any(c.AGENT.value in name for name in col_names): if any(c.AGENT in name for name in col_names):
return 'agent_collision', 'blank' return 'agent_collision', 'blank'
elif not agent.temp_valid or c.LEVEL.name in col_names or c.AGENT.name in col_names: elif not agent.temp_valid or c.LEVEL in col_names or c.AGENT in col_names:
return c.AGENT.value, 'invalid' return c.AGENT, 'invalid'
elif agent.temp_valid and not MovingAction.is_member(agent.temp_action): elif agent.temp_valid and not EnvActions.is_move(agent.temp_action):
return c.AGENT.value, 'valid' return c.AGENT, 'valid'
elif agent.temp_valid and MovingAction.is_member(agent.temp_action): elif agent.temp_valid and EnvActions.is_move(agent.temp_action):
return c.AGENT.value, 'move' return c.AGENT, 'move'
else: else:
return c.AGENT.value, 'idle' return c.AGENT, 'idle'
def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, allow_manhattan_connections=True): def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, allow_manhattan_connections=True):
@ -229,9 +213,3 @@ def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, all
elif allow_manhattan_connections and not allow_euclidean_connections and not all(diff) and any(diff): elif allow_manhattan_connections and not allow_euclidean_connections and not all(diff) and any(diff):
graph.add_edge(a, b) graph.add_edge(a, b)
return graph return graph
if __name__ == '__main__':
parsed_level = parse_level(Path(__file__).parent / 'factory' / 'levels' / 'simple.txt')
y = one_hot_level(parsed_level)
print(np.argwhere(y == 0))

View File

@ -60,7 +60,7 @@ class EnvRecorder(BaseCallback):
def save_records(self, filepath: Union[Path, str], save_occupation_map=False, save_trajectory_map=False): def save_records(self, filepath: Union[Path, str], save_occupation_map=False, save_trajectory_map=False):
filepath = Path(filepath) filepath = Path(filepath)
filepath.parent.mkdir(exist_ok=True, parents=True) filepath.parent.mkdir(exist_ok=True, parents=True)
# self.out_file.unlink(missing_ok=True) # cls.out_file.unlink(missing_ok=True)
with filepath.open('w') as f: with filepath.open('w') as f:
out_dict = {'episodes': self._recorder_out_list, 'header': self.unwrapped.params} out_dict = {'episodes': self._recorder_out_list, 'header': self.unwrapped.params}
try: try: