# Copyright (c) 2019-2026, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.
from optparse import Option
import os
import copy
from typing import Optional
import warnings
from grid2op.Environment import Environment
from grid2op.Exceptions import EnvError
from grid2op.Space import GRID2OP_CLASSES_ENV_FOLDER
from grid2op.Space import DEFAULT_N_BUSBAR_PER_SUB, DEFAULT_ALLOW_DETACHMENT
from grid2op.MakeEnv.PathUtils import _aux_fix_backend_internal_classes
from .get_default_env_kwargs import (
get_default_env_kwargs,
_check_path,
MakeKwargsTypeHints,
Unpack
)
[docs]def make_from_dataset_path(
dataset_path: str="/",
logger=None,
experimental_read_from_local_dir: bool=False,
n_busbar: int=DEFAULT_N_BUSBAR_PER_SUB,
allow_detachment: bool=DEFAULT_ALLOW_DETACHMENT,
_add_cls_nm_bk: bool=True,
_add_to_name: str="",
_compat_glop_version: Optional[str]=None,
_overload_name_multimix: Optional[str]=None,
**kwargs: Unpack[MakeKwargsTypeHints],
) -> Environment:
"""
INTERNAL USE ONLY
.. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\
Prefer using the :func:`grid2op.make` function.
.. danger::
The :func:`grid2op.make` function can execute arbitrary code. Do not attempt
to "make" an environment for which you don't trust (or even know) the authors.
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
logger:
Something to pass to grid2op environment to be used as logger.
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`.
n_busbar: ``int``
Number of independant busbars allowed per substations. By default it's 2.
allow_detachment; ``bool``
Whether to allow loads/generators to be detached without a game over. By default False.
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_space_type: ``type``, optional
The type of opponent space to use. If provided, it must be a subclass of `OpponentSpace`.
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`
kwargs_observation: ``dict``
Key words used to initialize the observation. For example, in case of NoisyObservation,
it might be the standar error for each underlying distribution. It might
be more complicated for other type of custom observations but should be
deep copiable.
Each observation will be initialized (by the observation_space) with:
.. code-block:: python
obs = observation_class(obs_env=self.obs_env,
action_helper=self.action_helper_env,
random_prng=self.space_prng,
**kwargs_observation # <- this kwargs is used here
)
observation_backend_class:
The class used to build the observation backend (used for Simulator
obs.simulate and obs.get_forecasted_env). If provided, this should
be a type / class and not an instance of this class. (by default it's None)
observation_backend_kwargs:
The key-word arguments to build the observation backend (used for Simulator,
obs.simulate and obs.get_forecasted_env). This should be a dictionnary.
(by default it's None)
_add_to_name:
Internal, used for test only. Do not attempt to modify under any circumstances.
_compat_glop_version:
Internal, used for test only. Do not attempt to modify under any circumstances.
# TODO update doc with attention budget
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")
res_default_kwargs = get_default_env_kwargs(
dataset_path=dataset_path,
logger=logger,
n_busbar=n_busbar,
allow_detachment=allow_detachment,
_add_cls_nm_bk=_add_cls_nm_bk,
_add_to_name=_add_to_name,
_compat_glop_version=_compat_glop_version,
_overload_name_multimix=_overload_name_multimix,
**kwargs
)
(default_kwargs,
use_class_in_files,
grid_path_abs,
data_feeding,
graph_layout,
backend,
thermal_limits,
allow_loaded_backend,
classes_path,
init_env,
this_local_dir,
do_not_erase_cls) = res_default_kwargs
if use_class_in_files:
# new behaviour
if _overload_name_multimix is None:
sys_path_cls = os.path.join(os.path.split(grid_path_abs)[0], GRID2OP_CLASSES_ENV_FOLDER)
else:
sys_path_cls = os.path.join(_overload_name_multimix[1], GRID2OP_CLASSES_ENV_FOLDER)
if not os.path.exists(sys_path_cls):
try:
os.mkdir(sys_path_cls)
except FileExistsError:
# if another process created it, no problem
pass
init_nm = os.path.join(sys_path_cls, "__init__.py")
if not os.path.exists(init_nm):
try:
with open(init_nm, "w", encoding="utf-8") as f:
f.write("This file has been created by grid2op in a `env.make(...)` call. Do not modify it or remove it")
except FileExistsError:
pass
import tempfile
if _overload_name_multimix is None or _overload_name_multimix[0] is None:
this_local_dir = tempfile.TemporaryDirectory(dir=sys_path_cls)
this_local_dir_name = this_local_dir.name
else:
this_local_dir_name = _overload_name_multimix[0]
this_local_dir = None
do_not_erase_cls = True
if experimental_read_from_local_dir:
warnings.warn("With the automatic class generation, we removed the possibility to "
"set `experimental_read_from_local_dir` to True.")
experimental_read_from_local_dir = False
# TODO: check the hash thingy is working in baseEnv._aux_gen_classes (currently a pdb)
# TODO check that it works if the backend changes, if shunt / no_shunt if name of env changes etc.
# TODO: what if it cannot write on disk => fallback to previous behaviour
data_feeding_fake = copy.deepcopy(data_feeding)
data_feeding_fake.cleanup_action_space()
# Set graph layout if not None and not an empty dict
if graph_layout is not None and graph_layout:
type(backend).attach_layout(graph_layout)
if not os.path.exists(this_local_dir_name):
raise EnvError(f"Path {this_local_dir_name} has not been created by the tempfile package")
init_env = Environment(**default_kwargs,
chronics_handler=data_feeding_fake,
_read_from_local_dir=None, # first environment to generate the classes and save them
_local_dir_cls=None,
)
if not os.path.exists(this_local_dir.name):
raise EnvError(f"Path {this_local_dir.name} has not been created by the tempfile package")
init_env.generate_classes(local_dir_id=this_local_dir.name)
# fix `my_bk_act_class` and `_complete_action_class`
_aux_fix_backend_internal_classes(type(backend), this_local_dir)
init_env.backend = None # to avoid to close the backend when init_env is deleted
init_env._local_dir_cls = None
classes_path = this_local_dir_name
allow_loaded_backend = True
else:
# legacy behaviour (<= 1.10.1 behaviour)
classes_path = None if not experimental_read_from_local_dir else experimental_read_from_local_dir
if experimental_read_from_local_dir:
if _overload_name_multimix is not None:
# I am in a multimix
sys_path = os.path.join(_overload_name_multimix.path_env, GRID2OP_CLASSES_ENV_FOLDER)
else:
# I am not in a multimix
sys_path = os.path.join(os.path.split(grid_path_abs)[0], GRID2OP_CLASSES_ENV_FOLDER)
if not os.path.exists(sys_path):
raise RuntimeError(
"Attempting to load the grid classes from the env path. Yet the directory "
"where they should be placed does not exists. Did you call `env.generate_classes()` "
"BEFORE creating an environment with `experimental_read_from_local_dir=True` ?"
)
if not os.path.isdir(sys_path) or not os.path.exists(
os.path.join(sys_path, "__init__.py")
):
raise RuntimeError(
f"Impossible to load the classes from the env path. There is something that is "
f"not a directory and that is called `_grid2op_classes`. "
f'Please remove "{sys_path}" and call `env.generate_classes()` where env is an '
f"environment created with `experimental_read_from_local_dir=False` (default)"
)
import sys
sys.path.append(os.path.split(os.path.abspath(sys_path))[0])
classes_path = sys_path
# new in 1.11.0
if _overload_name_multimix is not None:
# case of multimix
if _overload_name_multimix.mix_id >= 1 and _overload_name_multimix.local_dir_tmpfolder is not None:
# this is not the first mix
# for the other mix I need to read the data from files and NOT
# create the classes
this_local_dir = _overload_name_multimix.local_dir_tmpfolder
classes_path = this_local_dir.name
# Finally instantiate env from config & overrides
# including (if activated the new grid2op behaviour)
env = Environment(
**default_kwargs,
chronics_handler=data_feeding,
_allow_loaded_backend=allow_loaded_backend,
_read_from_local_dir=classes_path,
_local_dir_cls=this_local_dir,
)
if do_not_erase_cls is not None:
env._do_not_erase_local_dir_cls = do_not_erase_cls
# 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:
try:
env.attach_layout(graph_layout)
except EnvError as exc_:
warnings.warn(f"Error {exc_} while setting the environment layout.")
return env