示例#1
0
 def test_class_with_provided_instance_raises(self):
     with self.assertRaises(EnvError):
         _get_default_aux('param', {},
                          str,
                          defaultClass=str,
                          defaultinstance="strinstance",
                          isclass=True)
示例#2
0
 def test_no_default_provided_raises(self):
     with self.assertRaises(EnvError):
         _get_default_aux('param', {},
                          str,
                          defaultinstance=None,
                          defaultClass=None,
                          isclass=False)
示例#3
0
 def test_class_with_provided_build_kwargs_raises(self):
     with self.assertRaises(EnvError):
         _get_default_aux('param', {},
                          str,
                          defaultClass=str,
                          isclass=True,
                          build_kwargs=['s', 't', 'r'])
示例#4
0
 def test_default_instance_and_class_raises(self):
     with self.assertRaises(EnvError):
         _get_default_aux('param', {},
                          str,
                          defaultClass=str,
                          defaultinstance="strinstance",
                          isclass=False)
示例#5
0
 def test_default_instance_with_build_kwargs_raises(self):
     with self.assertRaises(EnvError):
         _get_default_aux('param', {},
                          str,
                          defaultinstance="strinstance",
                          isclass=False,
                          build_kwargs=['s', 't', 'r'])
示例#6
0
 def test_type_is_instance_raises(self):
     with self.assertRaises(EnvError):
         kwargs = {"param": 0}
         _get_default_aux('param',
                          kwargs,
                          defaultClassApp=int,
                          isclass=True)
示例#7
0
 def test_type_not_subtype_of_defaultClassApp_raises(self):
     with self.assertRaises(EnvError):
         kwargs = {"param": str}
         _get_default_aux('param',
                          kwargs,
                          defaultClassApp=int,
                          isclass=True)
示例#8
0
 def test_class_not_instance_of_defaultClassApp_raises(self):
     with self.assertRaises(EnvError):
         kwargs = {"param": int}
         _get_default_aux('param',
                          kwargs,
                          defaultClassApp=str,
                          isclass=False)
示例#9
0
 def test_give_class_nodefault(self):
     kwargs = {"param": str}
     param = _get_default_aux('param',
                              kwargs,
                              defaultClass=str,
                              defaultClassApp=str,
                              msg_error="bad stuff",
                              isclass=True)
     assert param == str, "This should have returned \"toto\""
示例#10
0
 def test_give_class_default(self):
     kwargs = {}
     param = _get_default_aux('param',
                              kwargs,
                              defaultClass=str,
                              defaultClassApp=str,
                              msg_error="bad stuff",
                              isclass=True)
     assert param == str, "This should have returned the empty string"
示例#11
0
 def test_give_instance_nodefault(self):
     kwargs = {"param": "toto"}
     param = _get_default_aux('param',
                              kwargs,
                              defaultClass=str,
                              defaultClassApp=str,
                              msg_error="bad stuff",
                              isclass=False)
     assert param == "toto", "This should have returned \"toto\""
