2016-04-27 08:00:58 -07:00
import re
2021-09-14 20:14:05 -06:00
import sys
2020-04-24 23:49:41 +02:00
import copy
2019-03-08 14:50:32 -08:00
import importlib
2021-09-16 08:23:32 -06:00
import contextlib
2021-09-14 20:14:05 -06:00
if sys . version_info < ( 3 , 8 ) :
import importlib_metadata as metadata
else :
import importlib . metadata as metadata
Cleanup, removal of unmaintained code (#836)
* add dtype to Box
* remove board_game, debugging, safety, parameter_tuning environments
* massive set of breaking changes
- remove python logging module
- _step, _reset, _seed, _close => non underscored method
- remove benchmark and scoring folder
* Improve render("human"), now resizable, closable window.
* get rid of default step and reset in wrappers, so it doesn’t silently fail for people with underscore methods
* CubeCrash unit test environment
* followup fixes
* MemorizeDigits unit test envrionment
* refactored spaces a bit
fixed indentation
disabled test_env_semantics
* fix unit tests
* fixes
* CubeCrash, MemorizeDigits tested
* gym backwards compatibility patch
* gym backwards compatibility, followup fixes
* changelist, add spaces to main namespaces
* undo_logger_setup for backwards compat
* remove configuration.py
2018-01-25 18:20:14 -08:00
from gym import error , logger
2016-04-27 08:00:58 -07:00
# This format is true today, but it's *not* an official spec.
2016-10-31 11:36:40 -07:00
# [username/](env-name)-v(version) env-name is group 1, version is group 2
#
# 2016-10-31: We're experimentally expanding the environment ID format
# to include an optional username.
2021-07-29 02:26:34 +02:00
env_id_re = re . compile ( r " ^(?:[ \ w:-]+ \ /)?([ \ w:.-]+)-v( \ d+)$ " )
2016-04-27 08:00:58 -07:00
2021-09-14 20:14:05 -06:00
# Whitelist of plugins which can hook into the `gym.envs.internal` entry point.
plugin_internal_whitelist = { " ale_py.gym " }
2019-03-08 14:50:32 -08:00
2016-04-27 08:00:58 -07:00
def load ( name ) :
2019-03-08 14:50:32 -08:00
mod_name , attr_name = name . split ( " : " )
mod = importlib . import_module ( mod_name )
fn = getattr ( mod , attr_name )
return fn
2016-04-27 08:00:58 -07:00
2021-11-14 01:53:32 +01:00
class EnvSpec :
2016-04-27 08:00:58 -07:00
""" A specification for a particular instance of the environment. Used
to register the parameters for official evaluations .
Args :
id ( str ) : The official environment ID
2016-05-02 14:33:55 -04:00
entry_point ( Optional [ str ] ) : The Python entrypoint of the environment class ( e . g . module . name : Class )
2016-04-27 08:00:58 -07:00
reward_threshold ( Optional [ int ] ) : The reward threshold before the task is considered solved
2016-05-29 09:07:09 -07:00
nondeterministic ( bool ) : Whether this environment is non - deterministic even after seeding
2019-05-25 00:55:40 +02:00
max_episode_steps ( Optional [ int ] ) : The maximum number of steps that an episode can consist of
2021-09-16 16:16:49 +02:00
order_enforce ( Optional [ int ] ) : Whether to wrap the environment in an orderEnforcing wrapper
2019-12-06 16:04:44 +01:00
kwargs ( dict ) : The kwargs to pass to the environment class
2016-04-27 08:00:58 -07:00
"""
2021-07-29 02:26:34 +02:00
def __init__ (
self ,
id ,
entry_point = None ,
reward_threshold = None ,
nondeterministic = False ,
max_episode_steps = None ,
2021-09-16 16:16:49 +02:00
order_enforce = True ,
2021-07-29 02:26:34 +02:00
kwargs = None ,
) :
2016-04-27 08:00:58 -07:00
self . id = id
2019-12-06 16:04:44 +01:00
self . entry_point = entry_point
2016-04-27 08:00:58 -07:00
self . reward_threshold = reward_threshold
2016-05-29 09:34:36 -07:00
self . nondeterministic = nondeterministic
2017-02-01 13:10:59 -08:00
self . max_episode_steps = max_episode_steps
2021-09-16 16:16:49 +02:00
self . order_enforce = order_enforce
2019-12-06 16:04:44 +01:00
self . _kwargs = { } if kwargs is None else kwargs
2016-12-28 15:38:55 -08:00
2016-04-27 08:00:58 -07:00
match = env_id_re . search ( id )
if not match :
2021-07-29 02:26:34 +02:00
raise error . Error (
" Attempted to register malformed environment ID: {} . (Currently all IDs must be of the form {} .) " . format (
id , env_id_re . pattern
)
)
self . _env_name = match . group ( 1 )
2016-04-27 08:00:58 -07:00
2019-01-29 13:37:43 -08:00
def make ( self , * * kwargs ) :
2016-04-27 08:00:58 -07:00
""" Instantiates an instance of the environment with appropriate kwargs """
2019-07-12 13:59:33 -04:00
if self . entry_point is None :
2021-07-29 02:26:34 +02:00
raise error . Error (
2021-11-14 01:53:32 +01:00
f " Attempting to make deprecated env { self . id } . (HINT: is there a newer registered version of this env?) "
2021-07-29 02:26:34 +02:00
)
2021-09-16 16:16:49 +02:00
2019-01-29 13:37:43 -08:00
_kwargs = self . _kwargs . copy ( )
_kwargs . update ( kwargs )
2021-09-16 16:16:49 +02:00
2019-07-12 13:59:33 -04:00
if callable ( self . entry_point ) :
env = self . entry_point ( * * _kwargs )
2017-09-18 11:25:12 -07:00
else :
2019-07-12 13:59:33 -04:00
cls = load ( self . entry_point )
2019-01-29 13:37:43 -08:00
env = cls ( * * _kwargs )
2016-04-27 08:00:58 -07:00
2020-03-06 22:37:23 +01:00
# Make the environment aware of which spec it came from.
2020-04-24 23:49:41 +02:00
spec = copy . deepcopy ( self )
spec . _kwargs = _kwargs
env . unwrapped . spec = spec
2021-09-16 16:16:49 +02:00
if env . spec . max_episode_steps is not None :
from gym . wrappers . time_limit import TimeLimit
2017-02-01 13:10:59 -08:00
2021-09-16 16:16:49 +02:00
env = TimeLimit ( env , max_episode_steps = env . spec . max_episode_steps )
else :
if self . order_enforce :
from gym . wrappers . order_enforcing import OrderEnforcing
env = OrderEnforcing ( env )
2016-04-27 08:00:58 -07:00
return env
def __repr__ ( self ) :
2021-11-14 01:53:32 +01:00
return f " EnvSpec( { self . id } ) "
2016-04-27 08:00:58 -07:00
2021-11-14 01:53:32 +01:00
class EnvRegistry :
2016-04-27 08:00:58 -07:00
""" Register an env by ID. IDs remain stable over time and are
guaranteed to resolve to the same environment dynamics ( or be
desupported ) . The goal is that results on a particular environment
should always be comparable , and not depend on the version of the
code that was running .
"""
def __init__ ( self ) :
self . env_specs = { }
2021-09-14 20:14:05 -06:00
self . _ns = None
2016-04-27 08:00:58 -07:00
2019-03-08 14:50:32 -08:00
def make ( self , path , * * kwargs ) :
2019-01-29 13:37:43 -08:00
if len ( kwargs ) > 0 :
2021-07-29 02:26:34 +02:00
logger . info ( " Making new env: %s ( %s ) " , path , kwargs )
2019-01-29 13:37:43 -08:00
else :
2021-07-29 02:26:34 +02:00
logger . info ( " Making new env: %s " , path )
2019-03-08 14:50:32 -08:00
spec = self . spec ( path )
2019-01-29 13:37:43 -08:00
env = spec . make ( * * kwargs )
2017-02-01 13:10:59 -08:00
return env
2016-04-27 08:00:58 -07:00
def all ( self ) :
return self . env_specs . values ( )
2019-03-08 14:50:32 -08:00
def spec ( self , path ) :
2021-07-29 02:26:34 +02:00
if " : " in path :
2021-09-01 14:27:01 -04:00
mod_name , _ , id = path . partition ( " : " )
2019-03-08 14:50:32 -08:00
try :
importlib . import_module ( mod_name )
2021-09-01 14:27:01 -04:00
except ModuleNotFoundError :
2021-07-29 02:26:34 +02:00
raise error . Error (
" A module ( {} ) was specified for the environment but was not found, make sure the package is installed with `pip install` before calling `gym.make()` " . format (
mod_name
)
)
2019-03-08 14:50:32 -08:00
else :
id = path
2016-04-27 08:00:58 -07:00
match = env_id_re . search ( id )
if not match :
2021-07-29 02:26:34 +02:00
raise error . Error (
" Attempted to look up malformed environment ID: {} . (Currently all IDs must be of the form {} .) " . format (
id . encode ( " utf-8 " ) , env_id_re . pattern
)
)
2016-04-27 08:00:58 -07:00
try :
return self . env_specs [ id ]
except KeyError :
2016-05-18 02:23:43 -07:00
# Parse the env name and check to see if it matches the non-version
# part of a valid env (could also check the exact number here)
env_name = match . group ( 1 )
2021-07-29 02:26:34 +02:00
matching_envs = [
valid_env_name
for valid_env_name , valid_env_spec in self . env_specs . items ( )
if env_name == valid_env_spec . _env_name
]
2021-08-27 09:48:24 -04:00
algorithmic_envs = [
" Copy " ,
" RepeatCopy " ,
" DuplicatedInput " ,
" Reverse " ,
" ReversedAdiiton " ,
" ReversedAddition3 " ,
]
2021-09-01 23:22:01 -04:00
toytext_envs = [
" KellyCoinflip " ,
" KellyCoinflipGeneralized " ,
" NChain " ,
" Roulette " ,
" GuessingGame " ,
" HotterColder " ,
]
2016-05-18 02:23:43 -07:00
if matching_envs :
2021-07-29 15:39:42 -04:00
raise error . DeprecatedEnv (
2021-11-14 01:53:32 +01:00
f " Env { id } not found (valid versions include { matching_envs } ) "
2021-07-29 15:39:42 -04:00
)
2021-08-27 09:48:24 -04:00
elif env_name in algorithmic_envs :
raise error . UnregisteredEnv (
2021-09-01 23:22:01 -04:00
" Algorithmic environment {} has been moved out of Gym. Install it via `pip install gym-algorithmic` and add `import gym_algorithmic` before using it. " . format (
id
)
)
elif env_name in toytext_envs :
raise error . UnregisteredEnv (
" Toytext environment {} has been moved out of Gym. Install it via `pip install gym-legacy-toytext` and add `import gym_toytext` before using it. " . format (
2021-08-27 09:48:24 -04:00
id
)
)
2016-05-18 02:23:43 -07:00
else :
2021-11-14 01:53:32 +01:00
raise error . UnregisteredEnv ( f " No registered env with id: { id } " )
2016-04-27 08:00:58 -07:00
2016-05-02 14:33:55 -04:00
def register ( self , id , * * kwargs ) :
2021-09-14 20:14:05 -06:00
if self . _ns is not None :
if " / " in id :
namespace , id = id . split ( " / " )
logger . warn (
f " Custom namespace ' { namespace } ' is being overrode by namespace ' { self . _ns } ' . "
" If you are developing a plugin you shouldn ' t specify a namespace in `register` calls. "
" The namespace is specified through the entry point key. "
)
id = f " { self . _ns } / { id } "
2016-04-27 08:00:58 -07:00
if id in self . env_specs :
2021-11-14 01:53:32 +01:00
logger . warn ( f " Overriding environment { id } " )
2016-05-02 14:33:55 -04:00
self . env_specs [ id ] = EnvSpec ( id , * * kwargs )
2016-04-27 08:00:58 -07:00
2021-09-16 08:23:32 -06:00
@contextlib.contextmanager
2021-09-14 20:14:05 -06:00
def namespace ( self , ns ) :
self . _ns = ns
yield
self . _ns = None
2021-07-29 02:26:34 +02:00
2016-04-27 08:00:58 -07:00
# Have a global registry
registry = EnvRegistry ( )
2016-08-20 16:05:50 -07:00
2021-07-29 02:26:34 +02:00
2016-08-20 16:05:50 -07:00
def register ( id , * * kwargs ) :
return registry . register ( id , * * kwargs )
2021-07-29 02:26:34 +02:00
2019-01-29 13:37:43 -08:00
def make ( id , * * kwargs ) :
return registry . make ( id , * * kwargs )
2016-08-20 16:05:50 -07:00
2021-07-29 02:26:34 +02:00
2016-08-20 16:05:50 -07:00
def spec ( id ) :
return registry . spec ( id )
2021-09-14 20:14:05 -06:00
2021-09-16 08:23:32 -06:00
@contextlib.contextmanager
2021-09-14 20:14:05 -06:00
def namespace ( ns ) :
with registry . namespace ( ns ) :
yield
2021-09-16 08:23:32 -06:00
def load_env_plugins ( entry_point = " gym.envs " ) :
2021-09-14 20:14:05 -06:00
# Load third-party environments
2021-09-16 08:23:32 -06:00
for plugin in metadata . entry_points ( ) . get ( entry_point , [ ] ) :
2021-09-28 17:45:01 -06:00
# Python 3.8 doesn't support plugin.module, plugin.attr
# So we'll have to try and parse this ourselves
try :
module , attr = plugin . module , plugin . attr
except AttributeError :
if " : " in plugin . value :
module , attr = plugin . value . split ( " : " , maxsplit = 1 )
else :
module , attr = plugin . value , None
finally :
if attr is None :
raise error . Error (
f " Gym environment plugin ` { module } ` must specify a function to execute, not a root module "
)
2021-09-16 08:23:32 -06:00
context = namespace ( plugin . name )
if plugin . name == " __internal__ " :
2021-09-28 17:45:01 -06:00
if module in plugin_internal_whitelist :
2021-09-16 08:23:32 -06:00
context = contextlib . nullcontext ( )
else :
logger . warn (
2021-09-28 17:45:01 -06:00
f " Trying to register an internal environment when ` { module } ` is not in the whitelist "
2021-09-16 08:23:32 -06:00
)
with context :
fn = plugin . load ( )
try :
fn ( )
except Exception as e :
logger . warn ( str ( e ) )