from __future__ import annotations
from typing import TYPE_CHECKING, Union, Dict
import re
from abc import ABC, abstractmethod
from collections.abc import Iterable
import numpy as np
from palaestrai.util import seeding
from palaestrai.util.dynaloader import locate
if TYPE_CHECKING:
from palaestrai.types import (
Box,
Discrete,
MultiDiscrete,
MultiBinary,
Tuple,
)
[docs]
class CanNotConvertException(Exception):
pass
[docs]
class Space(ABC):
"""Base class for space definitions
Derived classes allow a minimal mathematical representation of a space
concept.
Derived classes should also derive from a gymnasium.spaces class.
"""
_SPACENAME_RE = re.compile(r"\A\s*?(\w+)\(([^;]+)\)\s*\Z")
TYPES = [
"Box",
"Dict",
"Discrete",
"MultiBinary",
"MultiDiscrete",
"Tuple",
]
[docs]
@abstractmethod
def sample(self):
"""Uniformly randomly sample a random element of this space."""
pass
[docs]
def seed(self, seed=None):
"""Seed the PRNG of this space."""
self.np_random, seed = seeding.np_random(seed)
return [seed]
[docs]
@abstractmethod
def contains(self, x):
"""Return ``True`` if the value given is a valid member of the space.
:param x: Any value
:return: True iff ``x`` is a member of the space
"""
pass
def __contains__(self, x):
return self.contains(x)
[docs]
@abstractmethod
def to_vector(self, data: np.ndarray, **kwargs) -> np.ndarray:
"""Return a flat numpy array of data provided in the shape of the space.
Should give a warning if the shape of the data is unexpected.
:param data: The data to be transformed
:return: The data represented in a flat list
"""
pass
[docs]
@abstractmethod
def reshape_to_space(self, value: Iterable, **kwargs) -> np.ndarray:
"""Return a list of data in the form of the space
Should raise a CanNotConvertException if the values can not be converted
:param value: The values to be represented
:return: The values in the form of the space
"""
pass
[docs]
@abstractmethod
def to_string(self):
"""Returns the string representation of the space object.
String representations of space objects must read like a Python
constructor, such as ``Box(low=[1.0, 0.0], high=[42.0, 42.42])``. The
name of the class is used to dynamically construct a new object from a
string representation.
:return: The object's string representation.
"""
pass
[docs]
@classmethod
def from_string(
cls, s
) -> Union[Discrete, Box, MultiDiscrete, MultiBinary, Tuple]:
"""Dynamically constructs any descendant space object from a string.
On the :py:class:`Space` class, this method acts as a factory to
construct any known descrendant module from a string representation.
It dynamically locates and instantiates any module that is listed in
:py:data:`Space.TYPES`.
All descendant types must also implement this class method as an
alternative constructor (the known ones do). I.e., you can use
``palaestrai.types.Discrete.from_string("Discrete(2)")`` as well as
``palaestrai.types.Space.from_string("Discrete(2)")``.
:param str s: The string representation of a space object.
"""
if cls != Space:
raise NotImplementedError()
m = Space._SPACENAME_RE.match(s)
classname = m[1] if m else None
if not classname:
raise RuntimeError(
"String '%s' did not match pattern '%s'"
% (s, Space._SPACENAME_RE)
)
if not classname in Space.TYPES:
raise RuntimeError("Unknown type: palaestrai.types.%s" % classname)
classpath = "palaestrai.types.%s" % classname
cls = locate(classpath)
return cls.from_string(s)
def __str__(self):
return self.to_string()
def __repr__(self):
return self.to_string()
@abstractmethod
def __len__(self):
"""The number of values in the space"""
pass