示例#12
0
def make_old(name_env="case14_realistic", **kwargs):
    """
    .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

    (DEPRECATED) This function is a shortcut to rapidly create some (pre defined) environments within the grid2op Framework.

    For now, only the environment corresponding to the IEEE "case14" powergrid, with some pre defined chronics
    is available.

    Other environments, with different powergrids will be made available in the future.

    It mimic the ``gym.make`` function.

    Parameters
    ----------
    name_env: ``str``
        Name of the environment to create.

    param: ``grid2op.Parameters.Parameters``, optional
        Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an
        markov decision process, and some internal

    backend: ``grid2op.Backend.Backend``, optional
        The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`.

    action_class: ``type``, optional
        Type of BaseAction the BaseAgent will be able to perform.
        If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction`

    observation_class: ``type``, optional
        Type of BaseObservation the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation`

    reward_class: ``type``, optional
        Type of reward signal the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward`

    gamerules_class: ``type``, optional
        Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints.
        If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules`

    grid_path: ``str``, optional
        The path where the powergrid is located.
        If provided it must be a string, and point to a valid file present on the hard drive.

    data_feeding_kwargs: ``dict``, optional
        Dictionnary that is used to build the `data_feeding` (chronics) objects.

    chronics_class: ``type``, optional
        The type of chronics that represents the dynamics of the Environment created. Usually they come from different
        folders.

    data_feeding: ``type``, optional
        The type of chronics handler you want to use.

    chronics_path: ``str``
        Path where to look for the chronics dataset.

    volagecontroler_class: ``type``, optional
        The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to

    other_rewards: ``dict``, optional
        Dictionnary with other rewards we might want to look at at during training. It is given as a dictionnary with
        keys the name of the reward, and the values a class representing the new variables.

    Returns
    -------
    env: :class:`grid2op.Environment.Environment`
        The created environment.
    """

    warnings.warn("make_old is deprecated. Please consider using make instead")
    for el in kwargs:
        if not el in ALLOWED_KWARGS_MAKE:
            raise EnvError(
                "Unknown keyword argument \"{}\" used to create an Environement. "
                "No Environment will be created. "
                "Accepted keyword arguments are {}".format(
                    el, sorted(ALLOWED_KWARGS_MAKE)))

    # first extract parameters that doesn't not depend on the powergrid

    ## the parameters of the game, thermal limits threshold, simulate cascading failure, powerflow mode etc. (the gamification of the game)
    msg_error = "The parameters of the environment (keyword \"param\") must be an instance of grid2op.Parameters"
    param = _get_default_aux('param',
                             kwargs,
                             defaultClass=Parameters,
                             defaultClassApp=Parameters,
                             msg_error=msg_error)

    ## the backend use, to compute the powerflow
    msg_error = "The backend of the environment (keyword \"backend\") must be an instance of grid2op.Backend"
    backend = _get_default_aux("backend",
                               kwargs,
                               defaultClass=PandaPowerBackend,
                               defaultClassApp=Backend,
                               msg_error=msg_error)

    ## type of observation the agent will receive
    msg_error = "The type of observation of the environment (keyword \"observation_class\")"
    msg_error += " must be a subclass of grid2op.BaseObservation"
    observation_class = _get_default_aux("observation_class",
                                         kwargs,
                                         defaultClass=CompleteObservation,
                                         defaultClassApp=BaseObservation,
                                         msg_error=msg_error,
                                         isclass=True)

    ## type of rules of the game (mimic the operationnal constraints)
    msg_error = "The path where the data is located (keyword \"chronics_path\") should be a string."
    chronics_path = _get_default_aux("chronics_path",
                                     kwargs,
                                     defaultClassApp=str,
                                     defaultinstance='',
                                     msg_error=msg_error)

    # bulid the default parameters for each case file
    data_feeding_default_class = ChronicsHandler
    gamerules_class = AlwaysLegal
    defaultinstance_chronics_kwargs = {}
    if name_env.lower() == "case14_fromfile":
        default_grid_path = CASE_14_FILE
        if chronics_path == '':
            chronics_path = CHRONICS_MLUTIEPISODE

        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
    elif name_env.lower() == "l2rpn_2019":
        warnings.warn(
            "You are using the \"l2rpn_2019\" environmnet, which will be remove from this package in "
            "future versions. Please use \"make_new\" to download the real l2rpn dataset."
        )
        if chronics_path == '':
            msg_error = "Default chronics (provided in this package) cannot be used with the environment "
            msg_error += "\"l2rpn_2019\". Please download the training data using either the method described in" \
                         "Grid2Op/l2rpn_2019/README.md (if you downloaded the github repository) or\n" \
                         "running the command line script (in a terminal):\n" \
                         "python -m grid2op.download --name \"l2rpn_2019\" --path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD"
            raise EnvError(msg_error)
        default_grid_path = L2RPN2019_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": ReadPypowNetData
        }
        default_name_converter = L2RPN2019_DICT_NAMES
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case5_example":
        if chronics_path == '':
            chronics_path = EXAMPLE_CHRONICSPATH

        default_grid_path = EXAMPLE_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case14_test":
        if chronics_path == '':
            chronics_path = case14_test_CHRONICSPATH
            warnings.warn(
                "Your are using a case designed for testing purpose. Consider using the \"case14_redisp\" "
                "environment instead.")

        default_grid_path = case14_test_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAndDispatchAction
        default_reward_class = RedispReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case14_redisp":
        if chronics_path == '':
            chronics_path = case14_redisp_CHRONICSPATH
            warnings.warn(
                "Your are using only 2 chronics for this environment. More can be download by running, "
                "from a command line:\n"
                "python -m grid2op.download --name \"case14_redisp\" "
                "--path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD\DATA")

        default_grid_path = case14_redisp_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAndDispatchAction
        default_reward_class = RedispReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "case14_realistic":
        if chronics_path == '':
            chronics_path = case14_real_CHRONICSPATH
            warnings.warn(
                "Your are using only 2 chronics for this environment. More can be download by running, "
                "from a command line:\n"
                "python -m grid2op.download --name \"case14_realistic\" "
                "--path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD\DATA")

        default_grid_path = case14_real_CASEFILE
        defaultinstance_chronics_kwargs = {
            "chronicsClass": Multifolder,
            "path": chronics_path,
            "gridvalueClass": GridStateFromFileWithForecasts
        }
        default_name_converter = {}
        default_action_class = TopologyAndDispatchAction
        default_reward_class = RedispReward
        gamerules_class = DefaultRules
    elif name_env.lower() == "blank":
        default_name_converter = {}
        default_grid_path = ""
        default_action_class = TopologyAction
        default_reward_class = L2RPNReward
        gamerules_class = AlwaysLegal
    else:
        raise UnknownEnv(
            "Unknown Environment named \"{}\". Current known environments are \"case14_fromfile\" "
            "(default), \"case5_example\", \"case14_redisp\", \"case14_realistic\" "
            "and \"l2rpn_2019\"".format(name_env))

    if "chronicsClass" not in defaultinstance_chronics_kwargs:
        defaultinstance_chronics_kwargs["chronicsClass"] = ChangeNothing

    # extract powergrid dependant parameters
    ## type of rules of the game (mimic the operationnal constraints)
    msg_error = "The type of rules of the environment (keyword \"gamerules_class\")"
    msg_error += " must be a subclass of grid2op.BaseRules"
    gamerules_class = _get_default_aux("gamerules_class",
                                       kwargs,
                                       defaultClass=gamerules_class,
                                       defaultClassApp=BaseRules,
                                       msg_error=msg_error,
                                       isclass=True)

    ## type of reward the agent will receive
    msg_error = "The type of observation of the environment (keyword \"reward_class\")"
    msg_error += " must be a subclass of grid2op.BaseReward"
    reward_class = _get_default_aux("reward_class",
                                    kwargs,
                                    defaultClass=default_reward_class,
                                    defaultClassApp=BaseReward,
                                    msg_error=msg_error,
                                    isclass=True)

    ## type of action the BaseAgent can perform
    msg_error = "The type of action of the environment (keyword \"action_class\") must be a subclass of grid2op.BaseAction"
    action_class = _get_default_aux("action_class",
                                    kwargs,
                                    defaultClass=default_action_class,
                                    defaultClassApp=BaseAction,
                                    msg_error=msg_error,
                                    isclass=True)

    ## the powergrid path to use
    msg_error = "The path where the grid is located (keyword \"grid_path\") should be a string."
    grid_path = _get_default_aux("grid_path",
                                 kwargs,
                                 defaultClassApp=str,
                                 defaultinstance=default_grid_path,
                                 msg_error=msg_error)

    ##
    msg_error = "The converter between names (keyword \"names_chronics_to_backend\") should be a dictionnary."
    names_chronics_to_backend = _get_default_aux(
        "names_chronics_to_backend",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=default_name_converter,
        msg_error=msg_error)

    ## the chronics to use
    ### the arguments used to build the data, note that the arguments must be compatible with the chronics class
    msg_error = "The argument to build the data generation process [chronics] (keyword \"data_feeding_kwargs\")"
    msg_error += " should be a dictionnary."
    data_feeding_kwargs = _get_default_aux(
        "data_feeding_kwargs",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=defaultinstance_chronics_kwargs,
        msg_error=msg_error)
    for el in defaultinstance_chronics_kwargs:
        if not el in data_feeding_kwargs:
            data_feeding_kwargs[el] = defaultinstance_chronics_kwargs[el]

    ### the chronics generator
    msg_error = "The argument to build the data generation process [chronics] (keyword \"chronics_class\")"
    msg_error += " should be a class that inherit grid2op.ChronicsHandler.GridValue."
    chronics_class_used = _get_default_aux(
        "chronics_class",
        kwargs,
        defaultClassApp=GridValue,
        defaultClass=data_feeding_kwargs["chronicsClass"],
        msg_error=msg_error,
        isclass=True)
    data_feeding_kwargs["chronicsClass"] = chronics_class_used

    ### the chronics generator
    msg_error = "The argument to build the data generation process [chronics] (keyword \"data_feeding\")"
    msg_error += " should be a class that inherit grid2op.ChronicsHandler.ChronicsHandler."
    data_feeding = _get_default_aux("data_feeding",
                                    kwargs,
                                    defaultClassApp=ChronicsHandler,
                                    defaultClass=data_feeding_default_class,
                                    build_kwargs=data_feeding_kwargs,
                                    msg_error=msg_error)

    ### controler for voltages
    msg_error = "The argument to build the online controler for chronics (keyword \"volagecontroler_class\")"
    msg_error += " should be a class that inherit grid2op.VoltageControler.ControlVoltageFromFile."
    volagecontroler_class = _get_default_aux(
        "volagecontroler_class",
        kwargs,
        defaultClassApp=ControlVoltageFromFile,
        defaultClass=ControlVoltageFromFile,
        msg_error=msg_error,
        isclass=True)

    ### other rewards
    msg_error = "The argument to build the online controler for chronics (keyword \"other_rewards\")"
    msg_error += " should be dictionnary."
    other_rewards = _get_default_aux("other_rewards",
                                     kwargs,
                                     defaultClassApp=dict,
                                     defaultinstance={},
                                     msg_error=msg_error,
                                     isclass=False)

    # Opponent
    opponent_action_class = _get_default_aux(
        "opponent_action_class",
        kwargs,
        defaultClassApp=BaseAction,
        defaultClass=DontAct,
        msg_error=ERR_MSG_KWARGS["opponent_action_class"],
        isclass=True)
    opponent_class = _get_default_aux(
        "opponent_class",
        kwargs,
        defaultClassApp=BaseOpponent,
        defaultClass=BaseOpponent,
        msg_error=ERR_MSG_KWARGS["opponent_class"],
        isclass=True)
    opponent_init_budget = _get_default_aux(
        "opponent_init_budget",
        kwargs,
        defaultClassApp=float,
        defaultinstance=0.,
        msg_error=ERR_MSG_KWARGS["opponent_init_budget"],
        isclass=False)
    if not os.path.exists(grid_path):
        raise EnvError(
            "There is noting at \"{}\" where the powergrid should be located".
            format(os.path.abspath(grid_path)))

    env = Environment(init_grid_path=grid_path,
                      chronics_handler=data_feeding,
                      backend=backend,
                      parameters=param,
                      names_chronics_to_backend=names_chronics_to_backend,
                      actionClass=action_class,
                      observationClass=observation_class,
                      rewardClass=reward_class,
                      legalActClass=gamerules_class,
                      voltagecontrolerClass=volagecontroler_class,
                      other_rewards=other_rewards,
                      opponent_action_class=opponent_action_class,
                      opponent_class=opponent_class,
                      opponent_init_budget=opponent_init_budget,
                      name=name_env)

    # update the thermal limit if any
    if name_env.lower() == "case14_test":
        env.set_thermal_limit(case14_test_TH_LIM)
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "case14_redisp":
        env.set_thermal_limit(case14_redisp_TH_LIM)
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "case14_realistic":
        env.set_thermal_limit(case14_real_TH_LIM)
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "l2rpn_2019":
        env.attach_layout(CASE_14_L2RPN2019_LAYOUT)
    elif name_env.lower() == "case5_example":
        env.attach_layout(CASE_5_GRAPH_LAYOUT)
    return env
示例#13
0
 def test_use_sentinel_arg_raises(self):
     with self.assertRaises(RuntimeError):
         _get_default_aux('param', {}, str, _sentinel=True)
示例#14
0
def make_from_dataset_path(dataset_path="/", _add_to_name="", **kwargs):
    """
    .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\

        Prefer using the :func:`grid2op.make` function.

    This function is a shortcut to rapidly create environments within the grid2op Framework. We don't
    recommend using directly this function. Prefer using the :func:`make` function.

    It mimic the ``gym.make`` function.

    .. _Parameters-make-from-path:

    Parameters
    ----------

    dataset_path: ``str``
        Path to the dataset folder

    param: ``grid2op.Parameters.Parameters``, optional
        Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an
        markov decision process, and some internal

    backend: ``grid2op.Backend.Backend``, optional
        The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`.

    action_class: ``type``, optional
        Type of BaseAction the BaseAgent will be able to perform.
        If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction`

    observation_class: ``type``, optional
        Type of BaseObservation the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation`

    reward_class: ``type``, optional
        Type of reward signal the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward`

    other_rewards: ``dict``, optional
        Used to additional information than the "info" returned value after a call to env.step.

    gamerules_class: ``type``, optional
        Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints.
        If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules`

    data_feeding_kwargs: ``dict``, optional
        Dictionnary that is used to build the `data_feeding` (chronics) objects.

    chronics_class: ``type``, optional
        The type of chronics that represents the dynamics of the Environment created. Usually they come from different
        folders.

    data_feeding: ``type``, optional
        The type of chronics handler you want to use.

    volagecontroler_class: ``type``, optional
        The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to

    chronics_path: ``str``
        Path where to look for the chronics dataset (optional)

    grid_path: ``str``, optional
        The path where the powergrid is located.
        If provided it must be a string, and point to a valid file present on the hard drive.

    difficulty: ``str``, optional
        the difficulty level. If present it starts from "0" the "easiest" but least realistic mode. In the case of the
        dataset being used in the l2rpn competition, the level used for the competition is "competition" ("hardest" and
        most realistic mode). If multiple difficulty levels are available, the most realistic one
        (the "hardest") is the default choice.

    opponent_action_class: ``type``, optional
        The action class used for the opponent. The opponent will not be able to use action that are invalid with
        the given action class provided. It defaults to :class:`grid2op.Action.DontAct` which forbid any type
        of action possible.

    opponent_class: ``type``, optional
        The opponent class to use. The default class is :class:`grid2op.Opponent.BaseOpponent` which is a type
        of opponents that does nothing.

    opponent_init_budget: ``float``, optional
        The initial budget of the opponent. It defaults to 0.0 which means the opponent cannot perform any action
        if this is not modified.

    opponent_attack_duration: ``int``, optional
        The number of time steps an attack from the opponent lasts.

    opponent_attack_cooldown: ``int``, optional
        The number of time steps the opponent as to wait for an attack.

    opponent_budget_per_ts: ``float``, optional
        The increase of the opponent budget per time step. Each time step the opponent see its budget increase. It
        defaults to 0.0.

    opponent_budget_class: ``type``, optional
        defaults: :class:`grid2op.Opponent.UnlimitedBudget`

    _add_to_name:
        Internal, used for test only. Do not attempt to modify under any circumstances.

    Returns
    -------
    env: :class:`grid2op.Environment.Environment`
        The created environment with the given properties.

    """
    # Compute and find root folder
    _check_path(dataset_path, "Dataset root directory")
    dataset_path_abs = os.path.abspath(dataset_path)

    # Compute env name from directory name
    name_env = os.path.split(dataset_path_abs)[1]

    # Compute and find chronics folder
    chronics_path = _get_default_aux("chronics_path",
                                     kwargs,
                                     defaultClassApp=str,
                                     defaultinstance='',
                                     msg_error=ERR_MSG_KWARGS["chronics_path"])
    if chronics_path == "":
        # if no "chronics_path" argument is provided, look into the "chronics" folder
        chronics_path_abs = os.path.abspath(
            os.path.join(dataset_path_abs, NAME_CHRONICS_FOLDER))
    else:
        # otherwise use it
        chronics_path_abs = os.path.abspath(chronics_path)
    _check_path(chronics_path_abs, "Dataset chronics folder")

    # Compute and find backend/grid file
    grid_path = _get_default_aux("grid_path",
                                 kwargs,
                                 defaultClassApp=str,
                                 defaultinstance="",
                                 msg_error=ERR_MSG_KWARGS["grid_path"])
    if grid_path == "":
        grid_path_abs = os.path.abspath(
            os.path.join(dataset_path_abs, NAME_GRID_FILE))
    else:
        grid_path_abs = os.path.abspath(grid_path)
    _check_path(grid_path_abs, "Dataset power flow solver configuration")

    # Compute and find grid layout file
    grid_layout_path_abs = os.path.abspath(
        os.path.join(dataset_path_abs, NAME_GRID_LAYOUT_FILE))
    _check_path(grid_layout_path_abs, "Dataset grid layout")

    # Check provided config overrides are valid
    _check_kwargs(kwargs)

    # Compute and find config file
    config_path_abs = os.path.abspath(
        os.path.join(dataset_path_abs, NAME_CONFIG_FILE))
    _check_path(config_path_abs, "Dataset environment configuration")

    # Read config file
    try:
        spec = importlib.util.spec_from_file_location("config.config",
                                                      config_path_abs)
        config_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(config_module)
        config_data = config_module.config
    except Exception as e:
        print(e)
        raise EnvError("Invalid dataset config file: {}".format(
            config_path_abs)) from None

    # Get graph layout
    try:
        with open(grid_layout_path_abs) as layout_fp:
            graph_layout = json.load(layout_fp)
    except Exception as e:
        raise EnvError("Dataset {} doesn't have a valid graph layout".format(
            config_path_abs))

    # Get thermal limits
    thermal_limits = None
    if "thermal_limits" in config_data:
        thermal_limits = config_data["thermal_limits"]

    # Get chronics_to_backend
    name_converter = None
    if "names_chronics_to_grid" in config_data:
        name_converter = config_data["names_chronics_to_grid"]
    if name_converter is None:
        name_converter = {}
    names_chronics_to_backend = _get_default_aux(
        "names_chronics_to_backend",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=name_converter,
        msg_error=ERR_MSG_KWARGS["names_chronics_to_grid"])
    # Get default backend class
    backend_class_cfg = PandaPowerBackend
    if "backend_class" in config_data and config_data[
            "backend_class"] is not None:
        backend_class_cfg = config_data["backend_class"]
    ## Create the backend, to compute the powerflow
    backend = _get_default_aux("backend",
                               kwargs,
                               defaultClass=backend_class_cfg,
                               defaultClassApp=Backend,
                               msg_error=ERR_MSG_KWARGS["backend"])

    # Get default observation class
    observation_class_cfg = CompleteObservation
    if "observation_class" in config_data and config_data[
            "observation_class"] is not None:
        observation_class_cfg = config_data["observation_class"]
    ## Setup the type of observation the agent will receive
    observation_class = _get_default_aux(
        "observation_class",
        kwargs,
        defaultClass=observation_class_cfg,
        isclass=True,
        defaultClassApp=BaseObservation,
        msg_error=ERR_MSG_KWARGS["observation_class"])

    ## Create the parameters of the game, thermal limits threshold,
    # simulate cascading failure, powerflow mode etc. (the gamification of the game)
    if "param" in kwargs:
        param = _get_default_aux('param',
                                 kwargs,
                                 defaultClass=Parameters,
                                 defaultClassApp=Parameters,
                                 msg_error=ERR_MSG_KWARGS["param"])
    else:
        # param is not in kwargs
        param = Parameters()
        json_path = os.path.join(dataset_path_abs, "difficulty_levels.json")
        if os.path.exists(json_path):
            with open(json_path, "r", encoding="utf-8") as f:
                dict_ = json.load(f)
            available_parameters = sorted(dict_.keys())
            if DIFFICULTY_NAME in kwargs:
                # player enters a difficulty levels
                my_difficulty = kwargs[DIFFICULTY_NAME]
                try:
                    my_difficulty = str(my_difficulty)
                except:
                    raise EnvError(
                        "Impossible to convert your difficulty into a valid string. Please make sure to "
                        "pass a string (eg \"2\") and not something else (eg. int(2)) as a difficulty"
                    )
                if my_difficulty in dict_:
                    param.init_from_dict(dict_[my_difficulty])
                else:
                    raise EnvError(ERR_MSG_KWARGS[DIFFICULTY_NAME].format(
                        difficulty=my_difficulty,
                        difficulties=available_parameters))
            else:
                # no difficulty name provided, i need to chose the most suited one
                if CHALLENGE_NAME in dict_:
                    param.init_from_dict(dict_[CHALLENGE_NAME])
                else:
                    # i chose the most difficult one
                    available_parameters_int = {}
                    for el in available_parameters:
                        try:
                            int_ = int(el)
                            available_parameters_int[int_] = el
                        except:
                            pass
                    max_ = np.max(list(available_parameters_int.keys()))
                    keys_ = available_parameters_int[max_]
                    param.init_from_dict(dict_[keys_])
        else:
            json_path = os.path.join(dataset_path_abs, "parameters.json")
            if os.path.exists(json_path):
                param.init_from_json(json_path)

    # Get default rules class
    rules_class_cfg = DefaultRules
    if "rules_class" in config_data and config_data["rules_class"] is not None:
        rules_class_cfg = config_data["rules_class"]
    ## Create the rules of the game (mimic the operationnal constraints)
    gamerules_class = _get_default_aux(
        "gamerules_class",
        kwargs,
        defaultClass=rules_class_cfg,
        defaultClassApp=BaseRules,
        msg_error=ERR_MSG_KWARGS["gamerules_class"],
        isclass=True)

    # Get default reward class
    reward_class_cfg = L2RPNReward
    if "reward_class" in config_data and config_data[
            "reward_class"] is not None:
        reward_class_cfg = config_data["reward_class"]
    ## Setup the reward the agent will receive
    reward_class = _get_default_aux("reward_class",
                                    kwargs,
                                    defaultClass=reward_class_cfg,
                                    defaultClassApp=BaseReward,
                                    msg_error=ERR_MSG_KWARGS["reward_class"],
                                    isclass=True)

    # Get default BaseAction class
    action_class_cfg = BaseAction
    if "action_class" in config_data and config_data[
            "action_class"] is not None:
        action_class_cfg = config_data["action_class"]
    ## Setup the type of action the BaseAgent can perform
    action_class = _get_default_aux("action_class",
                                    kwargs,
                                    defaultClass=action_class_cfg,
                                    defaultClassApp=BaseAction,
                                    msg_error=ERR_MSG_KWARGS["action_class"],
                                    isclass=True)

    # Get default Voltage class
    voltage_class_cfg = ControlVoltageFromFile
    if "voltage_class" in config_data and config_data[
            "voltage_class"] is not None:
        voltage_class_cfg = config_data["voltage_class"]
    ### Create controler for voltages
    volagecontroler_class = _get_default_aux(
        "volagecontroler_class",
        kwargs,
        defaultClassApp=voltage_class_cfg,
        defaultClass=ControlVoltageFromFile,
        msg_error=ERR_MSG_KWARGS["voltagecontroler_class"],
        isclass=True)

    # Get default Chronics class
    chronics_class_cfg = ChangeNothing
    if "chronics_class" in config_data and config_data[
            "chronics_class"] is not None:
        chronics_class_cfg = config_data["chronics_class"]
    # Get default Grid class
    grid_value_class_cfg = GridStateFromFile
    if "grid_value_class" in config_data and config_data[
            "grid_value_class"] is not None:
        grid_value_class_cfg = config_data["grid_value_class"]

    ## the chronics to use
    ### the arguments used to build the data, note that the arguments must be compatible with the chronics class
    default_chronics_kwargs = {
        "chronicsClass": chronics_class_cfg,
        "path": chronics_path_abs,
        "gridvalueClass": grid_value_class_cfg
    }

    data_feeding_kwargs = _get_default_aux(
        "data_feeding_kwargs",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=default_chronics_kwargs,
        msg_error=ERR_MSG_KWARGS["data_feeding_kwargs"])
    for el in default_chronics_kwargs:
        if not el in data_feeding_kwargs:
            data_feeding_kwargs[el] = default_chronics_kwargs[el]

    ### the chronics generator
    chronics_class_used = _get_default_aux(
        "chronics_class",
        kwargs,
        defaultClassApp=GridValue,
        defaultClass=data_feeding_kwargs["chronicsClass"],
        msg_error=ERR_MSG_KWARGS["chronics_class"],
        isclass=True)
    data_feeding_kwargs["chronicsClass"] = chronics_class_used
    data_feeding = _get_default_aux(
        "data_feeding",
        kwargs,
        defaultClassApp=ChronicsHandler,
        defaultClass=ChronicsHandler,
        build_kwargs=data_feeding_kwargs,
        msg_error=ERR_MSG_KWARGS["chronics_handler"])

    ### other rewards
    other_rewards = _get_default_aux("other_rewards",
                                     kwargs,
                                     defaultClassApp=dict,
                                     defaultinstance={},
                                     msg_error=ERR_MSG_KWARGS["other_rewards"],
                                     isclass=False)

    # Opponent
    chronics_class_cfg = DontAct
    if "opponent_action_class" in config_data and config_data[
            "opponent_action_class"] is not None:
        chronics_class_cfg = config_data["opponent_action_class"]
    opponent_action_class = _get_default_aux(
        "opponent_action_class",
        kwargs,
        defaultClassApp=BaseAction,
        defaultClass=chronics_class_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_action_class"],
        isclass=True)
    opponent_class_cfg = BaseOpponent
    if "opponent_class" in config_data and config_data[
            "opponent_class"] is not None:
        opponent_class_cfg = config_data["opponent_class"]
    opponent_class = _get_default_aux(
        "opponent_class",
        kwargs,
        defaultClassApp=BaseOpponent,
        defaultClass=opponent_class_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_class"],
        isclass=True)
    opponent_budget_class_cfg = NeverAttackBudget
    if "opponent_budget_class" in config_data and config_data[
            "opponent_budget_class"] is not None:
        opponent_budget_class_cfg = config_data["opponent_budget_class"]
    opponent_budget_class = _get_default_aux(
        "opponent_budget_class",
        kwargs,
        defaultClassApp=BaseActionBudget,
        defaultClass=opponent_budget_class_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_budget_class"],
        isclass=True)
    opponent_init_budget_cfg = 0.
    if "opponent_init_budget" in config_data and config_data[
            "opponent_init_budget"] is not None:
        opponent_init_budget_cfg = config_data["opponent_init_budget"]
    opponent_init_budget = _get_default_aux(
        "opponent_init_budget",
        kwargs,
        defaultClassApp=float,
        defaultinstance=opponent_init_budget_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_init_budget"],
        isclass=False)
    opponent_budget_per_ts_cfg = 0.
    if "opponent_budget_per_ts" in config_data and config_data[
            "opponent_budget_per_ts"] is not None:
        opponent_budget_per_ts_cfg = config_data["opponent_budget_per_ts"]
    opponent_budget_per_ts = _get_default_aux(
        "opponent_budget_per_ts",
        kwargs,
        defaultClassApp=float,
        defaultinstance=opponent_budget_per_ts_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_budget_per_ts"],
        isclass=False)
    opponent_attack_duration_cfg = 0
    if "opponent_attack_duration" in config_data and config_data[
            "opponent_attack_duration"] is not None:
        opponent_attack_duration_cfg = config_data["opponent_attack_duration"]
    opponent_attack_duration = _get_default_aux(
        "opponent_attack_duration",
        kwargs,
        defaultClassApp=int,
        defaultinstance=opponent_attack_duration_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_attack_duration"],
        isclass=False)
    opponent_attack_cooldown_cfg = 99999
    if "opponent_attack_cooldown" in config_data and config_data[
            "opponent_attack_cooldown"] is not None:
        opponent_attack_cooldown_cfg = config_data["opponent_attack_cooldown"]
    opponent_attack_cooldown = _get_default_aux(
        "opponent_attack_cooldown",
        kwargs,
        defaultClassApp=int,
        defaultinstance=opponent_attack_cooldown_cfg,
        msg_error=ERR_MSG_KWARGS["opponent_attack_cooldown"],
        isclass=False)
    kwargs_opponent_cfg = {}
    if "kwargs_opponent" in config_data and config_data[
            "kwargs_opponent"] is not None:
        kwargs_opponent_cfg = config_data["kwargs_opponent"]
    kwargs_opponent = _get_default_aux(
        "kwargs_opponent",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=kwargs_opponent_cfg,
        msg_error=ERR_MSG_KWARGS["kwargs_opponent"],
        isclass=False)

    # Finally instanciate env from config & overrides
    env = Environment(
        init_grid_path=grid_path_abs,
        chronics_handler=data_feeding,
        backend=backend,
        parameters=param,
        name=name_env + _add_to_name,
        names_chronics_to_backend=names_chronics_to_backend,
        actionClass=action_class,
        observationClass=observation_class,
        rewardClass=reward_class,
        legalActClass=gamerules_class,
        voltagecontrolerClass=volagecontroler_class,
        other_rewards=other_rewards,
        opponent_action_class=opponent_action_class,
        opponent_class=opponent_class,
        opponent_init_budget=opponent_init_budget,
        opponent_attack_duration=opponent_attack_duration,
        opponent_attack_cooldown=opponent_attack_cooldown,
        opponent_budget_per_ts=opponent_budget_per_ts,
        opponent_budget_class=opponent_budget_class,
        kwargs_opponent=kwargs_opponent,
    )

    # Update the thermal limit if any
    if thermal_limits is not None:
        env.set_thermal_limit(thermal_limits)

    # Set graph layout if not None and not an empty dict
    if graph_layout is not None and graph_layout:
        env.attach_layout(graph_layout)

    return env
示例#15
0
def make_from_dataset_path(dataset_path="/", **kwargs):
    """
    This function is a shortcut to rapidly create environments within the grid2op Framework.

    It mimic the ``gym.make`` function.

    Parameters
    ----------
    dataset_path: ``str``
        Path to the dataset folder

    param: ``grid2op.Parameters.Parameters``, optional
        Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an
        markov decision process, and some internal

    backend: ``grid2op.Backend.Backend``, optional
        The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`.

    action_class: ``type``, optional
        Type of BaseAction the BaseAgent will be able to perform.
        If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction`

    observation_class: ``type``, optional
        Type of BaseObservation the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation`

    reward_class: ``type``, optional
        Type of reward signal the BaseAgent will receive.
        If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward`

    gamerules_class: ``type``, optional
        Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints.
        If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules`

    data_feeding_kwargs: ``dict``, optional
        Dictionnary that is used to build the `data_feeding` (chronics) objects.

    chronics_class: ``type``, optional
        The type of chronics that represents the dynamics of the Environment created. Usually they come from different
        folders.

    data_feeding: ``type``, optional
        The type of chronics handler you want to use.

    volagecontroler_class: ``type``, optional
        The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to

    chronics_path: ``str``
        Path where to look for the chronics dataset (optional)

    grid_path: ``str``, optional
        The path where the powergrid is located.
        If provided it must be a string, and point to a valid file present on the hard drive.

    Returns
    -------
    env: :class:`grid2op.Environment.Environment`
        The created environment.
    """
    # Compute and find root folder
    _check_path(dataset_path, "Dataset root directory")
    dataset_path_abs = os.path.abspath(dataset_path)
    # Compute env name from directory name
    name_env = os.path.split(dataset_path_abs)[1]

    # Compute and find chronics folder
    chronics_path = _get_default_aux("chronics_path",
                                     kwargs,
                                     defaultClassApp=str,
                                     defaultinstance='',
                                     msg_error=ERR_MSG_KWARGS["chronics_path"])
    if chronics_path == "":
        # if no "chronics_path" argument is provided, look into the "chronics" folder
        chronics_path_abs = os.path.abspath(
            os.path.join(dataset_path_abs, NAME_CHRONICS_FOLDER))
    else:
        # otherwise use it
        chronics_path_abs = os.path.abspath(chronics_path)
    _check_path(chronics_path_abs, "Dataset chronics folder")

    # Compute and find backend/grid file
    grid_path = _get_default_aux("grid_path",
                                 kwargs,
                                 defaultClassApp=str,
                                 defaultinstance="",
                                 msg_error=ERR_MSG_KWARGS["grid_path"])
    if grid_path == "":
        grid_path_abs = os.path.abspath(
            os.path.join(dataset_path_abs, NAME_GRID_FILE))
    else:
        grid_path_abs = os.path.abspath(grid_path)
    _check_path(grid_path_abs, "Dataset power flow solver configuration")

    # Compute and find grid layout file
    grid_layout_path_abs = os.path.abspath(
        os.path.join(dataset_path_abs, NAME_GRID_LAYOUT_FILE))
    _check_path(grid_layout_path_abs, "Dataset grid layout")

    # Check provided config overrides are valid
    _check_kwargs(kwargs)

    # Compute and find config file
    config_path_abs = os.path.abspath(
        os.path.join(dataset_path_abs, NAME_CONFIG_FILE))
    _check_path(config_path_abs, "Dataset environment configuration")

    # Read config file
    try:
        spec = importlib.util.spec_from_file_location("config.config",
                                                      config_path_abs)
        config_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(config_module)
        config_data = config_module.config
    except Exception as e:
        print(e)
        raise EnvError("Invalid dataset config file: {}".format(
            config_path_abs)) from None

    # Get graph layout
    try:
        with open(grid_layout_path_abs) as layout_fp:
            graph_layout = json.load(layout_fp)
    except Exception as e:
        raise EnvError("Dataset {} doesn't have a valid graph layout".format(
            config_path_abs))

    # Get thermal limits
    thermal_limits = None
    if "thermal_limits" in config_data:
        thermal_limits = config_data["thermal_limits"]

    # Get chronics_to_backend
    name_converter = None
    if "names_chronics_to_grid" in config_data:
        name_converter = config_data["names_chronics_to_grid"]
    if name_converter is None:
        name_converter = {}
    names_chronics_to_backend = _get_default_aux(
        "names_chronics_to_backend",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=name_converter,
        msg_error=ERR_MSG_KWARGS["names_chronics_to_grid"])
    # Get default backend class
    backend_class_cfg = PandaPowerBackend
    if "backend_class" in config_data and config_data[
            "backend_class"] is not None:
        backend_class_cfg = config_data["backend_class"]
    ## Create the backend, to compute the powerflow
    backend = _get_default_aux("backend",
                               kwargs,
                               defaultClass=backend_class_cfg,
                               defaultClassApp=Backend,
                               msg_error=ERR_MSG_KWARGS["backend"])

    # Get default observation class
    observation_class_cfg = CompleteObservation
    if "observation_class" in config_data and config_data[
            "observation_class"] is not None:
        observation_class_cfg = config_data["observation_class"]
    ## Setup the type of observation the agent will receive
    observation_class = _get_default_aux(
        "observation_class",
        kwargs,
        defaultClass=observation_class_cfg,
        isclass=True,
        defaultClassApp=BaseObservation,
        msg_error=ERR_MSG_KWARGS["observation_class"])

    ## Create the parameters of the game, thermal limits threshold,
    # simulate cascading failure, powerflow mode etc. (the gamification of the game)
    param = _get_default_aux('param',
                             kwargs,
                             defaultClass=Parameters,
                             defaultClassApp=Parameters,
                             msg_error=ERR_MSG_KWARGS["param"])

    # Get default rules class
    rules_class_cfg = DefaultRules
    if "rules_class" in config_data and config_data["rules_class"] is not None:
        rules_class_cfg = config_data["rules_class"]
    ## Create the rules of the game (mimic the operationnal constraints)
    gamerules_class = _get_default_aux(
        "gamerules_class",
        kwargs,
        defaultClass=rules_class_cfg,
        defaultClassApp=BaseRules,
        msg_error=ERR_MSG_KWARGS["gamerules_class"],
        isclass=True)

    # Get default reward class
    reward_class_cfg = L2RPNReward
    if "reward_class" in config_data and config_data[
            "reward_class"] is not None:
        reward_class_cfg = config_data["reward_class"]
    ## Setup the reward the agent will receive
    reward_class = _get_default_aux("reward_class",
                                    kwargs,
                                    defaultClass=reward_class_cfg,
                                    defaultClassApp=BaseReward,
                                    msg_error=ERR_MSG_KWARGS["reward_class"],
                                    isclass=True)

    # Get default BaseAction class
    action_class_cfg = BaseAction
    if "action_class" in config_data and config_data[
            "action_class"] is not None:
        action_class_cfg = config_data["action_class"]
    ## Setup the type of action the BaseAgent can perform
    action_class = _get_default_aux("action_class",
                                    kwargs,
                                    defaultClass=action_class_cfg,
                                    defaultClassApp=BaseAction,
                                    msg_error=ERR_MSG_KWARGS["action_class"],
                                    isclass=True)

    # Get default Voltage class
    voltage_class_cfg = ControlVoltageFromFile
    if "voltage_class" in config_data and config_data[
            "voltage_class"] is not None:
        voltage_class_cfg = config_data["voltage_class"]
    ### Create controler for voltages
    volagecontroler_class = _get_default_aux(
        "volagecontroler_class",
        kwargs,
        defaultClassApp=voltage_class_cfg,
        defaultClass=ControlVoltageFromFile,
        msg_error=ERR_MSG_KWARGS["voltagecontroler_class"],
        isclass=True)

    # Get default Chronics class
    chronics_class_cfg = ChangeNothing
    if "chronics_class" in config_data and config_data[
            "chronics_class"] is not None:
        chronics_class_cfg = config_data["chronics_class"]
    # Get default Grid class
    grid_value_class_cfg = GridStateFromFile
    if "grid_value_class" in config_data and config_data[
            "grid_value_class"] is not None:
        grid_value_class_cfg = config_data["grid_value_class"]

    ## the chronics to use
    ### the arguments used to build the data, note that the arguments must be compatible with the chronics class
    default_chronics_kwargs = {
        "chronicsClass": chronics_class_cfg,
        "path": chronics_path_abs,
        "gridvalueClass": grid_value_class_cfg
    }

    data_feeding_kwargs = _get_default_aux(
        "data_feeding_kwargs",
        kwargs,
        defaultClassApp=dict,
        defaultinstance=default_chronics_kwargs,
        msg_error=ERR_MSG_KWARGS["data_feeding_kwargs"])
    for el in default_chronics_kwargs:
        if not el in data_feeding_kwargs:
            data_feeding_kwargs[el] = default_chronics_kwargs[el]

    ### the chronics generator
    chronics_class_used = _get_default_aux(
        "chronics_class",
        kwargs,
        defaultClassApp=GridValue,
        defaultClass=data_feeding_kwargs["chronicsClass"],
        msg_error=ERR_MSG_KWARGS["chronics_class"],
        isclass=True)
    data_feeding_kwargs["chronicsClass"] = chronics_class_used
    data_feeding = _get_default_aux(
        "data_feeding",
        kwargs,
        defaultClassApp=ChronicsHandler,
        defaultClass=ChronicsHandler,
        build_kwargs=data_feeding_kwargs,
        msg_error=ERR_MSG_KWARGS["chronics_handler"])

    ### other rewards
    other_rewards = _get_default_aux("other_rewards",
                                     kwargs,
                                     defaultClassApp=dict,
                                     defaultinstance={},
                                     msg_error=ERR_MSG_KWARGS["other_rewards"],
                                     isclass=False)

    # Opponent
    # TODO make that in config file of the default environment !!!
    opponent_action_class = _get_default_aux(
        "opponent_action_class",
        kwargs,
        defaultClassApp=BaseAction,
        defaultClass=DontAct,
        msg_error=ERR_MSG_KWARGS["opponent_action_class"],
        isclass=True)
    opponent_class = _get_default_aux(
        "opponent_class",
        kwargs,
        defaultClassApp=BaseOpponent,
        defaultClass=BaseOpponent,
        msg_error=ERR_MSG_KWARGS["opponent_class"],
        isclass=True)
    opponent_init_budget = _get_default_aux(
        "opponent_init_budget",
        kwargs,
        defaultClassApp=float,
        defaultinstance=0.,
        msg_error=ERR_MSG_KWARGS["opponent_init_budget"],
        isclass=False)

    # Finally instanciate env from config & overrides
    env = Environment(init_grid_path=grid_path_abs,
                      chronics_handler=data_feeding,
                      backend=backend,
                      parameters=param,
                      name=name_env,
                      names_chronics_to_backend=names_chronics_to_backend,
                      actionClass=action_class,
                      observationClass=observation_class,
                      rewardClass=reward_class,
                      legalActClass=gamerules_class,
                      voltagecontrolerClass=volagecontroler_class,
                      other_rewards=other_rewards,
                      opponent_action_class=opponent_action_class,
                      opponent_class=opponent_class,
                      opponent_init_budget=opponent_init_budget)

    # Update the thermal limit if any
    if thermal_limits is not None:
        env.set_thermal_limit(thermal_limits)

    # Set graph layout if not None and not an empty dict
    if graph_layout is not None and graph_layout:
        env.attach_layout(graph_layout)

    return env