diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fe1baf03..d5ebb5d59 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -164,7 +164,7 @@ jobs: grid2op.testinstall legacy_lightsim: - executor: python38 + executor: python38 # needs to be 38: whl of lightsim were not released for 3.10 at the time resource_class: small steps: - checkout @@ -205,13 +205,13 @@ jobs: command: | export _GRID2OP_FORCE_TEST=1 source venv_test/bin/activate - python -m pip install -U pip setuptools wheel - python -m pip install chronix2grid>="1.1.0.post1" + python -m pip install -U pip setuptools wheel "numpy>=1.20,<1.21" "pandas<2.2" "scipy==1.10.1" numba + python -m pip install "chronix2grid>=1.1.0.post1" "gymnasium==0.26.3" "matplotlib==3.7.5" "xarray==2023.10.0" "scs==3.0.0" "ecos==2.0.0" python -m pip uninstall -y grid2op - run: - command: | + command: | # issue with previous more simple install, so I fix some versions source venv_test/bin/activate - python -m pip install -U "numpy>=1.20,<1.21" "pandas<2.2" "scipy<1.12" numba .[test] + python -m pip install "numpy>=1.20,<1.21" "pandas<2.2" "scipy==1.10.1" numba . pip freeze - run: command: | @@ -222,7 +222,7 @@ jobs: - run: command: | source venv_test/bin/activate - python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" numba .[test] + python -m pip install "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" numba pip freeze - run: command: | @@ -250,7 +250,7 @@ jobs: - run: command: | source venv_test/bin/activate - python -m pip install -U "numpy>=1.21,<1.22" "pandas<2.2" "scipy<1.12" numba .[test] + python -m pip install -U "numpy>=1.21,<1.22" "pandas<2.2" "scipy<1.12" numba . pip freeze - run: command: | @@ -261,7 +261,7 @@ jobs: - run: command: | source venv_test/bin/activate - python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" numba .[test] + python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" numba pip freeze - run: command: | @@ -288,7 +288,7 @@ jobs: - run: command: | source venv_test/bin/activate - python -m pip install -U "numpy>=1.23,<1.24" "pandas<2.2" "scipy<1.12" numba .[test] + python -m pip install -U "numpy>=1.23,<1.24" "pandas<2.2" "scipy<1.12" numba . pip freeze - run: command: | @@ -299,7 +299,7 @@ jobs: - run: command: | source venv_test/bin/activate - python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" numba .[test] + python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" numba pip freeze - run: command: | @@ -326,7 +326,7 @@ jobs: - run: command: | source venv_test/bin/activate - python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" .[test] + python -m pip install -U "numpy>=1.26,<1.27" "pandas<2.2" "scipy<1.12" . pip freeze - run: command: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1e311d426..a41c23e11 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,14 +63,16 @@ jobs: # auditwheel repair dist/*.whl # only for compiled code ! - name: Install wheel - run: pip3 install dist/*.whl --user + run: | + pip3 install dist/*.whl + pip freeze - name: Check package can be imported run: | python3 -c "import grid2op" python3 -c "from grid2op import *" python3 -c "from grid2op.Action._backendAction import _BackendAction" - + - name: Upload wheel uses: actions/upload-artifact@v2 with: @@ -136,13 +138,15 @@ jobs: - name: Install wheel shell: bash - run: python -m pip install dist/*.whl --user + run: | + python -m pip install dist/*.whl --user + pip freeze - name: Check package can be imported run: | - python3 -c "import grid2op" - python3 -c "from grid2op import *" - python3 -c "from grid2op.Action._backendAction import _BackendAction" + python -c "import grid2op" + python -c "from grid2op import *" + python -c "from grid2op.Action._backendAction import _BackendAction" - name: Build source archive if: matrix.config.name == 'darwin' && matrix.python.name == 'cp310' diff --git a/.gitignore b/.gitignore index e950fdba4..ba9e6e67b 100644 --- a/.gitignore +++ b/.gitignore @@ -403,6 +403,13 @@ grid2op/tests/requirements.txt grid2op/tests/venv_test_311/ issue_577/ junk.py +grid2op/tests/20240429_failed_tests.txt +grid2op/tests/20240429_failed_tests_small.txt +grid2op/tests/20240429_teq_test.txt +grid2op/tests/req_38_np121 +test_make_2_envs.py +getting_started/env_py38_grid2op110_ray110.ipynb +getting_started/env_py38_grid2op110_ray210.ipynb # profiling files **.prof diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5e71d791a..6d4271a18 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,7 +31,71 @@ Change Log - [???] "asynch" multienv - [???] properly model interconnecting powerlines -[1.10.1] - 2024-03-18 +[1.10.2] - 2024-05-27 +------------------------- +- [BREAKING] the `runner.run_one_episode` now returns an extra first argument: + `chron_id, chron_name, cum_reward, timestep, max_ts = runner.run_one_episode()` which + is consistant with `runner.run(...)` (previously it returned only + `chron_name, cum_reward, timestep, max_ts = runner.run_one_episode()`) +- [BREAKING] the runner now has no `chronics_handler` attribute (`runner.chronics_handler` + is not defined) +- [BREAKING] now grid2op forces everything to be connected at busbar 1 if + `param.IGNORE_INITIAL_STATE_TIME_SERIE == True` (**NOT** the default) and + no initial state is provided in `env.reset(..., options={"init state": ...})` +- [ADDED] it is now possible to call `change_reward` directly from + an observation (no need to do it from the Observation Space) +- [ADDED] method to change the reward from the observation (observation_space + is not needed anymore): you can use `obs.change_reward` +- [ADDED] a way to automatically set the `experimental_read_from_local_dir` flags + (with automatic class creation). For now it is disable by default, but you can + activate it transparently (see doc) +- [ADDED] possibility to set the grid to an initial state (using an action) when using the + "time series" classes. The supported classes are `GridStateFromFile` - and all its derivative, + `FromOneEpisodeData`, `FromMultiEpisodeData`, `FromNPY` and `FromHandlers`. The classes `ChangeNothing` + and `FromChronix2grid` are not supported at the moment. +- [ADDED] an "Handler" (`JSONInitStateHandler`) that can set the grid to an initial state (so as to make + compatible the `FromHandlers` time series class with this new feature) +- [ADDED] some more type hints in the `GridObject` class +- [ADDED] Possibility to deactive the support of shunts if subclassing `PandaPowerBackend` + (and add some basic tests) +- [ADDED] a parameters (`param.IGNORE_INITIAL_STATE_TIME_SERIE`) which defaults to + `False` that tells the environment whether it should ignore the + initial state of the grid provided in the time series. + By default it is NOT ignored, it is taken into account + (for the environment that supports this feature) +- [FIXED] a small issue that could lead to having + "redispatching_unit_commitment_availble" flag set even if the redispatching + data was not loaded correctly +- [FIXED] EducPandaPowerBackend now properly sends numpy array in the class attributes + (instead of pandas series) +- [FIXED] an issue when loading back data (with `EpisodeData`): when there were no storage units + on the grid it did not set properly the "storage relevant" class attributes +- [FIXED] a bug in the "gridobj.generate_classes()" function which crashes when no + grid layout was set +- [FIXED] notebook 5 on loading back data with `EpisodeData`. +- [FIXED] converter between backends (could not handle more than 2 busbars) +- [FIXED] a bug in `BaseMultiProcessEnvironment`: set_filter had no impact +- [FIXED] an issue in the `Runner` (`self.chronics_handler` was sometimes used, sometimes not + and most of the time incorrectly) +- [FIXED] on `RemoteEnv` class (impact all multi process environment): the kwargs used to build then backend + where not used which could lead to"wrong" backends being used in the sub processes. +- [FIXED] a bug when the name of the times series and the names of the elements in the backend were + different: it was not possible to set `names_chronics_to_grid` correctly when calling `env.make` +- [IMPROVED] documentation about `obs.simulate` to make it clearer the + difference between env.step and obs.simulate on some cases +- [IMPROVED] type hints on some methods of `GridObjects` +- [IMPROVED] replace `np.nonzero(arr)` calls with `arr.nonzero()` which could + save up a bit of computation time. +- [IMPROVED] force class attributes to be numpy arrays of proper types when the + classes are initialized from the backend. +- [IMPROVED] some (slight) speed improvments when comparing actions or deep copying objects +- [IMPROVED] the way the "grid2op compat" mode is handled +- [IMPROVED] the coverage of the tests in the "test_basic_env_ls.py" to test more in depth lightsim2grid + (creation of multiple environments, grid2op compatibility mode) +- [IMPROVED] the function to test the backend interface in case when shunts are not supported + (improved test `AAATestBackendAPI.test_01load_grid`) + +[1.10.1] - 2024-03-xx ---------------------- - [FIXED] issue https://github.com/rte-france/Grid2Op/issues/593 - [FIXED] backward compatibility issues with "oldest" lightsim2grid versions diff --git a/docs/conf.py b/docs/conf.py index 0566bef41..b83fba277 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = 'Benjamin Donnot' # The full version, including alpha/beta/rc tags -release = '1.10.1' +release = '1.10.2.dev3' version = '1.10' diff --git a/docs/environment.rst b/docs/environment.rst index b40c1483b..b90c0c8b3 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -104,6 +104,58 @@ increase the training time, especially at the beginning. This is due to the fact `env.reset` is called, the whole chronics is read from the hard drive. If you want to lower this impact then you might consult the :ref:`environment-module-data-pipeline` page of the doc. +Go to the next scenario +++++++++++++++++++++++++ + +Starting grid2op 1.9.8 we attempt to make an easier user experience in the +selection of time series, seed, initial state of the grid, etc. + +All of the above can be done when calling `env.reset()` function. + +For customizing the seed, you can for example do: + +.. code-block:: python + + import grid2op + env_name = "l2rpn_case14_sandbox" + env = grid2op.make(env_name) + + obs = env.reset(seed=0) + +For customizing the time series id you want to use: + +.. code-block:: python + + import grid2op + env_name = "l2rpn_case14_sandbox" + env = grid2op.make(env_name) + + obs = env.reset(options={"time serie id": 1}) # time serie by id (sorted alphabetically) + # or + obs = env.reset(options={"time serie id": "0001"}) # time serie by name (folder name) + +For customizing the initial state of the grid, for example forcing the +powerline 0 to be disconnected in the initial observation: + +.. code-block:: python + + import grid2op + env_name = "l2rpn_case14_sandbox" + env = grid2op.make(env_name) + + init_state_dict = {"set_line_status": [(0, -1)]} + obs = env.reset(options={"init state": init_state_dict}) + + +Feel free to consult the documentation of the :func:`Environment.reset` function +for more information (this doc might be outdated, the one of the function should +be more up to date with the code). + +.. info:: + In the near future (next few releases) we will also attempt to make the + customization of the `parameters` or the `skip number of steps`, `maximum duration + of the scenarios` also available in `env.reset()` options. + .. _environment-module-chronics-info: Time series Customization @@ -141,10 +193,15 @@ the call to "env.reset". This gives the following code: # and now the loop starts for i in range(episode_count): ################################### - env.set_id(THE_CHRONIC_ID) + # with recent grid2op + obs = env.reset(options={"time serie id": THE_CHRONIC_ID}) ################################### - obs = env.reset() + ################################### + # 'old method (oldest grid2op version)' + # env.set_id(THE_CHRONIC_ID) + # obs = env.reset() + ################################### # now play the episode as usual while True: diff --git a/getting_started/05_StudyYourAgent.ipynb b/getting_started/05_StudyYourAgent.ipynb index 0e9d142a4..44868f421 100644 --- a/getting_started/05_StudyYourAgent.ipynb +++ b/getting_started/05_StudyYourAgent.ipynb @@ -94,6 +94,8 @@ "outputs": [], "source": [ "try:\n", + " # use a (way) faster backend to reduce computation time\n", + " # to use it, you need to install `pip install lightsim2grid`\n", " from lightsim2grid import LightSimBackend\n", " bk_cls = LightSimBackend\n", "except ImportError as exc:\n", @@ -252,13 +254,15 @@ "outputs": [], "source": [ "id_line_inspected = 13\n", - "actions_on_line_14 = 0\n", + "actions_on_line_13 = 0\n", "for act in this_episode.actions:\n", " dict_ = act.effect_on(line_id=id_line_inspected) # which effect has this action action on the substation with given id\n", " # other objects are: load_id, gen_id, line_id or substation_id\n", " if dict_['change_line_status'] or dict_[\"set_line_status\"] != 0:\n", - " actions_on_line_14 += 1\n", - "print(f'Total actions on powerline 14 : {actions_on_line_14}')" + " actions_on_line_13 += 1\n", + "print(f\"Total actions on powerline 13 (named \"\n", + " f\"{type(env).name_line[id_line_inspected]}): \"\n", + " f\"{actions_on_line_13}\")\n" ] }, { diff --git a/getting_started/11_IntegrationWithExistingRLFrameworks.ipynb b/getting_started/11_IntegrationWithExistingRLFrameworks.ipynb index a2c43f898..561dd20b1 100644 --- a/getting_started/11_IntegrationWithExistingRLFrameworks.ipynb +++ b/getting_started/11_IntegrationWithExistingRLFrameworks.ipynb @@ -135,6 +135,37 @@ "\n", "More information are provided here: https://grid2op.readthedocs.io/en/latest/environment.html#splitting-into-raining-validation-test-scenarios\n", "\n", + "### Use the `experimental_read_from_local_dir` flag\n", + "\n", + "This flag allows python to better \"understands\" the classes in grid2op and avoid lots of issue with pickle / multi processing etc.\n", + "\n", + "The complete documentation is available here https://grid2op.readthedocs.io/en/latest/environment.html#grid2op.Environment.BaseEnv.generate_classes\n", + "\n", + "Basically, once, and only once, outside of this process, you can call:\n", + "\n", + "```python\n", + "import grid2op\n", + "env_name = \"l2rpn_case14_sandbox\" # or any other name\n", + "\n", + "env = grid2op.make(env_name, ...) # again: redo this step each time you customize \"...\"\n", + "# for example if you change the `action_class` or the `backend` etc.\n", + "\n", + "env.generate_classes()\n", + "```\n", + "\n", + "Then, each time you want to reload the same environment, you can do:\n", + "\n", + "```python\n", + "import grid2op\n", + "env_name = SAME NAME AS ABOVE\n", + "env = grid2op.make(env_name,\n", + " experimental_read_from_local_dir=True,\n", + " ..., # SAME ENV CUSTOMIZATION AS ABOVE\n", + " )\n", + "```\n", + "\n", + "This is known to solve bug related to multi processing, but also to reduce the amount of RAM taken (in some cases) as well as creation time (in some cases)\n", + "\n", "### Other steps\n", "\n", "The grid2op documentation is full of details to \"optimize\" the number of steps you can do per seconds. This number can rise from a few dozen per seconds to around a thousands per seconds with proper care.\n", @@ -190,25 +221,13 @@ "metadata": {}, "outputs": [], "source": [ - "import gym\n", + "import gymnasium\n", "import numpy as np\n", "from grid2op.gym_compat import GymEnv\n", "env_gym_init = GymEnv(env_glop)\n", "env_gym = GymEnv(env_glop)\n", - "print(f\"The \\\"env_gym\\\" is a gym environment: {isinstance(env_gym, gym.Env)}\")\n", - "obs_gym = env_gym.reset()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " In this notebook, we only present some basic (and really \"detailed\" use of the `GymEnv`). \n", - " \n", - " This is especially suited for advanced users wanting a deep control over everything happening. \n", - "\n", - " For a less advanced usage, feel free to consult the l2rpn baselines package, that embed some usefull environments, compatible with gym, that can embed some heuristics and other \"quality of life\" features. Feel free to use the l2rpn baselines package for more information.\n", - "" + "print(f\"The \\\"env_gym\\\" is a gym environment: {isinstance(env_gym, gymnasium.Env)}\")\n", + "obs_gym, info = env_gym.reset()" ] }, { @@ -388,7 +407,7 @@ "outputs": [], "source": [ "from grid2op.gym_compat import ScalerAttrConverter\n", - "from gym.spaces import Box\n", + "from gymnasium.spaces import Box\n", "ob_space = env_gym.observation_space\n", "ob_space = ob_space.reencode_space(\"actual_dispatch\",\n", " ScalerAttrConverter(substract=0.,\n", @@ -518,11 +537,11 @@ "source": [ "# gym specific, we simply do a copy paste of what we did in the previous cells, wrapping it in the\n", "# MyEnv class, and train a Proximal Policy Optimisation based agent\n", - "import gym\n", + "import gymnasium\n", "import ray\n", "import numpy as np\n", " \n", - "class MyEnv(gym.Env):\n", + "class MyEnv(gymnasium.Env):\n", " def __init__(self, env_config):\n", " import grid2op\n", " from grid2op.gym_compat import GymEnv\n", @@ -537,7 +556,7 @@ "\n", " # 2. create the gym environment\n", " self.env_gym = GymEnv(self.env_glop)\n", - " obs_gym = self.env_gym.reset()\n", + " obs_gym, info = self.env_gym.reset()\n", "\n", " # 3. (optional) customize it (see section above for more information)\n", " ## customize action space\n", @@ -576,9 +595,9 @@ " # 4. bis: to avoid other type of issues, we recommend to build the action space and observation\n", " # space directly from the spaces class.\n", " d = {k: v for k, v in self.env_gym.observation_space.spaces.items()}\n", - " self.observation_space = gym.spaces.Dict(d)\n", + " self.observation_space = gymnasium.spaces.Dict(d)\n", " a = {k: v for k, v in self.env_gym.action_space.items()}\n", - " self.action_space = gym.spaces.Dict(a)\n", + " self.action_space = gymnasium.spaces.Dict(a)\n", "\n", " def reset(self):\n", " obs = self.env_gym.reset()\n", @@ -791,7 +810,7 @@ " )\n", " }\n", " )\n", - "obs_gym = env_sb.reset()" + "obs_gym, info = env_sb.reset()" ] }, { @@ -877,13 +896,13 @@ "outputs": [], "source": [ "from grid2op.gym_compat import BoxGymActSpace\n", - "scale_gen = env_sb.init_env.gen_max_ramp_up + env_sb.init_env.gen_max_ramp_down\n", - "scale_gen[~env_sb.init_env.gen_redispatchable] = 1.0\n", + "scaler_gen = env_sb.init_env.gen_max_ramp_up + env_sb.init_env.gen_max_ramp_down\n", + "scaler_gen = scaler_gen[env_sb.init_env.gen_redispatchable]\n", "env_sb.action_space = BoxGymActSpace(env_sb.init_env.action_space,\n", " attr_to_keep=[\"redispatch\"],\n", - " multiply={\"redispatch\": scale_gen},\n", + " multiply={\"redispatch\": scaler_gen},\n", " )\n", - "obs_gym = env_sb.reset()" + "obs_gym, info = env_sb.reset()" ] }, { @@ -937,7 +956,7 @@ "reencoded_act_space = MultiDiscreteActSpace(env_sb.init_env.action_space,\n", " attr_to_keep=[\"set_line_status\", \"set_bus\", \"redispatch\"])\n", "env_sb.action_space = reencoded_act_space\n", - "obs_gym = env_sb.reset()" + "obs_gym, info = env_sb.reset()" ] }, { @@ -1041,7 +1060,7 @@ " )\n", " }\n", " )\n", - "obs_gym = env_tfa.reset()" + "obs_gym, info = env_tfa.reset()" ] }, { @@ -1297,7 +1316,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/getting_started/11_ray_integration.ipynb b/getting_started/11_ray_integration.ipynb new file mode 100644 index 000000000..9a20658aa --- /dev/null +++ b/getting_started/11_ray_integration.ipynb @@ -0,0 +1,449 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Grid2Op integration with ray / rllib framework\n", + "\n", + "Try me out interactively with: [![Binder](./img/badge_logo.svg)](https://mybinder.org/v2/gh/rte-france/Grid2Op/master)\n", + "\n", + "\n", + "**objectives** This notebooks briefly explains how to use grid2op with ray (rllib) RL framework. Make sure to read the previous notebook 11_IntegrationWithExistingRLFrameworks.ipynb for a deeper dive into what happens. We only show the working solution here.\n", + "\n", + " This explains the ideas and shows a \"self contained\" somewhat minimal example of use of ray / rllib framework with grid2op. It is not meant to be fully generic, code might need to be adjusted. \n", + "\n", + "This notebook is more an \"example of what works\" rather than a deep dive tutorial.\n", + "\n", + "See https://docs.ray.io/en/latest/rllib/rllib-env.html#configuring-environments for a more detailed information.\n", + "\n", + "See also https://docs.ray.io/en/latest/rllib/package_ref/doc/ray.rllib.algorithms.algorithm_config.AlgorithmConfig.html for other details\n", + "\n", + "This notebook is tested with grid2op 1.10 and ray 2.23 on an ubuntu 20.04 machine.\n", + "\n", + "\n", + "## 1 Create the \"Grid2opEnv\" class\n", + "\n", + "In the next cell, we define a custom environment (that will internally use the `GymEnv` grid2op class) that is needed for ray / rllib.\n", + "\n", + "Indeed, in order to work with ray / rllib you need to define a custom wrapper on top of the GymEnv wrapper. You then have:\n", + "\n", + "- self._g2op_env which is the default grid2op environment, receiving grid2op Action and producing grid2op Observation.\n", + "- self._gym_env which is a the grid2op defined `gymnasium Environment` that cannot be directly used with ray / rllib\n", + "- `Grid2opEnv` which is a the wrapper on top of `self._gym_env` to make it usable with ray / rllib.\n", + "\n", + "Ray / rllib expects the gymnasium environment to inherit from `gymnasium.Env` and to be initialized with a given configuration. This is why you need to create the `Grid2opEnv` wrapper on top of `GymEnv`.\n", + "\n", + "In the initialization of `Grid2opEnv`, the `env_config` variable is a dictionary that can take as key-word arguments:\n", + "\n", + "- `backend_cls` : what is the class of the backend. If not provided, it will use `LightSimBackend` from the `lightsim2grid` package\n", + "- `backend_options`: what options will be used to create the backend for your environment. Your backend will be created by calling\n", + " `backend_cls(**backend_options)`, for example if you want to build `LightSimBackend(detailed_info_for_cascading_failure=False)` you can pass `{\"backend_cls\": LightSimBackend, \"backend_options\": {\"detailed_info_for_cascading_failure\": False}}`\n", + "- `env_name` : name of the grid2op environment you want to use, by default it uses `\"l2rpn_case14_sandbox\"`\n", + "- `env_is_test` : whether to add `test=True` when creating the grid2op environment (if `env_is_test` is True it will add `test=True` when calling `grid2op.make(..., test=True)`) otherwise it uses `test=False`\n", + "- `obs_attr_to_keep` : in this wrapper we only allow your agent to see a Box as an observation. This parameter allows you to control which attributes of the grid2op observation will be present in the agent observation space. By default it's `[\"rho\", \"p_or\", \"gen_p\", \"load_p\"]` which is \"kind of random\" and is probably not suited for every agent.\n", + "- `act_type` : controls the type of actions your agent will be able to perform. Already coded in this notebook are:\n", + " - `\"discrete\"` to use a `Discrete` action space\n", + " - `\"box\"` to use a `Box` action space\n", + " - `\"multi_discrete\"` to use a `MultiDiscrete` action space\n", + "- `act_attr_to_keep` : that allows you to customize the action space. If not provided, it defaults to:\n", + " - `[\"set_line_status_simple\", \"set_bus\"]` if `act_type` is `\"discrete\"` \n", + " - `[\"redispatch\", \"set_storage\", \"curtail\"]` if `act_type` is `\"box\"` \n", + " - `[\"one_line_set\", \"one_sub_set\"]` if `act_type` is `\"multi_discrete\"`\n", + "\n", + "If you want to add more customization, for example the reward function, the parameters of the environment etc. etc. feel free to get inspired by this code and extend it. Any PR on this regard is more than welcome." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from gymnasium import Env\n", + "from gymnasium.spaces import Discrete, MultiDiscrete, Box\n", + "\n", + "import ray\n", + "from ray.rllib.algorithms.ppo import PPOConfig\n", + "from ray.rllib.algorithms import ppo\n", + "\n", + "from typing import Dict, Literal, Any\n", + "\n", + "import grid2op\n", + "from grid2op.gym_compat import GymEnv, BoxGymObsSpace, DiscreteActSpace, BoxGymActSpace, MultiDiscreteActSpace\n", + "from lightsim2grid import LightSimBackend\n", + "\n", + "\n", + "class Grid2opEnv(Env):\n", + " def __init__(self,\n", + " env_config: Dict[Literal[\"backend_cls\",\n", + " \"backend_options\",\n", + " \"env_name\",\n", + " \"env_is_test\",\n", + " \"obs_attr_to_keep\",\n", + " \"act_type\",\n", + " \"act_attr_to_keep\"],\n", + " Any]):\n", + " super().__init__()\n", + " if env_config is None:\n", + " env_config = {}\n", + "\n", + " # handle the backend\n", + " backend_cls = LightSimBackend\n", + " if \"backend_cls\" in env_config:\n", + " backend_cls = env_config[\"backend_cls\"]\n", + " backend_options = {}\n", + " if \"backend_options\" in env_config:\n", + " backend_options = env_config[\"backend_options\"]\n", + " backend = backend_cls(**backend_options)\n", + "\n", + " # create the grid2op environment\n", + " env_name = \"l2rpn_case14_sandbox\"\n", + " if \"env_name\" in env_config:\n", + " env_name = env_config[\"env_name\"]\n", + " if \"env_is_test\" in env_config:\n", + " is_test = bool(env_config[\"env_is_test\"])\n", + " else:\n", + " is_test = False\n", + " self._g2op_env = grid2op.make(env_name, backend=backend, test=is_test)\n", + " # NB by default this might be really slow (when the environment is reset)\n", + " # see https://grid2op.readthedocs.io/en/latest/data_pipeline.html for maybe 10x speed ups !\n", + " # TODO customize reward or action_class for example !\n", + "\n", + " # create the gym env (from grid2op)\n", + " self._gym_env = GymEnv(self._g2op_env)\n", + "\n", + " # customize observation space\n", + " obs_attr_to_keep = [\"rho\", \"p_or\", \"gen_p\", \"load_p\"]\n", + " if \"obs_attr_to_keep\" in env_config:\n", + " obs_attr_to_keep = copy.deepcopy(env_config[\"obs_attr_to_keep\"])\n", + " self._gym_env.observation_space.close()\n", + " self._gym_env.observation_space = BoxGymObsSpace(self._g2op_env.observation_space,\n", + " attr_to_keep=obs_attr_to_keep\n", + " )\n", + " # export observation space for the Grid2opEnv\n", + " self.observation_space = Box(shape=self._gym_env.observation_space.shape,\n", + " low=self._gym_env.observation_space.low,\n", + " high=self._gym_env.observation_space.high)\n", + "\n", + " # customize the action space\n", + " act_type = \"discrete\"\n", + " if \"act_type\" in env_config:\n", + " act_type = env_config[\"act_type\"]\n", + "\n", + " self._gym_env.action_space.close()\n", + " if act_type == \"discrete\":\n", + " # user wants a discrete action space\n", + " act_attr_to_keep = [\"set_line_status_simple\", \"set_bus\"]\n", + " if \"act_attr_to_keep\" in env_config:\n", + " act_attr_to_keep = copy.deepcopy(env_config[\"act_attr_to_keep\"])\n", + " self._gym_env.action_space = DiscreteActSpace(self._g2op_env.action_space,\n", + " attr_to_keep=act_attr_to_keep)\n", + " self.action_space = Discrete(self._gym_env.action_space.n)\n", + " elif act_type == \"box\":\n", + " # user wants continuous action space\n", + " act_attr_to_keep = [\"redispatch\", \"set_storage\", \"curtail\"]\n", + " if \"act_attr_to_keep\" in env_config:\n", + " act_attr_to_keep = copy.deepcopy(env_config[\"act_attr_to_keep\"])\n", + " self._gym_env.action_space = BoxGymActSpace(self._g2op_env.action_space,\n", + " attr_to_keep=act_attr_to_keep)\n", + " self.action_space = Box(shape=self._gym_env.action_space.shape,\n", + " low=self._gym_env.action_space.low,\n", + " high=self._gym_env.action_space.high)\n", + " elif act_type == \"multi_discrete\":\n", + " # user wants a multi-discrete action space\n", + " act_attr_to_keep = [\"one_line_set\", \"one_sub_set\"]\n", + " if \"act_attr_to_keep\" in env_config:\n", + " act_attr_to_keep = copy.deepcopy(env_config[\"act_attr_to_keep\"])\n", + " self._gym_env.action_space = MultiDiscreteActSpace(self._g2op_env.action_space,\n", + " attr_to_keep=act_attr_to_keep)\n", + " self.action_space = MultiDiscrete(self._gym_env.action_space.nvec)\n", + " else:\n", + " raise NotImplementedError(f\"action type '{act_type}' is not currently supported.\")\n", + " \n", + " \n", + " def reset(self, seed, options):\n", + " # use default _gym_env (from grid2op.gym_compat module)\n", + " return self._gym_env.reset(seed=seed, options=options)\n", + " \n", + " def step(self, action):\n", + " # use default _gym_env (from grid2op.gym_compat module)\n", + " return self._gym_env.step(action)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we init ray, because we need to." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ray.init()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2 Make a default environment, and train a PPO agent for one iteration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# example of the documentation, directly\n", + "# see https://docs.ray.io/en/latest/rllib/package_ref/doc/ray.rllib.algorithms.algorithm_config.AlgorithmConfig.html\n", + "\n", + "# Construct a generic config object, specifying values within different\n", + "# sub-categories, e.g. \"training\".\n", + "config = (PPOConfig().training(gamma=0.9, lr=0.01)\n", + " .environment(env=Grid2opEnv, env_config={})\n", + " .resources(num_gpus=0)\n", + " .env_runners(num_env_runners=0)\n", + " .framework(\"tf2\")\n", + " )\n", + "\n", + "# A config object can be used to construct the respective Algorithm.\n", + "rllib_algo = config.build()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we train it for one training iteration (might call `env.reset()` and `env.step()` multiple times)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "print(rllib_algo.train())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3 Train a PPO agent using 2 \"runners\" to make the rollouts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# see https://docs.ray.io/en/latest/rllib/package_ref/doc/ray.rllib.algorithms.algorithm_config.AlgorithmConfig.html\n", + "\n", + "# use multiple use multiple runners\n", + "config2 = (PPOConfig().training(gamma=0.9, lr=0.01)\n", + " .environment(env=Grid2opEnv, env_config={})\n", + " .resources(num_gpus=0)\n", + " .env_runners(num_env_runners=2, num_envs_per_env_runner=1, num_cpus_per_env_runner=1)\n", + " .framework(\"tf2\")\n", + " )\n", + "\n", + "# A config object can be used to construct the respective Algorithm.\n", + "rllib_algo2 = config2.build()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we train it for one training iteration (might call `env.reset()` and `env.step()` multiple times)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(rllib_algo2.train())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4 Use non default parameters to make the l2rpn environment\n", + "\n", + "In this first example, we will train a policy using the \"box\" action space." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# see https://docs.ray.io/en/latest/rllib/package_ref/doc/ray.rllib.algorithms.algorithm_config.AlgorithmConfig.html\n", + "\n", + "# Use a \"Box\" action space (mainly to use redispatching, curtailment and storage units)\n", + "env_config = {\"env_name\": \"l2rpn_idf_2023\",\n", + " \"env_is_test\": True,\n", + " \"act_type\": \"box\",\n", + " }\n", + "config3 = (PPOConfig().training(gamma=0.9, lr=0.01)\n", + " .environment(env=Grid2opEnv, env_config=env_config)\n", + " .resources(num_gpus=0)\n", + " .env_runners(num_env_runners=2, num_envs_per_env_runner=1, num_cpus_per_env_runner=1)\n", + " .framework(\"tf2\")\n", + " )\n", + "\n", + "# A config object can be used to construct the respective Algorithm.\n", + "rllib_algo3 = config3.build()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we train it for one training iteration (might call `env.reset()` and `env.step()` multiple times)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(rllib_algo3.train())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now a policy using the \"multi discrete\" action space: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# see https://docs.ray.io/en/latest/rllib/package_ref/doc/ray.rllib.algorithms.algorithm_config.AlgorithmConfig.html\n", + "\n", + "# Use a \"Box\" action space (mainly to use redispatching, curtailment and storage units)\n", + "env_config4 = {\"env_name\": \"l2rpn_idf_2023\",\n", + " \"env_is_test\": True,\n", + " \"act_type\": \"multi_discrete\",\n", + " }\n", + "config4 = (PPOConfig().training(gamma=0.9, lr=0.01)\n", + " .environment(env=Grid2opEnv, env_config=env_config4)\n", + " .resources(num_gpus=0)\n", + " .env_runners(num_env_runners=2, num_envs_per_env_runner=1, num_cpus_per_env_runner=1)\n", + " .framework(\"tf2\")\n", + " )\n", + "\n", + "# A config object can be used to construct the respective Algorithm.\n", + "rllib_algo4 = config4.build()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we train it for one training iteration (might call `env.reset()` and `env.step()` multiple times)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(rllib_algo4.train())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5 Customize the policy (number of layers, size of layers etc.)\n", + "\n", + "This notebook does not aim at covering all possibilities offered by ray / rllib. For that you need to refer to the ray / rllib documentation.\n", + "\n", + "We will simply show how to change the size of the neural network used as a policy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# see https://docs.ray.io/en/latest/rllib/package_ref/doc/ray.rllib.algorithms.algorithm_config.AlgorithmConfig.html\n", + "\n", + "# Use a \"Box\" action space (mainly to use redispatching, curtailment and storage units)\n", + "config5 = (PPOConfig().training(gamma=0.9, lr=0.01)\n", + " .environment(env=Grid2opEnv, env_config={})\n", + " .resources(num_gpus=0)\n", + " .env_runners(num_env_runners=2, num_envs_per_env_runner=1, num_cpus_per_env_runner=1)\n", + " .framework(\"tf2\")\n", + " .rl_module(\n", + " model_config_dict={\"fcnet_hiddens\": [32, 32, 32]}, # 3 layers (fully connected) of 32 units each\n", + " )\n", + " )\n", + "\n", + "# A config object can be used to construct the respective Algorithm.\n", + "rllib_algo5 = config5.build()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we train it for one training iteration (might call `env.reset()` and `env.step()` multiple times)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(rllib_algo5.train())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/grid2op/Action/actionSpace.py b/grid2op/Action/actionSpace.py index b8f870062..4ce24be68 100644 --- a/grid2op/Action/actionSpace.py +++ b/grid2op/Action/actionSpace.py @@ -8,8 +8,10 @@ import warnings import copy -from typing import Dict, List, Any, Literal +from typing import Dict, List, Any, Literal, Optional +import grid2op +from grid2op.typing_variables import DICT_ACT_TYPING from grid2op.Action.baseAction import BaseAction from grid2op.Action.serializableActionSpace import SerializableActionSpace @@ -74,22 +76,10 @@ def __init__( def __call__( self, - dict_: Dict[Literal["injection", - "hazards", - "maintenance", - "set_line_status", - "change_line_status", - "set_bus", - "change_bus", - "redispatch", - "set_storage", - "curtail", - "raise_alarm", - "raise_alert"], Any] = None, + dict_: DICT_ACT_TYPING = None, check_legal: bool = False, env: "grid2op.Environment.BaseEnv" = None, - *, - injection=None, + _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None, ) -> BaseAction: """ This utility allows you to build a valid action, with the proper sizes if you provide it with a valid @@ -132,9 +122,15 @@ def __call__( An action that is valid and corresponds to what the agent want to do with the formalism defined in see :func:`Action.udpate`. + Notes + ----- + + This function is not in the "SerializableActionSpace" because the + "legal_action" is not serialized. TODO ? + """ # build the action - res = self.actionClass() + res : BaseAction = self.actionClass(_names_chronics_to_backend) # update the action res.update(dict_) diff --git a/grid2op/Action/baseAction.py b/grid2op/Action/baseAction.py index 6a66c0833..db38feb00 100644 --- a/grid2op/Action/baseAction.py +++ b/grid2op/Action/baseAction.py @@ -9,7 +9,9 @@ import copy import numpy as np import warnings -from typing import Tuple, Dict, Literal, Any, List +from typing import Tuple, Dict, Literal, Any, List, Optional + + try: from typing import Self except ImportError: @@ -17,6 +19,8 @@ from packaging import version +import grid2op +from grid2op.typing_variables import DICT_ACT_TYPING from grid2op.dtypes import dt_int, dt_bool, dt_float from grid2op.Exceptions import * from grid2op.Space import GridObjects @@ -396,7 +400,7 @@ class BaseAction(GridObjects): ERR_ACTION_CUT = 'The action added to me will be cut, because i don\'t support modification of "{}"' ERR_NO_STOR_SET_BUS = 'Impossible to modify the storage bus (with "set") with this action type.' - def __init__(self): + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): """ INTERNAL USE ONLY @@ -412,7 +416,12 @@ def __init__(self): """ GridObjects.__init__(self) - + if _names_chronics_to_backend is not None: + # should only be the case for the "init state" action + self._names_chronics_to_backend = _names_chronics_to_backend + else: + self._names_chronics_to_backend = None + # False(line is disconnected) / True(line is connected) self._set_line_status = np.full(shape=self.n_line, fill_value=0, dtype=dt_int) self._switch_line_status = np.full( @@ -624,6 +633,7 @@ def as_serializable_dict(self) -> dict: """ res = {} + cls = type(self) # bool elements if self._modif_alert: res["raise_alert"] = [ @@ -645,7 +655,7 @@ def as_serializable_dict(self) -> dict: self._aux_serialize_add_key_change("gen_change_bus", "generators_id", res["change_bus"]) self._aux_serialize_add_key_change("line_or_change_bus", "lines_or_id", res["change_bus"]) self._aux_serialize_add_key_change("line_ex_change_bus", "lines_ex_id", res["change_bus"]) - if hasattr(type(self), "n_storage") and type(self).n_storage: + if hasattr(cls, "n_storage") and cls.n_storage: self._aux_serialize_add_key_change("storage_change_bus", "storages_id", res["change_bus"]) if not res["change_bus"]: del res["change_bus"] @@ -664,7 +674,7 @@ def as_serializable_dict(self) -> dict: self._aux_serialize_add_key_set("gen_set_bus", "generators_id", res["set_bus"]) self._aux_serialize_add_key_set("line_or_set_bus", "lines_or_id", res["set_bus"]) self._aux_serialize_add_key_set("line_ex_set_bus", "lines_ex_id", res["set_bus"]) - if hasattr(type(self), "n_storage") and type(self).n_storage: + if hasattr(cls, "n_storage") and cls.n_storage: self._aux_serialize_add_key_set("storage_set_bus", "storages_id", res["set_bus"]) if not res["set_bus"]: del res["set_bus"] @@ -715,7 +725,7 @@ def as_serializable_dict(self) -> dict: if not res["injection"]: del res["injection"] - if type(self).shunts_data_available: + if cls.shunts_data_available: res["shunt"] = {} if np.isfinite(self.shunt_p).any(): res["shunt"]["shunt_p"] = [ @@ -764,7 +774,7 @@ def alarm_raised(self) -> np.ndarray: The indexes of the areas where the agent has raised an alarm. """ - return np.nonzero(self._raise_alarm)[0] + return (self._raise_alarm).nonzero()[0] def alert_raised(self) -> np.ndarray: """ @@ -778,16 +788,17 @@ def alert_raised(self) -> np.ndarray: The indexes of the lines where the agent has raised an alert. """ - return np.nonzero(self._raise_alert)[0] + return (self._raise_alert).nonzero[0] @classmethod def _aux_process_old_compat(cls): + super()._aux_process_old_compat() + # this is really important, otherwise things from grid2op base types will be affected cls.authorized_keys = copy.deepcopy(cls.authorized_keys) cls.attr_list_vect = copy.deepcopy(cls.attr_list_vect) # deactivate storage - cls.set_no_storage() if "set_storage" in cls.authorized_keys: cls.authorized_keys.remove("set_storage") if "_storage_power" in cls.attr_list_vect: @@ -2093,11 +2104,31 @@ def _digest_redispatching(self, dict_): def _digest_storage(self, dict_): if "set_storage" in dict_: - self.storage_p = dict_["set_storage"] - + try: + self.storage_p = dict_["set_storage"] + except IllegalAction as exc_: + cls = type(self) + # only raise the error if I am not in compat mode + if cls.glop_version == grid2op.__version__: + raise exc_ + else: + # TODO be more specific on the version + warnings.warn(f"Ignored error on storage units, because " + f"you are in a backward compatibility mode.") + def _digest_curtailment(self, dict_): if "curtail" in dict_: - self.curtail = dict_["curtail"] + try: + self.curtail = dict_["curtail"] + except IllegalAction as exc_: + cls = type(self) + # only raise the error if I am not in compat mode + if cls.glop_version == grid2op.__version__: + raise exc_ + else: + # TODO be more specific on the version + warnings.warn(f"Ignored error on curtailment, because " + f"you are in a backward compatibility mode.") def _digest_alarm(self, dict_): """ @@ -2124,7 +2155,9 @@ def _reset_vect(self): self._subs_impacted = None self._lines_impacted = None - def update(self, dict_): + def update(self, + dict_: DICT_ACT_TYPING + ): """ Update the action with a comprehensible format specified by a dictionary. @@ -2701,10 +2734,10 @@ def _check_for_ambiguity(self): # if i disconnected of a line, but i modify also the bus where it's connected if self._modif_set_bus or self._modif_change_bus: idx = self._set_line_status == -1 - id_disc = np.nonzero(idx)[0] + id_disc = (idx).nonzero()[0] idx2 = self._set_line_status == 1 - id_reco = np.nonzero(idx2)[0] + id_reco = (idx2).nonzero()[0] if self._modif_set_bus: if "set_bus" not in cls.authorized_keys: @@ -2839,13 +2872,13 @@ def _is_storage_ambiguous(self): "units affected" ) if (self._storage_power < -cls.storage_max_p_prod).any(): - where_bug = np.nonzero(self._storage_power < -cls.storage_max_p_prod)[0] + where_bug = (self._storage_power < -cls.storage_max_p_prod).nonzero()[0] raise InvalidStorage( f"you asked a storage unit to absorb more than what it can: " f"self._storage_power[{where_bug}] < -self.storage_max_p_prod[{where_bug}]." ) if (self._storage_power > cls.storage_max_p_absorb).any(): - where_bug = np.nonzero(self._storage_power > cls.storage_max_p_absorb)[0] + where_bug = (self._storage_power > cls.storage_max_p_absorb).nonzero()[0] raise InvalidStorage( f"you asked a storage unit to produce more than what it can: " f"self._storage_power[{where_bug}] > self.storage_max_p_absorb[{where_bug}]." @@ -2880,14 +2913,14 @@ def _is_curtailment_ambiguous(self): ) if ((self._curtail < 0.0) & (np.abs(self._curtail + 1.0) >= 1e-7)).any(): - where_bug = np.nonzero((self._curtail < 0.0) & (np.abs(self._curtail + 1.0) >= 1e-7))[0] + where_bug = ((self._curtail < 0.0) & (np.abs(self._curtail + 1.0) >= 1e-7)).nonzero()[0] raise InvalidCurtailment( f"you asked to perform a negative curtailment: " f"self._curtail[{where_bug}] < 0. " f"Curtailment should be a real number between 0.0 and 1.0" ) if (self._curtail > 1.0).any(): - where_bug = np.nonzero(self._curtail > 1.0)[0] + where_bug = (self._curtail > 1.0).nonzero()[0] raise InvalidCurtailment( f"you asked a storage unit to produce more than what it can: " f"self._curtail[{where_bug}] > 1. " @@ -3112,7 +3145,7 @@ def __str__(self) -> str: if my_cls.dim_alarms > 0: if self._modif_alarm: li_area = np.array(my_cls.alarms_area_names)[ - np.nonzero(self._raise_alarm)[0] + (self._raise_alarm).nonzero()[0] ] if len(li_area) == 1: area_str = ": " + li_area[0] @@ -3124,7 +3157,7 @@ def __str__(self) -> str: if my_cls.dim_alerts > 0: if self._modif_alert: - i_alert = np.nonzero(self._raise_alert)[0] + i_alert = (self._raise_alert).nonzero()[0] li_line = np.array(my_cls.alertable_line_names)[i_alert] if len(li_line) == 1: line_str = f": {i_alert[0]} (on line {li_line[0]})" @@ -3170,9 +3203,8 @@ def impact_on_objects(self) -> dict: force_line_status["reconnections"]["count"] = ( self._set_line_status == 1 ).sum() - force_line_status["reconnections"]["powerlines"] = np.nonzero( - self._set_line_status == 1 - )[0] + force_line_status["reconnections"]["powerlines"] = ( + (self._set_line_status == 1).nonzero()[0]) if (self._set_line_status == -1).any(): force_line_status["changed"] = True @@ -3180,9 +3212,9 @@ def impact_on_objects(self) -> dict: force_line_status["disconnections"]["count"] = ( self._set_line_status == -1 ).sum() - force_line_status["disconnections"]["powerlines"] = np.nonzero( - self._set_line_status == -1 - )[0] + force_line_status["disconnections"]["powerlines"] = ( + (self._set_line_status == -1).nonzero()[0] + ) # handles action on swtich line status switch_line_status = {"changed": False, "count": 0, "powerlines": []} @@ -3190,7 +3222,7 @@ def impact_on_objects(self) -> dict: switch_line_status["changed"] = True has_impact = True switch_line_status["count"] = self._switch_line_status.sum() - switch_line_status["powerlines"] = np.nonzero(self._switch_line_status)[0] + switch_line_status["powerlines"] = (self._switch_line_status).nonzero()[0] topology = { "changed": False, @@ -3310,19 +3342,19 @@ def _aux_as_dict_set_line(self, res): res["set_line_status"]["nb_disconnected"] = ( self._set_line_status == -1 ).sum() - res["set_line_status"]["connected_id"] = np.nonzero( - self._set_line_status == 1 - )[0] - res["set_line_status"]["disconnected_id"] = np.nonzero( - self._set_line_status == -1 - )[0] + res["set_line_status"]["connected_id"] = ( + (self._set_line_status == 1).nonzero()[0] + ) + res["set_line_status"]["disconnected_id"] = ( + (self._set_line_status == -1).nonzero()[0] + ) def _aux_as_dict_change_line(self, res): res["change_line_status"] = {} res["change_line_status"]["nb_changed"] = self._switch_line_status.sum() - res["change_line_status"]["changed_id"] = np.nonzero( - self._switch_line_status - )[0] + res["change_line_status"]["changed_id"] = ( + self._switch_line_status.nonzero()[0] + ) def _aux_as_dict_change_bus(self, res): res["change_bus_vect"] = {} @@ -3469,11 +3501,11 @@ def as_dict(self) -> Dict[Literal["load_p", "load_q", "prod_p", "prod_v", self._aux_as_dict_set_bus(res) if self._hazards.any(): - res["hazards"] = np.nonzero(self._hazards)[0] + res["hazards"] = self._hazards.nonzero()[0] res["nb_hazards"] = self._hazards.sum() if self._maintenance.any(): - res["maintenance"] = np.nonzero(self._maintenance)[0] + res["maintenance"] = self._maintenance.nonzero()[0] res["nb_maintenance"] = self._maintenance.sum() if (np.abs(self._redispatch) >= 1e-7).any(): @@ -3883,6 +3915,7 @@ def _aux_affect_object_int( outer_vect, min_val=-1, max_val=2, + _nm_ch_bk_key: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None, ): """ NB : this do not set the _modif_set_bus attribute. It is expected to be set in the property setter. @@ -4005,6 +4038,7 @@ def _aux_affect_object_int( outer_vect=outer_vect, min_val=min_val, max_val=max_val, + _nm_ch_bk_key=_nm_ch_bk_key ) return @@ -4017,7 +4051,11 @@ def _aux_affect_object_int( ) el_id, new_bus = el if isinstance(el_id, str) and name_els is not None: - tmp = np.nonzero(name_els == el_id)[0] + if self._names_chronics_to_backend is not None and _nm_ch_bk_key in self._names_chronics_to_backend: + # initial action to set the state, might use the name in the time series... + nms_conv = self._names_chronics_to_backend[_nm_ch_bk_key] + el_id = nms_conv[el_id] + tmp = (name_els == el_id).nonzero()[0] if len(tmp) == 0: raise IllegalAction(f"No known {name_el} with name {el_id}") el_id = tmp[0] @@ -4030,12 +4068,17 @@ def _aux_affect_object_int( outer_vect=outer_vect, min_val=min_val, max_val=max_val, + _nm_ch_bk_key=_nm_ch_bk_key ) elif isinstance(values, dict): # 2 cases: either key = load_id and value = new_bus or key = load_name and value = new bus for key, new_bus in values.items(): if isinstance(key, str) and name_els is not None: - tmp = np.nonzero(name_els == key)[0] + if self._names_chronics_to_backend is not None and _nm_ch_bk_key in self._names_chronics_to_backend: + # initial action to set the state, might use the name in the time series... + nms_conv = self._names_chronics_to_backend[_nm_ch_bk_key] + key = nms_conv[key] + tmp = (name_els == key).nonzero()[0] if len(tmp) == 0: raise IllegalAction(f"No known {name_el} with name {key}") key = tmp[0] @@ -4048,6 +4091,7 @@ def _aux_affect_object_int( outer_vect=outer_vect, min_val=min_val, max_val=max_val, + _nm_ch_bk_key=_nm_ch_bk_key, ) else: raise IllegalAction( @@ -4108,7 +4152,8 @@ def load_set_bus(self, values): cls.name_load, cls.load_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="loads" ) self._modif_set_bus = True except Exception as exc_: @@ -4119,12 +4164,13 @@ def load_set_bus(self, values): cls.name_load, cls.load_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="loads" ) raise IllegalAction( f"Impossible to modify the load bus with your input. Please consult the documentation. " f'The error was "{exc_}"' - ) + ) from exc_ @property def gen_set_bus(self) -> np.ndarray: @@ -4248,7 +4294,8 @@ def gen_set_bus(self, values): cls.name_gen, cls.gen_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="prods" ) self._modif_set_bus = True except Exception as exc_: @@ -4259,12 +4306,13 @@ def gen_set_bus(self, values): cls.name_gen, cls.gen_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="prods" ) raise IllegalAction( f"Impossible to modify the gen bus with your input. Please consult the documentation. " f'The error was:\n"{exc_}"' - ) + ) from exc_ @property def storage_set_bus(self) -> np.ndarray: @@ -4338,7 +4386,7 @@ def storage_set_bus(self, values): f"Impossible to modify the storage bus with your input. " f"Please consult the documentation. " f'The error was:\n"{exc_}"' - ) + ) from exc_ @property def line_or_set_bus(self) -> np.ndarray: @@ -4393,7 +4441,8 @@ def line_or_set_bus(self, values): self.name_line, self.line_or_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="lines" ) self._modif_set_bus = True except Exception as exc_: @@ -4404,13 +4453,14 @@ def line_or_set_bus(self, values): cls.name_line, cls.line_or_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="lines" ) raise IllegalAction( f"Impossible to modify the line origin bus with your input. " f"Please consult the documentation. " f'The error was:\n"{exc_}"' - ) + ) from exc_ @property def line_ex_set_bus(self) -> np.ndarray: @@ -4439,7 +4489,8 @@ def line_ex_set_bus(self, values): cls.name_line, cls.line_ex_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="lines" ) self._modif_set_bus = True except Exception as exc_: @@ -4450,13 +4501,14 @@ def line_ex_set_bus(self, values): cls.name_line, cls.line_ex_pos_topo_vect, self._set_topo_vect, - max_val=cls.n_busbar_per_sub + max_val=cls.n_busbar_per_sub, + _nm_ch_bk_key="lines" ) raise IllegalAction( f"Impossible to modify the line extrmity bus with your input. " f"Please consult the documentation. " f'The error was:\n"{exc_}"' - ) + ) from exc_ @property def set_bus(self) -> np.ndarray: @@ -4536,7 +4588,7 @@ def set_bus(self, values): f"Impossible to modify the bus with your input. " f"Please consult the documentation. " f'The error was:\n"{exc_}"' - ) + ) from exc_ @property def line_set_status(self) -> np.ndarray: @@ -4590,6 +4642,7 @@ def line_set_status(self, values): np.arange(self.n_line), self._set_line_status, max_val=1, + _nm_ch_bk_key="lines" ) self._modif_set_status = True except Exception as exc_: @@ -4601,12 +4654,13 @@ def line_set_status(self, values): np.arange(self.n_line), self._set_line_status, max_val=1, + _nm_ch_bk_key="lines" ) raise IllegalAction( f"Impossible to modify the line status with your input. " f"Please consult the documentation. " f'The error was:\n"{exc_}"' - ) + ) from exc_ @property def set_line_status(self) -> np.ndarray: @@ -4627,7 +4681,7 @@ def change_line_status(self, values): self.line_change_status = values def _aux_affect_object_bool( - self, values, name_el, nb_els, name_els, inner_vect, outer_vect + self, values, name_el, nb_els, name_els, inner_vect, outer_vect, _nm_ch_bk_key=None ): """ NB : this do not set the _modif_set_bus attribute. It is expected to be set in the property setter. @@ -4724,7 +4778,11 @@ def _aux_affect_object_bool( # (note: i cannot convert to numpy array other I could mix types...) for el_id_or_name in values: if isinstance(el_id_or_name, str): - tmp = np.nonzero(name_els == el_id_or_name)[0] + if self._names_chronics_to_backend is not None and _nm_ch_bk_key in self._names_chronics_to_backend: + # initial action to set the state, might use the name in the time series... + nms_conv = self._names_chronics_to_backend[_nm_ch_bk_key] + el_id_or_name = nms_conv[el_id_or_name] + tmp = (name_els == el_id_or_name).nonzero()[0] if len(tmp) == 0: raise IllegalAction( f'No known {name_el} with name "{el_id_or_name}"' @@ -4751,6 +4809,7 @@ def _aux_affect_object_bool( name_els, inner_vect=inner_vect, outer_vect=outer_vect, + _nm_ch_bk_key=_nm_ch_bk_key ) elif isinstance(values, set): # 2 cases: either set of load_id or set of load_name @@ -4762,6 +4821,7 @@ def _aux_affect_object_bool( name_els, inner_vect=inner_vect, outer_vect=outer_vect, + _nm_ch_bk_key=_nm_ch_bk_key ) else: raise IllegalAction( @@ -4864,6 +4924,7 @@ def load_change_bus(self, values): self.name_load, self.load_pos_topo_vect, self._change_bus_vect, + _nm_ch_bk_key="loads", ) self._modif_change_bus = True except Exception as exc_: @@ -4986,6 +5047,7 @@ def gen_change_bus(self, values): self.name_gen, self.gen_pos_topo_vect, self._change_bus_vect, + _nm_ch_bk_key="prods", ) self._modif_change_bus = True except Exception as exc_: @@ -5061,6 +5123,7 @@ def line_or_change_bus(self, values): self.name_line, self.line_or_pos_topo_vect, self._change_bus_vect, + _nm_ch_bk_key="lines", ) self._modif_change_bus = True except Exception as exc_: @@ -5097,6 +5160,7 @@ def line_ex_change_bus(self, values): self.name_line, self.line_ex_pos_topo_vect, self._change_bus_vect, + _nm_ch_bk_key="lines", ) self._modif_change_bus = True except Exception as exc_: @@ -5138,6 +5202,7 @@ def line_change_status(self, values): self.name_line, np.arange(self.n_line), self._switch_line_status, + _nm_ch_bk_key="lines", ) self._modif_change_status = True except Exception as exc_: @@ -5266,6 +5331,7 @@ def _aux_affect_object_float( name_els, inner_vect, outer_vect, + _nm_ch_bk_key=None, ): """ INTERNAL USE ONLY @@ -5337,7 +5403,7 @@ def _aux_affect_object_float( except Exception as exc_: raise IllegalAction( f'{name_el}_id should be convertible to integer. Error was : "{exc_}"' - ) + ) from exc_ if el_id < 0: raise IllegalAction( f"Impossible to set the bus of a {name_el} with negative id" @@ -5368,7 +5434,7 @@ def _aux_affect_object_float( except Exception as exc_: raise IllegalAction( f'{name_el}_id should be convertible to float. Error was : "{exc_}"' - ) + ) from exc_ indx_ok = np.isfinite(values) outer_vect[inner_vect[indx_ok]] = values[indx_ok] return @@ -5403,6 +5469,7 @@ def _aux_affect_object_float( name_els, inner_vect=inner_vect, outer_vect=outer_vect, + _nm_ch_bk_key=_nm_ch_bk_key ) return @@ -5415,7 +5482,11 @@ def _aux_affect_object_float( ) el_id, new_val = el if isinstance(el_id, str): - tmp = np.nonzero(name_els == el_id)[0] + if self._names_chronics_to_backend is not None and _nm_ch_bk_key in self._names_chronics_to_backend: + # initial action to set the state, might use the name in the time series... + nms_conv = self._names_chronics_to_backend[_nm_ch_bk_key] + el_id_or_name = nms_conv[el_id_or_name] + tmp = (name_els == el_id).nonzero()[0] if len(tmp) == 0: raise IllegalAction(f"No known {name_el} with name {el_id}") el_id = tmp[0] @@ -5426,12 +5497,17 @@ def _aux_affect_object_float( name_els, inner_vect=inner_vect, outer_vect=outer_vect, + _nm_ch_bk_key=_nm_ch_bk_key, ) elif isinstance(values, dict): # 2 cases: either key = load_id and value = new_bus or key = load_name and value = new bus for key, new_val in values.items(): if isinstance(key, str): - tmp = np.nonzero(name_els == key)[0] + if self._names_chronics_to_backend is not None and _nm_ch_bk_key in self._names_chronics_to_backend: + # initial action to set the state, might use the name in the time series... + nms_conv = self._names_chronics_to_backend[_nm_ch_bk_key] + key = nms_conv[key] + tmp = (name_els == key).nonzero()[0] if len(tmp) == 0: raise IllegalAction(f"No known {name_el} with name {key}") key = tmp[0] @@ -5442,6 +5518,7 @@ def _aux_affect_object_float( name_els, inner_vect=inner_vect, outer_vect=outer_vect, + _nm_ch_bk_key=_nm_ch_bk_key ) else: raise IllegalAction( @@ -5562,6 +5639,7 @@ def redispatch(self, values): self.name_gen, np.arange(self.n_gen), self._redispatch, + _nm_ch_bk_key="prods", ) self._modif_redispatch = True except Exception as exc_: @@ -5669,6 +5747,7 @@ def curtail(self, values): self.name_gen, np.arange(self.n_gen), self._curtail, + _nm_ch_bk_key="prods", ) self._modif_curtailment = True except Exception as exc_: @@ -5928,7 +6007,7 @@ def _aux_change_bus_sub(self, values): def _aux_sub_when_dict_get_id(self, sub_id): if isinstance(sub_id, str): - tmp = np.nonzero(self.name_sub == sub_id)[0] + tmp = (self.name_sub == sub_id).nonzero()[0] if len(tmp) == 0: raise IllegalAction(f"No substation named {sub_id}") sub_id = tmp[0] @@ -5998,6 +6077,7 @@ def curtailment_mw_to_ratio(self, curtailment_mw) -> np.ndarray: self.name_gen, np.arange(self.n_gen), values, + _nm_ch_bk_key="prods", ) values /= self.gen_pmax values[values >= 1.0] = 1.0 @@ -6240,7 +6320,7 @@ def _aux_decompose_as_unary_actions_change_ls(self, cls, group_line_status, res) tmp._switch_line_status = copy.deepcopy(self._switch_line_status) res["change_line_status"] = [tmp] else: - lines_changed = np.nonzero(self._switch_line_status)[0] + lines_changed = (self._switch_line_status).nonzero()[0] res["change_line_status"] = [] for l_id in lines_changed: tmp = cls() @@ -6272,7 +6352,7 @@ def _aux_decompose_as_unary_actions_set_ls(self, cls, group_line_status, res): tmp._set_line_status = 1 * self._set_line_status res["set_line_status"] = [tmp] else: - lines_changed = np.nonzero(self._set_line_status != 0)[0] + lines_changed = (self._set_line_status != 0).nonzero()[0] res["set_line_status"] = [] for l_id in lines_changed: tmp = cls() @@ -6287,7 +6367,7 @@ def _aux_decompose_as_unary_actions_redisp(self, cls, group_redispatch, res): tmp._redispatch = 1. * self._redispatch res["redispatch"] = [tmp] else: - gen_changed = np.nonzero(np.abs(self._redispatch) >= 1e-7)[0] + gen_changed = (np.abs(self._redispatch) >= 1e-7).nonzero()[0] res["redispatch"] = [] for g_id in gen_changed: tmp = cls() @@ -6302,7 +6382,7 @@ def _aux_decompose_as_unary_actions_storage(self, cls, group_storage, res): tmp._storage_power = 1. * self._storage_power res["set_storage"] = [tmp] else: - sto_changed = np.nonzero(np.abs(self._storage_power) >= 1e-7)[0] + sto_changed = (np.abs(self._storage_power) >= 1e-7).nonzero()[0] res["set_storage"] = [] for s_id in sto_changed: tmp = cls() @@ -6317,7 +6397,7 @@ def _aux_decompose_as_unary_actions_curtail(self, cls, group_curtailment, res): tmp._curtail = 1. * self._curtail res["curtail"] = [tmp] else: - gen_changed = np.nonzero(np.abs(self._curtail + 1.) >= 1e-7)[0] #self._curtail != -1 + gen_changed = (np.abs(self._curtail + 1.) >= 1e-7).nonzero()[0] #self._curtail != -1 res["curtail"] = [] for g_id in gen_changed: tmp = cls() @@ -6410,7 +6490,6 @@ def decompose_as_unary_actions(self, tmp += a assert tmp == act - Parameters ---------- group_topo : bool, optional @@ -6473,3 +6552,56 @@ def decompose_as_unary_actions(self, if self._modif_curtailment: self._aux_decompose_as_unary_actions_curtail(cls, group_curtail, res) return res + + def _add_act_and_remove_line_status_only_set(self, other: "BaseAction") -> "BaseAction": + """INTERNAL + + This is used by the environment when combining action in the "set state" in env.reset. + + It supposes both self and other are only "set" actions + + .. versionadded:: 1.10.2 + + """ + self += other + cls = type(self) + # switch off in self the element disconnected in other + switched_off = other._set_line_status == -1 + switched_off |= other._set_topo_vect[cls.line_or_pos_topo_vect] == -1 + switched_off |= other._set_topo_vect[cls.line_ex_pos_topo_vect] == -1 + self._set_topo_vect[cls.line_or_pos_topo_vect[switched_off]] = -1 + self._set_topo_vect[cls.line_ex_pos_topo_vect[switched_off]] = -1 + self._set_line_status[switched_off] = -1 + + # switch on in self the element reconnected in other + switched_on = other._set_line_status == 1 + switched_on |= other._set_topo_vect[cls.line_or_pos_topo_vect] > 0 + switched_on |= other._set_topo_vect[cls.line_ex_pos_topo_vect] > 0 + self._set_line_status[switched_on] = 1 + # "reconnect" object through topo vect + topo_vect = other._set_topo_vect > 0 + self._set_topo_vect[topo_vect] = other._set_topo_vect[topo_vect] + + if (self._set_line_status != 0).any(): + self._modif_set_status = True + if (self._set_topo_vect != 0).any(): + self._modif_set_bus = True + return self + + def remove_change(self) -> "BaseAction": + """This function will modify 'self' and remove all "change" action type. + + It is mainly used in the environment, when removing the "change" type for setting the original + state of the grid. + + .. versionadded:: 1.10.2 + + """ + if self._change_bus_vect.any(): + warnings.warn("This action modified the buses with `change_bus` ") + self._change_bus_vect[:] = False + self._modif_change_bus = False + if self._switch_line_status.any(): + self._switch_line_status[:] = False + self._modif_change_status = False + return self diff --git a/grid2op/Action/completeAction.py b/grid2op/Action/completeAction.py index 548b59009..7b7fef238 100644 --- a/grid2op/Action/completeAction.py +++ b/grid2op/Action/completeAction.py @@ -5,6 +5,8 @@ # 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 typing import Optional, Dict, Literal from grid2op.Action.baseAction import BaseAction @@ -16,5 +18,5 @@ class CompleteAction(BaseAction): class is used by the chronics, the environment the opponent or the voltage controler for example. """ - def __init__(self): - BaseAction.__init__(self) + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + BaseAction.__init__(self, _names_chronics_to_backend) diff --git a/grid2op/Action/dispatchAction.py b/grid2op/Action/dispatchAction.py index b0ec07fc9..63e24f971 100644 --- a/grid2op/Action/dispatchAction.py +++ b/grid2op/Action/dispatchAction.py @@ -6,6 +6,7 @@ # 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 typing import Optional, Dict, Literal from grid2op.Action.playableAction import PlayableAction @@ -22,5 +23,5 @@ class DispatchAction(PlayableAction): attr_list_vect = ["_redispatch"] attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/dontAct.py b/grid2op/Action/dontAct.py index 09fd0d1c1..b8ce0be41 100644 --- a/grid2op/Action/dontAct.py +++ b/grid2op/Action/dontAct.py @@ -6,6 +6,7 @@ # 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 typing import Optional, Dict, Literal from grid2op.Action.playableAction import PlayableAction @@ -28,7 +29,7 @@ class DontAct(PlayableAction): authorized_keys = set() attr_list_vect = [] - def __init__(self): + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): """ INTERNAL @@ -38,7 +39,7 @@ def __init__(self): more is done in this constructor. """ - PlayableAction.__init__(self) + PlayableAction.__init__(self, _names_chronics_to_backend) def update(self, dict_): """ diff --git a/grid2op/Action/playableAction.py b/grid2op/Action/playableAction.py index fd854863e..dc6a65be9 100644 --- a/grid2op/Action/playableAction.py +++ b/grid2op/Action/playableAction.py @@ -7,6 +7,7 @@ # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. import warnings +from typing import Optional, Dict, Literal from grid2op.Exceptions import AmbiguousAction from grid2op.Action.baseAction import BaseAction @@ -44,8 +45,8 @@ class PlayableAction(BaseAction): attr_list_set = set(attr_list_vect) shunt_added = True # no shunt here - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) self.authorized_keys_to_digest = { "set_line_status": self._digest_set_status, diff --git a/grid2op/Action/powerlineChangeAction.py b/grid2op/Action/powerlineChangeAction.py index e678d6a03..8dce42337 100644 --- a/grid2op/Action/powerlineChangeAction.py +++ b/grid2op/Action/powerlineChangeAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -23,5 +25,5 @@ class PowerlineChangeAction(PlayableAction): attr_list_vect = ["_switch_line_status"] attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/powerlineChangeAndDispatchAction.py b/grid2op/Action/powerlineChangeAndDispatchAction.py index 759d241e0..58fd38462 100644 --- a/grid2op/Action/powerlineChangeAndDispatchAction.py +++ b/grid2op/Action/powerlineChangeAndDispatchAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -24,5 +26,5 @@ class PowerlineChangeAndDispatchAction(PlayableAction): attr_list_vect = ["_switch_line_status", "_redispatch"] attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/powerlineChangeDispatchAndStorageAction.py b/grid2op/Action/powerlineChangeDispatchAndStorageAction.py index 7a0dfa0d0..5a5111a7f 100644 --- a/grid2op/Action/powerlineChangeDispatchAndStorageAction.py +++ b/grid2op/Action/powerlineChangeDispatchAndStorageAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -20,5 +22,5 @@ class PowerlineChangeDispatchAndStorageAction(PlayableAction): attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/powerlineSetAction.py b/grid2op/Action/powerlineSetAction.py index 81c6b67b9..cd477f784 100644 --- a/grid2op/Action/powerlineSetAction.py +++ b/grid2op/Action/powerlineSetAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -23,5 +25,5 @@ class PowerlineSetAction(PlayableAction): attr_list_vect = ["_set_line_status"] attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/powerlineSetAndDispatchAction.py b/grid2op/Action/powerlineSetAndDispatchAction.py index 97920d65a..bb6cefab3 100644 --- a/grid2op/Action/powerlineSetAndDispatchAction.py +++ b/grid2op/Action/powerlineSetAndDispatchAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -33,5 +35,5 @@ class PowerlineSetAndDispatchAction(PlayableAction): attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/serializableActionSpace.py b/grid2op/Action/serializableActionSpace.py index 723da7527..f1c8bbee7 100644 --- a/grid2op/Action/serializableActionSpace.py +++ b/grid2op/Action/serializableActionSpace.py @@ -125,7 +125,19 @@ def _get_possible_action_types(self): rnd_types.append(cls.RAISE_ALERT_ID) return rnd_types - def supports_type(self, action_type): + def supports_type(self, + action_type: Literal["set_line_status", + "change_line_status", + "set_bus", + "change_bus", + "redispatch", + "storage_power", + "set_storage", + "curtail", + "curtail_mw", + "raise_alarm", + "raise_alert"] + ): """ Returns if the current action_space supports the current action type. @@ -133,7 +145,7 @@ def supports_type(self, action_type): ---------- action_type: ``str`` One of "set_line_status", "change_line_status", "set_bus", "change_bus", "redispatch", - "storage_power", "set_storage", "curtail" or "curtail_mw" + "storage_power", "set_storage", "curtail", "curtail_mw", "raise_alarm" or "raise_alert" A string representing the action types you want to inspect. Returns @@ -181,7 +193,7 @@ def supports_type(self, action_type): "set_storage" in self.actionClass.authorized_keys ) elif action_type == "curtail_mw": - return "curtail" in self.actionClass.authorized_keys + return self.supports_type("curtail") else: return action_type in self.actionClass.authorized_keys @@ -420,7 +432,7 @@ def disconnect_powerline(self, ) if line_id is None: - line_id = np.nonzero(cls.name_line == line_name)[0] + line_id = (cls.name_line == line_name).nonzero()[0] if not len(line_id): raise AmbiguousAction( 'Line with name "{}" is not on the grid. The powerlines names are:\n{}' @@ -522,7 +534,7 @@ def reconnect_powerline( ) cls = type(self) if line_id is None: - line_id = np.nonzero(cls.name_line == line_name)[0] + line_id = (cls.name_line == line_name).nonzero()[0] if previous_action is None: res = self.actionClass() @@ -1511,7 +1523,7 @@ def _aux_get_back_to_ref_state_curtail(self, res, obs): def _aux_get_back_to_ref_state_line(self, res, obs): disc_lines = ~obs.line_status if disc_lines.any(): - li_disc = np.nonzero(disc_lines)[0] + li_disc = (disc_lines).nonzero()[0] res["powerline"] = [] for el in li_disc: act = self.actionClass() @@ -1555,7 +1567,7 @@ def _aux_get_back_to_ref_state_redisp(self, res, obs, precision=1e-5): # TODO this is ugly, probably slow and could definitely be optimized notredisp_setpoint = np.abs(obs.target_dispatch) >= 1e-7 if notredisp_setpoint.any(): - need_redisp = np.nonzero(notredisp_setpoint)[0] + need_redisp = (notredisp_setpoint).nonzero()[0] res["redispatching"] = [] # combine generators and do not exceed ramps (up or down) rem = np.zeros(self.n_gen, dtype=dt_float) @@ -1620,7 +1632,7 @@ def _aux_get_back_to_ref_state_storage( notredisp_setpoint = obs.storage_charge / obs.storage_Emax != storage_setpoint delta_time_hour = dt_float(obs.delta_time / 60.0) if notredisp_setpoint.any(): - need_ajust = np.nonzero(notredisp_setpoint)[0] + need_ajust = (notredisp_setpoint).nonzero()[0] res["storage"] = [] # combine storage units and do not exceed maximum power rem = np.zeros(self.n_storage, dtype=dt_float) diff --git a/grid2op/Action/topologyAction.py b/grid2op/Action/topologyAction.py index 4fadb649d..5f3c9617a 100644 --- a/grid2op/Action/topologyAction.py +++ b/grid2op/Action/topologyAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -36,5 +38,5 @@ class TopologyAction(PlayableAction): attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/topologyAndDispatchAction.py b/grid2op/Action/topologyAndDispatchAction.py index b85443724..66cf93cea 100644 --- a/grid2op/Action/topologyAndDispatchAction.py +++ b/grid2op/Action/topologyAndDispatchAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -38,5 +40,5 @@ class TopologyAndDispatchAction(PlayableAction): attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/topologyChangeAction.py b/grid2op/Action/topologyChangeAction.py index c8ede25a2..70423e23c 100644 --- a/grid2op/Action/topologyChangeAction.py +++ b/grid2op/Action/topologyChangeAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -24,5 +26,5 @@ class TopologyChangeAction(PlayableAction): attr_list_vect = ["_change_bus_vect", "_switch_line_status"] attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/topologyChangeAndDispatchAction.py b/grid2op/Action/topologyChangeAndDispatchAction.py index 11947f262..4e719375f 100644 --- a/grid2op/Action/topologyChangeAndDispatchAction.py +++ b/grid2op/Action/topologyChangeAndDispatchAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -30,5 +32,5 @@ class TopologyChangeAndDispatchAction(PlayableAction): attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/topologySetAction.py b/grid2op/Action/topologySetAction.py index 204109694..2e58867df 100644 --- a/grid2op/Action/topologySetAction.py +++ b/grid2op/Action/topologySetAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -25,5 +27,5 @@ class TopologySetAction(PlayableAction): attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/topologySetAndDispatchAction.py b/grid2op/Action/topologySetAndDispatchAction.py index dee7d797a..85ba10beb 100644 --- a/grid2op/Action/topologySetAndDispatchAction.py +++ b/grid2op/Action/topologySetAndDispatchAction.py @@ -6,6 +6,8 @@ # 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 typing import Optional, Dict, Literal + from grid2op.Action.playableAction import PlayableAction @@ -24,5 +26,5 @@ class TopologySetAndDispatchAction(PlayableAction): attr_list_vect = ["_set_line_status", "_set_topo_vect", "_redispatch"] attr_list_set = set(attr_list_vect) - def __init__(self): - super().__init__() + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): + super().__init__(_names_chronics_to_backend) diff --git a/grid2op/Action/voltageOnlyAction.py b/grid2op/Action/voltageOnlyAction.py index 996be38e9..a90eed785 100644 --- a/grid2op/Action/voltageOnlyAction.py +++ b/grid2op/Action/voltageOnlyAction.py @@ -6,6 +6,7 @@ # 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 typing import Optional, Dict, Literal import warnings from grid2op.Exceptions import AmbiguousAction @@ -32,13 +33,13 @@ class VoltageOnlyAction(BaseAction): _shunt_added = False _first_init = True - def __init__(self): + def __init__(self, _names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None): """ See the definition of :func:`BaseAction.__init__` and of :class:`BaseAction` for more information. Nothing more is done in this constructor. """ - BaseAction.__init__(self) + BaseAction.__init__(self, _names_chronics_to_backend) if VoltageOnlyAction._shunt_added is False and type(self).shunts_data_available: VoltageOnlyAction.attr_list_vect += ["shunt_p", "shunt_q", "shunt_bus"] diff --git a/grid2op/Agent/oneChangeThenNothing.py b/grid2op/Agent/oneChangeThenNothing.py index 49f8e6df8..3b8aa2299 100644 --- a/grid2op/Agent/oneChangeThenNothing.py +++ b/grid2op/Agent/oneChangeThenNothing.py @@ -6,6 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. +import warnings from grid2op.Agent.baseAgent import BaseAgent @@ -29,6 +30,11 @@ class OneChangeThenNothing(BaseAgent): .. code-block:: python + # This class has been deprecated, please use the env.reset() + # with proper options instead + + + DEPRECATED ! import grid2op from grid2op.Agent import OneChangeThenNothing acts_dict_ = [{}, {"set_line_status": [(0,-1)]}] # list of dictionaries. Each dictionary @@ -44,12 +50,35 @@ class OneChangeThenNothing(BaseAgent): # run 2 episode with it res_2 = runner.run(nb_episode=2) + Notes: + ------ + + After grid2op 1.10.2, this class has been deprecated. A cleaner alternative + to use it is to set the initial state of grid when calling `env.reset` like this: + + .. code-block:: python + + import grid2op + + env = grid2op.make("l2rpn_case14_sandbox") # create an environment + dict_act_json = ... # dict representing an action + obs = env.reset(options={"init state": dict_act_json}) + + This way of doing offers: + + - more flexibility: rules are not checked + - more flexibility: any type of actions acting on anything can be performed + (even if the action would be illegal for the agent) + - less trouble: cooldown are not affected + """ my_dict = {} def __init__(self, action_space): BaseAgent.__init__(self, action_space) + cls = type(self) + warnings.warn(f"Deprecated class, please use `env.reset(options={{'init state': {self.action_space(cls.my_dict).to_json()}, 'method': 'ignore' }})` instead") self.has_changed = False self.do_nothing_action = self.action_space({}) diff --git a/grid2op/Agent/recoPowerLinePerArea.py b/grid2op/Agent/recoPowerLinePerArea.py index e6142124c..322d04b0f 100644 --- a/grid2op/Agent/recoPowerLinePerArea.py +++ b/grid2op/Agent/recoPowerLinePerArea.py @@ -57,7 +57,7 @@ def act(self, observation: BaseObservation, reward: float, done : bool=False): return self.action_space() area_used = np.full(self.nb_area, fill_value=False, dtype=bool) reco_ids = [] - for l_id in np.nonzero(can_be_reco)[0]: + for l_id in can_be_reco.nonzero()[0]: if not area_used[self.lines_to_area_id[l_id]]: reco_ids.append(l_id) area_used[self.lines_to_area_id[l_id]] = True diff --git a/grid2op/Agent/recoPowerlineAgent.py b/grid2op/Agent/recoPowerlineAgent.py index 97ba1ed36..c7462877f 100644 --- a/grid2op/Agent/recoPowerlineAgent.py +++ b/grid2op/Agent/recoPowerlineAgent.py @@ -28,6 +28,6 @@ def _get_tested_action(self, observation): if can_be_reco.any(): res = [ self.action_space({"set_line_status": [(id_, +1)]}) - for id_ in np.nonzero(can_be_reco)[0] + for id_ in (can_be_reco).nonzero()[0] ] return res diff --git a/grid2op/Backend/backend.py b/grid2op/Backend/backend.py index e84822716..3e2b96d21 100644 --- a/grid2op/Backend/backend.py +++ b/grid2op/Backend/backend.py @@ -22,6 +22,7 @@ # python version is probably bellow 3.11 from typing_extensions import Self +import grid2op from grid2op.dtypes import dt_int, dt_float, dt_bool from grid2op.Exceptions import ( EnvError, @@ -66,19 +67,22 @@ class Backend(GridObjects, ABC): All the abstract methods (that need to be implemented for a backend to work properly) are (more information given in the :ref:`create-backend-module` page): - - :func:`Backend.load_grid` - - :func:`Backend.apply_action` - - :func:`Backend.runpf` - - :func:`Backend.get_topo_vect` - - :func:`Backend.generators_info` - - :func:`Backend.loads_info` - - :func:`Backend.lines_or_info` - - :func:`Backend.lines_ex_info` + - :func:`Backend.load_grid` (called once per episode, or if :func:`Backend.reset` is implemented, once for the entire + lifetime of the environment) + - :func:`Backend.apply_action` (called once per episode -initialization- and at least once per step) + - :func:`Backend.runpf` (called once per episode -initialization- and at least once per step) + - :func:`Backend.get_topo_vect` (called once per episode -initialization- and at least once per step) + - :func:`Backend.generators_info` (called once per episode -initialization- and at least once per step) + - :func:`Backend.loads_info` (called once per episode -initialization- and at least once per step) + - :func:`Backend.lines_or_info` (called once per episode -initialization- and at least once per step) + - :func:`Backend.lines_ex_info` (called once per episode -initialization- and at least once per step) And optionally: + - :func:`Backend.reset` will reload the powergrid from the hard drive by default. This is rather slow and we + recommend to overload it. - :func:`Backend.close` (this is mandatory if your backend implementation (`self._grid`) is relying on some - c / c++ code that do not free memory automatically. + c / c++ code that do not free memory automatically.) - :func:`Backend.copy` (not that this is mandatory if your backend implementation (in `self._grid`) cannot be deep copied using the python copy.deepcopy function) [as of grid2op >= 1.7.1 it is no more required. If not implemented, you won't be able to use some of grid2op feature however] @@ -88,8 +92,6 @@ class Backend(GridObjects, ABC): at the "origin" side and just return the "a_or" vector. You want to do something smarter here. - :func:`Backend._disconnect_line`: has a default slow implementation using "apply_action" that might can most likely be optimized in your backend. - - :func:`Backend.reset` will reload the powergrid from the hard drive by default. This is rather slow and we - recommend to overload it. And, if the flag :attr:Backend.shunts_data_available` is set to ``True`` the method :func:`Backend.shunt_info` should also be implemented. @@ -99,12 +101,6 @@ class Backend(GridObjects, ABC): `shunt_to_subid`, `name_shunt` and function `shunt_info` and handle the modification of shunts bus, active value and reactive value in the "apply_action" function). - - In order to be valid and carry out some computations, you should call :func:`Backend.load_grid` and later - :func:`grid2op.Spaces.GridObjects.assert_grid_correct`. It is also more than recommended to call - :func:`Backend.assert_grid_correct_after_powerflow` after the first powerflow. This is all carried ou in the - environment properly. - Attributes ---------- detailed_infos_for_cascading_failures: :class:`bool` @@ -119,9 +115,7 @@ class Backend(GridObjects, ABC): """ IS_BK_CONVERTER : bool = False - - env_name : str = "unknown" - + # action to set me my_bk_act_class : "Optional[grid2op.Action._backendAction._BackendAction]"= None _complete_action_class : "Optional[grid2op.Action.CompleteAction]"= None @@ -224,7 +218,7 @@ def cannot_handle_more_than_2_busbar(self): If not called, then the `environment` will not be able to use more than 2 busbars per substations. .. seealso:: - :func:`Backend.cnot_handle_more_than_2_busbar` + :func:`Backend.cannot_handle_more_than_2_busbar` .. note:: From grid2op 1.10.0 it is preferable that your backend calls one of @@ -1418,6 +1412,119 @@ def check_kirchoff(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray diff_v_bus[:, :] = v_bus[:, :, 1] - v_bus[:, :, 0] return p_subs, q_subs, p_bus, q_bus, diff_v_bus + def _fill_names_obj(self): + """fill the name vectors (**eg** name_line) if not done already in the backend. + This function is used to fill the name of an object of a class. It will also check the existence + of these vectors in the class. + """ + cls = type(self) + if self.name_line is None: + if cls.name_line is None: + line_or_to_subid = cls.line_or_to_subid if cls.line_or_to_subid is not None else self.line_or_to_subid + line_ex_to_subid = cls.line_ex_to_subid if cls.line_ex_to_subid is not None else self.line_ex_to_subid + self.name_line = [ + "{}_{}_{}".format(or_id, ex_id, l_id) + for l_id, (or_id, ex_id) in enumerate( + zip(line_or_to_subid, line_ex_to_subid) + ) + ] + self.name_line = np.array(self.name_line) + warnings.warn( + "name_line is None so default line names have been assigned to your grid. " + "(FYI: Line names are used to make the correspondence between the chronics and the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + else: + self.name_line = cls.name_line + + if self.name_load is None: + if cls.name_load is None: + load_to_subid = cls.load_to_subid if cls.load_to_subid is not None else self.load_to_subid + self.name_load = [ + "load_{}_{}".format(bus_id, load_id) + for load_id, bus_id in enumerate(load_to_subid) + ] + self.name_load = np.array(self.name_load) + warnings.warn( + "name_load is None so default load names have been assigned to your grid. " + "(FYI: load names are used to make the correspondence between the chronics and the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + else: + self.name_load = cls.name_load + + if self.name_gen is None: + if cls.name_gen is None: + gen_to_subid = cls.gen_to_subid if cls.gen_to_subid is not None else self.gen_to_subid + self.name_gen = [ + "gen_{}_{}".format(bus_id, gen_id) + for gen_id, bus_id in enumerate(gen_to_subid) + ] + self.name_gen = np.array(self.name_gen) + warnings.warn( + "name_gen is None so default generator names have been assigned to your grid. " + "(FYI: generator names are used to make the correspondence between the chronics and " + "the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + else: + self.name_gen = cls.name_gen + + if self.name_sub is None: + if cls.name_sub is None: + n_sub = cls.n_sub if cls.n_sub is not None and cls.n_sub > 0 else self.n_sub + self.name_sub = ["sub_{}".format(sub_id) for sub_id in range(n_sub)] + self.name_sub = np.array(self.name_sub) + warnings.warn( + "name_sub is None so default substation names have been assigned to your grid. " + "(FYI: substation names are used to make the correspondence between the chronics and " + "the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + else: + self.name_sub = cls.name_sub + + if self.name_storage is None: + if cls.name_storage is None: + storage_to_subid = cls.storage_to_subid if cls.storage_to_subid is not None else self.storage_to_subid + self.name_storage = [ + "storage_{}_{}".format(bus_id, sto_id) + for sto_id, bus_id in enumerate(storage_to_subid) + ] + self.name_storage = np.array(self.name_storage) + warnings.warn( + "name_storage is None so default storage unit names have been assigned to your grid. " + "(FYI: storage names are used to make the correspondence between the chronics and " + "the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + else: + self.name_storage = cls.name_storage + + if cls.shunts_data_available: + if self.name_shunt is None: + if cls.name_shunt is None: + shunt_to_subid = cls.shunt_to_subid if cls.shunt_to_subid is not None else self.shunt_to_subid + self.name_shunt = [ + "shunt_{}_{}".format(bus_id, sh_id) + for sh_id, bus_id in enumerate(shunt_to_subid) + ] + self.name_shunt = np.array(self.name_shunt) + warnings.warn( + "name_shunt is None so default storage unit names have been assigned to your grid. " + "(FYI: storage names are used to make the correspondence between the chronics and " + "the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + else: + self.name_shunt = cls.name_shunt + def load_redispacthing_data(self, path : Union[os.PathLike, str], name : Optional[str]="prods_charac.csv") -> None: @@ -1430,6 +1537,13 @@ def load_redispacthing_data(self, We don't recommend at all to modify this function. + Notes + ----- + Before you use this function, make sure the names of the generators are properly set. + + For example you can either read them from the grid (setting self.name_gen) or call + self._fill_names_obj() beforehand (this later is done in the environment.) + Parameters ---------- path: ``str`` @@ -1458,7 +1572,6 @@ def load_redispacthing_data(self, to change it. """ - self._fill_names() self.redispatching_unit_commitment_availble = False # for redispatching @@ -1510,7 +1623,6 @@ def load_redispacthing_data(self, "min_up_time": row["min_up_time"], "min_down_time": row["min_down_time"], } - self.redispatching_unit_commitment_availble = True self.gen_type = np.full(self.n_gen, fill_value="aaaaaaaaaa") self.gen_pmin = np.full(self.n_gen, fill_value=1.0, dtype=dt_float) @@ -1575,6 +1687,13 @@ def load_storage_data(self, This method will load everything needed in presence of storage unit on the grid. We don't recommend at all to modify this function. + + Notes + ----- + Before you use this function, make sure the names of the generators are properly set. + + For example you can either read them from the grid (setting self.name_gen) or call + self._fill_names_obj() beforehand (this later is done in the environment.) Parameters ---------- @@ -1624,7 +1743,7 @@ def load_storage_data(self, fullpath = os.path.join(path, name) if not os.path.exists(fullpath): raise BackendError( - f"There are storage unit on the grid, yet we could not locate their description." + f"There are {self.n_storage} storage unit(s) on the grid, yet we could not locate their description." f'Please make sure to have a file "{name}" where the environment data are located.' f'For this environment the location is "{path}"' ) @@ -1984,19 +2103,44 @@ def assert_grid_correct(self) -> None: self.__class__.__name__, self.__class__, ) + # reset the attribute of the grid2op.Backend.Backend class # that can be messed up with depending on the initialization of the backend Backend._clear_class_attribute() # reset totally the grid2op Backend type - # orig_type._clear_class_attribute() - orig_type._clear_grid_dependant_class_attributes() # only reset the attributes that could be modified by user - + + # only reset the attributes that could be modified by the environment while keeping the + # attribute that can be defined in the Backend implementation (eg support of shunt) + orig_type._clear_grid_dependant_class_attributes() + my_cls = type(self) my_cls.my_bk_act_class = _BackendAction.init_grid(my_cls) my_cls._complete_action_class = CompleteAction.init_grid(my_cls) my_cls._complete_action_class._add_shunt_data() my_cls._complete_action_class._update_value_set() my_cls.assert_grid_correct_cls() + self._remove_my_attr_cls() + def _remove_my_attr_cls(self): + """ + INTERNAL + + .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ + + This function is called at the end of :func:`Backend.assert_grid_correct` and it "cleans" the attribute of the + backend object that are stored in the class now, to avoid discrepency between what has been read from the + grid and what have been processed by grid2op (for example in "compatibility" mode, storage are deactivated, so + `self.n_storage` would be different that `type(self).n_storage`) + + For this to work, the grid must first be initialized correctly, with the proper type (name of the environment + in the class name !) + """ + cls = type(self) + if cls._CLS_DICT_EXTENDED is not None: + for attr_nm, val in cls._CLS_DICT_EXTENDED.items(): + if hasattr(self, attr_nm) and hasattr(cls, attr_nm): + if id(getattr(self, attr_nm)) != id(getattr(cls, attr_nm)): + delattr(self, attr_nm) + def assert_grid_correct_after_powerflow(self) -> None: """ INTERNAL diff --git a/grid2op/Backend/educPandaPowerBackend.py b/grid2op/Backend/educPandaPowerBackend.py index ec045736d..a56d66000 100644 --- a/grid2op/Backend/educPandaPowerBackend.py +++ b/grid2op/Backend/educPandaPowerBackend.py @@ -62,7 +62,9 @@ class EducPandaPowerBackend(Backend): real :class:`grid2op.Backend.PandaPowerBackend` class. """ - + + shunts_data_available = False + def __init__(self, detailed_infos_for_cascading_failures : Optional[bool]=False, can_be_copied : Optional[bool]=True): @@ -94,6 +96,7 @@ def __init__(self, # NB: this instance of backend is here for academic purpose only. For clarity, it does not handle # neither shunt nor storage unit. + self.shunts_data_available = False ####### load the grid def load_grid(self, @@ -178,21 +181,21 @@ def load_grid(self, # initialize the number of elements per substation # now export to grid2op the substation to which objects are connected - self.load_to_subid = copy.deepcopy(self._grid.load["bus"]) - self.gen_to_subid = copy.deepcopy(self._grid.gen["bus"]) + self.load_to_subid = copy.deepcopy(self._grid.load["bus"].values) + self.gen_to_subid = copy.deepcopy(self._grid.gen["bus"].values) # here we just decide (but that is a convention we could have done it differently) # that "origin side" (grid2op) corresponds to "from_bus" from pandapower line and "hv_bus" for # pandapower trafo. self.line_or_to_subid = np.concatenate( ( - copy.deepcopy(self._grid.line["from_bus"]), - copy.deepcopy(self._grid.trafo["hv_bus"]), + copy.deepcopy(self._grid.line["from_bus"].values), + copy.deepcopy(self._grid.trafo["hv_bus"].values), ) ) self.line_ex_to_subid = np.concatenate( ( - copy.deepcopy(self._grid.line["to_bus"]), - copy.deepcopy(self._grid.trafo["lv_bus"]), + copy.deepcopy(self._grid.line["to_bus"].values), + copy.deepcopy(self._grid.trafo["lv_bus"].values), ) ) @@ -211,8 +214,8 @@ def load_grid(self, # NB: this instance of backend is here for academic purpose only. For clarity, it does not handle # neither shunt nor storage unit. - type(self).shunts_data_available = False - type(self).set_no_storage() + # type(self).shunts_data_available = False + # type(self).set_no_storage() ###### modify the grid def apply_action(self, backendAction: Union["grid2op.Action._backendAction._BackendAction", None]) -> None: diff --git a/grid2op/Backend/pandaPowerBackend.py b/grid2op/Backend/pandaPowerBackend.py index 0cb000c36..95876334c 100644 --- a/grid2op/Backend/pandaPowerBackend.py +++ b/grid2op/Backend/pandaPowerBackend.py @@ -176,7 +176,6 @@ def __init__( self._number_true_line = -1 self._corresp_name_fun = {} self._get_vector_inj = {} - self.dim_topo = -1 self._vars_action = BaseAction.attr_list_vect self._vars_action_set = BaseAction.attr_list_vect self.cst_1 = dt_float(1.0) @@ -437,7 +436,7 @@ def load_grid(self, # TODO here i force the distributed slack bus too, by removing the other from the ext_grid... self._grid.ext_grid = self._grid.ext_grid.iloc[:1] else: - self.slack_id = np.nonzero(self._grid.gen["slack"])[0] + self.slack_id = (self._grid.gen["slack"].values).nonzero()[0] with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -554,6 +553,11 @@ def load_grid(self, self.name_sub = ["sub_{}".format(i) for i, row in self._grid.bus.iterrows()] self.name_sub = np.array(self.name_sub) + if type(self).shunts_data_available: + self.n_shunt = self._grid.shunt.shape[0] + else: + self.n_shunt = None + # "hack" to handle topological changes, for now only 2 buses per substation add_topo = copy.deepcopy(self._grid.bus) # TODO n_busbar: what if non contiguous indexing ??? @@ -565,9 +569,9 @@ def load_grid(self, self._init_private_attrs() # do this at the end - self._in_service_line_col_id = int(np.nonzero(self._grid.line.columns == "in_service")[0][0]) - self._in_service_trafo_col_id = int(np.nonzero(self._grid.trafo.columns == "in_service")[0][0]) - self._in_service_storage_cold_id = int(np.nonzero(self._grid.storage.columns == "in_service")[0][0]) + self._in_service_line_col_id = int((self._grid.line.columns == "in_service").nonzero()[0][0]) + self._in_service_trafo_col_id = int((self._grid.trafo.columns == "in_service").nonzero()[0][0]) + self._in_service_storage_cold_id = int((self._grid.storage.columns == "in_service").nonzero()[0][0]) def _init_private_attrs(self) -> None: # number of elements per substation @@ -653,6 +657,21 @@ def _init_private_attrs(self) -> None: self._what_object_where[sub_id].append(("storage", "bus", i)) self.dim_topo = self.sub_info.sum() + + # shunts data + if type(self).shunts_data_available: + self.shunt_to_subid = np.zeros(self.n_shunt, dtype=dt_int) - 1 + name_shunt = [] + # TODO read name from the grid if provided + for i, (_, row) in enumerate(self._grid.shunt.iterrows()): + bus = int(row["bus"]) + name_shunt.append("shunt_{bus}_{index_shunt}".format(**row, index_shunt=i)) + self.shunt_to_subid[i] = bus + self.name_shunt = np.array(name_shunt).astype(str) + self._sh_vnkv = self._grid.bus["vn_kv"][self.shunt_to_subid].values.astype( + dt_float + ) + self._compute_pos_big_topo() # utilities for imeplementing apply_action @@ -717,21 +736,6 @@ def _init_private_attrs(self) -> None: self.storage_v = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN) self._nb_bus_before = None - # shunts data - self.n_shunt = self._grid.shunt.shape[0] - self.shunt_to_subid = np.zeros(self.n_shunt, dtype=dt_int) - 1 - name_shunt = [] - # TODO read name from the grid if provided - for i, (_, row) in enumerate(self._grid.shunt.iterrows()): - bus = int(row["bus"]) - name_shunt.append("shunt_{bus}_{index_shunt}".format(**row, index_shunt=i)) - self.shunt_to_subid[i] = bus - self.name_shunt = np.array(name_shunt) - self._sh_vnkv = self._grid.bus["vn_kv"][self.shunt_to_subid].values.astype( - dt_float - ) - # self.shunts_data_available = True # TODO shunts_data_available - # store the topoid -> objid self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)] nm_ = "load" @@ -792,7 +796,12 @@ def _init_private_attrs(self) -> None: ) # will be initialized in the "assert_grid_correct" def storage_deact_for_backward_comaptibility(self) -> None: - self._init_private_attrs() + cls = type(self) + self.storage_theta = np.full(cls.n_storage, fill_value=np.NaN, dtype=dt_float) + self.storage_p = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_q = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_v = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self._topo_vect = self._get_topo_vect() def _convert_id_topo(self, id_big_topo): """ @@ -1016,14 +1025,14 @@ def _aux_runpf_pp(self, is_dc: bool): raise pp.powerflow.LoadflowNotConverged("Disconnected load: for now grid2op cannot handle properly" " disconnected load. If you want to disconnect one, say it" " consumes 0. instead. Please check loads: " - f"{np.nonzero(~self._grid.load['in_service'])[0]}" + f"{(~self._grid.load['in_service'].values).nonzero()[0]}" ) if (~self._grid.gen["in_service"]).any(): # TODO see if there is a better way here -> do not handle this here, but rather in Backend._next_grid_state raise pp.powerflow.LoadflowNotConverged("Disconnected gen: for now grid2op cannot handle properly" " disconnected generators. If you want to disconnect one, say it" " produces 0. instead. Please check generators: " - f"{np.nonzero(~self._grid.gen['in_service'])[0]}" + f"{(~self._grid.gen['in_service'].values).nonzero()[0]}" ) try: if is_dc: @@ -1105,9 +1114,9 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]: # see https://github.com/e2nIEE/pandapower/issues/1996 for a fix for l_id in range(cls.n_load): if cls.load_to_subid[l_id] in cls.gen_to_subid: - ind_gens = np.nonzero( + ind_gens = ( cls.gen_to_subid == cls.load_to_subid[l_id] - )[0] + ).nonzero()[0] for g_id in ind_gens: if ( self._topo_vect[cls.load_pos_topo_vect[l_id]] @@ -1178,16 +1187,6 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]: msg = exc_.__str__() return False, BackendError(f'powerflow diverged with error :"{msg}"') - def assert_grid_correct(self) -> None: - """ - INTERNAL - - .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ - - This is done as it should be by the Environment - """ - super().assert_grid_correct() - def _reset_all_nan(self) -> None: self.p_or[:] = np.NaN self.q_or[:] = np.NaN diff --git a/grid2op/Chronics/GSFFWFWM.py b/grid2op/Chronics/GSFFWFWM.py index 385886a34..28a0bf6fb 100644 --- a/grid2op/Chronics/GSFFWFWM.py +++ b/grid2op/Chronics/GSFFWFWM.py @@ -251,7 +251,7 @@ def _generate_matenance_static(name_line, size=n_Generated_Maintenance - maxDailyMaintenance, ) are_lines_in_maintenance[ - np.nonzero(are_lines_in_maintenance)[0][not_chosen] + (are_lines_in_maintenance).nonzero()[0][not_chosen] ] = False maintenance_me[ selected_rows_beg:selected_rows_end, are_lines_in_maintenance diff --git a/grid2op/Chronics/chronicsHandler.py b/grid2op/Chronics/chronicsHandler.py index a1b66bce7..44ad9256f 100644 --- a/grid2op/Chronics/chronicsHandler.py +++ b/grid2op/Chronics/chronicsHandler.py @@ -11,6 +11,7 @@ import numpy as np from datetime import timedelta +from grid2op.Exceptions.envExceptions import EnvError from grid2op.dtypes import dt_int from grid2op.Exceptions import Grid2OpException, ChronicsError from grid2op.Space import RandomObject @@ -88,6 +89,21 @@ def __init__( ) from exc_ @property + def action_space(self): + return self._real_data.action_space + + @action_space.setter + def action_space(self, values): + try: + self._real_data.action_space = values + except EnvError as exc_: + raise EnvError("Impossible to set the action_space for this 'chronics_handler'. " + f"It appears they have already been set previously. Do you try to use " + "The same 'chronics_handler' for two different environment ? " + "If so, you probably should not. \n" + "If you deep copied a 'chronics_handler', you can call `cpy.cleanup_action_space()` " + "on the copy to solve this issue.") from exc_ + @property def kwargs(self): res = copy.deepcopy(self._kwargs) if self._real_data is not None: @@ -206,3 +222,10 @@ def __getattr__(self, name): # https://github.com/matplotlib/matplotlib/issues/7852/ return object.__getattr__(self, name) return getattr(self._real_data, name) + + def cleanup_action_space(self): + """INTERNAL, used to forget the "old" action_space when the + chronics_handler is copied for example. + """ + self._real_data.cleanup_action_space() + \ No newline at end of file diff --git a/grid2op/Chronics/fromMultiEpisodeData.py b/grid2op/Chronics/fromMultiEpisodeData.py index 309f990e6..7cfc2433f 100644 --- a/grid2op/Chronics/fromMultiEpisodeData.py +++ b/grid2op/Chronics/fromMultiEpisodeData.py @@ -11,7 +11,7 @@ import numpy as np import copy import warnings -from typing import Optional, Union, List +from typing import Optional, Union, List, Dict, Literal from pathlib import Path from grid2op.Exceptions import ( @@ -152,6 +152,9 @@ def initialize( names_chronics_to_backend=names_chronics_to_backend, ) self._episode_data = self.data._episode_data + if self.action_space is not None: + if self.data.action_space is None: + self.data.action_space = self.action_space def done(self): return self.data.done() @@ -186,3 +189,10 @@ def max_timestep(self): def fast_forward(self, nb_timestep): self.data.fast_forward(nb_timestep) + + def get_init_action(self, names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + return self.data.get_init_action(names_chronics_to_backend) + + def cleanup_action_space(self): + super().cleanup_action_space() + self.data.cleanup_action_space() diff --git a/grid2op/Chronics/fromNPY.py b/grid2op/Chronics/fromNPY.py index 983d10ebd..4d34f80ac 100644 --- a/grid2op/Chronics/fromNPY.py +++ b/grid2op/Chronics/fromNPY.py @@ -6,7 +6,7 @@ # 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 typing import Optional, Union +from typing import Optional, Union, Dict, Literal import numpy as np import hashlib from datetime import datetime, timedelta @@ -68,12 +68,13 @@ class FromNPY(GridValue): "load_q": load_q, "prod_p": prod_p, "prod_v": prod_v - # other parameters includes + ## other parameters includes # maintenance # load_p_forecast # load_q_forecast # prod_p_forecast # prod_v_forecast + # init_state # new in 1.10.2 }) # you can use env normally, including in runners @@ -129,6 +130,7 @@ def __init__( chunk_size: Optional[int] = None, i_start: Optional[int] = None, i_end: Optional[int] = None, # excluded, as always in python + init_state: Optional["grid2op.Action.BaseAction"] = None, **kwargs ): GridValue.__init__( @@ -193,17 +195,6 @@ def __init__( "This feature is not available at the moment. Fill a github issue at " "https://github.com/rte-france/Grid2Op/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=" ) - # self.has_hazards = True - # if self.n_line is None: - # self.n_line = hazards.shape[1] - # else: - # assert self.n_line == hazards.shape[1] - # assert load_p.shape[0] == hazards.shape[0] - - # self.hazards = hazards # TODO copy ! - # self.hazard_duration = np.zeros(shape=(self.hazards.shape[0], self.n_line), dtype=dt_int) - # for line_id in range(self.n_line): - # self.hazard_duration[:, line_id] = self.get_hazard_duration_1d(self.hazards[:, line_id]) self._forecasts = None if load_p_forecast is not None: @@ -229,6 +220,8 @@ def __init__( raise ChronicsError( "if prod_p_forecast is not None, then load_p_forecast should not be None" ) + + self._init_state = init_state def initialize( self, @@ -700,3 +693,9 @@ def change_i_end(self, new_i_end: Union[int, None]): self.__new_iend = int(new_i_end) else: self.__new_iend = None + + def get_init_action(self, names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + # names_chronics_to_backend is ignored, names should be consistent between the environment + # and the initial state + + return self._init_state diff --git a/grid2op/Chronics/fromOneEpisodeData.py b/grid2op/Chronics/fromOneEpisodeData.py index e3214b5b7..9dbe959ec 100644 --- a/grid2op/Chronics/fromOneEpisodeData.py +++ b/grid2op/Chronics/fromOneEpisodeData.py @@ -11,9 +11,10 @@ import numpy as np import copy import warnings -from typing import Union, Tuple +from typing import Union, Tuple, Optional, Dict, Literal from pathlib import Path +import grid2op from grid2op.Exceptions import ( ChronicsError, ChronicsNotFoundError ) @@ -25,6 +26,7 @@ TYPE_EP_DATA_INGESTED = Union[str, Path, EpisodeData, Tuple[str, str]] + class FromOneEpisodeData(GridValue): """This class allows to use the :class:`grid2op.Chronics.handlers.BaseHandler` to read back data stored in :class:`grid2op.Episode.EpisodeData` @@ -422,3 +424,18 @@ def fast_forward(self, nb_timestep): self.load_next() # for this class I suppose the real data AND the forecast are read each step self.forecasts() + + def get_init_action(self, names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + # names_chronics_to_backend is ignored because it does not really make sense + # when read from the hard drive + obs = self._episode_data.observations[0] + dict_set = {"set_bus": obs.topo_vect} + if self.action_space.supports_type("redispatch"): + dict_set["redispatch"] = obs.target_dispatch + if self.action_space.supports_type("set_storage"): + dict_set["set_storage"] = obs.storage_power_target + if self.action_space.supports_type("curtail"): + dict_set["curtail"] = obs.curtailment_limit + dict_set["curtail"][~type(obs).gen_renewable] = -1 + # TODO shunts ! + return self.action_space(dict_set, check_legal=False) diff --git a/grid2op/Chronics/gridStateFromFile.py b/grid2op/Chronics/gridStateFromFile.py index d9824637f..4874a51a4 100644 --- a/grid2op/Chronics/gridStateFromFile.py +++ b/grid2op/Chronics/gridStateFromFile.py @@ -6,13 +6,17 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. +import json import os import copy +from typing import Union, Optional, Dict, Literal import numpy as np import pandas as pd import warnings from datetime import datetime, timedelta +import grid2op +from grid2op.Exceptions import Grid2OpException from grid2op.dtypes import dt_int, dt_float, dt_bool from grid2op.Exceptions import ( IncorrectNumberOfElements, @@ -1225,3 +1229,35 @@ def split_and_save(self, datetime_beg, datetime_end, path_out): ) with open(os.path.join(path_out, "time_interval.info"), "w") as f: f.write("{:%H:%M}\n".format(tmp_for_time_delta)) + + def get_init_action(self, names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + from grid2op.Action import BaseAction + maybe_path = os.path.join(self.path, "init_state.json") + if not os.path.exists(maybe_path): + return None + if self.action_space is None: + raise Grid2OpException(f"We detected an action to set the intial state of the grid " + f"but we cannot build it because the 'action_space' of the time" + f"serie is not set.") + try: + with open(maybe_path, "r", encoding="utf-8") as f: + maybe_act_dict = json.load(f) + except Exception as exc_: + raise Grid2OpException(f"Invalid action provided to initialize the powergrid (not readable by json)." + f"Check file located at {maybe_path}") from exc_ + + try: + act : BaseAction = self.action_space(maybe_act_dict, + _names_chronics_to_backend=names_chronics_to_backend, + check_legal=False) + except Grid2OpException as exc_: + raise Grid2OpException(f"Impossible to build the action to set the grid. Please fix the " + f"file located at {maybe_path}.") from exc_ + + # TODO check change bus, redispatching, change status etc. + # TODO basically anything that would be suspicious here + error, reason = act.is_ambiguous() + if error: + raise Grid2OpException(f"The action to set the grid to its original configuration " + f"is ambiguous. Please check {maybe_path}") from reason + return act diff --git a/grid2op/Chronics/gridValue.py b/grid2op/Chronics/gridValue.py index 90e3227e2..e49c6bb57 100644 --- a/grid2op/Chronics/gridValue.py +++ b/grid2op/Chronics/gridValue.py @@ -5,14 +5,17 @@ # 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. + import numpy as np import warnings from datetime import datetime, timedelta from abc import ABC, abstractmethod +from typing import Union, Dict, Literal +import grid2op from grid2op.dtypes import dt_int from grid2op.Space import RandomObject -from grid2op.Exceptions import EnvError +from grid2op.Exceptions import EnvError, Grid2OpException # TODO sous echantillonner ou sur echantilloner les scenario: need to modify everything that affect the number # TODO of time steps there, for example "Space.gen_min_time_on" or "params.NB_TIMESTEP_POWERFLOW_ALLOWED" for @@ -106,6 +109,23 @@ def __init__( self.maintenance_time = None self.maintenance_duration = None self.hazard_duration = None + + # complete action space set by the environment + self.__action_space : Union["grid2op.Action.SerializableActionSpace", None] = None + + @property + def action_space(self)-> Union["grid2op.Action.SerializableActionSpace", None]: + return self.__action_space + + @action_space.setter + def action_space(self, values): + from grid2op.Action import SerializableActionSpace + if not isinstance(values, SerializableActionSpace): + raise EnvError(f"Impossible to set the action space with a value of type {type(values)}") + if self.__action_space is not None: + raise EnvError(f"Impossible to change the action space once initialized.") + # TODO maybe raise a warning if the underlying action class is not CompleteAction + self.__action_space = values def get_kwargs(self, dict_): """ @@ -115,7 +135,7 @@ def get_kwargs(self, dict_): pass @property - def max_iter(self): + def max_iter(self) -> int: return self._max_iter @max_iter.setter @@ -130,7 +150,7 @@ def initialize( order_backend_lines, order_backend_subs, names_chronics_to_backend, - ): + ) -> None: """ This function is used to initialize the data generator. It can be use to load scenarios, or to initialize noise if scenarios are generated on the fly. It must also @@ -288,8 +308,8 @@ def get_maintenance_time_1d(maintenance): a = np.diff(maintenance) # +1 is because numpy does the diff `t+1` - `t` so to get index of the initial array # I need to "+1" - start = np.nonzero(a == 1)[0] + 1 # start of maintenance - end = np.nonzero(a == -1)[0] + 1 # end of maintenance + start = (a == 1).nonzero()[0] + 1 # start of maintenance + end = (a == -1).nonzero()[0] + 1 # end of maintenance prev_ = 0 # it's efficient here as i do a loop only on the number of time there is a maintenance # and maintenance are quite rare @@ -362,8 +382,8 @@ def get_maintenance_duration_1d(maintenance): a = np.diff(maintenance) # +1 is because numpy does the diff `t+1` - `t` so to get index of the initial array # I need to "+1" - start = np.nonzero(a == 1)[0] + 1 # start of maintenance - end = np.nonzero(a == -1)[0] + 1 # end of maintenance + start = (a == 1).nonzero()[0] + 1 # start of maintenance + end = (a == -1).nonzero()[0] + 1 # end of maintenance prev_ = 0 # it's efficient here as i do a loop only on the number of time there is a maintenance # and maintenance are quite rare @@ -440,8 +460,8 @@ def get_hazard_duration_1d(hazard): a = np.diff(hazard) # +1 is because numpy does the diff `t+1` - `t` so to get index of the initial array # I need to "+1" - start = np.nonzero(a == 1)[0] + 1 # start of maintenance - end = np.nonzero(a == -1)[0] + 1 # end of maintenance + start = (a == 1).nonzero()[0] + 1 # start of maintenance + end = (a == -1).nonzero()[0] + 1 # end of maintenance prev_ = 0 # it's efficient here as i do a loop only on the number of time there is a maintenance # and maintenance are quite rare @@ -800,3 +820,39 @@ def fast_forward(self, nb_timestep): """ for _ in range(nb_timestep): self.load_next() + + def get_init_action(self, names_chronics_to_backend: Dict[Literal["loads", "prods", "lines"], Dict[str, str]]) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + """ + It is used when the environment is reset (*ie* when :func:`grid2op.Environment.Environment.reset` is called) + to set the grid in its "original" state. + + .. versionadded 1.10.2 + + Before grid2op 1.10.2 the original state is necessarily "everything connected together". + + For later version, we let the possibility to set, in the "time series folder" (or time series generators) + the possibility to change the initial condition of the grid. + + Notes + ----- + If the environment parameters :attr:`grid2op.Parameters.Parameters.IGNORE_INITIAL_STATE_TIME_SERIE` + is set to `True` (not its default value) then this is ignored. + + Returns + ------- + grid2op.Action.playableAction.PlayableAction + The desired intial configuration of the grid + """ + return None + + def cleanup_action_space(self): + """ + INTERNAL + + Used internally, do not overide + + It is for example used when making a deepcopy of a `chronics_handler` to make sure + the new copy references the proper action space of the new environment. + """ + self.__action_space = None + # NB the action space is not closed as it is NOT own by this class diff --git a/grid2op/Chronics/handlers/__init__.py b/grid2op/Chronics/handlers/__init__.py index f665ea896..bbd51d7f6 100644 --- a/grid2op/Chronics/handlers/__init__.py +++ b/grid2op/Chronics/handlers/__init__.py @@ -16,6 +16,7 @@ "PerfectForecastHandler", "NoisyForecastHandler", "LoadQFromPHandler", + "JSONInitStateHandler" ] from .baseHandler import BaseHandler @@ -28,3 +29,4 @@ from .perfectForecastHandler import PerfectForecastHandler from .noisyForecastHandler import NoisyForecastHandler from .load_q_from_p_handler import LoadQFromPHandler +from .jsonInitStateHandler import JSONInitStateHandler diff --git a/grid2op/Chronics/handlers/baseHandler.py b/grid2op/Chronics/handlers/baseHandler.py index 6de083e78..0cb51d9a9 100644 --- a/grid2op/Chronics/handlers/baseHandler.py +++ b/grid2op/Chronics/handlers/baseHandler.py @@ -9,7 +9,8 @@ import copy import os import numpy as np -from typing import Optional, Tuple +from typing import Optional, Tuple, Union +import grid2op from grid2op.Space import RandomObject from datetime import timedelta, datetime @@ -341,7 +342,7 @@ def load_next(self, dict_: dict) -> Optional[np.ndarray]: """ raise NotImplementedError() - def check_validity(self, backend): + def check_validity(self, backend) -> None: """ INTERNAL @@ -479,3 +480,17 @@ def next_chronics(self) -> None: end of each episode when the next episode is loaded. """ return None + + def get_init_dict_action(self) -> Union[dict, None]: + """ + INTERNAL + + .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ + + This function is called by the :class:`grid2op.Chronics.FromHandlers` only for the handlers responsible + for setting the "initial state" of the grid, for example :class:`JSONInitStateHandler`. + + If overidden, it is expected to return a dictionnary which can be converted to an action with an + action space. + """ + raise NotImplementedError() diff --git a/grid2op/Chronics/handlers/csvForecastHandler.py b/grid2op/Chronics/handlers/csvForecastHandler.py index a8c8285d7..046ac8704 100644 --- a/grid2op/Chronics/handlers/csvForecastHandler.py +++ b/grid2op/Chronics/handlers/csvForecastHandler.py @@ -64,6 +64,8 @@ class CSVForecastHandler(CSVHandler): not for maintenance (in this case use :class:`CSVMaintenanceHandler`) nor for environment data (in this case use :class:`CSVHandler`) + nor for setting the initial state state (in this case use + :class:`JSONInitStateHandler`) This is the default way to provide data to grid2op and its used for most l2rpn environments when forecasts are available. diff --git a/grid2op/Chronics/handlers/csvHandler.py b/grid2op/Chronics/handlers/csvHandler.py index b1ef18765..ae16f8e89 100644 --- a/grid2op/Chronics/handlers/csvHandler.py +++ b/grid2op/Chronics/handlers/csvHandler.py @@ -52,6 +52,8 @@ class CSVHandler(BaseHandler): "prod_p" or "prod_v") and not for maintenance (in this case use :class:`CSVMaintenanceHandler`) nor for forecast (in this case use :class:`CSVForecastHandler`) + nor for setting the initial state state (in this case use + :class:`JSONInitStateHandler`) This is the default way to provide data to grid2op and its used for most l2rpn environments. diff --git a/grid2op/Chronics/handlers/csvMaintenanceHandler.py b/grid2op/Chronics/handlers/csvMaintenanceHandler.py index 2c47c510f..6efc3eea4 100644 --- a/grid2op/Chronics/handlers/csvMaintenanceHandler.py +++ b/grid2op/Chronics/handlers/csvMaintenanceHandler.py @@ -41,10 +41,11 @@ class CSVMaintenanceHandler(CSVHandler): no string etc. .. warning:: - Use this class only for the ENVIRONMENT data ("load_p", "load_q", - "prod_p" or "prod_v") and not for maintenance (in this case - use :class:`CSVMaintenanceHandler`) nor for + Use this class only for the MAINTENANCE and not for environment + data ("load_p", "load_q", "prod_p" or "prod_v") nor for forecast (in this case use :class:`CSVForecastHandler`) + nor for setting the initial state state (in this case use + :class:`JSONInitStateHandler`) This is the default way to provide data to grid2op and its used for most l2rpn environments. diff --git a/grid2op/Chronics/handlers/do_nothing_handler.py b/grid2op/Chronics/handlers/do_nothing_handler.py index bcfa98bdc..bd21c8ef0 100644 --- a/grid2op/Chronics/handlers/do_nothing_handler.py +++ b/grid2op/Chronics/handlers/do_nothing_handler.py @@ -6,6 +6,7 @@ # 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 typing import Union from grid2op.Chronics.handlers.baseHandler import BaseHandler @@ -50,4 +51,6 @@ def load_next_maintenance(self): def load_next_hazard(self): return None - \ No newline at end of file + + def get_init_dict_action(self) -> Union[dict, None]: + return None \ No newline at end of file diff --git a/grid2op/Chronics/handlers/jsonInitStateHandler.py b/grid2op/Chronics/handlers/jsonInitStateHandler.py new file mode 100644 index 000000000..b7740746b --- /dev/null +++ b/grid2op/Chronics/handlers/jsonInitStateHandler.py @@ -0,0 +1,52 @@ +# Copyright (c) 2024, 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 typing import Union +import json +import os + +import grid2op +from grid2op.Exceptions import Grid2OpException +from grid2op.Chronics.handlers.baseHandler import BaseHandler + + +class JSONInitStateHandler(BaseHandler): + """Base class to initialize the grid state using a method in the time series. + + .. versionadded:: 1.10.2 + + This class will look for a file named "init_state.json" (located at `self.path`) which should be a valid + json file (you can load it with the `json` module) representing a valid + action on the grid. + + This action should preferably be using only the `set` (*eg* `set_bus` and `set_status`) + keyword arguments and set only the topology of the grid (and not the injection or + the redispatching.) + + If no "init_state.json" file is found, then nothing is done. + + """ + + def check_validity(self, backend) -> None: + """This type of handler is always valid.""" + pass + + def done(self) -> bool: + return False + + def get_init_dict_action(self) -> Union[dict, None]: + maybe_path = os.path.join(self.path, "init_state.json") + if not os.path.exists(maybe_path): + return None + try: + with open(maybe_path, "r", encoding="utf-8") as f: + maybe_act_dict = json.load(f) + except Exception as exc_: + raise Grid2OpException(f"Invalid action provided to initialize the powergrid (not readable by json)." + f"Check file located at {maybe_path}") from exc_ + return maybe_act_dict diff --git a/grid2op/Chronics/handlers/jsonMaintenanceHandler.py b/grid2op/Chronics/handlers/jsonMaintenanceHandler.py index 779c877ba..27d2eef7f 100644 --- a/grid2op/Chronics/handlers/jsonMaintenanceHandler.py +++ b/grid2op/Chronics/handlers/jsonMaintenanceHandler.py @@ -40,6 +40,13 @@ class JSONMaintenanceHandler(BaseHandler): - "max_daily_number_per_month_maintenance": maximum number of powerlines allowed in maintenance at the same time. + .. warning:: + Use this class only for the MAINTENANCE and not for environment + data ("load_p", "load_q", "prod_p" or "prod_v") nor for + forecast (in this case use :class:`CSVForecastHandler`) + nor for setting the initial state state (in this case use + :class:`JSONInitStateHandler`) + """ def __init__(self, array_name="maintenance", diff --git a/grid2op/Chronics/multiFolder.py b/grid2op/Chronics/multiFolder.py index 7ab2be644..47ed2fa53 100644 --- a/grid2op/Chronics/multiFolder.py +++ b/grid2op/Chronics/multiFolder.py @@ -8,10 +8,12 @@ import os import json +from typing import Union, Optional, Dict, Literal import warnings import numpy as np from datetime import timedelta, datetime +import grid2op from grid2op.dtypes import dt_int, dt_float from grid2op.Exceptions import ChronicsNotFoundError, ChronicsError from grid2op.Chronics.gridValue import GridValue @@ -352,7 +354,7 @@ def sample_next_chronics(self, probabilities=None): probabilities /= sum_prob # take one at "random" among these selected = self.space_prng.choice(self._order, p=probabilities) - id_sel = np.nonzero(self._order == selected)[0] + id_sel = (self._order == selected).nonzero()[0] self._prev_cache_id = selected - 1 return id_sel @@ -437,6 +439,8 @@ def initialize( order_backend_subs, names_chronics_to_backend=names_chronics_to_backend, ) + if self.action_space is not None: + self.data.action_space = self.action_space def done(self): """ @@ -777,3 +781,10 @@ def split_and_save(self, datetime_beg, datetime_end, path_out): def fast_forward(self, nb_timestep): self.data.fast_forward(nb_timestep) + + def get_init_action(self, names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + return self.data.get_init_action(names_chronics_to_backend) + + def cleanup_action_space(self): + super().cleanup_action_space() + self.data.cleanup_action_space() diff --git a/grid2op/Chronics/multifolderWithCache.py b/grid2op/Chronics/multifolderWithCache.py index 02b0c5e46..e5a5755bd 100644 --- a/grid2op/Chronics/multifolderWithCache.py +++ b/grid2op/Chronics/multifolderWithCache.py @@ -200,6 +200,8 @@ def reset(self): ) self._cached_data[i] = data self.cache_size += 1 + if self.action_space is not None: + data.action_space = self.action_space if self.cache_size == 0: raise RuntimeError("Impossible to initialize the new cache.") @@ -292,3 +294,10 @@ def get_kwargs(self, dict_): dict_["_DONTUSE_nb_step_called"] = self.__nb_step_called dict_["_DONTUSE_nb_init_called"] = self.__nb_init_called return super().get_kwargs(dict_) + + def cleanup_action_space(self): + super().cleanup_action_space() + for el in self._cached_data: + if el is None: + continue + el.cleanup_action_space() diff --git a/grid2op/Chronics/time_series_from_handlers.py b/grid2op/Chronics/time_series_from_handlers.py index 2d4510685..d3a3af4aa 100644 --- a/grid2op/Chronics/time_series_from_handlers.py +++ b/grid2op/Chronics/time_series_from_handlers.py @@ -10,8 +10,9 @@ import os import numpy as np import copy -from typing import Optional +from typing import Optional, Union, Dict, Literal +import grid2op from grid2op.Exceptions import ( ChronicsNotFoundError, HandlerError ) @@ -19,6 +20,7 @@ from grid2op.Chronics.gridValue import GridValue from grid2op.Chronics.handlers import BaseHandler +from grid2op.Exceptions.grid2OpException import Grid2OpException from grid2op.dtypes import dt_int, dt_float @@ -131,6 +133,7 @@ def __init__( load_q_for_handler=None, gen_p_for_handler=None, gen_v_for_handler=None, + init_state_handler=None, time_interval=timedelta(minutes=5), sep=";", # here for compatibility with grid2op, but not used max_iter=-1, @@ -161,6 +164,7 @@ def __init__( self.gen_v_for_handler : Optional[BaseHandler] = copy.deepcopy(gen_v_for_handler) self.load_p_for_handler : Optional[BaseHandler] = copy.deepcopy(load_p_for_handler) self.load_q_for_handler : Optional[BaseHandler] = copy.deepcopy(load_q_for_handler) + self.init_state_handler : Optional[BaseHandler] = copy.deepcopy(init_state_handler) # when there are no maintenance / hazards, build this only once self._no_mh_time = None @@ -185,6 +189,8 @@ def __init__( if self.load_q_for_handler is not None: self._active_handlers.append(self.load_q_for_handler) self._forcast_handlers.append(self.load_q_for_handler) + if self.init_state_handler is not None: + self._active_handlers.append(self.init_state_handler) self._check_types() # now synch all handlers @@ -395,7 +401,7 @@ def init_datetime(self): def seed(self, seed): super().seed(seed) max_seed = np.iinfo(dt_int).max - seeds = self.space_prng.randint(max_seed, size=10) + seeds = self.space_prng.randint(max_seed, size=11) # this way of doing ensure the same seed given by the environment is # used even if some "handlers" are missing # (if env.seed(0) is called, then regardless of maintenance_handler or not, @@ -422,9 +428,12 @@ def seed(self, seed): gvf_seed = None if self.gen_v_for_handler is not None: gvf_seed = self.gen_v_for_handler.seed(seeds[9]) + init_state_seed = None + if self.init_state_handler is not None: + init_state_seed = self.init_state_handler.seed(seeds[10]) return (seed, gp_seed, gv_seed, lp_seed, lq_seed, maint_seed, haz_seed, gpf_seed, gvf_seed, - lpf_seed, lqf_seed) + lpf_seed, lqf_seed, init_state_seed) def _set_path(self, path): """tell the handler where this chronics is located""" @@ -521,3 +530,33 @@ def fast_forward(self, nb_timestep): self.load_next() # for this class I suppose the real data AND the forecast are read each step self.forecasts() + + def get_init_action(self, names_chronics_to_backend: Optional[Dict[Literal["loads", "prods", "lines"], Dict[str, str]]]=None) -> Union["grid2op.Action.playableAction.PlayableAction", None]: + from grid2op.Action import BaseAction + if self.init_state_handler is None: + return None + + act_as_dict = self.init_state_handler.get_init_dict_action() + if act_as_dict is None: + return None + + if self.action_space is None: + raise Grid2OpException(f"We detected an action to set the intial state of the grid " + f"but we cannot build it because the 'action_space' of the time" + f"serie is not set.") + + try: + act : BaseAction = self.action_space(act_as_dict, + check_legal=False, + _names_chronics_to_backend=names_chronics_to_backend) + except Grid2OpException as exc_: + raise Grid2OpException(f"Impossible to build the action to set the grid. Please fix the " + f"file located at {self.init_state_handler.path}.") from exc_ + + # TODO check change bus, redispatching, change status etc. + # TODO basically anything that would be suspicious here + error, reason = act.is_ambiguous() + if error: + raise Grid2OpException(f"The action to set the grid to its original configuration " + f"is ambiguous. Please check {self.init_state_handler.path}") from reason + return act diff --git a/grid2op/Converter/BackendConverter.py b/grid2op/Converter/BackendConverter.py index a6db64614..dfd0ced63 100644 --- a/grid2op/Converter/BackendConverter.py +++ b/grid2op/Converter/BackendConverter.py @@ -106,18 +106,24 @@ def __init__( difcf = detailed_infos_for_cascading_failures if kwargs_source_backend is None: kwargs_source_backend = {} - self.source_backend = source_backend_class( + + #: represents the backend used for the order / name of the elements + #: Agent will not see any difference between the converter and this backend + self.source_backend : Backend = source_backend_class( detailed_infos_for_cascading_failures=difcf, **kwargs_source_backend - ) # the one for the order of the elements + ) if kwargs_target_backend is None: kwargs_target_backend = {} - self.target_backend = target_backend_class( + + #: represents the backend used to compute the powerflows + self.target_backend : Backend = target_backend_class( detailed_infos_for_cascading_failures=difcf, **kwargs_target_backend ) # the one to computes powerflow - # if the target backend (the one performing the powerflows) needs a different file - self.target_backend_grid_path = target_backend_grid_path + + #: if the target backend (the one performing the powerflows) needs a different file + self.target_backend_grid_path :str = target_backend_grid_path # key: name in the source backend, value name in the target backend, for the substations self.sub_source_target = sub_source_target @@ -156,6 +162,14 @@ def __init__( # TODO storage check all this class ! + the doc of the backend def load_grid(self, path=None, filename=None): + # register the "n_busbar_per_sub" (set for the backend class) + # TODO in case source supports the "more than 2" feature but not target + # it's unclear how I can "reload" the grid... + from grid2op.Space import DEFAULT_N_BUSBAR_PER_SUB + type(self.source_backend).set_n_busbar_per_sub(DEFAULT_N_BUSBAR_PER_SUB) + type(self.target_backend).set_n_busbar_per_sub(DEFAULT_N_BUSBAR_PER_SUB) + self.cannot_handle_more_than_2_busbar() + self.source_backend.load_grid(path, filename) # and now i load the target backend if self.target_backend_grid_path is not None: @@ -163,7 +177,18 @@ def load_grid(self, path=None, filename=None): else: # both source and target backend understands the same format self.target_backend.load_grid(path, filename) - + + # TODO in case source supports the "more than 2" feature but not target + # it's unclear how I can "reload" the grid... + # if (not self.target_backend._missing_two_busbars_support_info and + # not self.source_backend._missing_two_busbars_support_info + # ): + # ??? + # else: + # # at least one backend cannot handle the number of busbars, so I deactivate it for all + # self.target_backend.cannot_handle_more_than_2_busbar() + # self.source_backend.cannot_handle_more_than_2_busbar() + def _assert_same_grid(self): """basic assertion that self and the target backend have the same grid but not necessarily the same object at the same place of course""" @@ -206,13 +231,13 @@ def _init_myself(self): == sorted(self.target_backend.name_sub) ): for id_source, nm_source in enumerate(self.source_backend.name_sub): - id_target = np.nonzero(self.target_backend.name_sub == nm_source)[0] + id_target = (self.target_backend.name_sub == nm_source).nonzero()[0] self._sub_tg2sr[id_source] = id_target self._sub_sr2tg[id_target] = id_source else: for id_source, nm_source in enumerate(self.source_backend.name_sub): nm_target = self.sub_source_target[nm_source] - id_target = np.nonzero(self.target_backend.name_sub == nm_target)[0] + id_target = (self.target_backend.name_sub == nm_target).nonzero()[0] self._sub_tg2sr[id_source] = id_target self._sub_sr2tg[id_target] = id_source @@ -300,7 +325,7 @@ def _init_myself(self): def _get_possible_target_ids(self, id_source, source_2_id_sub, target_2_id_sub, nm): id_sub_source = source_2_id_sub[id_source] id_sub_target = self._sub_tg2sr[id_sub_source] - ids_target = np.nonzero(target_2_id_sub == id_sub_target)[0] + ids_target = (target_2_id_sub == id_sub_target).nonzero()[0] if ids_target.shape[0] == 0: raise RuntimeError( ERROR_ELEMENT_CONNECTED.format(nm, id_sub_target, id_sub_source) @@ -346,10 +371,10 @@ def _auto_fill_vect_powerline(self): idor_sub_target = self._sub_tg2sr[idor_sub_source] idex_sub_source = source_ex_2_id_sub[id_source] idex_sub_target = self._sub_tg2sr[idex_sub_source] - ids_target = np.nonzero( + ids_target = ( (target_or_2_id_sub == idor_sub_target) & (target_ex_2_id_sub == idex_sub_target) - )[0] + ).nonzero()[0] if ids_target.shape[0] == 0: raise RuntimeError( ERROR_ELEMENT_CONNECTED.format( @@ -550,6 +575,12 @@ def assert_grid_correct_after_powerflow(self): super().assert_grid_correct_after_powerflow() self._sh_vnkv = self.target_backend._sh_vnkv + def _fill_names_obj(self): + self.target_backend._fill_names_obj() + self.source_backend._fill_names_obj() + for attr_nm in ["name_line", "name_gen", "name_load", "name_sub", "name_storage"]: + setattr(self, attr_nm, copy.deepcopy(getattr(self.source_backend, attr_nm))) + def reset(self, grid_path, grid_filename=None): """ Reload the power grid. diff --git a/grid2op/Converter/ConnectivityConverter.py b/grid2op/Converter/ConnectivityConverter.py index e9864d1dd..5b971238b 100644 --- a/grid2op/Converter/ConnectivityConverter.py +++ b/grid2op/Converter/ConnectivityConverter.py @@ -188,11 +188,11 @@ def init_converter(self, all_actions=None, **kwargs): if nb_element < 4: continue - c_id = np.nonzero(self.load_to_subid == sub_id)[0] - g_id = np.nonzero(self.gen_to_subid == sub_id)[0] - lor_id = np.nonzero(self.line_or_to_subid == sub_id)[0] - lex_id = np.nonzero(self.line_ex_to_subid == sub_id)[0] - storage_id = np.nonzero(self.storage_to_subid == sub_id)[0] + c_id = (self.load_to_subid == sub_id).nonzero()[0] + g_id = (self.gen_to_subid == sub_id).nonzero()[0] + lor_id = (self.line_or_to_subid == sub_id).nonzero()[0] + lex_id = (self.line_ex_to_subid == sub_id).nonzero()[0] + storage_id = (self.storage_to_subid == sub_id).nonzero()[0] c_pos = self.load_to_sub_pos[self.load_to_subid == sub_id] g_pos = self.gen_to_sub_pos[self.gen_to_subid == sub_id] @@ -380,7 +380,7 @@ def convert_act(self, encoded_act, explore=None): ) if ((encoded_act < -1.0) | (encoded_act > 1.0)).any(): errors = (encoded_act < -1.0) | (encoded_act > 1.0) - indexes = np.nonzero(errors)[0] + indexes = (errors).nonzero()[0] raise RuntimeError( f'All elements of "encoded_act" must be in range [-1, 1]. Please check your ' f"encoded action at positions {indexes[:5]}... (only first 5 displayed)" @@ -393,7 +393,7 @@ def convert_act(self, encoded_act, explore=None): return super().__call__() argsort_changed = np.argsort(-np.abs(encoded_act_filtered)) - argsort = np.nonzero(act_want_change)[0][argsort_changed] + argsort = (act_want_change).nonzero()[0][argsort_changed] act, disag = self._aux_act_from_order(argsort, encoded_act) self.indx_sel = 0 if explore is None: diff --git a/grid2op/Environment/_obsEnv.py b/grid2op/Environment/_obsEnv.py index 347aaeaa9..0c713f707 100644 --- a/grid2op/Environment/_obsEnv.py +++ b/grid2op/Environment/_obsEnv.py @@ -9,8 +9,11 @@ import copy import numpy as np import warnings -from grid2op.Exceptions.envExceptions import EnvError +from typing import Dict, Union, Tuple, List, Optional, Any, Literal +import grid2op +from grid2op.Exceptions.envExceptions import EnvError +from grid2op.typing_variables import STEP_INFO_TYPING from grid2op.dtypes import dt_int, dt_float, dt_bool from grid2op.Environment.baseEnv import BaseEnv from grid2op.Chronics import ChangeNothing @@ -362,7 +365,8 @@ def init( ) self._backend_action_set += new_state_action # for storage unit - self._backend_action_set.storage_power.values[:] = 0.0 + if time_step > 0: + self._backend_action_set.storage_power.values[:] = 0.0 self._backend_action_set.all_changed() self._backend_action = copy.deepcopy(self._backend_action_set) @@ -397,7 +401,7 @@ def reset(self): super().reset() self.current_obs = self.current_obs_init - def simulate(self, action): + def simulate(self, action : "grid2op.Action.BaseAction") -> Tuple["grid2op.Observation.BaseObservation", float, bool, STEP_INFO_TYPING]: """ INTERNAL @@ -416,12 +420,12 @@ def simulate(self, action): Parameters ---------- - action: :class:`grid2op.Action.Action` + action: :class:`grid2op.Action.BaseAction` The action to test Returns ------- - observation: :class:`grid2op.Observation.Observation` + observation: :class:`grid2op.Observation.BaseObservation` agent's observation of the current environment reward: ``float`` @@ -431,13 +435,9 @@ def simulate(self, action): whether the episode has ended, in which case further step() calls will return undefined results info: ``dict`` - contains auxiliary diagnostic information (helpful for debugging, and sometimes learning). It is a - dictionary with keys: - - - "disc_lines": a numpy array (or ``None``) saying, for each powerline if it has been disconnected - due to overflow - - "is_illegal" (``bool``) whether the action given as input was illegal - - "is_ambiguous" (``bool``) whether the action given as input was ambiguous. + contains auxiliary diagnostic information (helpful for debugging, and sometimes learning). See + description of :func:`grid2op.Environment.BaseEnv.step` for more information about the + keys of this dictionary. """ if self.__unusable: diff --git a/grid2op/Environment/baseEnv.py b/grid2op/Environment/baseEnv.py index 6670a736b..8dca86a37 100644 --- a/grid2op/Environment/baseEnv.py +++ b/grid2op/Environment/baseEnv.py @@ -8,6 +8,7 @@ from datetime import datetime +import shutil import logging import time import copy @@ -298,7 +299,7 @@ def foo(manager): #: this are the keys of the dictionnary `options` #: that can be used when calling `env.reset(..., options={})` - KEYS_RESET_OPTIONS = {"time serie id"} + KEYS_RESET_OPTIONS = {"time serie id", "init state"} def __init__( @@ -741,13 +742,20 @@ def _custom_deepcopy_for_copy(self, new_obj, dict_=None): new_obj._hazards = copy.deepcopy(self._hazards) new_obj._env_modification = copy.deepcopy(self._env_modification) + # action space used by the environment + new_obj._game_rules = copy.deepcopy(self._game_rules) + new_obj._helper_action_env = self._helper_action_env.copy() + new_obj._helper_action_env.legal_action = new_obj._game_rules.legal_action + # to use the data new_obj.done = self.done new_obj.current_reward = copy.deepcopy(self.current_reward) new_obj.chronics_handler = copy.deepcopy(self.chronics_handler) - new_obj._game_rules = copy.deepcopy(self._game_rules) - new_obj._helper_action_env = self._helper_action_env.copy() - new_obj._helper_action_env.legal_action = new_obj._game_rules.legal_action + # retrieve the "pointer" to the new_obj action space (for initializing the grid) + new_obj.chronics_handler.cleanup_action_space() + new_obj.chronics_handler.action_space = new_obj._helper_action_env + + # action space new_obj._action_space = self._action_space.copy() new_obj._action_space.legal_action = new_obj._game_rules.legal_action @@ -1021,7 +1029,7 @@ def load_alert_data(self): alertable_line_names = copy.deepcopy(lines_attacked) alertable_line_ids = np.empty(len(alertable_line_names), dtype=dt_int) for i, el in enumerate(alertable_line_names): - indx = np.nonzero(self.backend.name_line == el)[0] + indx = (self.backend.name_line == el).nonzero()[0] if not len(indx): raise Grid2OpException(f"Attacked line {el} is not found in the grid.") alertable_line_ids[i] = indx[0] @@ -1750,7 +1758,7 @@ def set_thermal_limit(self, thermal_limit): f"names. We found: {key} which is not a line name. The names of the " f"powerlines are {self.name_line}" ) - ind_line = np.nonzero(self.name_line == key)[0][0] + ind_line = (self.name_line == key).nonzero()[0][0] if np.isfinite(tmp[ind_line]): raise Grid2OpException( f"Humm, there is a really strange bug, some lines are set twice." @@ -1860,7 +1868,7 @@ def _prepare_redisp(self, action, new_p, already_modified_gen): "invalid because, even if the sepoint is pmin, this dispatch would set it " "to a number higher than pmax, which is impossible]. Invalid dispatch for " "generator(s): " - "{}".format(np.nonzero(cond_invalid)[0]) + "{}".format((cond_invalid).nonzero()[0]) ) self._target_dispatch -= redisp_act_orig return valid, except_, info_ @@ -1872,7 +1880,7 @@ def _prepare_redisp(self, action, new_p, already_modified_gen): "invalid because, even if the sepoint is pmax, this dispatch would set it " "to a number bellow pmin, which is impossible]. Invalid dispatch for " "generator(s): " - "{}".format(np.nonzero(cond_invalid)[0]) + "{}".format((cond_invalid).nonzero()[0]) ) self._target_dispatch -= redisp_act_orig return valid, except_, info_ @@ -1892,11 +1900,9 @@ def _prepare_redisp(self, action, new_p, already_modified_gen): if (redisp_act_orig_cut != redisp_act_orig).any(): info_.append( { - "INFO: redispatching cut because generator will be turned_off": np.nonzero( + "INFO: redispatching cut because generator will be turned_off": ( redisp_act_orig_cut != redisp_act_orig - )[ - 0 - ] + ).nonzero()[0] } ) return valid, except_, info_ @@ -1919,6 +1925,13 @@ def _make_redisp(self, already_modified_gen, new_p): def _compute_dispatch_vect(self, already_modified_gen, new_p): except_ = None + + # handle the case where there are storage or redispatching + # action or curtailment action on the "init state" + # of the grid + if self.nb_time_step == 0: + self._gen_activeprod_t_redisp[:] = new_p + # first i define the participating generators # these are the generators that will be adjusted for redispatching gen_participating = ( @@ -2352,8 +2365,8 @@ def _handle_updown_times(self, gen_up_before, redisp_act): self._gen_downtime[gen_connected_this_timestep] < self.gen_min_downtime[gen_connected_this_timestep] ) - id_gen = np.nonzero(id_gen)[0] - id_gen = np.nonzero(gen_connected_this_timestep[id_gen])[0] + id_gen = (id_gen).nonzero()[0] + id_gen = (gen_connected_this_timestep[id_gen]).nonzero()[0] except_ = GeneratorTurnedOnTooSoon( "Some generator has been connected too early ({})".format(id_gen) ) @@ -2374,8 +2387,8 @@ def _handle_updown_times(self, gen_up_before, redisp_act): self._gen_uptime[gen_disconnected_this] < self.gen_min_uptime[gen_disconnected_this] ) - id_gen = np.nonzero(id_gen)[0] - id_gen = np.nonzero(gen_connected_this_timestep[id_gen])[0] + id_gen = (id_gen).nonzero()[0] + id_gen = (gen_connected_this_timestep[id_gen]).nonzero()[0] except_ = GeneratorTurnedOffTooSoon( "Some generator has been disconnected too early ({})".format(id_gen) ) @@ -2887,7 +2900,7 @@ def _aux_apply_redisp(self, action, new_p, new_p_th, gen_curtailed, except_): is_illegal_redisp = True except_.append(except_tmp) - if self.n_storage > 0: + if type(self).n_storage > 0: # TODO curtailment: cancel it here too ! self._storage_current_charge[:] = self._storage_previous_charge self._amount_storage -= self._amount_storage_prev @@ -2909,7 +2922,6 @@ def _aux_apply_redisp(self, action, new_p, new_p_th, gen_curtailed, except_): self._storage_power_prev[:] = self._storage_power # case where the action modifies load (TODO maybe make a different env for that...) self._aux_handle_act_inj(action) - valid_disp, except_tmp = self._make_redisp(already_modified_gen, new_p) if not valid_disp or except_tmp is not None: @@ -3155,11 +3167,10 @@ def step(self, action: BaseAction) -> Tuple[BaseObservation, the simulation of the "cascading failures". - "rewards": dictionary of all "other_rewards" provided when the env was built. - "time_series_id": id of the time series used (if any, similar to a call to `env.chronics_handler.get_id()`) - Examples --------- - As any openAI gym environment, this is used like: + This is used like: .. code-block:: python @@ -3203,6 +3214,7 @@ def step(self, action: BaseAction) -> Tuple[BaseObservation, # somehow "env.step()" or "env.reset()" self._has_just_been_seeded = False + cls = type(self) has_error = True is_done = False is_illegal = False @@ -3219,7 +3231,7 @@ def step(self, action: BaseAction) -> Tuple[BaseObservation, detailed_info = [] init_disp = 1.0 * action._redispatch # dispatching action init_alert = None - if type(self).dim_alerts > 0: + if cls.dim_alerts > 0: init_alert = copy.deepcopy(action._raise_alert) action_storage_power = 1.0 * action._storage_power # battery information @@ -3233,7 +3245,6 @@ def step(self, action: BaseAction) -> Tuple[BaseObservation, beg_step = time.perf_counter() self._last_obs : Optional[BaseObservation] = None self._forecasts = None # force reading the forecast from the time series - cls = type(self) try: beg_ = time.perf_counter() @@ -3344,7 +3355,6 @@ def step(self, action: BaseAction) -> Tuple[BaseObservation, ) else: has_error = True - except StopIteration: # episode is over is_done = True @@ -3931,11 +3941,11 @@ def _aux_gen_classes(self, cls, sys_path): raise RuntimeError(f"cls should inherit from GridObjects: {cls}") from pathlib import Path - path_env = cls._PATH_ENV - cls._PATH_ENV = str(Path(self.get_path_env()).as_posix()) + path_env = cls._PATH_GRID_CLASSES + cls._PATH_GRID_CLASSES = str(Path(self.get_path_env()).as_posix()) res = cls._get_full_cls_str() - cls._PATH_ENV = path_env + cls._PATH_GRID_CLASSES = path_env output_file = os.path.join(sys_path, f"{cls.__name__}_file.py") if not os.path.exists(output_file): # if the file is not already saved, i save it and add it to the __init__ file @@ -3943,10 +3953,13 @@ def _aux_gen_classes(self, cls, sys_path): f.write(res) return f"\nfrom .{cls.__name__}_file import {cls.__name__}" else: - # otherwise i do nothing + # if the file exists, I check it's the same + # from grid2op.MakeEnv.UpdateEnv import _aux_hash_file, _aux_update_hash_text + # hash_saved = _aux_hash_file(output_file) + # my_hash = _aux_update_hash_text(res) return "" - def generate_classes(self, _guard=None, _is_base_env__=True, sys_path=None): + def generate_classes(self, *, local_dir_id=None, _guard=None, _is_base_env__=True, sys_path=None): """ Use with care, but can be incredibly useful ! @@ -4019,10 +4032,10 @@ def generate_classes(self, _guard=None, _is_base_env__=True, sys_path=None): if _guard is not None: raise RuntimeError("use `env.generate_classes()` with no arguments !") - if type(self)._PATH_ENV is not None: + if type(self)._PATH_GRID_CLASSES is not None: raise RuntimeError( "This function should only be called ONCE without specifying that the classes " - "need to be read from disk (class attribute type(self)._PATH_ENV should be None)" + "need to be read from disk (class attribute type(self)._PATH_GRID_CLASSES should be None)" ) import shutil @@ -4031,7 +4044,10 @@ def generate_classes(self, _guard=None, _is_base_env__=True, sys_path=None): raise RuntimeError("Cannot generate file from a \"sub env\" " "(eg no the top level env) if I don't know the path of " "the top level environment.") - sys_path = os.path.join(self.get_path_env(), "_grid2op_classes") + if local_dir_id is not None: + sys_path = os.path.join(self.get_path_env(), "_grid2op_classes", local_dir_id) + else: + sys_path = os.path.join(self.get_path_env(), "_grid2op_classes") if _is_base_env__: if os.path.exists(sys_path): @@ -4071,7 +4087,9 @@ def generate_classes(self, _guard=None, _is_base_env__=True, sys_path=None): init_grid_tmp = self._observation_space.obs_env._init_grid_path self._observation_space.obs_env._init_grid_path = self._init_grid_path - self._observation_space.obs_env.generate_classes(_is_base_env__=False, sys_path=sys_path) + self._observation_space.obs_env.generate_classes(local_dir_id=local_dir_id, + _is_base_env__=False, + sys_path=sys_path) self._observation_space.obs_env._init_grid_path = init_grid_tmp # now write the __init__ file @@ -4079,6 +4097,50 @@ def generate_classes(self, _guard=None, _is_base_env__=True, sys_path=None): with open(os.path.join(sys_path, "__init__.py"), mode, encoding="utf-8") as f: f.write(_init_txt) + def _forget_classes(self): + """ + This function allows python to "forget" the classes created at the initialization of the environment. + + It should not be used in most cases and is reserved for internal use only. + + .. versionadded: 1.10.2 + Function added following the new behaviour introduced in this version. + + """ + from grid2op.MakeEnv.PathUtils import USE_CLASS_IN_FILE + if not USE_CLASS_IN_FILE: + return + pass + + def remove_all_class_folders(self): + """ + This function allows python to remove all the files containing all the classes + in the environment. + + .. warning:: + If you have pending grid2op "job" using this environment, they will most likely crash + so use with extra care ! + + It should not be used in most cases and is reserved for internal use only. + + .. versionadded: 1.10.2 + Function added following the new behaviour introduced in this version. + + """ + directory_path = os.path.join(self.get_path_env(), "_grid2op_classes") + try: + with os.scandir(directory_path) as entries: + for entry in entries: + try: + if entry.is_file(): + os.unlink(entry.path) + else: + shutil.rmtree(entry.path) + except (OSError, FileNotFoundError): + pass + except OSError: + pass + def __del__(self): """when the environment is garbage collected, free all the memory, including cross reference to itself in the observation space.""" if hasattr(self, "_BaseEnv__closed") and not self.__closed: diff --git a/grid2op/Environment/baseMultiProcessEnv.py b/grid2op/Environment/baseMultiProcessEnv.py index 37d571702..0f76ca9d9 100644 --- a/grid2op/Environment/baseMultiProcessEnv.py +++ b/grid2op/Environment/baseMultiProcessEnv.py @@ -87,7 +87,9 @@ def init_env(self): """ self.space_prng = np.random.RandomState() self.space_prng.seed(seed=self.seed_used) - self.backend = self.env_params["_raw_backend_class"]() + self.backend = self.env_params["_raw_backend_class"](**self.env_params["_backend_kwargs"]) + del self.env_params["_backend_kwargs"] + with warnings.catch_warnings(): # warnings have bee already sent in the main process, no need to resend them warnings.filterwarnings("ignore") @@ -206,6 +208,7 @@ def run(self): self.remote.send(self.env._time_step) elif cmd == "set_filter": self.env.chronics_handler.set_filter(data) + self.env.chronics_handler.reset() self.remote.send(None) elif cmd == "set_id": self.env.set_id(data) @@ -288,7 +291,7 @@ def __init__(self, envs, obs_as_class=True, return_info=True, logger=None): max_int = np.iinfo(dt_int).max _remotes, _work_remotes = zip(*[Pipe() for _ in range(self.nb_env)]) - env_params = [sub_env.get_kwargs(with_backend=False) for sub_env in envs] + env_params = [sub_env.get_kwargs(with_backend=False, with_backend_kwargs=True) for sub_env in envs] self._ps = [ RemoteEnv( env_params=env_, diff --git a/grid2op/Environment/environment.py b/grid2op/Environment/environment.py index 0d5476b06..113b20482 100644 --- a/grid2op/Environment/environment.py +++ b/grid2op/Environment/environment.py @@ -10,7 +10,7 @@ import warnings import numpy as np import re -from typing import Union, Any, Dict, Literal +from typing import Optional, Union, Any, Dict, Literal import grid2op from grid2op.Opponent import OpponentSpace @@ -33,6 +33,7 @@ from grid2op.Opponent import BaseOpponent, NeverAttackBudget from grid2op.operator_attention import LinearAttentionBudget from grid2op.Space import DEFAULT_N_BUSBAR_PER_SUB +from grid2op.typing_variables import RESET_OPTIONS_TYPING class Environment(BaseEnv): @@ -116,8 +117,9 @@ def __init__( _init_obs=None, _raw_backend_class=None, _compat_glop_version=None, - _read_from_local_dir=True, # TODO runner and all here ! + _read_from_local_dir=True, _is_test=False, + _allow_loaded_backend=False, ): BaseEnv.__init__( self, @@ -161,10 +163,12 @@ def __init__( ) self.name = name self._read_from_local_dir = _read_from_local_dir + + #: starting grid2Op 1.11 classes are stored on the disk when an environment is created + #: so the "environment" is created twice (one to generate the class and then correctly to load them) + self._allow_loaded_backend : bool = _allow_loaded_backend - # for gym compatibility (initialized below) - # self.action_space = None - # self.observation_space = None + # for gym compatibility (action_spacen and observation_space initialized below) self.reward_range = None self._viewer = None self.metadata = None @@ -231,7 +235,7 @@ def _init_backend( 'grid2op.Backend class, type provided is "{}"'.format(type(backend)) ) self.backend = backend - if self.backend.is_loaded and self._init_obs is None: + if self.backend.is_loaded and self._init_obs is None and not self._allow_loaded_backend: raise EnvError( "Impossible to use the same backend twice. Please create your environment with a " "new backend instance (new object)." @@ -239,19 +243,29 @@ def _init_backend( need_process_backend = False if not self.backend.is_loaded: + if hasattr(self.backend, "init_pp_backend") and self.backend.init_pp_backend is not None: + # hack for lightsim2grid ... + if type(self.backend.init_pp_backend)._INIT_GRID_CLS is not None: + type(self.backend.init_pp_backend)._INIT_GRID_CLS._clear_grid_dependant_class_attributes() + type(self.backend.init_pp_backend)._clear_grid_dependant_class_attributes() + # usual case: the backend is not loaded # NB it is loaded when the backend comes from an observation for # example - if self._read_from_local_dir: + if self._read_from_local_dir is not None: # test to support pickle conveniently - self.backend._PATH_ENV = self.get_path_env() + self.backend._PATH_GRID_CLASSES = self.get_path_env() # all the above should be done in this exact order, otherwise some weird behaviour might occur # this is due to the class attribute type(self.backend).set_env_name(self.name) type(self.backend).set_n_busbar_per_sub(self._n_busbar) + if self._compat_glop_version is not None: + type(self.backend).glop_version = self._compat_glop_version self.backend.load_grid( self._init_grid_path ) # the real powergrid of the environment + self.backend.load_storage_data(self.get_path_env()) + self.backend._fill_names_obj() try: self.backend.load_redispacthing_data(self.get_path_env()) except BackendError as exc_: @@ -259,14 +273,12 @@ def _init_backend( warnings.warn(f"Impossible to load redispatching data. This is not an error but you will not be able " f"to use all grid2op functionalities. " f"The error was: \"{exc_}\"") - self.backend.load_storage_data(self.get_path_env()) exc_ = self.backend.load_grid_layout(self.get_path_env()) if exc_ is not None: warnings.warn( f"No layout have been found for you grid (or the layout provided was corrupted). You will " f'not be able to use the renderer, plot the grid etc. The error was "{exc_}"' ) - self.backend.is_loaded = True # alarm set up self.load_alarm_data() @@ -274,6 +286,7 @@ def _init_backend( # to force the initialization of the backend to the proper type self.backend.assert_grid_correct() + self.backend.is_loaded = True need_process_backend = True self._handle_compat_glop_version(need_process_backend) @@ -325,9 +338,8 @@ def _init_backend( ) # action affecting the grid that will be made by the agent - bk_type = type( - self.backend - ) # be careful here: you need to initialize from the class, and not from the object + # be careful here: you need to initialize from the class, and not from the object + bk_type = type(self.backend) self._rewardClass = rewardClass self._actionClass = actionClass.init_grid(gridobj=bk_type) self._actionClass._add_shunt_data() @@ -368,6 +380,8 @@ def _init_backend( self.name_sub, names_chronics_to_backend=names_chronics_to_backend, ) + # new in grdi2op 1.10.2: used + self.chronics_handler.action_space = self._helper_action_env self._names_chronics_to_backend = names_chronics_to_backend self.delta_time_seconds = dt_float(self.chronics_handler.time_interval.seconds) @@ -435,8 +449,25 @@ def _init_backend( # test the backend returns object of the proper size if need_process_backend: - self.backend.assert_grid_correct_after_powerflow() + + # hack to fix an issue with lightsim2grid... + # (base class is not reset correctly, will be fixed ASAP) + base_cls_ls = None + if hasattr(self.backend, "init_pp_backend") and self.backend.init_pp_backend is not None: + base_cls_ls = type(self.backend.init_pp_backend) + self.backend.assert_grid_correct_after_powerflow() + + # hack to fix an issue with lightsim2grid... + # (base class is not reset correctly, will be fixed ASAP) + if hasattr(self.backend, "init_pp_backend") and self.backend.init_pp_backend is not None: + if self.backend._INIT_GRID_CLS is not None: + # the init grid class has already been properly computed + self.backend._INIT_GRID_CLS._clear_grid_dependant_class_attributes() + elif base_cls_ls is not None: + # we need to clear the class of the original type as it has not been properly computed + base_cls_ls._clear_grid_dependant_class_attributes() + # for gym compatibility self.reward_range = self._reward_helper.range() self._viewer = None @@ -503,81 +534,10 @@ def _handle_compat_glop_version(self, need_process_backend): "read back data (for example with EpisodeData) that were stored with previous " "grid2op version." ) - if need_process_backend: - self.backend.set_env_name(f"{self.name}_{self._compat_glop_version}") - cls_bk = type(self.backend) - cls_bk.glop_version = self._compat_glop_version - if cls_bk.glop_version == cls_bk.BEFORE_COMPAT_VERSION: - # oldest version: no storage and no curtailment available - # deactivate storage - # recompute the topology vector (more or less everything need to be adjusted... - stor_locs = [pos for pos in cls_bk.storage_pos_topo_vect] - for stor_loc in sorted(stor_locs, reverse=True): - for vect in [ - cls_bk.load_pos_topo_vect, - cls_bk.gen_pos_topo_vect, - cls_bk.line_or_pos_topo_vect, - cls_bk.line_ex_pos_topo_vect, - ]: - vect[vect >= stor_loc] -= 1 - - # deals with the "sub_pos" vector - for sub_id in range(cls_bk.n_sub): - if (cls_bk.storage_to_subid == sub_id).any(): - stor_ids = np.nonzero(cls_bk.storage_to_subid == sub_id)[0] - stor_locs = cls_bk.storage_to_sub_pos[stor_ids] - for stor_loc in sorted(stor_locs, reverse=True): - for vect, sub_id_me in zip( - [ - cls_bk.load_to_sub_pos, - cls_bk.gen_to_sub_pos, - cls_bk.line_or_to_sub_pos, - cls_bk.line_ex_to_sub_pos, - ], - [ - cls_bk.load_to_subid, - cls_bk.gen_to_subid, - cls_bk.line_or_to_subid, - cls_bk.line_ex_to_subid, - ], - ): - vect[(vect >= stor_loc) & (sub_id_me == sub_id)] -= 1 - - # remove storage from the number of element in the substation - for sub_id in range(cls_bk.n_sub): - cls_bk.sub_info[sub_id] -= (cls_bk.storage_to_subid == sub_id).sum() - # remove storage from the total number of element - cls_bk.dim_topo -= cls_bk.n_storage - - # recompute this private member - cls_bk._topo_vect_to_sub = np.repeat( - np.arange(cls_bk.n_sub), repeats=cls_bk.sub_info - ) - self.backend._topo_vect_to_sub = np.repeat( - np.arange(cls_bk.n_sub), repeats=cls_bk.sub_info - ) - new_grid_objects_types = cls_bk.grid_objects_types - new_grid_objects_types = new_grid_objects_types[ - new_grid_objects_types[:, cls_bk.STORAGE_COL] == -1, : - ] - cls_bk.grid_objects_types = 1 * new_grid_objects_types - self.backend.grid_objects_types = 1 * new_grid_objects_types - - # erase all trace of storage units - cls_bk.set_no_storage() - Environment.deactivate_storage(self.backend) - - if need_process_backend: - # the following line must be called BEFORE "self.backend.assert_grid_correct()" ! - self.backend.storage_deact_for_backward_comaptibility() - - # and recomputes everything while making sure everything is consistent - self.backend.assert_grid_correct() - type(self.backend)._topo_vect_to_sub = np.repeat( - np.arange(cls_bk.n_sub), repeats=cls_bk.sub_info - ) - type(self.backend).grid_objects_types = new_grid_objects_types + if need_process_backend: + # the following line must be called BEFORE "self.backend.assert_grid_correct()" ! + self.backend.storage_deact_for_backward_comaptibility() def _voltage_control(self, agent_action, prod_v_chronics): """ @@ -848,7 +808,9 @@ def __str__(self): return "<{} instance named {}>".format(type(self).__name__, self.name) # TODO be closer to original gym implementation - def reset_grid(self): + def reset_grid(self, + init_act_opt : Optional[BaseAction]=None, + method:Literal["combine", "ignore"]="combine"): """ INTERNAL @@ -871,15 +833,47 @@ def reset_grid(self): self.backend.set_thermal_limit(self._thermal_limit_a.astype(dt_float)) self._backend_action = self._backend_action_class() - self.nb_time_step = -1 # to have init obs at step 1 - do_nothing = self._helper_action_env({}) - *_, fail_to_start, info = self.step(do_nothing) + self.nb_time_step = -1 # to have init obs at step 1 (and to prevent 'setting to proper state' "action" to be illegal) + init_action = None + if not self._parameters.IGNORE_INITIAL_STATE_TIME_SERIE: + # load the initial state from the time series (default) + # TODO logger: log that + init_action : BaseAction = self.chronics_handler.get_init_action(self._names_chronics_to_backend) + else: + # do as if everything was connected to busbar 1 + # TODO logger: log that + init_action = self._helper_action_env({"set_bus": np.ones(type(self).dim_topo, dtype=dt_int)}) + if type(self).shunts_data_available: + init_action += self._helper_action_env({"shunt": {"set_bus": np.ones(type(self).n_shunt, dtype=dt_int)}}) + if init_action is None: + # default behaviour for grid2op < 1.10.2 + init_action = self._helper_action_env({}) + else: + # remove the "change part" of the action + init_action.remove_change() + + if init_act_opt is not None: + init_act_opt.remove_change() + if method == "combine": + init_action._add_act_and_remove_line_status_only_set(init_act_opt) + elif method == "ignore": + init_action = init_act_opt + else: + raise Grid2OpException(f"kwargs `method` used to set the initial state of the grid " + f"is not understood (use one of `combine` or `ignore` and " + f"not `{method}`)") + init_action._set_topo_vect.nonzero() + *_, fail_to_start, info = self.step(init_action) if fail_to_start: raise Grid2OpException( "Impossible to initialize the powergrid, the powerflow diverge at iteration 0. " "Available information are: {}".format(info) ) - + if info["exception"] and init_action.can_affect_something(): + raise Grid2OpException(f"There has been an error at the initialization, most likely due to a " + f"incorrect 'init state'. You need to change either the time series used (chronics, chronics_handler, " + f"gridvalue, etc.) or the 'init state' option provided in " + f"`env.reset(..., options={'init state': XXX, ...})`. Error was: {info['exception']}") # assign the right self._observation_space.set_real_env_kwargs(self) @@ -901,7 +895,7 @@ def add_text_logger(self, logger=None): def reset(self, *, seed: Union[int, None] = None, - options: Union[Dict[Union[str, Literal["time serie id"]], Union[int, str]], None] = None) -> BaseObservation: + options: RESET_OPTIONS_TYPING = None) -> BaseObservation: """ Reset the environment to a clean state. It will reload the next chronics if any. And reset the grid to a clean state. @@ -910,7 +904,18 @@ def reset(self, to ensure the episode is fully over. This method should be called only at the end of an episode. - + + Parameters + ---------- + seed: int + The seed to used (new in version 1.9.8), see examples for more details. Ignored if not set (meaning no seeds will + be used, experiments might not be reproducible) + + options: dict + Some options to "customize" the reset call. For example specifying the "time serie id" (grid2op >= 1.9.8) to use + or the "initial state of the grid" (grid2op >= 1.10.2). See examples for more information about this. Ignored if + not set. + Examples -------- The standard "gym loop" can be done with the following code: @@ -970,7 +975,91 @@ def reset(self, obs = env.reset(options={"time serie id": time_serie_id}) ... + .. versionadded:: 1.10.2 + + Another feature has been added in version 1.10.2, which is the possibility to set the + grid to a given "topological" state at the first observation (before this version, + you could only retrieve an observation with everything connected together). + + In grid2op 1.10.2, you can do that by using the keys `"init state"` in the "options" kwargs of + the reset function. The value associated to this key should be dictionnary that can be + converted to a non ambiguous grid2op action using an "action space". + + .. notes:: + The "action space" used here is not the action space of the agent. It's an "action + space" that uses a :grid2op:`Action.Action.BaseAction` class meaning you can do any + type of action, on shunts, on topology, on line status etc. even if the agent is not + allowed to. + + Likewise, nothing check if this action is legal or not. + + You can use it like this: + + .. code-block:: python + + # to start an episode with a line disconnected, you can do: + init_state_dict = {"set_line_status": [(0, -1)]} + obs = env.reset(options={"init state": init_state_dict}) + obs.line_status[0] is False + + # to start an episode with a different topolovy + init_state_dict = {"set_bus": {"lines_or_id": [(0, 2)], "lines_ex_id": [(3, 2)]}} + obs = env.reset(options={"init state": init_state_dict}) + + .. note:: + Since grid2op version 1.10.2, there is also the possibility to set the "initial state" + of the grid directly in the time series. The priority is always given to the + argument passed in the "options" value. + + Concretely if, in the "time series" (formelly called "chronics") provides an action would change + the topology of substation 1 and 2 (for example) and you provide an action that disable the + line 6, then the initial state will see substation 1 and 2 changed (as in the time series) + and line 6 disconnected. + + Another example in this case: if the action you provide would change topology of substation 2 and 4 + then the initial state (after `env.reset`) will give: + - substation 1 as in the time serie + - substation 2 as in "options" + - substation 4 as in "options" + + .. note:: + Concerning the previously described behaviour, if you want to ignore the data in the + time series, you can add : `"method": "ignore"` in the dictionary describing the action. + In this case the action in the time series will be totally ignored and the initial + state will be fully set by the action passed in the "options" dict. + + An example is: + + .. code-block:: python + + init_state_dict = {"set_line_status": [(0, -1)], "method": "force"} + obs = env.reset(options={"init state": init_state_dict}) + obs.line_status[0] is False + """ + # process the "options" kwargs + # (if there is an init state then I need to process it to remove the + # some keys) + method = "combine" + init_state = None + if options is not None and "init state" in options: + act_as_dict = options["init state"] + if isinstance(act_as_dict, dict): + if "method" in act_as_dict: + method = act_as_dict["method"] + del act_as_dict["method"] + init_state : BaseAction = self._helper_action_env(act_as_dict) + elif isinstance(act_as_dict, BaseAction): + init_state = act_as_dict + else: + raise Grid2OpException("`init state` kwargs in `env.reset(, options=XXX) should either be a " + "grid2op action (instance of grid2op.Action.BaseAction) or a dictionaray " + f"representing an action. You provided {act_as_dict} which is a {type(act_as_dict)}") + ambiguous, except_tmp = init_state.is_ambiguous() + if ambiguous: + raise Grid2OpException("You provided an invalid (ambiguous) action to set the 'init state'") from except_tmp + init_state.remove_change() + super().reset(seed=seed, options=options) self.chronics_handler.next_chronics() @@ -985,7 +1074,8 @@ def reset(self, self._reset_maintenance() self._reset_redispatching() self._reset_vectors_and_timings() # it need to be done BEFORE to prevent cascading failure when there has been - self.reset_grid() + + self.reset_grid(init_state, method) if self.viewer_fig is not None: del self.viewer_fig self.viewer_fig = None @@ -1104,7 +1194,10 @@ def copy(self) -> "Environment": self._custom_deepcopy_for_copy(res) return res - def get_kwargs(self, with_backend=True, with_chronics_handler=True): + def get_kwargs(self, + with_backend=True, + with_chronics_handler=True, + with_backend_kwargs=False): """ This function allows to make another Environment with the same parameters as the one that have been used to make this one. @@ -1145,12 +1238,16 @@ def get_kwargs(self, with_backend=True, with_chronics_handler=True): res["init_grid_path"] = self._init_grid_path if with_chronics_handler: res["chronics_handler"] = copy.deepcopy(self.chronics_handler) + res["chronics_handler"].cleanup_action_space() + + # deals with the backend if with_backend: if not self.backend._can_be_copied: raise RuntimeError("Impossible to get the kwargs for this " "environment, the backend cannot be copied.") res["backend"] = self.backend.copy() res["backend"]._is_loaded = False # i can reload a copy of an environment + res["parameters"] = copy.deepcopy(self._parameters) res["names_chronics_to_backend"] = copy.deepcopy( self._names_chronics_to_backend @@ -1165,7 +1262,14 @@ def get_kwargs(self, with_backend=True, with_chronics_handler=True): res["voltagecontrolerClass"] = self._voltagecontrolerClass res["other_rewards"] = {k: v.rewardClass for k, v in self.other_rewards.items()} res["name"] = self.name + res["_raw_backend_class"] = self._raw_backend_class + if with_backend_kwargs: + # used for multi processing, to pass exactly the + # right things when building the backends + # in each sub process + res["_backend_kwargs"] = self.backend._my_kwargs + res["with_forecast"] = self.with_forecast res["opponent_space_type"] = self._opponent_space_type @@ -1839,7 +1943,7 @@ def init_obj_from_kwargs(cls, observation_bk_kwargs, _raw_backend_class, _read_from_local_dir, - n_busbar=DEFAULT_N_BUSBAR_PER_SUB): + n_busbar=DEFAULT_N_BUSBAR_PER_SUB): res = cls(init_env_path=init_env_path, init_grid_path=init_grid_path, chronics_handler=chronics_handler, diff --git a/grid2op/Episode/CompactEpisodeData.py b/grid2op/Episode/CompactEpisodeData.py index 3ed6af14a..30a138311 100644 --- a/grid2op/Episode/CompactEpisodeData.py +++ b/grid2op/Episode/CompactEpisodeData.py @@ -6,22 +6,13 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. import json -import os -import warnings -import copy import numpy as np -import grid2op -from grid2op.Exceptions import ( - Grid2OpException, - EnvError, - IncorrectNumberOfElements, - NonFiniteElement, -) from grid2op.Action import ActionSpace from grid2op.Observation import ObservationSpace from pathlib import Path as p + class CompactEpisodeData(): """ @@ -222,7 +213,6 @@ def store_metadata(self): """ Store this Episode's meta data to disk. """ - print({k:(v,type(v)) for k,v in self.meta.items()}) with open(self.exp_dir / f"{self.ep_id}_metadata.json", "w", encoding="utf-8") as f: json.dump(self.meta, f, indent=4, sort_keys=True) diff --git a/grid2op/MakeEnv/MakeFromPath.py b/grid2op/MakeEnv/MakeFromPath.py index bb3a4847a..81f31d218 100644 --- a/grid2op/MakeEnv/MakeFromPath.py +++ b/grid2op/MakeEnv/MakeFromPath.py @@ -7,19 +7,25 @@ # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. import os +import time import importlib.util import numpy as np import json import warnings +from grid2op.MakeEnv.PathUtils import USE_CLASS_IN_FILE from grid2op.Environment import Environment from grid2op.Backend import Backend, PandaPowerBackend from grid2op.Opponent.opponentSpace import OpponentSpace from grid2op.Parameters import Parameters -from grid2op.Chronics import ChronicsHandler, ChangeNothing, FromNPY, FromChronix2grid -from grid2op.Chronics import GridStateFromFile, GridValue +from grid2op.Chronics import (ChronicsHandler, + ChangeNothing, + FromNPY, + FromChronix2grid, + GridStateFromFile, + GridValue) from grid2op.Action import BaseAction, DontAct -from grid2op.Exceptions import * +from grid2op.Exceptions import EnvError from grid2op.Observation import CompleteObservation, BaseObservation from grid2op.Reward import BaseReward, L2RPNReward from grid2op.Rules import BaseRules, DefaultRules @@ -274,7 +280,7 @@ def make_from_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", @@ -354,7 +360,7 @@ def make_from_dataset_path( else: is_none = False names_chronics_to_backend = _get_default_aux( - "names_chronics_to_backend", + "names_chronics_to_grid", kwargs, defaultClassApp=dict, defaultinstance=name_converter, @@ -362,7 +368,7 @@ def make_from_dataset_path( ) if is_none and names_chronics_to_backend == {}: names_chronics_to_backend = None - + # Get default backend class backend_class_cfg = PandaPowerBackend if "backend_class" in config_data and config_data["backend_class"] is not None: @@ -812,24 +818,6 @@ def make_from_dataset_path( isclass=False, ) - if experimental_read_from_local_dir: - sys_path = os.path.join(os.path.split(grid_path_abs)[0], "_grid2op_classes") - 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)" - ) - # observation key word arguments kwargs_observation = _get_default_aux( "kwargs_observation", @@ -881,8 +869,114 @@ def make_from_dataset_path( ) if observation_backend_kwargs is observation_backend_kwargs_cfg_: observation_backend_kwargs = None + + # new in 1.10.2 : + allow_loaded_backend = False + classes_path = None + if USE_CLASS_IN_FILE: + sys_path = os.path.join(os.path.split(grid_path_abs)[0], "_grid2op_classes") + if not os.path.exists(sys_path): + try: + os.mkdir(sys_path) + except FileExistsError: + # if another process created it, no problem + pass + + # TODO: automatic delete the directory if needed + + # TODO: check the "new" path works + # TODO: in the BaseEnv.generate_classes make sure the classes are added to the "__init__" if the file is created + # TODO: make that only if backend can be copied ! + + # TODO: check the hash thingy is working in baseEnv._aux_gen_classes (currently a pdb) + + # TODO: check that previous behaviour is working correctly + + # TODO: create again the environment with the proper "read from local_dir" + + # 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 + + # TODO: allow for a way to disable that (with env variable or config in grid2op) + # TODO: keep only one environment that will delete the files (with a flag in its constructor) + + # TODO: explain in doc new behaviour with regards to "class in file" + + # TODO: basic CI for this "new" mode + + # TODO: use the tempfile.TemporaryDirectory() to hold the classes, and in the (real) env copy, runner , env.get_kwargs() + # or whatever + # reference this "tempfile.TemporaryDirectory()" which will be deleted automatically + # when every "pointer" to it are deleted, this sounds more reasonable + if not experimental_read_from_local_dir: + init_env = Environment(init_env_path=os.path.abspath(dataset_path), + 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_space_type=opponent_space_type, + 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, + has_attention_budget=has_attention_budget, + attention_budget_cls=attention_budget_class, + kwargs_attention_budget=kwargs_attention_budget, + logger=logger, + n_busbar=n_busbar, + _compat_glop_version=_compat_glop_version, + _read_from_local_dir=None, # first environment to generate the classes and save them + kwargs_observation=kwargs_observation, + observation_bk_class=observation_backend_class, + observation_bk_kwargs=observation_backend_kwargs, + ) + this_local_dir = f"{time.time()}_{os.getpid()}" + init_env.generate_classes(local_dir_id=this_local_dir) + init_env.backend = None # to avoid to close the backend when init_env is deleted + classes_path = os.path.join(sys_path, this_local_dir) + # to force the reading back of the classes from the hard drive + init_env._forget_classes() # TODO not implemented + init_env.close() + else: + classes_path = sys_path + 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: + sys_path = os.path.join(os.path.split(grid_path_abs)[0], "_grid2op_classes") + 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)" + ) + # Finally instantiate env from config & overrides + # including (if activated the new grid2op behaviour) env = Environment( init_env_path=os.path.abspath(dataset_path), init_grid_path=grid_path_abs, @@ -912,12 +1006,13 @@ def make_from_dataset_path( logger=logger, n_busbar=n_busbar, _compat_glop_version=_compat_glop_version, - _read_from_local_dir=experimental_read_from_local_dir, + _read_from_local_dir=classes_path, + _allow_loaded_backend=allow_loaded_backend, kwargs_observation=kwargs_observation, observation_bk_class=observation_backend_class, observation_bk_kwargs=observation_backend_kwargs, ) - + # Update the thermal limit if any if thermal_limits is not None: env.set_thermal_limit(thermal_limits) diff --git a/grid2op/MakeEnv/PathUtils.py b/grid2op/MakeEnv/PathUtils.py index 8551f39ce..99db27b5e 100644 --- a/grid2op/MakeEnv/PathUtils.py +++ b/grid2op/MakeEnv/PathUtils.py @@ -10,16 +10,47 @@ import os import json + DEFAULT_PATH_CONFIG = os.path.expanduser("~/.grid2opconfig.json") DEFAULT_PATH_DATA = os.path.expanduser("~/data_grid2op") +USE_CLASS_IN_FILE = False # set to True for new behaviour (will be set to True in grid2op 1.11) + + KEY_DATA_PATH = "data_path" +KEY_CLASS_IN_FILE = "class_in_file" + +def str_to_bool(string: str) -> bool: + """convert a "string" to a boolean, with the convention: + + - "t", "y", "yes", "true", "True", "TRUE" etc. returns True + - "false", "False", "FALSE" etc. returns False + - "1" returns True + - "0" returns False + + """ + string_ = string.lower() + if string_ in ["t", "true", "y", "yes", "on", "1"]: + return True + if string_ in ["f", "false", "n", "no", "off", "0"]: + return False + raise ValueError(f"Uknown way to convert `{string}` to a boolean. Please either set it to \"1\" or \"0\"") + + if os.path.exists(DEFAULT_PATH_CONFIG): with open(DEFAULT_PATH_CONFIG, "r") as f: dict_ = json.load(f) if KEY_DATA_PATH in dict_: DEFAULT_PATH_DATA = os.path.abspath(dict_[KEY_DATA_PATH]) + + if KEY_CLASS_IN_FILE in dict_: + USE_CLASS_IN_FILE = bool(dict_[KEY_CLASS_IN_FILE]) + if KEY_CLASS_IN_FILE in os.environ: + try: + USE_CLASS_IN_FILE = str_to_bool(os.environ[KEY_CLASS_IN_FILE]) + except ValueError as exc: + raise RuntimeError(f"Impossible to read the behaviour from `{KEY_CLASS_IN_FILE}` environment variable") from exc def _create_path_folder(data_path): diff --git a/grid2op/MakeEnv/UpdateEnv.py b/grid2op/MakeEnv/UpdateEnv.py index 01413f94e..abb2c208a 100644 --- a/grid2op/MakeEnv/UpdateEnv.py +++ b/grid2op/MakeEnv/UpdateEnv.py @@ -5,9 +5,11 @@ # 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. -import time +import time import os +import re + import grid2op.MakeEnv.PathUtils from grid2op.Exceptions import UnknownEnv @@ -157,18 +159,45 @@ def _update_files(env_name=None, answer_json=None, env_hashes=None): ) +def _aux_get_hash_if_none(hash_=None): + """Auxilliary function used to avoid copy pasting the `hash_ = hashlib.blake2b()` part and that can + be further changed if another hash is better later. + + Do not modify unless you have a good reason too. + """ + if hash_ is None: + # we use this as it is supposedly faster than md5 + # we don't really care about the "secure" part of it (though it's a nice tool to have) + import hashlib # lazy import + hash_ = hashlib.blake2b() + return hash_ + + +def _aux_update_hash_text(text_, hash_=None): + hash_ = _aux_get_hash_if_none(hash_) + text_ = re.sub("\s", "", text_) + hash_.update(text_.encode("utf-8")) + return hash_ + + +def _aux_hash_file(full_path_file, hash_=None): + hash_ = _aux_get_hash_if_none(hash_) + with open(full_path_file, "r", encoding="utf-8") as f: + text_ = f.read() + # this is done to ensure a compatibility between platform + # sometime git replaces the "\r\n" in windows with "\n" on linux / macos and it messes + # up the hash + _aux_update_hash_text(text_, hash_) + return hash_ + + # TODO make that a method of the environment maybe ? def _hash_env( path_local_env, hash_=None, blocksize=64, # TODO is this correct ? ): - import hashlib # lazy import - - if hash_ is None: - # we use this as it is supposedly faster than md5 - # we don't really care about the "secure" part of it (though it's a nice tool to have) - hash_ = hashlib.blake2b() + hash_ = _aux_get_hash_if_none(hash_) if os.path.exists(os.path.join(path_local_env, ".multimix")): # this is a multi mix, so i need to run through all sub env mixes = sorted(os.listdir(path_local_env)) @@ -197,17 +226,9 @@ def _hash_env( "scenario_params.json", ]: # list the file we want to hash (we don't hash everything full_path_file = os.path.join(path_local_env, fn_) - import re if os.path.exists(full_path_file): - with open(full_path_file, "r", encoding="utf-8") as f: - text_ = f.read() - text_ = re.sub( - "\s", "", text_ - ) # this is done to ensure a compatibility between platform - # sometime git replaces the "\r\n" in windows with "\n" on linux / macos and it messes - # up the hash - hash_.update(text_.encode("utf-8")) + _aux_hash_file(full_path_file, hash_) # now I hash the chronics # but as i don't want to read every chronics (for time purposes) i will only hash the names diff --git a/grid2op/MakeEnv/__init__.py b/grid2op/MakeEnv/__init__.py index 28a56d905..00eaef8c5 100644 --- a/grid2op/MakeEnv/__init__.py +++ b/grid2op/MakeEnv/__init__.py @@ -8,13 +8,13 @@ "list_available_test_env", "update_env", ] -try: - from grid2op.MakeEnv.MakeOld import make_old - - # deprecated in v 0.8.0 - __all__.append("make_old") -except ImportError: - pass + +# try: +# from grid2op.MakeEnv.MakeOld import make_old +# # deprecated in v 0.8.0 +# __all__.append("make_old") +# except ImportError: +# pass from grid2op.MakeEnv.MakeFromPath import make_from_dataset_path from grid2op.MakeEnv.Make import make diff --git a/grid2op/MakeEnv/get_default_aux.py b/grid2op/MakeEnv/get_default_aux.py index 364418177..423127d74 100644 --- a/grid2op/MakeEnv/get_default_aux.py +++ b/grid2op/MakeEnv/get_default_aux.py @@ -143,7 +143,7 @@ def _get_default_aux( res = defaultClassApp(res) except Exception as exc_: # if there is any error, i raise the error message - raise EnvError(msg_error) + raise EnvError(msg_error) from exc_ else: # if there is any error, i raise the error message raise EnvError(msg_error) diff --git a/grid2op/Observation/baseObservation.py b/grid2op/Observation/baseObservation.py index be05db50a..513b0ccfa 100644 --- a/grid2op/Observation/baseObservation.py +++ b/grid2op/Observation/baseObservation.py @@ -508,8 +508,9 @@ def __init__(self, self._forecasted_inj = [] self._env_internal_params = {} - self._obs_env = obs_env - self._ptr_kwargs_env = kwargs_env + from grid2op.Environment._obsEnv import _ObsEnv + self._obs_env : _ObsEnv = obs_env + self._ptr_kwargs_env : Dict = kwargs_env # calendar data self.year = dt_int(1970) @@ -699,6 +700,33 @@ def _aux_copy(self, other : Self) -> None: for attr_nm in attr_vect: getattr(other, attr_nm)[:] = getattr(self, attr_nm) + def change_reward(self, reward_func: "grid2op.Reward.BaseReward"): + """Allow to change the reward used when calling :func:`BaseObservation.simulate` + without having to access the observation space. + + .. versionadded:: 1.10.2 + + .. seealso:: :func:`grid2op.ObservationSpace.change_reward` + It has the same effet as :func:`grid2op.ObservationSpace.change_reward` + + Parameters + ---------- + reward_func : grid2op.Reward.BaseReward + _description_ + + Raises + ------ + BaseObservationError + _description_ + """ + if self._obs_env is not None: + if self._obs_env.is_valid(): + self._obs_env._reward_helper.change_reward(reward_func) + else: + raise BaseObservationError("Impossible to change the reward of the simulate " + "function when you cannot simulate (because the " + "backend could not be copied)") + def __copy__(self) -> Self: res = type(self)(obs_env=self._obs_env, action_helper=self.action_helper, @@ -3087,7 +3115,34 @@ def simulate(self, action : "grid2op.Action.BaseAction", time_step:int=1) -> Tup If the data of the :class:`grid2op.Environment.Environment` you are using supports it (**ie** you can access multiple steps ahead forecasts), then you can now "chain" the simulate calls. - + + .. danger:: + A simulation can be different from the reality, even in case of perfect forecast or if + you "simulate" an action on the current step (time_step=0). + + For example, the solver used can be different for "simulate" and for the environment + "step" or you can be a setting with noisy action. + + A more subtle difference includes the "initialization" of the solver which is different + in env.step and in obs.simulate so the outcomes of the solver might be different + (this is especially relevant for larger grid). + + Even more subtle is the behaviour of the ramps for some generators. + + More concretely, + say you want to dispatch upward a generator (with a ramp of +5) of +5MW at a given + step. But in the same time this generator would see its production increased by +2MW + "naturally" in the time series. Then, grid2op would limit the increase of +5MW (instead + of +7 = +5 +2) by limiting the redispatching action to +3MW. + + If you simulate the same action on the resulting step, as there are no "previous step" + then your action will not be limited and the +5MW of redispatching will be given. + + You have the same phenomenon for storage losses: they are applied even if you simulate + at the current step and conversely are not applied "multiple times" if you simulate + for an horizon longer than 1 (say time_step=2) or if you chain two or more + calls to "simulate". + Examples --------- @@ -3307,7 +3362,7 @@ def simulate(self, action : "grid2op.Action.BaseAction", time_step:int=1) -> Tup "Forecast for {} timestep(s) ahead is not possible with your chronics." "".format(time_step) ) - + if time_step not in self._forecasted_grid_act: timestamp, inj_forecasted = self._forecasted_inj[time_step] self._forecasted_grid_act[time_step] = { @@ -3723,7 +3778,7 @@ def _aux_add_act_set_line_status(self, cls, cls_act, act, res, issue_warn): & (res.topo_vect[cls.line_ex_pos_topo_vect] == -1) ) if tmp.any(): - id_issue_ex = np.nonzero(tmp)[0] + id_issue_ex = tmp.nonzero()[0] if issue_warn: warnings.warn(error_no_bus_set.format(id_issue_ex)) if "set_bus" in cls_act.authorized_keys: @@ -3735,7 +3790,7 @@ def _aux_add_act_set_line_status(self, cls, cls_act, act, res, issue_warn): & (res.topo_vect[cls.line_or_pos_topo_vect] == -1) ) if tmp.any(): - id_issue_or = np.nonzero(tmp)[0] + id_issue_or = tmp.nonzero()[0] if issue_warn: warnings.warn(error_no_bus_set.format(id_issue_or)) if "set_bus" in cls_act.authorized_keys: @@ -4156,9 +4211,6 @@ def _update_obs_complete(self, env: "grid2op.Environment.BaseEnv", with_forecast self.storage_charge[:] = env._storage_current_charge self.storage_power_target[:] = env._action_storage self.storage_power[:] = env._storage_power - - # handles forecasts here - self._update_forecast(env, with_forecast) # cool down and reconnection time after hard overflow, soft overflow or cascading failure self.time_before_cooldown_line[:] = env._times_before_line_status_actionable @@ -4197,10 +4249,15 @@ def _update_obs_complete(self, env: "grid2op.Environment.BaseEnv", with_forecast self.curtailment_limit[:] = 1.0 self.curtailment_limit_effective[:] = 1.0 - self._update_alarm(env) - self.delta_time = dt_float(1.0 * env.delta_time_seconds / 60.0) + # handles forecasts here + self._update_forecast(env, with_forecast) + + # handle alarms + self._update_alarm(env) + + # handle alerts self._update_alert(env) def _update_forecast(self, env: "grid2op.Environment.BaseEnv", with_forecast: bool) -> None: diff --git a/grid2op/Opponent/geometricOpponent.py b/grid2op/Opponent/geometricOpponent.py index ee0e23a00..1c811aa54 100644 --- a/grid2op/Opponent/geometricOpponent.py +++ b/grid2op/Opponent/geometricOpponent.py @@ -109,7 +109,7 @@ def init( # Store attackable lines IDs self._lines_ids = [] for l_name in lines_attacked: - l_id = np.nonzero(self.action_space.name_line == l_name) + l_id = (self.action_space.name_line == l_name).nonzero() if len(l_id) and len(l_id[0]): self._lines_ids.append(l_id[0][0]) else: diff --git a/grid2op/Opponent/randomLineOpponent.py b/grid2op/Opponent/randomLineOpponent.py index da8ba3058..c59cdc4f2 100644 --- a/grid2op/Opponent/randomLineOpponent.py +++ b/grid2op/Opponent/randomLineOpponent.py @@ -57,7 +57,7 @@ def init(self, partial_env, lines_attacked=[], **kwargs): # Store attackable lines IDs self._lines_ids = [] for l_name in lines_attacked: - l_id = np.nonzero(self.action_space.name_line == l_name) + l_id = (self.action_space.name_line == l_name).nonzero() if len(l_id) and len(l_id[0]): self._lines_ids.append(l_id[0][0]) else: diff --git a/grid2op/Opponent/weightedRandomOpponent.py b/grid2op/Opponent/weightedRandomOpponent.py index c1298e1e1..4771a57c1 100644 --- a/grid2op/Opponent/weightedRandomOpponent.py +++ b/grid2op/Opponent/weightedRandomOpponent.py @@ -73,7 +73,7 @@ def init( # Store attackable lines IDs self._lines_ids = [] for l_name in lines_attacked: - l_id = np.nonzero(self.action_space.name_line == l_name) + l_id = (self.action_space.name_line == l_name).nonzero() if len(l_id) and len(l_id[0]): self._lines_ids.append(l_id[0][0]) else: diff --git a/grid2op/Parameters.py b/grid2op/Parameters.py index c16d9a939..c5ec67b23 100644 --- a/grid2op/Parameters.py +++ b/grid2op/Parameters.py @@ -148,6 +148,19 @@ class Parameters: MAX_SIMULATE_PER_EPISODE: ``int`` Maximum number of calls to `obs.simuate(...)` allowed per episode (reset each "env.simulate(...)"). Defaults to -1 meaning "as much as you want". + IGNORE_INITIAL_STATE_TIME_SERIE: ``bool`` + If set to True (which is NOT the default), then the initial state of the grid + will always be "everything connected" and "everything connected to busbar 1" + regardless of the information present in the time series (see + :func:`grid2op.Chronics.GridValue.get_init_action`) + + .. versionadded:: 1.10.2 + + .. note:: + This flag has no impact if an initial state is set through a call to + `env.reset(options={"init state": ...})` (see doc of :func:`grid2op.Environment.Environment.reset` + for more information) + """ def __init__(self, parameters_path=None): @@ -227,6 +240,8 @@ def __init__(self, parameters_path=None): else: warn_msg = "Parameters: the file {} is not found. Continuing with default parameters." warnings.warn(warn_msg.format(parameters_path)) + + self.IGNORE_INITIAL_STATE_TIME_SERIE = False @staticmethod def _isok_txt(arg): @@ -368,6 +383,11 @@ def init_from_dict(self, dict_): if "MAX_SIMULATE_PER_EPISODE" in dict_: self.MAX_SIMULATE_PER_EPISODE = dt_int(dict_["MAX_SIMULATE_PER_EPISODE"]) + if "IGNORE_INITIAL_STATE_TIME_SERIE" in dict_: + self.IGNORE_INITIAL_STATE_TIME_SERIE = Parameters._isok_txt( + dict_["IGNORE_INITIAL_STATE_TIME_SERIE"] + ) + authorized_keys = set(self.__dict__.keys()) authorized_keys = authorized_keys | { "NB_TIMESTEP_POWERFLOW_ALLOWED", @@ -416,6 +436,7 @@ def to_dict(self): res["ALERT_TIME_WINDOW"] = int(self.ALERT_TIME_WINDOW) res["MAX_SIMULATE_PER_STEP"] = int(self.MAX_SIMULATE_PER_STEP) res["MAX_SIMULATE_PER_EPISODE"] = int(self.MAX_SIMULATE_PER_EPISODE) + res["IGNORE_INITIAL_STATE_TIME_SERIE"] = int(self.IGNORE_INITIAL_STATE_TIME_SERIE) return res def init_from_json(self, json_path): @@ -470,8 +491,10 @@ def check_valid(self): Raises ------- - An exception if the parameter is not valid + An exception (`RuntimeError`) if the parameter is not valid + """ + try: if not isinstance(self.NO_OVERFLOW_DISCONNECTION, (bool, dt_bool)): raise RuntimeError("NO_OVERFLOW_DISCONNECTION should be a boolean") @@ -479,7 +502,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert NO_OVERFLOW_DISCONNECTION to bool with error \n:"{exc_}"' - ) + ) from exc_ try: self.NB_TIMESTEP_OVERFLOW_ALLOWED = int( @@ -491,7 +514,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert NB_TIMESTEP_OVERFLOW_ALLOWED to int with error \n:"{exc_}"' - ) + ) from exc_ if self.NB_TIMESTEP_OVERFLOW_ALLOWED < 0: raise RuntimeError( @@ -505,7 +528,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert NB_TIMESTEP_RECONNECTION to int with error \n:"{exc_}"' - ) + ) from exc_ if self.NB_TIMESTEP_RECONNECTION < 0: raise RuntimeError("NB_TIMESTEP_RECONNECTION < 0., this should be >= 0.") try: @@ -514,7 +537,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert NB_TIMESTEP_COOLDOWN_LINE to int with error \n:"{exc_}"' - ) + ) from exc_ if self.NB_TIMESTEP_COOLDOWN_LINE < 0: raise RuntimeError("NB_TIMESTEP_COOLDOWN_LINE < 0., this should be >= 0.") try: @@ -525,7 +548,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert NB_TIMESTEP_COOLDOWN_SUB to int with error \n:"{exc_}"' - ) + ) from exc_ if self.NB_TIMESTEP_COOLDOWN_SUB < 0: raise RuntimeError("NB_TIMESTEP_COOLDOWN_SUB < 0., this should be >= 0.") try: @@ -536,7 +559,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert HARD_OVERFLOW_THRESHOLD to float with error \n:"{exc_}"' - ) + ) from exc_ if self.HARD_OVERFLOW_THRESHOLD < 1.0: raise RuntimeError( "HARD_OVERFLOW_THRESHOLD < 1., this should be >= 1. (use env.set_thermal_limit " @@ -551,7 +574,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert SOFT_OVERFLOW_THRESHOLD to float with error \n:"{exc_}"' - ) + ) from exc_ if self.SOFT_OVERFLOW_THRESHOLD < 1.0: raise RuntimeError( "SOFT_OVERFLOW_THRESHOLD < 1., this should be >= 1. (use env.set_thermal_limit " @@ -570,14 +593,14 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert ENV_DC to bool with error \n:"{exc_}"' - ) + ) from exc_ try: self.MAX_SUB_CHANGED = int(self.MAX_SUB_CHANGED) # to raise if numpy array self.MAX_SUB_CHANGED = dt_int(self.MAX_SUB_CHANGED) except Exception as exc_: raise RuntimeError( f'Impossible to convert MAX_SUB_CHANGED to int with error \n:"{exc_}"' - ) + ) from exc_ if self.MAX_SUB_CHANGED < 0: raise RuntimeError( "MAX_SUB_CHANGED should be >=0 (or -1 if you want to be able to change every " @@ -591,7 +614,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert MAX_LINE_STATUS_CHANGED to int with error \n:"{exc_}"' - ) + ) from exc_ if self.MAX_LINE_STATUS_CHANGED < 0: raise RuntimeError( "MAX_LINE_STATUS_CHANGED should be >=0 " @@ -604,7 +627,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert IGNORE_MIN_UP_DOWN_TIME to bool with error \n:"{exc_}"' - ) + ) from exc_ try: if not isinstance(self.ALLOW_DISPATCH_GEN_SWITCH_OFF, (bool, dt_bool)): raise RuntimeError("ALLOW_DISPATCH_GEN_SWITCH_OFF should be a boolean") @@ -614,7 +637,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert ALLOW_DISPATCH_GEN_SWITCH_OFF to bool with error \n:"{exc_}"' - ) + ) from exc_ try: if not isinstance( self.LIMIT_INFEASIBLE_CURTAILMENT_STORAGE_ACTION, (bool, dt_bool) @@ -628,7 +651,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert LIMIT_INFEASIBLE_CURTAILMENT_STORAGE_ACTION to bool with error \n:"{exc_}"' - ) + ) from exc_ try: self.INIT_STORAGE_CAPACITY = float( @@ -638,16 +661,16 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert INIT_STORAGE_CAPACITY to float with error \n:"{exc_}"' - ) + ) from exc_ if self.INIT_STORAGE_CAPACITY < 0.0: raise RuntimeError( "INIT_STORAGE_CAPACITY < 0., this should be within range [0., 1.]" - ) + ) from exc_ if self.INIT_STORAGE_CAPACITY > 1.0: raise RuntimeError( "INIT_STORAGE_CAPACITY > 1., this should be within range [0., 1.]" - ) + ) from exc_ try: if not isinstance(self.ACTIVATE_STORAGE_LOSS, (bool, dt_bool)): @@ -656,26 +679,26 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert ACTIVATE_STORAGE_LOSS to bool with error \n:"{exc_}"' - ) + ) from exc_ try: self.ALARM_WINDOW_SIZE = dt_int(self.ALARM_WINDOW_SIZE) except Exception as exc_: raise RuntimeError( f'Impossible to convert ALARM_WINDOW_SIZE to int with error \n:"{exc_}"' - ) + ) from exc_ try: self.ALARM_BEST_TIME = dt_int(self.ALARM_BEST_TIME) except Exception as exc_: raise RuntimeError( f'Impossible to convert ALARM_BEST_TIME to int with error \n:"{exc_}"' - ) + ) from exc_ try: self.ALERT_TIME_WINDOW = dt_int(self.ALERT_TIME_WINDOW) except Exception as exc_: raise RuntimeError( f'Impossible to convert ALERT_TIME_WINDOW to int with error \n:"{exc_}"' - ) + ) from exc_ if self.ALARM_WINDOW_SIZE <= 0: raise RuntimeError("self.ALARM_WINDOW_SIZE should be a positive integer !") @@ -692,7 +715,7 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert MAX_SIMULATE_PER_STEP to int with error \n:"{exc_}"' - ) + ) from exc_ if self.MAX_SIMULATE_PER_STEP <= -2: raise RuntimeError( f"self.MAX_SIMULATE_PER_STEP should be a positive integer or -1, we found {self.MAX_SIMULATE_PER_STEP}" @@ -706,8 +729,15 @@ def check_valid(self): except Exception as exc_: raise RuntimeError( f'Impossible to convert MAX_SIMULATE_PER_EPISODE to int with error \n:"{exc_}"' - ) + ) from exc_ if self.MAX_SIMULATE_PER_EPISODE <= -2: raise RuntimeError( f"self.MAX_SIMULATE_PER_EPISODE should be a positive integer or -1, we found {self.MAX_SIMULATE_PER_EPISODE}" ) + + try: + self.IGNORE_INITIAL_STATE_TIME_SERIE = dt_bool(self.IGNORE_INITIAL_STATE_TIME_SERIE) + except Exception as exc_: + raise RuntimeError( + f'Impossible to convert IGNORE_INITIAL_STATE_TIME_SERIE to bool with error \n:"{exc_}"' + ) from exc_ diff --git a/grid2op/Reward/alarmReward.py b/grid2op/Reward/alarmReward.py index cee617d2c..884f78338 100644 --- a/grid2op/Reward/alarmReward.py +++ b/grid2op/Reward/alarmReward.py @@ -107,7 +107,7 @@ def _mult_for_zone(self, alarm, disc_lines, env): """compute the multiplicative factor that increases the score if the right zone is predicted""" res = 1.0 # extract the lines that have been disconnected due to cascading failures - lines_disconnected_first = np.nonzero(disc_lines == 0)[0] + lines_disconnected_first = (disc_lines == 0).nonzero()[0] if ( alarm.sum() > 1 @@ -124,7 +124,7 @@ def _mult_for_zone(self, alarm, disc_lines, env): # now retrieve the id of the zones in which a powerline has been disconnected list_zone_names = list(zones_these_lines) - list_zone_ids = np.nonzero(np.isin(env.alarms_area_names, list_zone_names))[0] + list_zone_ids = (np.isin(env.alarms_area_names, list_zone_names)).nonzero()[0] # and finally, award some extra points if one of the zone, containing one of the powerline disconnected # by protection is in the alarm if alarm[list_zone_ids].any(): diff --git a/grid2op/Reward/alertReward.py b/grid2op/Reward/alertReward.py index aac6236d5..c0c3ae03a 100644 --- a/grid2op/Reward/alertReward.py +++ b/grid2op/Reward/alertReward.py @@ -157,7 +157,7 @@ def _update_state(self, env, action): def _compute_score_attack_blackout(self, env, ts_attack_in_order, indexes_to_look): # retrieve the lines that have been attacked in the time window - ts_ind, line_ind = np.nonzero(ts_attack_in_order) + ts_ind, line_ind = (ts_attack_in_order).nonzero() line_first_attack, first_ind_line_attacked = np.unique(line_ind, return_index=True) ts_first_line_attacked = ts_ind[first_ind_line_attacked] # now retrieve the array starting at the correct place diff --git a/grid2op/Rules/DefaultRules.py b/grid2op/Rules/DefaultRules.py index 4685c38a1..9e4832a6e 100644 --- a/grid2op/Rules/DefaultRules.py +++ b/grid2op/Rules/DefaultRules.py @@ -27,6 +27,11 @@ class DefaultRules(LookParam, PreventDiscoStorageModif, PreventReconnection): def __call__(self, action, env): """ See :func:`BaseRules.__call__` for a definition of the _parameters of this function. + + ..versionchanged:: 1.10.2 + In grid2op 1.10.2 this function is not called when the environment is reset: + The "action" made by the environment to set the environment in the desired state is always legal + """ is_legal, reason = LookParam.__call__(self, action, env) if not is_legal: diff --git a/grid2op/Rules/LookParam.py b/grid2op/Rules/LookParam.py index 797f42e5a..e2e463fef 100644 --- a/grid2op/Rules/LookParam.py +++ b/grid2op/Rules/LookParam.py @@ -29,19 +29,24 @@ class LookParam(BaseRules): def __call__(self, action, env): """ See :func:`BaseRules.__call__` for a definition of the parameters of this function. + + ..versionchanged:: 1.10.2 + In grid2op 1.10.2 this function is not called when the environment is reset: + The "action" made by the environment to set the environment in the desired state is always legal + """ # at first iteration, env.current_obs is None... powerline_status = env.get_current_line_status() aff_lines, aff_subs = action.get_topological_impact(powerline_status) if aff_lines.sum() > env._parameters.MAX_LINE_STATUS_CHANGED: - ids = np.nonzero(aff_lines)[0] + ids = (aff_lines).nonzero()[0] return False, IllegalAction( "More than {} line status affected by the action: {}" "".format(env.parameters.MAX_LINE_STATUS_CHANGED, ids) ) if aff_subs.sum() > env._parameters.MAX_SUB_CHANGED: - ids = np.nonzero(aff_subs)[0] + ids = (aff_subs).nonzero()[0] return False, IllegalAction( "More than {} substation affected by the action: {}" "".format(env.parameters.MAX_SUB_CHANGED, ids) diff --git a/grid2op/Rules/PreventDiscoStorageModif.py b/grid2op/Rules/PreventDiscoStorageModif.py index d75f449d2..fb20ae344 100644 --- a/grid2op/Rules/PreventDiscoStorageModif.py +++ b/grid2op/Rules/PreventDiscoStorageModif.py @@ -23,6 +23,11 @@ class PreventDiscoStorageModif(BaseRules): def __call__(self, action, env): """ See :func:`BaseRules.__call__` for a definition of the parameters of this function. + + ..versionchanged:: 1.10.2 + In grid2op 1.10.2 this function is not called when the environment is reset: + The "action" made by the environment to set the environment in the desired state is always legal + """ env_cls = type(env) if env_cls.n_storage == 0: @@ -41,6 +46,6 @@ def __call__(self, action, env): tmp_ = power_modif_disco & not_set_status & not_change_status return False, IllegalAction( f"Attempt to modify the power produced / absorbed by a storage unit " - f"without reconnecting it (check storage with id {np.nonzero(tmp_)[0]}." + f"without reconnecting it (check storage with id {(tmp_).nonzero()[0]}." ) return True, None diff --git a/grid2op/Rules/PreventReconnection.py b/grid2op/Rules/PreventReconnection.py index 354a77535..d1356ddd7 100644 --- a/grid2op/Rules/PreventReconnection.py +++ b/grid2op/Rules/PreventReconnection.py @@ -27,6 +27,10 @@ def __call__(self, action, env): due to an overflow. See :func:`BaseRules.__call__` for a definition of the parameters of this function. + + ..versionchanged:: 1.10.2 + In grid2op 1.10.2 this function is not called when the environment is reset: + The "action" made by the environment to set the environment in the desired state is always legal """ # at first iteration, env.current_obs is None... @@ -38,7 +42,7 @@ def __call__(self, action, env): if (env._times_before_line_status_actionable[aff_lines] > 0).any(): # i tried to act on a powerline too shortly after a previous action # or shut down due to an overflow or opponent or hazards or maintenance - ids = np.nonzero((env._times_before_line_status_actionable > 0) & aff_lines)[ + ids = ((env._times_before_line_status_actionable > 0) & aff_lines).nonzero()[ 0 ] return False, IllegalAction( @@ -49,7 +53,7 @@ def __call__(self, action, env): if (env._times_before_topology_actionable[aff_subs] > 0).any(): # I tried to act on a topology too shortly after a previous action - ids = np.nonzero((env._times_before_topology_actionable > 0) & aff_subs)[0] + ids = ((env._times_before_topology_actionable > 0) & aff_subs).nonzero()[0] return False, IllegalAction( "Substation with ids {} have been modified illegally (cooldown of {})".format( ids, env._times_before_topology_actionable[ids] diff --git a/grid2op/Rules/RulesChecker.py b/grid2op/Rules/RulesChecker.py index a362344a3..6f857c453 100644 --- a/grid2op/Rules/RulesChecker.py +++ b/grid2op/Rules/RulesChecker.py @@ -81,4 +81,8 @@ def __call__(self, action, env): reason: A grid2op IllegalException given the reason for which the action is illegal """ + if env.nb_time_step <= 0: + # only access when env is reset + return True, None + return self.legal_action(action, env) diff --git a/grid2op/Rules/rulesByArea.py b/grid2op/Rules/rulesByArea.py index 1338cb91f..4c01dccee 100644 --- a/grid2op/Rules/rulesByArea.py +++ b/grid2op/Rules/rulesByArea.py @@ -87,13 +87,18 @@ def initialize(self, env): raise Grid2OpException("The number of listed ids of substations in rule initialization does not match the number of " "substations of the chosen environement. Look for missing ids or doublon") else: - self.lines_id_by_area = {key : sorted(list(chain(*[[item for item in np.nonzero(env.line_or_to_subid == subid)[0] + self.lines_id_by_area = {key : sorted(list(chain(*[[item for item in (env.line_or_to_subid == subid).nonzero()[0] ] for subid in subid_list]))) for key,subid_list in self.substations_id_by_area.items()} def __call__(self, action, env): """ See :func:`BaseRules.__call__` for a definition of the _parameters of this function. + + ..versionchanged:: 1.10.2 + In grid2op 1.10.2 this function is not called when the environment is reset: + The "action" made by the environment to set the environment in the desired state is always legal + """ is_legal, reason = PreventDiscoStorageModif.__call__(self, action, env) if not is_legal: @@ -120,13 +125,13 @@ def _lookparam_byarea(self, action, env): aff_lines, aff_subs = action.get_topological_impact(powerline_status) if any([(aff_lines[line_ids]).sum() > env._parameters.MAX_LINE_STATUS_CHANGED for line_ids in self.lines_id_by_area.values()]): - ids = [[k for k in np.nonzero(aff_lines)[0] if k in line_ids] for line_ids in self.lines_id_by_area.values()] + ids = [[k for k in (aff_lines).nonzero()[0] if k in line_ids] for line_ids in self.lines_id_by_area.values()] return False, IllegalAction( "More than {} line status affected by the action in one area: {}" "".format(env.parameters.MAX_LINE_STATUS_CHANGED, ids) ) if any([(aff_subs[sub_ids]).sum() > env._parameters.MAX_SUB_CHANGED for sub_ids in self.substations_id_by_area.values()]): - ids = [[k for k in np.nonzero(aff_subs)[0] if k in sub_ids] for sub_ids in self.substations_id_by_area.values()] + ids = [[k for k in (aff_subs).nonzero()[0] if k in sub_ids] for sub_ids in self.substations_id_by_area.values()] return False, IllegalAction( "More than {} substation affected by the action in one area: {}" "".format(env.parameters.MAX_SUB_CHANGED, ids) diff --git a/grid2op/Runner/aux_fun.py b/grid2op/Runner/aux_fun.py index db8b4ba68..b9839f5c1 100644 --- a/grid2op/Runner/aux_fun.py +++ b/grid2op/Runner/aux_fun.py @@ -46,29 +46,36 @@ def _aux_one_process_parrallel( max_iter=None, add_detailed_output=False, add_nb_highres_sim=False, + init_states=None ): """this is out of the runner, otherwise it does not work on windows / macos""" - chronics_handler = ChronicsHandler( - chronicsClass=runner.gridStateclass, - path=runner.path_chron, - **runner.gridStateclass_kwargs - ) + # chronics_handler = ChronicsHandler( + # chronicsClass=runner.gridStateclass, + # path=runner.path_chron, + # **runner.gridStateclass_kwargs + # ) parameters = copy.deepcopy(runner.parameters) nb_episode_this_process = len(episode_this_process) res = [(None, None, None) for _ in range(nb_episode_this_process)] for i, ep_id in enumerate(episode_this_process): # `ep_id`: grid2op id of the episode i want to play # `i`: my id of the episode played (0, 1, ... episode_this_process) - env, agent = runner._new_env( - chronics_handler=chronics_handler, parameters=parameters + env, agent = runner._new_env(parameters=parameters ) try: env_seed = None if env_seeds is not None: env_seed = env_seeds[i] + agt_seed = None if agent_seeds is not None: agt_seed = agent_seeds[i] + + if init_states is not None: + init_state = init_states[i] + else: + init_state = None + tmp_ = _aux_run_one_episode( env, agent, @@ -80,9 +87,10 @@ def _aux_one_process_parrallel( agent_seed=agt_seed, detailed_output=add_detailed_output, use_compact_episode_data=runner.use_compact_episode_data, + init_state=init_state ) (name_chron, cum_reward, nb_time_step, max_ts, episode_data, nb_highres_sim) = tmp_ - id_chron = chronics_handler.get_id() + id_chron = env.chronics_handler.get_id() res[i] = (id_chron, name_chron, float(cum_reward), nb_time_step, max_ts) if add_detailed_output: @@ -106,6 +114,7 @@ def _aux_run_one_episode( max_iter=None, detailed_output=False, use_compact_episode_data=False, + init_state=None ): done = False time_step = int(0) @@ -121,9 +130,13 @@ def _aux_run_one_episode( # handle max_iter if max_iter is not None: env.chronics_handler.set_max_iter(max_iter) - + # reset it - obs = env.reset() + if init_state is None: + obs = env.reset() + else: + obs = env.reset(options={"init state": init_state}) + # reset the number of calls to high resolution simulator env._highres_sim_counter._HighResSimCounter__nb_highres_called = 0 diff --git a/grid2op/Runner/runner.py b/grid2op/Runner/runner.py index 1da2a19bc..647630ae5 100644 --- a/grid2op/Runner/runner.py +++ b/grid2op/Runner/runner.py @@ -9,8 +9,9 @@ import os import warnings import copy -from multiprocessing import Pool -from typing import Tuple, Optional, List, Union +import numpy as np +from multiprocessing import get_start_method, get_context, Pool +from typing import Tuple, List, Union from grid2op.Environment import BaseEnv from grid2op.Action import BaseAction, TopologyAction, DontAct @@ -18,7 +19,7 @@ from grid2op.Observation import CompleteObservation, BaseObservation from grid2op.Opponent.opponentSpace import OpponentSpace from grid2op.Reward import FlatReward, BaseReward -from grid2op.Rules import AlwaysLegal, BaseRules +from grid2op.Rules import AlwaysLegal from grid2op.Environment import Environment from grid2op.Chronics import ChronicsHandler, GridStateFromFile, GridValue from grid2op.Backend import Backend, PandaPowerBackend @@ -34,7 +35,7 @@ _aux_one_process_parrallel, ) from grid2op.Runner.basic_logger import DoNothingLog, ConsoleLog -from grid2op.Episode import EpisodeData, CompactEpisodeData +from grid2op.Episode import EpisodeData # on windows if i start using sequential, i need to continue using sequential # if i start using parallel i need to continue using parallel @@ -538,11 +539,11 @@ def __init__( self.max_iter = max_iter if max_iter > 0: self.gridStateclass_kwargs["max_iter"] = max_iter - self.chronics_handler = ChronicsHandler( - chronicsClass=self.gridStateclass, - path=self.path_chron, - **self.gridStateclass_kwargs - ) + # self.chronics_handler = ChronicsHandler( + # chronicsClass=self.gridStateclass, + # path=self.path_chron, + # **self.gridStateclass_kwargs + # ) self.verbose = verbose self.thermal_limit_a = thermal_limit_a @@ -634,12 +635,18 @@ def _make_new_backend(self): res = self.backendClass(**this_kwargs) return res - def _new_env(self, chronics_handler, parameters) -> Tuple[BaseEnv, BaseAgent]: + def _new_env(self, parameters) -> Tuple[BaseEnv, BaseAgent]: # the same chronics_handler is used for all the environments. # make sure to "reset" it properly # (this is handled elsewhere in case of "multi chronics") - if not self.chronics_handler.chronicsClass.MULTI_CHRONICS: - self.chronics_handler.next_chronics() + # ch_used = copy.deepcopy(chronics_handler) + # if not ch_used.chronicsClass.MULTI_CHRONICS: + # ch_used.next_chronics() + chronics_handler = ChronicsHandler( + chronicsClass=self.gridStateclass, + path=self.path_chron, + **self.gridStateclass_kwargs + ) backend = self._make_new_backend() with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -704,7 +711,7 @@ def init_env(self) -> BaseEnv: Function used to initialized the environment and the agent. It is called by :func:`Runner.reset`. """ - env, self.agent = self._new_env(self.chronics_handler, self.parameters) + env, self.agent = self._new_env(self.parameters) return env def reset(self): @@ -729,6 +736,7 @@ def run_one_episode( episode_id=None, detailed_output=False, add_nb_highres_sim=False, + init_state=None ) -> runner_returned_type: """ INTERNAL @@ -740,7 +748,7 @@ def run_one_episode( Parameters ---------- indx: ``int`` - The number of episode previously run + The index of the episode to run (ignored if `episode_id` is not None) path_save: ``str``, optional Path where to save the data. See the description of :mod:`grid2op.Runner` for the structure of the saved @@ -765,12 +773,12 @@ def run_one_episode( """ self.reset() - with self.init_env() as env: + with self.init_env() as env: res = _aux_run_one_episode( env, self.agent, self.logger, - indx, + indx if episode_id is None else episode_id, path_save, pbar=pbar, env_seed=env_seed, @@ -778,15 +786,20 @@ def run_one_episode( agent_seed=agent_seed, detailed_output=detailed_output, use_compact_episode_data = self.use_compact_episode_data, + init_state=init_state, ) if max_iter is not None: env.chronics_handler.set_max_iter(-1) - + + id_chron = env.chronics_handler.get_id() # `res` here necessarily contains detailed_output and nb_highres_call if not add_nb_highres_sim: res = res[:-1] if not detailed_output: res = res[:-1] + + # new in 1.10.2: id_chron is computed from here + res = (id_chron, *res) return res def _run_sequential( @@ -800,6 +813,7 @@ def _run_sequential( episode_id=None, add_detailed_output=False, add_nb_highres_sim=False, + init_states=None ) -> List[runner_returned_type]: """ INTERNAL @@ -838,7 +852,11 @@ def _run_sequential( By default ``None``, no seeds are set. If provided, its size should match ``nb_episode``. - add_detailed_output: see Runner.run method + add_detailed_output: + see :func:`Runner.run` method + + init_states: + see :func:`Runner.run` method Returns ------- @@ -865,10 +883,14 @@ def _run_sequential( agt_seed = None if agent_seeds is not None: agt_seed = agent_seeds[i] + init_state = None + if init_states is not None: + init_state = init_states[i] ep_id = i # if no "episode_id" is provided i used the i th one if episode_id is not None: ep_id = episode_id[i] # otherwise i use the provided one ( + id_chron, name_chron, cum_reward, nb_time_step, @@ -883,9 +905,9 @@ def _run_sequential( agent_seed=agt_seed, max_iter=max_iter, detailed_output=True, - add_nb_highres_sim=True + add_nb_highres_sim=True, + init_state=init_state, ) - id_chron = self.chronics_handler.get_id() res[i] = (id_chron, name_chron, float(cum_reward), @@ -910,6 +932,7 @@ def _run_parrallel( episode_id=None, add_detailed_output=False, add_nb_highres_sim=False, + init_states=None ) -> List[runner_returned_type]: """ INTERNAL @@ -950,8 +973,12 @@ def _run_parrallel( If provided, its size should match the ``nb_episode``. The agent will be seeded at the beginning of each scenario BEFORE calling `agent.reset()`. - add_detailed_output: see Runner.run method - + add_detailed_output: + See :func:`Runner.run` method + + init_states: + See :func:`Runner.run` method + Returns ------- res: ``list`` @@ -987,6 +1014,7 @@ def _run_parrallel( episode_id=episode_id, add_detailed_output=add_detailed_output, add_nb_highres_sim=add_nb_highres_sim, + init_states=init_states, ) else: self._clean_up() @@ -1003,7 +1031,7 @@ def _run_parrallel( seeds_env_res = [None for _ in range(nb_process)] else: # split the seeds according to the process - seeds_env_res = [[] for i in range(nb_process)] + seeds_env_res = [[] for _ in range(nb_process)] for i in range(nb_episode): seeds_env_res[i % nb_process].append(env_seeds[i]) @@ -1011,9 +1039,17 @@ def _run_parrallel( seeds_agt_res = [None for _ in range(nb_process)] else: # split the seeds according to the process - seeds_agt_res = [[] for i in range(nb_process)] + seeds_agt_res = [[] for _ in range(nb_process)] for i in range(nb_episode): seeds_agt_res[i % nb_process].append(agent_seeds[i]) + + if init_states is None: + init_states_res = [None for _ in range(nb_process)] + else: + # split the seeds according to the process + init_states_res = [[] for _ in range(nb_process)] + for i in range(nb_episode): + init_states_res[i % nb_process].append(init_states[i]) res = [] if _IS_LINUX: @@ -1030,10 +1066,16 @@ def _run_parrallel( seeds_agt_res[i], max_iter, add_detailed_output, - add_nb_highres_sim) + add_nb_highres_sim, + init_states_res[i]) - with Pool(nb_process) as p: - tmp = p.starmap(_aux_one_process_parrallel, lists) + if get_start_method() == 'spawn': + # https://github.com/rte-france/Grid2Op/issues/600 + with get_context("spawn").Pool(nb_process) as p: + tmp = p.starmap(_aux_one_process_parrallel, lists) + else: + with Pool(nb_process) as p: + tmp = p.starmap(_aux_one_process_parrallel, lists) for el in tmp: res += el return res @@ -1107,6 +1149,7 @@ def run( episode_id=None, add_detailed_output=False, add_nb_highres_sim=False, + init_states=None, ) -> List[runner_returned_type]: """ Main method of the :class:`Runner` class. It will either call :func:`Runner._run_sequential` if "nb_process" is @@ -1160,21 +1203,32 @@ def run( add_nb_highres_sim: ``bool`` Whether to add an estimated number of "high resolution simulator" called performed by the agent (either by obs.simulate, or by obs.get_forecast_env or by obs.get_simulator) + + init_states: + (added in grid2op 1.10.2) Possibility to set the initial state of the powergrid (when calling `env.reset`). + It should either be: + + - a dictionary representing an action (see doc of :func:`grid2op.Environment.Environment.reset`) + - a grid2op action (see doc of :func:`grid2op.Environment.Environment.reset`) + - a list / tuple of one of the above with the same size as the number of episode you want. + + If you provide a dictionary or a grid2op action, then this element will be used for all scenarios you + want to run. Returns ------- res: ``list`` List of tuple. Each tuple having 3[4] elements: - - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the - returned list are not necessarily sorted by this value) + - "id_chron" unique identifier of the episode + - "name_chron" name of the time series (usually it is the path where it is stored) - "cum_reward" the cumulative reward obtained by the :attr:`Runner.Agent` on this episode i - "nb_time_step": the number of time steps played in this episode. - "total_step": the total number of time steps possible in this episode. - "episode_data" : [Optional] The :class:`EpisodeData` corresponding to this episode run only if `add_detailed_output=True` - "add_nb_highres_sim": [Optional] The estimated number of calls to high resolution simulator made - by the agent + by the agent. Only preset if `add_nb_highres_sim=True` in the kwargs Examples -------- @@ -1220,6 +1274,40 @@ def run( runner = Runner(**env.get_params_for_runner(), agentClass=None, agentInstance=my_agent) res = runner.run(nb_episode=1, agent_seeds=[42], env_seeds=[0]) + Since grid2op 1.10.2 you can also set the initial state of the grid when + calling the runner. You can do that with the kwargs `init_states`, for example like this: + + .. code-block: python + + import grid2op + from gri2op.Runner import Runner + from grid2op.Agent import RandomAgent + + env = grid2op.make("l2rpn_case14_sandbox") + my_agent = RandomAgent(env.action_space) + runner = Runner(**env.get_params_for_runner(), agentClass=None, agentInstance=my_agent) + res = runner.run(nb_episode=1, + agent_seeds=[42], + env_seeds=[0], + init_states=[{"set_line_status": [(0, -1)]}] + ) + + .. note:: + We recommend that you provide `init_states` as a list having a length of + `nb_episode`. Each episode will be initialized with the provided + element of the list. However, if you provide only one element, then + all episodes you want to compute will be initialized with this same + action. + + .. note:: + At the beginning of each episode, if an `init_state` is set, + the environment is reset with a call like: `env.reset(options={"init state": init_state})` + + This is why we recommend you to use dictionary to set the initial state so + that you can control what exactly is done (set the `"method"`) more + information about this on the doc of the :func:`grid2op.Environment.Environment.reset` + function. + """ if nb_episode < 0: raise RuntimeError("Impossible to run a negative number of scenarios.") @@ -1246,6 +1334,28 @@ def run( "".format(nb_episode, len(episode_id)) ) + if init_states is not None: + if isinstance(init_states, (dict, BaseAction)): + # user provided one initial state, I copy it to all + # evaluation + init_states = [init_states.copy() for _ in range(nb_episode)] + elif isinstance(init_states, (list, tuple, np.ndarray)): + # user provided a list of initial states, it should match the + # number of scenarios + if len(init_states) != nb_episode: + raise RuntimeError( + 'You want to compute "{}" run(s) but provide only "{}" different initial state.' + "".format(nb_episode, len(init_states)) + ) + for i, el in enumerate(init_states): + if not isinstance(el, (dict, BaseAction)): + raise RuntimeError("When specifying `init_states` kwargs with a list (or a tuple) " + "it should be a list (or a tuple) of dictionary or BaseAction. " + f"You provided {type(el)} at position {i}.") + else: + raise RuntimeError("When using `init_state` in the runner, you should make sure to use " + "either use dictionnary, grid2op actions or list of actions.") + if max_iter is not None: max_iter = int(max_iter) @@ -1268,6 +1378,7 @@ def run( episode_id=episode_id, add_detailed_output=add_detailed_output, add_nb_highres_sim=add_nb_highres_sim, + init_states=init_states ) else: if add_detailed_output and (_IS_WINDOWS or _IS_MACOS): @@ -1286,6 +1397,7 @@ def run( episode_id=episode_id, add_detailed_output=add_detailed_output, add_nb_highres_sim=add_nb_highres_sim, + init_states=init_states ) else: self.logger.info("Parallel runner used.") @@ -1299,6 +1411,7 @@ def run( episode_id=episode_id, add_detailed_output=add_detailed_output, add_nb_highres_sim=add_nb_highres_sim, + init_states=init_states ) finally: self._clean_up() diff --git a/grid2op/Space/GridObjects.py b/grid2op/Space/GridObjects.py index 38d0cdc63..361f91253 100644 --- a/grid2op/Space/GridObjects.py +++ b/grid2op/Space/GridObjects.py @@ -21,10 +21,11 @@ import copy import numpy as np from packaging import version -from typing import Dict, Union, Literal +from typing import Dict, Union, Literal, Any, List, Optional, ClassVar, Tuple import grid2op from grid2op.dtypes import dt_int, dt_float, dt_bool +from grid2op.typing_variables import CLS_AS_DICT_TYPING from grid2op.Exceptions import * from grid2op.Space.space_utils import extract_from_dict, save_to_dict @@ -473,71 +474,75 @@ class GridObjects: .. versionadded:: 1.9.1 """ - BEFORE_COMPAT_VERSION = "neurips_2020_compat" - glop_version = grid2op.__version__ - _PATH_ENV = None # especially do not modify that - - SUB_COL = 0 - LOA_COL = 1 - GEN_COL = 2 - LOR_COL = 3 - LEX_COL = 4 - STORAGE_COL = 5 - - attr_list_vect = None + BEFORE_COMPAT_VERSION : ClassVar[str] = "neurips_2020_compat" + glop_version : ClassVar[str] = grid2op.__version__ + + _INIT_GRID_CLS = None # do not modify that, this is handled by grid2op automatically + _PATH_GRID_CLASSES : ClassVar[Optional[str]] = None # especially do not modify that + _CLS_DICT : ClassVar[Optional[CLS_AS_DICT_TYPING]] = None # init once to avoid yet another serialization of the class as dict (in make_cls_dict) + _CLS_DICT_EXTENDED : ClassVar[Optional[CLS_AS_DICT_TYPING]] = None # init once to avoid yet another serialization of the class as dict (in make_cls_dict) + + SUB_COL : ClassVar[int] = 0 + LOA_COL : ClassVar[int] = 1 + GEN_COL : ClassVar[int] = 2 + LOR_COL : ClassVar[int] = 3 + LEX_COL : ClassVar[int] = 4 + STORAGE_COL : ClassVar[int] = 5 + + attr_list_vect : ClassVar[Optional[List[str]]] = None attr_list_set = {} - attr_list_json = [] + attr_list_json : ClassVar[Optional[List[str]]] = [] attr_nan_list_set = set() # name of the objects - env_name = "unknown" - name_load = None - name_gen = None - name_line = None - name_sub = None - name_storage = None - - n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB - n_gen = -1 - n_load = -1 - n_line = -1 - n_sub = -1 - n_storage = -1 - - sub_info = None - dim_topo = -1 + env_name : ClassVar[str] = "unknown" + name_load : ClassVar[np.ndarray] = None + name_gen : ClassVar[np.ndarray] = None + name_line : ClassVar[np.ndarray] = None + name_sub : ClassVar[np.ndarray] = None + name_storage : ClassVar[np.ndarray] = None + + n_busbar_per_sub : ClassVar[int] = DEFAULT_N_BUSBAR_PER_SUB + n_gen : ClassVar[int] = -1 + n_load : ClassVar[int] = -1 + n_line : ClassVar[int] = -1 + n_sub : ClassVar[int] = -1 + n_storage : ClassVar[int] = -1 + + sub_info : ClassVar[np.ndarray] = None + dim_topo : ClassVar[np.ndarray] = -1 # to which substation is connected each element - load_to_subid = None - gen_to_subid = None - line_or_to_subid = None - line_ex_to_subid = None - storage_to_subid = None + load_to_subid : ClassVar[np.ndarray] = None + gen_to_subid : ClassVar[np.ndarray] = None + line_or_to_subid : ClassVar[np.ndarray] = None + line_ex_to_subid : ClassVar[np.ndarray] = None + storage_to_subid : ClassVar[np.ndarray] = None # which index has this element in the substation vector - load_to_sub_pos = None - gen_to_sub_pos = None - line_or_to_sub_pos = None - line_ex_to_sub_pos = None - storage_to_sub_pos = None + load_to_sub_pos : ClassVar[np.ndarray] = None + gen_to_sub_pos : ClassVar[np.ndarray] = None + line_or_to_sub_pos : ClassVar[np.ndarray] = None + line_ex_to_sub_pos : ClassVar[np.ndarray] = None + storage_to_sub_pos : ClassVar[np.ndarray] = None # which index has this element in the topology vector - load_pos_topo_vect = None - gen_pos_topo_vect = None - line_or_pos_topo_vect = None - line_ex_pos_topo_vect = None - storage_pos_topo_vect = None + load_pos_topo_vect : ClassVar[np.ndarray] = None + gen_pos_topo_vect : ClassVar[np.ndarray] = None + line_or_pos_topo_vect : ClassVar[np.ndarray] = None + line_ex_pos_topo_vect : ClassVar[np.ndarray] = None + storage_pos_topo_vect : ClassVar[np.ndarray] = None # "convenient" way to retrieve information of the grid - grid_objects_types = None + grid_objects_types : ClassVar[np.ndarray] = None # to which substation each element of the topovect is connected - _topo_vect_to_sub = None + _topo_vect_to_sub : ClassVar[np.ndarray] = None # list of attribute to convert it from/to a vector _vectorized = None # for redispatching / unit commitment - _li_attr_disp = [ + _li_attr_disp : ClassVar[List[str]] = [ "gen_type", "gen_pmin", "gen_pmax", @@ -552,7 +557,7 @@ class GridObjects: "gen_renewable", ] - _type_attr_disp = [ + _type_attr_disp : ClassVar[List] = [ str, float, float, @@ -568,39 +573,39 @@ class GridObjects: ] # redispatch data, not available in all environment - redispatching_unit_commitment_availble = False - gen_type = None - gen_pmin = None - gen_pmax = None - gen_redispatchable = None - gen_max_ramp_up = None - gen_max_ramp_down = None - gen_min_uptime = None - gen_min_downtime = None - gen_cost_per_MW = None # marginal cost (in currency / (power.step) and not in $/(MW.h) it would be $ / (MW.5mins) ) - gen_startup_cost = None # start cost (in currency) - gen_shutdown_cost = None # shutdown cost (in currency) - gen_renewable = None + redispatching_unit_commitment_availble : ClassVar[bool] = False + gen_type : ClassVar[Optional[np.ndarray]] = None + gen_pmin : ClassVar[Optional[np.ndarray]] = None + gen_pmax : ClassVar[Optional[np.ndarray]] = None + gen_redispatchable : ClassVar[Optional[np.ndarray]] = None + gen_max_ramp_up : ClassVar[Optional[np.ndarray]] = None + gen_max_ramp_down : ClassVar[Optional[np.ndarray]] = None + gen_min_uptime : ClassVar[Optional[np.ndarray]] = None + gen_min_downtime : ClassVar[Optional[np.ndarray]] = None + gen_cost_per_MW : ClassVar[Optional[np.ndarray]] = None # marginal cost (in currency / (power.step) and not in $/(MW.h) it would be $ / (MW.5mins) ) + gen_startup_cost : ClassVar[Optional[np.ndarray]] = None # start cost (in currency) + gen_shutdown_cost : ClassVar[Optional[np.ndarray]] = None # shutdown cost (in currency) + gen_renewable : ClassVar[Optional[np.ndarray]] = None # storage unit static data - storage_type = None - storage_Emax = None - storage_Emin = None - storage_max_p_prod = None - storage_max_p_absorb = None - storage_marginal_cost = None - storage_loss = None - storage_charging_efficiency = None - storage_discharging_efficiency = None + storage_type : ClassVar[Optional[np.ndarray]] = None + storage_Emax : ClassVar[Optional[np.ndarray]] = None + storage_Emin : ClassVar[Optional[np.ndarray]] = None + storage_max_p_prod : ClassVar[Optional[np.ndarray]] = None + storage_max_p_absorb : ClassVar[Optional[np.ndarray]] = None + storage_marginal_cost : ClassVar[Optional[np.ndarray]] = None + storage_loss : ClassVar[Optional[np.ndarray]] = None + storage_charging_efficiency : ClassVar[Optional[np.ndarray]] = None + storage_discharging_efficiency : ClassVar[Optional[np.ndarray]] = None # grid layout - grid_layout = None + grid_layout : ClassVar[Optional[Dict[str, Tuple[float, float]]]] = None # shunt data, not available in every backend - shunts_data_available = False - n_shunt = None - name_shunt = None - shunt_to_subid = None + shunts_data_available : ClassVar[bool] = False + n_shunt : ClassVar[Optional[int]] = None + name_shunt : ClassVar[Optional[np.ndarray]] = None + shunt_to_subid : ClassVar[Optional[np.ndarray]] = None # alarm / alert assistant_warning_type = None @@ -623,18 +628,18 @@ class GridObjects: alertable_line_ids = [] # test - _IS_INIT = False + _IS_INIT : ClassVar[Optional[bool]] = False def __init__(self): """nothing to do when an object of this class is created, the information is held by the class attributes""" pass @classmethod - def set_n_busbar_per_sub(cls, n_busbar_per_sub): + def set_n_busbar_per_sub(cls, n_busbar_per_sub: int) -> None: cls.n_busbar_per_sub = n_busbar_per_sub @classmethod - def tell_dim_alarm(cls, dim_alarms): + def tell_dim_alarm(cls, dim_alarms: int) -> None: if cls.dim_alarms != 0: # number of alarms has already been set, i issue a warning warnings.warn( @@ -649,7 +654,7 @@ def tell_dim_alarm(cls, dim_alarms): cls.assistant_warning_type = "zonal" @classmethod - def tell_dim_alert(cls, dim_alerts): + def tell_dim_alert(cls, dim_alerts: int) -> None: if cls.dim_alerts != 0: # number of alerts has already been set, i issue a warning warnings.warn( @@ -664,7 +669,17 @@ def tell_dim_alert(cls, dim_alerts): cls.assistant_warning_type = "by_line" @classmethod - def _clear_class_attribute(cls): + def _reset_cls_dict(cls): + cls._CLS_DICT = None + cls._CLS_DICT_EXTENDED = None + + @classmethod + def _clear_class_attribute(cls) -> None: + """Also calls :func:`GridObjects._clear_grid_dependant_class_attributes` : this clear the attribute that + may be backend dependant too (eg shunts_data) + + This clear the class as if it was defined in grid2op directly. + """ cls.shunts_data_available = False cls.n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB @@ -702,9 +717,13 @@ def _clear_class_attribute(cls): cls._clear_grid_dependant_class_attributes() @classmethod - def _clear_grid_dependant_class_attributes(cls): + def _clear_grid_dependant_class_attributes(cls) -> None: + """reset to an original state all the class attributes that depends on an environment""" + cls._reset_cls_dict() + cls._INIT_GRID_CLS = None # do not modify that, this is handled by grid2op automatically + cls._PATH_GRID_CLASSES = None # especially do not modify that + cls.glop_version = grid2op.__version__ - cls._PATH_ENV = None cls.SUB_COL = 0 cls.LOA_COL = 1 @@ -816,7 +835,7 @@ def _clear_grid_dependant_class_attributes(cls): cls.alertable_line_ids = [] @classmethod - def _update_value_set(cls): + def _update_value_set(cls) -> None: """ INTERNAL @@ -826,7 +845,7 @@ def _update_value_set(cls): """ cls.attr_list_set = set(cls.attr_list_vect) - def _raise_error_attr_list_none(self): + def _raise_error_attr_list_none(self) -> None: """ INTERNAL @@ -846,7 +865,7 @@ def _raise_error_attr_list_none(self): "nor to know its size, shape or dtype.".format(type(self)) ) - def _get_array_from_attr_name(self, attr_name): + def _get_array_from_attr_name(self, attr_name: str) -> Union[np.ndarray, int, str]: """ INTERNAL @@ -871,7 +890,7 @@ def _get_array_from_attr_name(self, attr_name): """ return np.array(getattr(self, attr_name)).flatten() - def to_vect(self): + def to_vect(self) -> np.ndarray: """ Convert this instance of GridObjects to a numpy ndarray. The size of the array is always the same and is determined by the :func:`GridObject.size` method. @@ -916,7 +935,7 @@ def to_vect(self): self._vectorized = np.array([], dtype=dt_float) return self._vectorized - def to_json(self, convert=True): + def to_json(self, convert : bool=True) -> Dict[str, Any]: """ Convert this instance of GridObjects to a dictionary that can be json serialized. @@ -951,7 +970,7 @@ def to_json(self, convert=True): cls._convert_to_json(res) return res - def from_json(self, dict_): + def from_json(self, dict_: Dict[str, Any]) -> None: """ This transform an gridobject (typically an action or an observation) serialized in json format to the corresponding grid2op action / observation (subclass of grid2op.Action.BaseAction @@ -981,7 +1000,7 @@ def from_json(self, dict_): setattr(self, key, type_(array_[0])) @classmethod - def _convert_to_json(cls, dict_): + def _convert_to_json(cls, dict_: Dict[str, Any]) -> None: for attr_nm in cls.attr_list_vect + cls.attr_list_json: tmp = dict_[attr_nm] dtype = tmp.dtype @@ -998,7 +1017,7 @@ def _convert_to_json(cls, dict_): elif dtype == bool: dict_[attr_nm] = [bool(el) for el in tmp] - def shapes(self): + def shapes(self) -> np.ndarray: """ The shapes of all the components of the action, mainly used for gym compatibility is the shape of all part of the action. @@ -1044,7 +1063,7 @@ def shapes(self): ).astype(dt_int) return res - def dtypes(self): + def dtypes(self) -> np.ndarray: """ The types of the components of the GridObjects, mainly used for gym compatibility is the shape of all part of the action. @@ -1164,6 +1183,7 @@ def from_vect(self, vect, check_legit=True): act_cpy = env.action_space.from_vect(act_as_vect) """ + cls = type(self) if vect.shape[0] != self.size(): raise IncorrectNumberOfElements( "Incorrect number of elements found while load a GridObjects " @@ -1182,7 +1202,7 @@ def from_vect(self, vect, check_legit=True): self._raise_error_attr_list_none() prev_ = 0 - for attr_nm, sh, dt in zip(type(self).attr_list_vect, self.shapes(), self.dtypes()): + for attr_nm, sh, dt in zip(cls.attr_list_vect, self.shapes(), self.dtypes()): tmp = vect[prev_ : (prev_ + sh)] # TODO a flag that says "default Nan" for example for when attributes are initialized with @@ -1190,10 +1210,20 @@ def from_vect(self, vect, check_legit=True): # if np.any(~np.isfinite(tmp)) and default_nan: # raise NonFiniteElement("None finite number in from_vect detected") - if attr_nm not in type(self).attr_nan_list_set and ( + if attr_nm not in cls.attr_nan_list_set and ( (~np.isfinite(tmp)).any() ): - raise NonFiniteElement("None finite number in from_vect detected") + attrs_debug = [] + prev_debug = 0 + for attr_nm_debug, sh_debug, dt_debug in zip(cls.attr_list_vect, self.shapes(), self.dtypes()): + tmp = vect[prev_debug : (prev_debug + sh_debug)] + if attr_nm not in cls.attr_nan_list_set and ( + (~np.isfinite(tmp)).any()): + attrs_debug.append(attr_nm_debug) + prev_debug += sh_debug + raise NonFiniteElement(f"None finite number in from_vect " + f"detected for corresponding to attributes " + f"{attrs_debug}") try: tmp = tmp.astype(dt) @@ -1284,25 +1314,54 @@ def _aux_pos_big_topo(cls, vect_to_subid, vect_to_sub_pos): res[i] = obj_before + my_pos return res - def _init_class_attr(self, obj=None): + def _init_class_attr(self, obj=None, _topo_vect_only=False): """Init the class attribute from an instance of the class THIS IS NOT A CLASS ATTR obj should be an object and NOT a class ! + + Notes + ------- + _topo_vect_only: this function is called once when the backend is initialized in `backend.load_grid` + (in `backend._compute_pos_big_topo`) and then once when everything is set up + (after redispatching and storage data are loaded). + + This is why I need the `_topo_vect_only` flag that tells this function when it's called only for + `topo_vect` related attributed """ + if obj is None: obj = self - cls = type(self) + cls = type(self) cls_as_dict = {} - GridObjects._make_cls_dict_extended(obj, cls_as_dict, as_list=False) + GridObjects._make_cls_dict_extended(obj, cls_as_dict, as_list=False, _topo_vect_only=_topo_vect_only) for attr_nm, attr in cls_as_dict.items(): - setattr(cls, attr_nm, attr) + if _topo_vect_only: + # safety guard: only set the attribute needed for the computation of the topo_vect vector + # this should be the only attribute in cls_as_dict but let's be sure + if (attr_nm.endswith("to_subid") or + attr_nm.endswith("to_sub_pos") or + attr_nm.startswith("n_") or + attr_nm.startswith("dim_topo") or + attr_nm.startswith("name_") or + attr_nm.startswith("shunts_data_available") + ): + setattr(cls, attr_nm, attr) + else: + # set all the attributes + setattr(cls, attr_nm, attr) + + # make sure to catch data intiialized even outside of this function + if not _topo_vect_only: + cls._reset_cls_dict() + tmp = {} + GridObjects._make_cls_dict_extended(obj, tmp, as_list=False, copy_=True, _topo_vect_only=False) def _compute_pos_big_topo(self): # move the object attribute as class attribute ! if not type(self)._IS_INIT: - self._init_class_attr() + self._init_class_attr(_topo_vect_only=True) cls = type(self) cls._compute_pos_big_topo_cls() @@ -1356,6 +1415,7 @@ def _compute_pos_big_topo_cls(cls): ).astype(dt_int) cls._topo_vect_to_sub = np.repeat(np.arange(cls.n_sub), repeats=cls.sub_info) + cls._check_convert_to_np_array(raise_if_none=False) # there can still be "None" attribute at this stage cls.grid_objects_types = np.full( shape=(cls.dim_topo, 6), fill_value=-1, dtype=dt_int ) @@ -1470,9 +1530,12 @@ def _check_sub_id(cls): "is greater than the number of substations of the grid, which is {}." "".format(np.max(cls.line_or_to_subid), cls.n_sub) ) - + @classmethod def _fill_names(cls): + """fill the name vectors (**eg** name_line) if not done already in the backend. + This function is used to fill the name of the class. + """ if cls.name_line is None: cls.name_line = [ "{}_{}_{}".format(or_id, ex_id, l_id) @@ -1487,6 +1550,8 @@ def _fill_names(cls): "This might result in impossibility to load data." '\n\tIf "env.make" properly worked, you can safely ignore this warning.' ) + cls._reset_cls_dict() + if cls.name_load is None: cls.name_load = [ "load_{}_{}".format(bus_id, load_id) @@ -1499,6 +1564,8 @@ def _fill_names(cls): "This might result in impossibility to load data." '\n\tIf "env.make" properly worked, you can safely ignore this warning.' ) + cls._reset_cls_dict() + if cls.name_gen is None: cls.name_gen = [ "gen_{}_{}".format(bus_id, gen_id) @@ -1512,6 +1579,8 @@ def _fill_names(cls): "This might result in impossibility to load data." '\n\tIf "env.make" properly worked, you can safely ignore this warning.' ) + cls._reset_cls_dict() + if cls.name_sub is None: cls.name_sub = ["sub_{}".format(sub_id) for sub_id in range(cls.n_sub)] cls.name_sub = np.array(cls.name_sub) @@ -1522,6 +1591,8 @@ def _fill_names(cls): "This might result in impossibility to load data." '\n\tIf "env.make" properly worked, you can safely ignore this warning.' ) + cls._reset_cls_dict() + if cls.name_storage is None: cls.name_storage = [ "storage_{}_{}".format(bus_id, sto_id) @@ -1535,6 +1606,22 @@ def _fill_names(cls): "This might result in impossibility to load data." '\n\tIf "env.make" properly worked, you can safely ignore this warning.' ) + cls._reset_cls_dict() + + if cls.shunts_data_available and cls.name_shunt is None: + cls.name_shunt = [ + "shunt_{}_{}".format(bus_id, sh_id) + for sh_id, bus_id in enumerate(cls.shunt_to_subid) + ] + cls.name_shunt = np.array(cls.name_shunt) + warnings.warn( + "name_shunt is None so default storage unit names have been assigned to your grid. " + "(FYI: storage names are used to make the correspondence between the chronics and " + "the backend)" + "This might result in impossibility to load data." + '\n\tIf "env.make" properly worked, you can safely ignore this warning.' + ) + cls._reset_cls_dict() @classmethod def _check_names(cls): @@ -1546,45 +1633,40 @@ def _check_names(cls): cls.name_line = cls.name_line.astype(str) except Exception as exc_: raise EnvError( - f"self.name_line should be convertible to a numpy array of type str. Error was " - f"{exc_}" - ) + f"self.name_line should be convertible to a numpy array of type str" + ) from exc_ if not isinstance(cls.name_load, np.ndarray): try: cls.name_load = np.array(cls.name_load) cls.name_load = cls.name_load.astype(str) except Exception as exc_: raise EnvError( - "self.name_load should be convertible to a numpy array of type str. Error was " - f"{exc_}" - ) + "self.name_load should be convertible to a numpy array of type str." + ) from exc_ if not isinstance(cls.name_gen, np.ndarray): try: cls.name_gen = np.array(cls.name_gen) cls.name_gen = cls.name_gen.astype(str) except Exception as exc_: raise EnvError( - "self.name_gen should be convertible to a numpy array of type str. Error was " - f"{exc_}" - ) + "self.name_gen should be convertible to a numpy array of type str." + ) from exc_ if not isinstance(cls.name_sub, np.ndarray): try: cls.name_sub = np.array(cls.name_sub) cls.name_sub = cls.name_sub.astype(str) except Exception as exc_: raise EnvError( - "self.name_sub should be convertible to a numpy array of type str. Error was " - f"{exc_}" - ) + "self.name_sub should be convertible to a numpy array of type str." + ) from exc_ if not isinstance(cls.name_storage, np.ndarray): try: cls.name_storage = np.array(cls.name_storage) cls.name_storage = cls.name_storage.astype(str) except Exception as exc_: raise EnvError( - "self.name_storage should be convertible to a numpy array of type str. Error was " - f"{exc_}" - ) + "self.name_storage should be convertible to a numpy array of type str." + ) from exc_ attrs_nms = [ cls.name_gen, @@ -1600,7 +1682,13 @@ def _check_names(cls): nms.append("shunts") for arr_, nm in zip(attrs_nms, nms): - tmp = np.unique(arr_) + try: + tmp = np.unique(arr_) + tmp.shape[0] + arr_.shape[0] + except AttributeError as exc_: + raise Grid2OpException(f"Error for {nm}: name is most likely None") from exc_ + if tmp.shape[0] != arr_.shape[0]: nms = "\n\t - ".join(sorted(arr_)) raise EnvError( @@ -1829,6 +1917,79 @@ def _compute_sub_elements(cls): for s_id in cls.storage_to_subid: cls.sub_info[s_id] += 1 + @classmethod + def _assign_attr(cls, attrs_list, tp, tp_nm, raise_if_none=False): + for el in attrs_list: + arr = getattr(cls, el) + if arr is None: + if raise_if_none: + raise Grid2OpException(f"class attribute {el} is None, but should not be.") + continue + try: + arr2 = np.array(arr).astype(tp) + except ValueError as exc_: + raise Grid2OpException(f"Impossible to convert attribute name {el} to {tp_nm}.") from exc_ + if (arr != arr2).any(): + mask = arr != arr2 + raise Grid2OpException(f"Impossible to safely convert attribute name {el} to {tp_nm}: {arr[mask]} vs {arr2[mask]}.") + setattr(cls, el, arr2) + + @classmethod + def _check_convert_to_np_array(cls, raise_if_none=True): + # convert int to array of ints + attrs_int = ["load_pos_topo_vect", + "load_to_subid", + "load_to_sub_pos", + "gen_pos_topo_vect", + "gen_to_subid", + "gen_to_sub_pos", + "storage_pos_topo_vect", + "storage_to_subid", + "storage_to_sub_pos", + "line_or_pos_topo_vect", + "line_or_to_subid", + "line_or_to_sub_pos", + "line_ex_pos_topo_vect", + "line_ex_to_subid", + "line_ex_to_sub_pos", + ] + if cls.redispatching_unit_commitment_availble: + attrs_int.append("gen_min_uptime") + attrs_int.append("gen_min_downtime") + cls._assign_attr(attrs_int, dt_int, "int", raise_if_none) + + # convert str to array of str + attrs_str = ["name_load", + "name_gen", + "name_line", + "name_sub", + "name_storage", + "storage_type", + ] + if cls.redispatching_unit_commitment_availble: + attrs_str.append("gen_type") + cls._assign_attr(attrs_str, str, "str", raise_if_none) + + # convert float to array of float + attrs_float = ["storage_Emax", + "storage_Emin", + "storage_max_p_prod", + "storage_max_p_absorb", + "storage_marginal_cost", + "storage_loss", + "storage_charging_efficiency", + "storage_discharging_efficiency", + ] + if cls.redispatching_unit_commitment_availble: + attrs_float += ["gen_pmin", + "gen_pmax", + "gen_max_ramp_up", + "gen_max_ramp_down", + "gen_cost_per_MW", + "gen_startup_cost", + "gen_shutdown_cost"] + cls._assign_attr(attrs_float, dt_float, "float", raise_if_none) + @classmethod def assert_grid_correct_cls(cls): """ @@ -1905,13 +2066,12 @@ def assert_grid_correct_cls(cls): f"self.sub_info should be convertible to a numpy array. " f'It fails with error "{exc_}"' ) - + # check everything can be converted to numpy array of right types + cls._check_convert_to_np_array() + # to which subtation they are connected cls._check_sub_id() - # for names - cls._check_names() - # compute the position in substation if not done already cls._compute_sub_pos() @@ -1978,6 +2138,10 @@ def assert_grid_correct_cls(cls): ) raise IncorrectNumberOfElements(err_msg) + + # for names + cls._check_names() + if len(cls.name_load) != cls.n_load: raise IncorrectNumberOfLoads("len(self.name_load) != self.n_load") if len(cls.name_gen) != cls.n_gen: @@ -2036,7 +2200,7 @@ def assert_grid_correct_cls(cls): if not np.all(obj_per_sub == cls.sub_info): raise IncorrectNumberOfElements( - f"for substation(s): {np.nonzero(obj_per_sub != cls.sub_info)[0]}" + f"for substation(s): {(obj_per_sub != cls.sub_info).nonzero()[0]}" ) # test right number of element in substations @@ -2337,57 +2501,57 @@ def _check_validity_storage_data(cls): ) if (cls.storage_Emax < cls.storage_Emin).any(): - tmp = np.nonzero(cls.storage_Emax < cls.storage_Emin)[0] + tmp = (cls.storage_Emax < cls.storage_Emin).nonzero()[0] raise BackendError( f"storage_Emax < storage_Emin for storage units with ids: {tmp}" ) if (cls.storage_Emax < 0.0).any(): - tmp = np.nonzero(cls.storage_Emax < 0.0)[0] + tmp = (cls.storage_Emax < 0.0).nonzero()[0] raise BackendError( f"self.storage_Emax < 0. for storage units with ids: {tmp}" ) if (cls.storage_Emin < 0.0).any(): - tmp = np.nonzero(cls.storage_Emin < 0.0)[0] + tmp = (cls.storage_Emin < 0.0).nonzero()[0] raise BackendError( f"self.storage_Emin < 0. for storage units with ids: {tmp}" ) if (cls.storage_max_p_prod < 0.0).any(): - tmp = np.nonzero(cls.storage_max_p_prod < 0.0)[0] + tmp = (cls.storage_max_p_prod < 0.0).nonzero()[0] raise BackendError( f"self.storage_max_p_prod < 0. for storage units with ids: {tmp}" ) if (cls.storage_max_p_absorb < 0.0).any(): - tmp = np.nonzero(cls.storage_max_p_absorb < 0.0)[0] + tmp = (cls.storage_max_p_absorb < 0.0).nonzero()[0] raise BackendError( f"self.storage_max_p_absorb < 0. for storage units with ids: {tmp}" ) if (cls.storage_loss < 0.0).any(): - tmp = np.nonzero(cls.storage_loss < 0.0)[0] + tmp = (cls.storage_loss < 0.0).nonzero()[0] raise BackendError( f"self.storage_loss < 0. for storage units with ids: {tmp}" ) if (cls.storage_discharging_efficiency <= 0.0).any(): - tmp = np.nonzero(cls.storage_discharging_efficiency <= 0.0)[0] + tmp = (cls.storage_discharging_efficiency <= 0.0).nonzero()[0] raise BackendError( f"self.storage_discharging_efficiency <= 0. for storage units with ids: {tmp}" ) if (cls.storage_discharging_efficiency > 1.0).any(): - tmp = np.nonzero(cls.storage_discharging_efficiency > 1.0)[0] + tmp = (cls.storage_discharging_efficiency > 1.0).nonzero()[0] raise BackendError( f"self.storage_discharging_efficiency > 1. for storage units with ids: {tmp}" ) if (cls.storage_charging_efficiency < 0.0).any(): - tmp = np.nonzero(cls.storage_charging_efficiency < 0.0)[0] + tmp = (cls.storage_charging_efficiency < 0.0).nonzero()[0] raise BackendError( f"self.storage_charging_efficiency < 0. for storage units with ids: {tmp}" ) if (cls.storage_charging_efficiency > 1.0).any(): - tmp = np.nonzero(cls.storage_charging_efficiency > 1.0)[0] + tmp = (cls.storage_charging_efficiency > 1.0).nonzero()[0] raise BackendError( f"self.storage_charging_efficiency > 1. for storage units with ids: {tmp}" ) if (cls.storage_loss > cls.storage_max_p_absorb).any(): - tmp = np.nonzero(cls.storage_loss > cls.storage_max_p_absorb)[0] + tmp = (cls.storage_loss > cls.storage_max_p_absorb).nonzero()[0] raise BackendError( f"Some storage units are such that their loss (self.storage_loss) is higher " f"than the maximum power at which they can be charged (self.storage_max_p_absorb). " @@ -2723,11 +2887,11 @@ def init_grid(cls, gridobj, force=False, extra_name=None, force_module=None): name_res = "{}_{}".format(cls.__name__, gridobj.env_name) if gridobj.glop_version != grid2op.__version__: name_res += f"_{gridobj.glop_version}" - - if gridobj._PATH_ENV is not None: + + if gridobj._PATH_GRID_CLASSES is not None: # the configuration equires to initialize the classes from the local environment path # this might be usefull when using pickle module or multiprocessing on Windows for example - my_class = GridObjects._build_cls_from_import(name_res, gridobj._PATH_ENV) + my_class = GridObjects._build_cls_from_import(name_res, gridobj._PATH_GRID_CLASSES) if my_class is not None: return my_class @@ -2764,12 +2928,24 @@ def init_grid(cls, gridobj, force=False, extra_name=None, force_module=None): res_cls._compute_pos_big_topo_cls() res_cls.process_shunt_satic_data() - res_cls.process_grid2op_compat() - + compat_mode = res_cls.process_grid2op_compat() + res_cls._check_convert_to_np_array() # convert everything to numpy array if force_module is not None: res_cls.__module__ = force_module # hack because otherwise it says "abc" which is not the case # best would be to have a look at https://docs.python.org/3/library/types.html - + + if not compat_mode: + # I can reuse the "cls" dictionnary as they did not changed + if cls._CLS_DICT is not None: + res_cls._CLS_DICT = cls._CLS_DICT + if cls._CLS_DICT_EXTENDED is not None: + res_cls._CLS_DICT_EXTENDED = cls._CLS_DICT_EXTENDED + else: + # I need to rewrite the _CLS_DICT and _CLS_DICT_EXTENDED + # as the class has been modified with a "compatibility version" mode + tmp = {} + res_cls._make_cls_dict_extended(res_cls, tmp, as_list=False) + # store the type created here in the "globals" to prevent the initialization of the same class over and over globals()[name_res] = res_cls del res_cls @@ -2797,24 +2973,98 @@ def process_grid2op_compat(cls): This function can be overloaded, but in this case it's best to call this original method too. """ + res = False glop_ver = cls._get_grid2op_version_as_version_obj() + if cls.glop_version == cls.BEFORE_COMPAT_VERSION: + # oldest version: no storage and no curtailment available + cls._aux_process_old_compat() + res = True + if glop_ver < version.parse("1.6.0"): # this feature did not exist before. cls.dim_alarms = 0 cls.assistant_warning_type = None + res = True if glop_ver < version.parse("1.9.1"): # this feature did not exists before cls.dim_alerts = 0 cls.alertable_line_names = [] cls.alertable_line_ids = [] + res = True if glop_ver < version.parse("1.10.0.dev0"): # this feature did not exists before # I need to set it to the default if set elsewhere cls.n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB + res = True + + if res: + cls._reset_cls_dict() # forget the previous class (stored as dict) + return res + @classmethod + def _aux_fix_topo_vect_removed_storage(cls): + if cls.n_storage == 0: + return + + stor_locs = [pos for pos in cls.storage_pos_topo_vect] + for stor_loc in sorted(stor_locs, reverse=True): + for vect in [ + cls.load_pos_topo_vect, + cls.gen_pos_topo_vect, + cls.line_or_pos_topo_vect, + cls.line_ex_pos_topo_vect, + ]: + vect[vect >= stor_loc] -= 1 + + # deals with the "sub_pos" vector + for sub_id in range(cls.n_sub): + if (cls.storage_to_subid == sub_id).any(): + stor_ids = (cls.storage_to_subid == sub_id).nonzero()[0] + stor_locs = cls.storage_to_sub_pos[stor_ids] + for stor_loc in sorted(stor_locs, reverse=True): + for vect, sub_id_me in zip( + [ + cls.load_to_sub_pos, + cls.gen_to_sub_pos, + cls.line_or_to_sub_pos, + cls.line_ex_to_sub_pos, + ], + [ + cls.load_to_subid, + cls.gen_to_subid, + cls.line_or_to_subid, + cls.line_ex_to_subid, + ], + ): + vect[(vect >= stor_loc) & (sub_id_me == sub_id)] -= 1 + + # remove storage from the number of element in the substation + for sub_id in range(cls.n_sub): + cls.sub_info[sub_id] -= (cls.storage_to_subid == sub_id).sum() + # remove storage from the total number of element + cls.dim_topo -= cls.n_storage + + # recompute this private member + cls._topo_vect_to_sub = np.repeat( + np.arange(cls.n_sub), repeats=cls.sub_info + ) + + new_grid_objects_types = cls.grid_objects_types + new_grid_objects_types = new_grid_objects_types[ + new_grid_objects_types[:, cls.STORAGE_COL] == -1, : + ] + cls.grid_objects_types = 1 * new_grid_objects_types + + @classmethod + def _aux_process_old_compat(cls): + # remove "storage dependant attributes (topo_vect etc.) that are modified !" + cls._aux_fix_topo_vect_removed_storage() + # deactivate storage + cls.set_no_storage() + @classmethod def get_obj_connect_to(cls, _sentinel=None, substation_id=None): """ @@ -2884,11 +3134,11 @@ def get_obj_connect_to(cls, _sentinel=None, substation_id=None): "".format(substation_id) ) res = { - "loads_id": np.nonzero(cls.load_to_subid == substation_id)[0], - "generators_id": np.nonzero(cls.gen_to_subid == substation_id)[0], - "lines_or_id": np.nonzero(cls.line_or_to_subid == substation_id)[0], - "lines_ex_id": np.nonzero(cls.line_ex_to_subid == substation_id)[0], - "storages_id": np.nonzero(cls.storage_to_subid == substation_id)[0], + "loads_id": (cls.load_to_subid == substation_id).nonzero()[0], + "generators_id": (cls.gen_to_subid == substation_id).nonzero()[0], + "lines_or_id": (cls.line_or_to_subid == substation_id).nonzero()[0], + "lines_ex_id": (cls.line_ex_to_subid == substation_id).nonzero()[0], + "storages_id": (cls.storage_to_subid == substation_id).nonzero()[0], "nb_elements": cls.sub_info[substation_id], } return res @@ -3373,11 +3623,42 @@ def topo_vect_element(cls, topo_vect_id: int) -> Dict[Literal["load_id", "gen_id raise Grid2OpException(f"Unknown element at position {topo_vect_id}") @staticmethod - def _make_cls_dict(cls, res, as_list=True, copy_=True): - """NB: `cls` can be here a class or an object of a class...""" - save_to_dict(res, cls, "glop_version", str, copy_) - res["_PATH_ENV"] = cls._PATH_ENV # i do that manually for more control - save_to_dict(res, cls, "n_busbar_per_sub", str, copy_) + def _make_cls_dict(cls, res, as_list=True, copy_=True, _topo_vect_only=False): + """ + INTERNAL + + .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ + + NB: `cls` can be here a class or an object of a class... + + Notes + ------- + _topo_vect_only: this function is called once when the backend is initialized in `backend.load_grid` + (in `backend._compute_pos_big_topo`) and then once when everything is set up + (after redispatching and storage data are loaded). + + This is why I need the `_topo_vect_only` flag that tells this function when it's called only for + `topo_vect` related attributed + + """ + if cls._CLS_DICT is not None and not as_list and not _topo_vect_only: + # speed optimization: it has already been computed, so + # I reuse it (class attr are const) + for k, v in cls._CLS_DICT.items(): + if copy_: + res[k] = copy.deepcopy(v) + else: + res[k] = v + return + + if not _topo_vect_only: + # all the attributes bellow are not needed for the "first call" + # to this function when the elements are put together in the topo_vect. + # Indeed, at this stage (first call in the backend.load_grid) these + # attributes are not (necessary) loaded yet + save_to_dict(res, cls, "glop_version", str, copy_) + res["_PATH_GRID_CLASSES"] = cls._PATH_GRID_CLASSES # i do that manually for more control + save_to_dict(res, cls, "n_busbar_per_sub", str, copy_) save_to_dict( res, @@ -3532,183 +3813,225 @@ def _make_cls_dict(cls, res, as_list=True, copy_=True): copy_, ) - # redispatching - if cls.redispatching_unit_commitment_availble: - for nm_attr, type_attr in zip(cls._li_attr_disp, cls._type_attr_disp): + # shunts (not in topo vect but still usefull) + if cls.shunts_data_available: + save_to_dict( + res, + cls, + "name_shunt", + (lambda li: [str(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "shunt_to_subid", + (lambda li: [int(el) for el in li]) if as_list else None, + copy_, + ) + else: + res["name_shunt"] = None + res["shunt_to_subid"] = None + + if not _topo_vect_only: + # all the attributes bellow are not needed for the "first call" + # to this function when the elements are put together in the topo_vect. + # Indeed, at this stage (first call in the backend.load_grid) these + # attributes are not loaded yet + + # redispatching + if cls.redispatching_unit_commitment_availble: + for nm_attr, type_attr in zip(cls._li_attr_disp, cls._type_attr_disp): + save_to_dict( + res, + cls, + nm_attr, + (lambda li: [type_attr(el) for el in li]) if as_list else None, + copy_, + ) + else: + for nm_attr in cls._li_attr_disp: + res[nm_attr] = None + + # layout (position of substation on a map of the grid) + if cls.grid_layout is not None: save_to_dict( res, cls, - nm_attr, - (lambda li: [type_attr(el) for el in li]) if as_list else None, + "grid_layout", + (lambda gl: {str(k): [float(x), float(y)] for k, (x, y) in gl.items()}) + if as_list + else None, copy_, ) - else: - for nm_attr in cls._li_attr_disp: - res[nm_attr] = None + else: + res["grid_layout"] = None - # shunts - if cls.grid_layout is not None: + # storage data save_to_dict( res, cls, - "grid_layout", - (lambda gl: {str(k): [float(x), float(y)] for k, (x, y) in gl.items()}) - if as_list - else None, + "storage_type", + (lambda li: [str(el) for el in li]) if as_list else None, copy_, ) - else: - res["grid_layout"] = None - - # shunts - if cls.shunts_data_available: save_to_dict( res, cls, - "name_shunt", - (lambda li: [str(el) for el in li]) if as_list else None, + "storage_Emax", + (lambda li: [float(el) for el in li]) if as_list else None, copy_, ) save_to_dict( res, cls, - "shunt_to_subid", - (lambda li: [int(el) for el in li]) if as_list else None, + "storage_Emin", + (lambda li: [float(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "storage_max_p_prod", + (lambda li: [float(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "storage_max_p_absorb", + (lambda li: [float(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "storage_marginal_cost", + (lambda li: [float(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "storage_loss", + (lambda li: [float(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "storage_charging_efficiency", + (lambda li: [float(el) for el in li]) if as_list else None, + copy_, + ) + save_to_dict( + res, + cls, + "storage_discharging_efficiency", + (lambda li: [float(el) for el in li]) if as_list else None, copy_, ) - else: - res["name_shunt"] = None - res["shunt_to_subid"] = None - - # storage data - save_to_dict( - res, - cls, - "storage_type", - (lambda li: [str(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_Emax", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_Emin", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_max_p_prod", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_max_p_absorb", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_marginal_cost", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_loss", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_charging_efficiency", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - save_to_dict( - res, - cls, - "storage_discharging_efficiency", - (lambda li: [float(el) for el in li]) if as_list else None, - copy_, - ) - # alert or alarm - if cls.assistant_warning_type is not None: - res["assistant_warning_type"] = str(cls.assistant_warning_type) - else: - res["assistant_warning_type"] = None + # alert or alarm + if cls.assistant_warning_type is not None: + res["assistant_warning_type"] = str(cls.assistant_warning_type) + else: + res["assistant_warning_type"] = None + + # area for the alarm feature + res["dim_alarms"] = cls.dim_alarms - # area for the alarm feature - res["dim_alarms"] = cls.dim_alarms - - save_to_dict( - res, cls, "alarms_area_names", (lambda li: [str(el) for el in li]), copy_ - ) - save_to_dict( - res, - cls, - "alarms_lines_area", - ( - lambda dict_: { - str(l_nm): [str(ar_nm) for ar_nm in areas] - for l_nm, areas in dict_.items() - } - ), - copy_, - ) - save_to_dict( - res, - cls, - "alarms_area_lines", - (lambda lili: [[str(l_nm) for l_nm in lines] for lines in lili]), - copy_, - ) - - # number of line alert for the alert feature - res['dim_alerts'] = cls.dim_alerts - # save alert line names to dict - save_to_dict( - res, cls, "alertable_line_names", (lambda li: [str(el) for el in li]) if as_list else None, copy_ - ) - save_to_dict( - res, cls, "alertable_line_ids", (lambda li: [int(el) for el in li]) if as_list else None, copy_ - ) + save_to_dict( + res, cls, "alarms_area_names", (lambda li: [str(el) for el in li]), copy_ + ) + save_to_dict( + res, + cls, + "alarms_lines_area", + ( + lambda dict_: { + str(l_nm): [str(ar_nm) for ar_nm in areas] + for l_nm, areas in dict_.items() + } + ), + copy_, + ) + save_to_dict( + res, + cls, + "alarms_area_lines", + (lambda lili: [[str(l_nm) for l_nm in lines] for lines in lili]), + copy_, + ) + + # number of line alert for the alert feature + res['dim_alerts'] = cls.dim_alerts + # save alert line names to dict + save_to_dict( + res, cls, "alertable_line_names", (lambda li: [str(el) for el in li]) if as_list else None, copy_ + ) + save_to_dict( + res, cls, "alertable_line_ids", (lambda li: [int(el) for el in li]) if as_list else None, copy_ + ) + # avoid further computation and save it + if not as_list: + cls._CLS_DICT = res.copy() return res @staticmethod - def _make_cls_dict_extended(cls, res, as_list=True, copy_=True): - """add the n_gen and all in the class created""" - GridObjects._make_cls_dict(cls, res, as_list=as_list, copy_=copy_) + def _make_cls_dict_extended(cls, res: CLS_AS_DICT_TYPING, as_list=True, copy_=True, _topo_vect_only=False): + """add the n_gen and all in the class created + + Notes + ------- + _topo_vect_only: this function is called once when the backend is initialized in `backend.load_grid` + (in `backend._compute_pos_big_topo`) and then once when everything is set up + (after redispatching and storage data are loaded). + + This is why I need the `_topo_vect_only` flag that tells this function when it's called only for + `topo_vect` related attributed + + """ + if cls._CLS_DICT_EXTENDED is not None and not as_list and not _topo_vect_only: + # speed optimization: it has already been computed, so + # I reuse it (class attr are const) + for k, v in cls._CLS_DICT_EXTENDED.items(): + if copy_: + res[k] = copy.deepcopy(v) + else: + res[k] = v + return + + GridObjects._make_cls_dict(cls, res, as_list=as_list, copy_=copy_, _topo_vect_only=_topo_vect_only) res["n_gen"] = cls.n_gen res["n_load"] = cls.n_load res["n_line"] = cls.n_line res["n_sub"] = cls.n_sub res["dim_topo"] = 1 * cls.dim_topo - # shunt - res["n_shunt"] = cls.n_shunt - res["shunts_data_available"] = cls.shunts_data_available # storage res["n_storage"] = cls.n_storage - # redispatching / curtailment - res[ - "redispatching_unit_commitment_availble" - ] = cls.redispatching_unit_commitment_availble - # n_busbar_per_sub - res["n_busbar_per_sub"] = cls.n_busbar_per_sub + # shunt (not in topo vect but might be usefull) + res["shunts_data_available"] = cls.shunts_data_available + res["n_shunt"] = cls.n_shunt + + if not _topo_vect_only: + # all the attributes bellow are not needed for the "first call" + # to this function when the elements are put together in the topo_vect. + # Indeed, at this stage (first call in the backend.load_grid) these + # attributes are not loaded yet + + # redispatching / curtailment + res[ + "redispatching_unit_commitment_availble" + ] = cls.redispatching_unit_commitment_availble + + # n_busbar_per_sub + res["n_busbar_per_sub"] = cls.n_busbar_per_sub + + # avoid further computation and save it + if not as_list and not _topo_vect_only: + cls._CLS_DICT_EXTENDED = res.copy() @classmethod def cls_to_dict(cls): @@ -3728,7 +4051,7 @@ def cls_to_dict(cls): The representation of the object as a dictionary that can be json serializable. """ res = {} - GridObjects._make_cls_dict(cls, res) + cls._make_cls_dict(cls, res) return res @staticmethod @@ -3765,10 +4088,13 @@ class res(GridObjects): else: cls.glop_version = cls.BEFORE_COMPAT_VERSION - if "_PATH_ENV" in dict_: - cls._PATH_ENV = str(dict_["_PATH_ENV"]) + if "_PATH_GRID_CLASSES" in dict_: + cls._PATH_GRID_CLASSES = str(dict_["_PATH_GRID_CLASSES"]) + elif "_PATH_ENV" in dict_: + # legacy mode in grid2op <= 1.10.1 this was saved in "PATH_ENV" + cls._PATH_GRID_CLASSES = str(dict_["_PATH_ENV"]) else: - cls._PATH_ENV = None + cls._PATH_GRID_CLASSES = None if 'n_busbar_per_sub' in dict_: cls.n_busbar_per_sub = int(dict_["n_busbar_per_sub"]) @@ -3907,32 +4233,33 @@ class res(GridObjects): dict_, "storage_pos_topo_vect", lambda x: np.array(x).astype(dt_int) ) cls.n_storage = len(cls.name_storage) + # storage static data - extract_from_dict(dict_, "storage_type", lambda x: np.array(x).astype(str)) - extract_from_dict( + cls.storage_type = extract_from_dict(dict_, "storage_type", lambda x: np.array(x).astype(str)) + cls.storage_Emax = extract_from_dict( dict_, "storage_Emax", lambda x: np.array(x).astype(dt_float) ) - extract_from_dict( + cls.storage_Emin = extract_from_dict( dict_, "storage_Emin", lambda x: np.array(x).astype(dt_float) ) - extract_from_dict( + cls.storage_max_p_prod = extract_from_dict( dict_, "storage_max_p_prod", lambda x: np.array(x).astype(dt_float) ) - extract_from_dict( + cls.storage_max_p_absorb = extract_from_dict( dict_, "storage_max_p_absorb", lambda x: np.array(x).astype(dt_float) ) - extract_from_dict( + cls.storage_marginal_cost = extract_from_dict( dict_, "storage_marginal_cost", lambda x: np.array(x).astype(dt_float) ) - extract_from_dict( + cls.storage_loss = extract_from_dict( dict_, "storage_loss", lambda x: np.array(x).astype(dt_float) ) - extract_from_dict( + cls.storage_charging_efficiency = extract_from_dict( dict_, "storage_charging_efficiency", lambda x: np.array(x).astype(dt_float), ) - extract_from_dict( + cls.storage_discharging_efficiency = extract_from_dict( dict_, "storage_discharging_efficiency", lambda x: np.array(x).astype(dt_float), @@ -3977,11 +4304,15 @@ class res(GridObjects): else: cls.alertable_line_names = [] cls.alertable_line_ids = [] - + + # save the representation of this class as dict + tmp = {} + cls._make_cls_dict_extended(cls, tmp, as_list=False, copy_=True) + # retrieve the redundant information that are not stored (for efficiency) obj_ = cls() obj_._compute_pos_big_topo_cls() - cls = cls.init_grid(obj_, force=True) + cls = cls.init_grid(obj_) # , force=True return cls() @classmethod @@ -4041,9 +4372,7 @@ def same_grid_class(cls, other_cls) -> bool: me_dict = {} GridObjects._make_cls_dict_extended(cls, me_dict, as_list=False, copy_=False) other_cls_dict = {} - GridObjects._make_cls_dict_extended( - other_cls, other_cls_dict, as_list=False, copy_=False - ) + GridObjects._make_cls_dict_extended(other_cls, other_cls_dict, as_list=False, copy_=False) if me_dict.keys() - other_cls_dict.keys(): # one key is in me but not in other @@ -4098,9 +4427,9 @@ def init_grid_from_dict_for_pickle(name_res, orig_cls, cls_attr): object in the __reduce__ method. """ res_cls = None - if "_PATH_ENV" in cls_attr and cls_attr["_PATH_ENV"] is not None: + if "_PATH_GRID_CLASSES" in cls_attr and cls_attr["_PATH_GRID_CLASSES"] is not None: res_cls = GridObjects._build_cls_from_import( - name_res, cls_attr["_PATH_ENV"] + name_res, cls_attr["_PATH_GRID_CLASSES"] ) # check if the class already exists, if so returns it @@ -4132,11 +4461,13 @@ def __reduce__(self): """ It here to avoid issue with pickle. But the problem is that it's also used by deepcopy... So its implementation is used a lot + + see https://docs.python.org/3/library/pickle.html#object.__reduce__ """ # TODO this is not really a convenient use of that i'm sure ! # Try to see if it can be better cls_attr_as_dict = {} - GridObjects._make_cls_dict_extended(type(self), cls_attr_as_dict, as_list=False) + GridObjects._make_cls_dict_extended(type(self), cls_attr_as_dict, as_list=False) # TODO save that in the class definition if hasattr(self, "__getstate__"): my_state = self.__getstate__() else: @@ -4310,7 +4641,7 @@ def _format_bool_vect_to_cls_str(bool_vect): @classmethod def _get_full_cls_str(cls): - _PATH_ENV_str = "None" if cls._PATH_ENV is None else f'"{cls._PATH_ENV}"' + _PATH_ENV_str = "None" if cls._PATH_GRID_CLASSES is None else f'"{cls._PATH_GRID_CLASSES}"' attr_list_vect_str = None attr_list_set_str = "{}" if cls.attr_list_vect is not None: @@ -4444,9 +4775,12 @@ def format_el_int(values): def format_el(values): return ",".join([f'"{el}"' for el in values]) - tmp_tmp_ = [f'"{k}": [{format_el(v)}]' for k, v in cls.grid_layout.items()] - tmp_ = ",".join(tmp_tmp_) - grid_layout_str = f"{{{tmp_}}}" + if cls.grid_layout is not None: + tmp_tmp_ = [f'"{k}": [{format_el(v)}]' for k, v in cls.grid_layout.items()] + tmp_ = ",".join(tmp_tmp_) + grid_layout_str = f"{{{tmp_}}}" + else: + grid_layout_str = "None" name_shunt_str = ",".join([f'"{el}"' for el in cls.name_shunt]) shunt_to_subid_str = GridObjects._format_int_vect_to_cls_str(cls.shunt_to_subid) @@ -4496,8 +4830,10 @@ def format_el(values): class {cls.__name__}({cls._INIT_GRID_CLS.__name__}): BEFORE_COMPAT_VERSION = \"{cls.BEFORE_COMPAT_VERSION}\" glop_version = grid2op.__version__ # tells it's the installed grid2op version - _PATH_ENV = {_PATH_ENV_str} - _INIT_GRID_CLS = {cls._INIT_GRID_CLS.__name__} + _PATH_GRID_CLASSES = {_PATH_ENV_str} # especially do not modify that + _INIT_GRID_CLS = {cls._INIT_GRID_CLS.__name__} + _CLS_DICT = None # init once to avoid yet another serialization of the class as dict (in make_cls_dict) + _CLS_DICT_EXTENDED = None # init once to avoid yet another serialization of the class as dict (in make_cls_dict) SUB_COL = 0 LOA_COL = 1 diff --git a/grid2op/Space/SerializableSpace.py b/grid2op/Space/SerializableSpace.py index 7aa514a69..a19a57b5a 100644 --- a/grid2op/Space/SerializableSpace.py +++ b/grid2op/Space/SerializableSpace.py @@ -175,7 +175,7 @@ def from_dict(dict_): path = dict_ if not os.path.exists(path): raise Grid2OpException( - 'Unable to find the file "{}" to load the ObservationSpace'.format( + 'Unable to find the file "{}" to load the grid2op classes'.format( path ) ) diff --git a/grid2op/__init__.py b/grid2op/__init__.py index 285091890..aca26c5a5 100644 --- a/grid2op/__init__.py +++ b/grid2op/__init__.py @@ -11,7 +11,7 @@ Grid2Op """ -__version__ = '1.10.1' +__version__ = '1.10.2.dev3' __all__ = [ "Action", @@ -45,15 +45,14 @@ ] - -from grid2op.MakeEnv import (make, - update_env, - list_available_remote_env, - list_available_local_env, - get_current_local_dir, - change_local_dir, - list_available_test_env - ) +from grid2op.MakeEnv import (make, + update_env, + list_available_remote_env, + list_available_local_env, + get_current_local_dir, + change_local_dir, + list_available_test_env + ) try: from grid2op._create_test_suite import create_test_suite diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/5bus_example.json b/grid2op/data_test/5bus_example_act_topo_set_init/5bus_example.json new file mode 100644 index 000000000..2e9dd0c79 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/5bus_example.json @@ -0,0 +1,1339 @@ +{ + "_module": "pandapower.auxiliary", + "_class": "pandapowerNet", + "_object": { + "bus": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"vn_kv\",\"type\",\"zone\",\"in_service\"],\"index\":[0,1,2,3,4],\"data\":[[\"substation_1\",100.0,\"b\",null,true],[\"substation_2\",100.0,\"b\",null,true],[\"substation_3\",100.0,\"b\",null,true],[\"substation_4\",100.0,\"b\",null,true],[\"substation_5\",100.0,\"b\",null,true]]}", + "dtype": { + "name": "object", + "vn_kv": "float64", + "type": "object", + "zone": "object", + "in_service": "bool" + }, + "orient": "split" + }, + "load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"const_z_percent\",\"const_i_percent\",\"sn_mva\",\"scaling\",\"in_service\",\"type\"],\"index\":[0,1,2],\"data\":[[\"load_0_0\",0,10.0,7.0,0.0,0.0,null,1.0,true,null],[\"load_3_1\",3,10.0,7.0,0.0,0.0,null,1.0,true,null],[\"load_4_2\",4,10.0,7.0,0.0,0.0,null,1.0,true,null]]}", + "dtype": { + "name": "object", + "bus": "uint32", + "p_mw": "float64", + "q_mvar": "float64", + "const_z_percent": "float64", + "const_i_percent": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + }, + "orient": "split" + }, + "sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\",\"current_source\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "bus": "int64", + "p_mw": "float64", + "q_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object", + "current_source": "bool" + }, + "orient": "split" + }, + "storage": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"sn_mva\",\"soc_percent\",\"min_e_mwh\",\"max_e_mwh\",\"scaling\",\"in_service\",\"type\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "bus": "int64", + "p_mw": "float64", + "q_mvar": "float64", + "sn_mva": "float64", + "soc_percent": "float64", + "min_e_mwh": "float64", + "max_e_mwh": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + }, + "orient": "split" + }, + "gen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"vm_pu\",\"sn_mva\",\"min_q_mvar\",\"max_q_mvar\",\"scaling\",\"slack\",\"in_service\",\"type\"],\"index\":[0,1],\"data\":[[\"gen_0_0\",0,10.0,1.02,null,null,null,1.0,false,true,null],[\"gen_1_1\",1,20.0,1.02,null,null,null,1.0,true,true,null]]}", + "dtype": { + "name": "object", + "bus": "uint32", + "p_mw": "float64", + "vm_pu": "float64", + "sn_mva": "float64", + "min_q_mvar": "float64", + "max_q_mvar": "float64", + "scaling": "float64", + "slack": "bool", + "in_service": "bool", + "type": "object" + }, + "orient": "split" + }, + "switch": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"bus\",\"element\",\"et\",\"type\",\"closed\",\"name\",\"z_ohm\"],\"index\":[],\"data\":[]}", + "dtype": { + "bus": "int64", + "element": "int64", + "et": "object", + "type": "object", + "closed": "bool", + "name": "object", + "z_ohm": "float64" + }, + "orient": "split" + }, + "shunt": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"bus\",\"name\",\"q_mvar\",\"p_mw\",\"vn_kv\",\"step\",\"max_step\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "bus": "uint32", + "name": "object", + "q_mvar": "float64", + "p_mw": "float64", + "vn_kv": "float64", + "step": "uint32", + "max_step": "uint32", + "in_service": "bool" + }, + "orient": "split" + }, + "ext_grid": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"vm_pu\",\"va_degree\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "bus": "uint32", + "vm_pu": "float64", + "va_degree": "float64", + "in_service": "bool" + }, + "orient": "split" + }, + "line": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"from_bus\",\"to_bus\",\"length_km\",\"r_ohm_per_km\",\"x_ohm_per_km\",\"c_nf_per_km\",\"g_us_per_km\",\"max_i_ka\",\"df\",\"parallel\",\"type\",\"in_service\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[null,\"NAYY 4x50 SE\",0,1,4.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"0_2_2\",\"NAYY 4x50 SE\",0,2,4.47,0.642,0.083,210.0,0.0,0.22,1.0,1,\"cs\",true],[\"0_3_3\",\"NAYY 4x50 SE\",0,3,5.65,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"0_4_4\",\"NAYY 4x50 SE\",0,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"1_2_5\",\"NAYY 4x50 SE\",1,2,2.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"2_3_6\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"2_3_7\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"3_4_8\",\"NAYY 4x50 SE\",3,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true]]}", + "dtype": { + "name": "object", + "std_type": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "length_km": "float64", + "r_ohm_per_km": "float64", + "x_ohm_per_km": "float64", + "c_nf_per_km": "float64", + "g_us_per_km": "float64", + "max_i_ka": "float64", + "df": "float64", + "parallel": "uint32", + "type": "object", + "in_service": "bool" + }, + "orient": "split" + }, + "trafo": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"hv_bus\",\"lv_bus\",\"sn_mva\",\"vn_hv_kv\",\"vn_lv_kv\",\"vk_percent\",\"vkr_percent\",\"pfe_kw\",\"i0_percent\",\"shift_degree\",\"tap_side\",\"tap_neutral\",\"tap_min\",\"tap_max\",\"tap_step_percent\",\"tap_step_degree\",\"tap_pos\",\"tap_phase_shifter\",\"parallel\",\"df\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "std_type": "object", + "hv_bus": "uint32", + "lv_bus": "uint32", + "sn_mva": "float64", + "vn_hv_kv": "float64", + "vn_lv_kv": "float64", + "vk_percent": "float64", + "vkr_percent": "float64", + "pfe_kw": "float64", + "i0_percent": "float64", + "shift_degree": "float64", + "tap_side": "object", + "tap_neutral": "int32", + "tap_min": "int32", + "tap_max": "int32", + "tap_step_percent": "float64", + "tap_step_degree": "float64", + "tap_pos": "int32", + "tap_phase_shifter": "bool", + "parallel": "uint32", + "df": "float64", + "in_service": "bool" + }, + "orient": "split" + }, + "trafo3w": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"hv_bus\",\"mv_bus\",\"lv_bus\",\"sn_hv_mva\",\"sn_mv_mva\",\"sn_lv_mva\",\"vn_hv_kv\",\"vn_mv_kv\",\"vn_lv_kv\",\"vk_hv_percent\",\"vk_mv_percent\",\"vk_lv_percent\",\"vkr_hv_percent\",\"vkr_mv_percent\",\"vkr_lv_percent\",\"pfe_kw\",\"i0_percent\",\"shift_mv_degree\",\"shift_lv_degree\",\"tap_side\",\"tap_neutral\",\"tap_min\",\"tap_max\",\"tap_step_percent\",\"tap_step_degree\",\"tap_pos\",\"tap_at_star_point\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "std_type": "object", + "hv_bus": "uint32", + "mv_bus": "uint32", + "lv_bus": "uint32", + "sn_hv_mva": "float64", + "sn_mv_mva": "float64", + "sn_lv_mva": "float64", + "vn_hv_kv": "float64", + "vn_mv_kv": "float64", + "vn_lv_kv": "float64", + "vk_hv_percent": "float64", + "vk_mv_percent": "float64", + "vk_lv_percent": "float64", + "vkr_hv_percent": "float64", + "vkr_mv_percent": "float64", + "vkr_lv_percent": "float64", + "pfe_kw": "float64", + "i0_percent": "float64", + "shift_mv_degree": "float64", + "shift_lv_degree": "float64", + "tap_side": "object", + "tap_neutral": "int32", + "tap_min": "int32", + "tap_max": "int32", + "tap_step_percent": "float64", + "tap_step_degree": "float64", + "tap_pos": "int32", + "tap_at_star_point": "bool", + "in_service": "bool" + }, + "orient": "split" + }, + "impedance": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"from_bus\",\"to_bus\",\"rft_pu\",\"xft_pu\",\"rtf_pu\",\"xtf_pu\",\"sn_mva\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "rft_pu": "float64", + "xft_pu": "float64", + "rtf_pu": "float64", + "xtf_pu": "float64", + "sn_mva": "float64", + "in_service": "bool" + }, + "orient": "split" + }, + "dcline": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"from_bus\",\"to_bus\",\"p_mw\",\"loss_percent\",\"loss_mw\",\"vm_from_pu\",\"vm_to_pu\",\"max_p_mw\",\"min_q_from_mvar\",\"min_q_to_mvar\",\"max_q_from_mvar\",\"max_q_to_mvar\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "p_mw": "float64", + "loss_percent": "float64", + "loss_mw": "float64", + "vm_from_pu": "float64", + "vm_to_pu": "float64", + "max_p_mw": "float64", + "min_q_from_mvar": "float64", + "min_q_to_mvar": "float64", + "max_q_from_mvar": "float64", + "max_q_to_mvar": "float64", + "in_service": "bool" + }, + "orient": "split" + }, + "ward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"ps_mw\",\"qs_mvar\",\"qz_mvar\",\"pz_mw\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "bus": "uint32", + "ps_mw": "float64", + "qs_mvar": "float64", + "qz_mvar": "float64", + "pz_mw": "float64", + "in_service": "bool" + }, + "orient": "split" + }, + "xward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"ps_mw\",\"qs_mvar\",\"qz_mvar\",\"pz_mw\",\"r_ohm\",\"x_ohm\",\"vm_pu\",\"in_service\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "bus": "uint32", + "ps_mw": "float64", + "qs_mvar": "float64", + "qz_mvar": "float64", + "pz_mw": "float64", + "r_ohm": "float64", + "x_ohm": "float64", + "vm_pu": "float64", + "in_service": "bool" + }, + "orient": "split" + }, + "measurement": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"measurement_type\",\"element_type\",\"element\",\"value\",\"std_dev\",\"side\"],\"index\":[],\"data\":[]}", + "dtype": { + "name": "object", + "measurement_type": "object", + "element_type": "object", + "element": "uint32", + "value": "float64", + "std_dev": "float64", + "side": "object" + }, + "orient": "split" + }, + "pwl_cost": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"power_type\",\"element\",\"et\",\"points\"],\"index\":[],\"data\":[]}", + "dtype": { + "power_type": "object", + "element": "object", + "et": "object", + "points": "object" + }, + "orient": "split" + }, + "poly_cost": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"element\",\"et\",\"cp0_eur\",\"cp1_eur_per_mw\",\"cp2_eur_per_mw2\",\"cq0_eur\",\"cq1_eur_per_mvar\",\"cq2_eur_per_mvar2\"],\"index\":[],\"data\":[]}", + "dtype": { + "element": "object", + "et": "object", + "cp0_eur": "float64", + "cp1_eur_per_mw": "float64", + "cp2_eur_per_mw2": "float64", + "cq0_eur": "float64", + "cq1_eur_per_mvar": "float64", + "cq2_eur_per_mvar2": "float64" + }, + "orient": "split" + }, + "line_geodata": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"coords\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[[[0,0],[0,4]]],[[[0,0],[2,4]]],[[[0,0],[4,4]]],[[[0,0],[4,0]]],[[[0,4],[2,4]]],[[[2,4],[3,4.2],[4,4]]],[[[2,4],[3,3.8],[4,4]]],[[[4,4],[4,0]]]]}", + "dtype": { + "coords": "object" + }, + "orient": "split" + }, + "bus_geodata": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"x\",\"y\",\"coords\"],\"index\":[0,1,2,3,4],\"data\":[[0.0,0.0,null],[0.0,4.0,null],[2.0,4.0,null],[4.0,4.0,null],[4.0,0.0,null]]}", + "dtype": { + "x": "float64", + "y": "float64", + "coords": "object" + }, + "orient": "split" + }, + "version": "2.1.0", + "converged": true, + "name": "5bus", + "f_hz": 50.0, + "sn_mva": 1, + "std_types": { + "line": { + "NAYY 4x50 SE": { + "c_nf_per_km": 210, + "r_ohm_per_km": 0.642, + "x_ohm_per_km": 0.083, + "max_i_ka": 0.142, + "type": "cs", + "q_mm2": 50, + "alpha": 0.00403 + }, + "NAYY 4x120 SE": { + "c_nf_per_km": 264, + "r_ohm_per_km": 0.225, + "x_ohm_per_km": 0.08, + "max_i_ka": 0.242, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NAYY 4x150 SE": { + "c_nf_per_km": 261, + "r_ohm_per_km": 0.208, + "x_ohm_per_km": 0.08, + "max_i_ka": 0.27, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x95 RM/25 12/20 kV": { + "c_nf_per_km": 216, + "r_ohm_per_km": 0.313, + "x_ohm_per_km": 0.132, + "max_i_ka": 0.252, + "type": "cs", + "q_mm2": 95, + "alpha": 0.00403 + }, + "NA2XS2Y 1x185 RM/25 12/20 kV": { + "c_nf_per_km": 273, + "r_ohm_per_km": 0.161, + "x_ohm_per_km": 0.117, + "max_i_ka": 0.362, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00403 + }, + "NA2XS2Y 1x240 RM/25 12/20 kV": { + "c_nf_per_km": 304, + "r_ohm_per_km": 0.122, + "x_ohm_per_km": 0.112, + "max_i_ka": 0.421, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00403 + }, + "NA2XS2Y 1x95 RM/25 6/10 kV": { + "c_nf_per_km": 315, + "r_ohm_per_km": 0.313, + "x_ohm_per_km": 0.123, + "max_i_ka": 0.249, + "type": "cs", + "q_mm2": 95, + "alpha": 0.00403 + }, + "NA2XS2Y 1x185 RM/25 6/10 kV": { + "c_nf_per_km": 406, + "r_ohm_per_km": 0.161, + "x_ohm_per_km": 0.11, + "max_i_ka": 0.358, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00403 + }, + "NA2XS2Y 1x240 RM/25 6/10 kV": { + "c_nf_per_km": 456, + "r_ohm_per_km": 0.122, + "x_ohm_per_km": 0.105, + "max_i_ka": 0.416, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00403 + }, + "NA2XS2Y 1x150 RM/25 12/20 kV": { + "c_nf_per_km": 250, + "r_ohm_per_km": 0.206, + "x_ohm_per_km": 0.116, + "max_i_ka": 0.319, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x120 RM/25 12/20 kV": { + "c_nf_per_km": 230, + "r_ohm_per_km": 0.253, + "x_ohm_per_km": 0.119, + "max_i_ka": 0.283, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NA2XS2Y 1x70 RM/25 12/20 kV": { + "c_nf_per_km": 190, + "r_ohm_per_km": 0.443, + "x_ohm_per_km": 0.132, + "max_i_ka": 0.22, + "type": "cs", + "q_mm2": 70, + "alpha": 0.00403 + }, + "NA2XS2Y 1x150 RM/25 6/10 kV": { + "c_nf_per_km": 360, + "r_ohm_per_km": 0.206, + "x_ohm_per_km": 0.11, + "max_i_ka": 0.315, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x120 RM/25 6/10 kV": { + "c_nf_per_km": 340, + "r_ohm_per_km": 0.253, + "x_ohm_per_km": 0.113, + "max_i_ka": 0.28, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NA2XS2Y 1x70 RM/25 6/10 kV": { + "c_nf_per_km": 280, + "r_ohm_per_km": 0.443, + "x_ohm_per_km": 0.123, + "max_i_ka": 0.217, + "type": "cs", + "q_mm2": 70, + "alpha": 0.00403 + }, + "N2XS(FL)2Y 1x120 RM/35 64/110 kV": { + "c_nf_per_km": 112, + "r_ohm_per_km": 0.153, + "x_ohm_per_km": 0.166, + "max_i_ka": 0.366, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x185 RM/35 64/110 kV": { + "c_nf_per_km": 125, + "r_ohm_per_km": 0.099, + "x_ohm_per_km": 0.156, + "max_i_ka": 0.457, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x240 RM/35 64/110 kV": { + "c_nf_per_km": 135, + "r_ohm_per_km": 0.075, + "x_ohm_per_km": 0.149, + "max_i_ka": 0.526, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x300 RM/35 64/110 kV": { + "c_nf_per_km": 144, + "r_ohm_per_km": 0.06, + "x_ohm_per_km": 0.144, + "max_i_ka": 0.588, + "type": "cs", + "q_mm2": 300, + "alpha": 0.00393 + }, + "15-AL1/3-ST1A 0.4": { + "c_nf_per_km": 11, + "r_ohm_per_km": 1.8769, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.105, + "type": "ol", + "q_mm2": 16, + "alpha": 0.00403 + }, + "24-AL1/4-ST1A 0.4": { + "c_nf_per_km": 11.25, + "r_ohm_per_km": 1.2012, + "x_ohm_per_km": 0.335, + "max_i_ka": 0.14, + "type": "ol", + "q_mm2": 24, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 0.4": { + "c_nf_per_km": 12.2, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.3, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 0.4": { + "c_nf_per_km": 13.2, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.29, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "34-AL1/6-ST1A 10.0": { + "c_nf_per_km": 9.7, + "r_ohm_per_km": 0.8342, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.17, + "type": "ol", + "q_mm2": 34, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 10.0": { + "c_nf_per_km": 10.1, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 10.0": { + "c_nf_per_km": 10.4, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.339, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 10.0": { + "c_nf_per_km": 10.75, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.33, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 10.0": { + "c_nf_per_km": 11.1, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.323, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 10.0": { + "c_nf_per_km": 11.25, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.315, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "34-AL1/6-ST1A 20.0": { + "c_nf_per_km": 9.15, + "r_ohm_per_km": 0.8342, + "x_ohm_per_km": 0.382, + "max_i_ka": 0.17, + "type": "ol", + "q_mm2": 34, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 20.0": { + "c_nf_per_km": 9.5, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.372, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 20.0": { + "c_nf_per_km": 9.7, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 20.0": { + "c_nf_per_km": 10, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 20.0": { + "c_nf_per_km": 10.3, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.344, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 20.0": { + "c_nf_per_km": 10.5, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.337, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "184-AL1/30-ST1A 20.0": { + "c_nf_per_km": 10.75, + "r_ohm_per_km": 0.1571, + "x_ohm_per_km": 0.33, + "max_i_ka": 0.535, + "type": "ol", + "q_mm2": 184, + "alpha": 0.00403 + }, + "243-AL1/39-ST1A 20.0": { + "c_nf_per_km": 11, + "r_ohm_per_km": 0.1188, + "x_ohm_per_km": 0.32, + "max_i_ka": 0.645, + "type": "ol", + "q_mm2": 243, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 110.0": { + "c_nf_per_km": 8, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.46, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 110.0": { + "c_nf_per_km": 8.4, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.45, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 110.0": { + "c_nf_per_km": 8.65, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.44, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 110.0": { + "c_nf_per_km": 8.5, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.43, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 110.0": { + "c_nf_per_km": 8.75, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.41, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "184-AL1/30-ST1A 110.0": { + "c_nf_per_km": 8.8, + "r_ohm_per_km": 0.1571, + "x_ohm_per_km": 0.4, + "max_i_ka": 0.535, + "type": "ol", + "q_mm2": 184, + "alpha": 0.00403 + }, + "243-AL1/39-ST1A 110.0": { + "c_nf_per_km": 9, + "r_ohm_per_km": 0.1188, + "x_ohm_per_km": 0.39, + "max_i_ka": 0.645, + "type": "ol", + "q_mm2": 243, + "alpha": 0.00403 + }, + "305-AL1/39-ST1A 110.0": { + "c_nf_per_km": 9.2, + "r_ohm_per_km": 0.0949, + "x_ohm_per_km": 0.38, + "max_i_ka": 0.74, + "type": "ol", + "q_mm2": 305, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 110.0": { + "c_nf_per_km": 9.75, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.37, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 110.0": { + "c_nf_per_km": 9.95, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.115, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 220.0": { + "c_nf_per_km": 10, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.285, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 220.0": { + "c_nf_per_km": 11.7, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.275, + "max_i_ka": 0.115, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 380.0": { + "c_nf_per_km": 11, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.253, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 380.0": { + "c_nf_per_km": 14.6, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.25, + "max_i_ka": 0.115, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + } + }, + "trafo": { + "160 MVA 380/110 kV": { + "i0_percent": 0.06, + "pfe_kw": 60, + "vkr_percent": 0.25, + "sn_mva": 160, + "vn_lv_kv": 110.0, + "vn_hv_kv": 380.0, + "vk_percent": 12.2, + "shift_degree": 0, + "vector_group": "Yy0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "100 MVA 220/110 kV": { + "i0_percent": 0.06, + "pfe_kw": 55, + "vkr_percent": 0.26, + "sn_mva": 100, + "vn_lv_kv": 110.0, + "vn_hv_kv": 220.0, + "vk_percent": 12.0, + "shift_degree": 0, + "vector_group": "Yy0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "63 MVA 110/20 kV": { + "i0_percent": 0.04, + "pfe_kw": 22, + "vkr_percent": 0.32, + "sn_mva": 63, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 18, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "40 MVA 110/20 kV": { + "i0_percent": 0.05, + "pfe_kw": 18, + "vkr_percent": 0.34, + "sn_mva": 40, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 16.2, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "25 MVA 110/20 kV": { + "i0_percent": 0.07, + "pfe_kw": 14, + "vkr_percent": 0.41, + "sn_mva": 25, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 12, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "63 MVA 110/10 kV": { + "sn_mva": 63, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 18, + "vkr_percent": 0.32, + "pfe_kw": 22, + "i0_percent": 0.04, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "40 MVA 110/10 kV": { + "sn_mva": 40, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 16.2, + "vkr_percent": 0.34, + "pfe_kw": 18, + "i0_percent": 0.05, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "25 MVA 110/10 kV": { + "sn_mva": 25, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 12, + "vkr_percent": 0.41, + "pfe_kw": 14, + "i0_percent": 0.07, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "0.25 MVA 20/0.4 kV": { + "sn_mva": 0.25, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.44, + "pfe_kw": 0.8, + "i0_percent": 0.32, + "shift_degree": 150, + "vector_group": "Yzn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.4 MVA 20/0.4 kV": { + "sn_mva": 0.4, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.425, + "pfe_kw": 1.35, + "i0_percent": 0.3375, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.63 MVA 20/0.4 kV": { + "sn_mva": 0.63, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.206, + "pfe_kw": 1.65, + "i0_percent": 0.2619, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.25 MVA 10/0.4 kV": { + "sn_mva": 0.25, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.2, + "pfe_kw": 0.6, + "i0_percent": 0.24, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.4 MVA 10/0.4 kV": { + "sn_mva": 0.4, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.325, + "pfe_kw": 0.95, + "i0_percent": 0.2375, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.63 MVA 10/0.4 kV": { + "sn_mva": 0.63, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.0794, + "pfe_kw": 1.18, + "i0_percent": 0.1873, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + } + }, + "trafo3w": { + "63/25/38 MVA 110/20/10 kV": { + "sn_hv_mva": 63, + "sn_mv_mva": 25, + "sn_lv_mva": 38, + "vn_hv_kv": 110, + "vn_mv_kv": 20, + "vn_lv_kv": 10, + "vk_hv_percent": 10.4, + "vk_mv_percent": 10.4, + "vk_lv_percent": 10.4, + "vkr_hv_percent": 0.28, + "vkr_mv_percent": 0.32, + "vkr_lv_percent": 0.35, + "pfe_kw": 35, + "i0_percent": 0.89, + "shift_mv_degree": 0, + "shift_lv_degree": 0, + "vector_group": "YN0yn0yn0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -10, + "tap_max": 10, + "tap_step_percent": 1.2 + }, + "63/25/38 MVA 110/10/10 kV": { + "sn_hv_mva": 63, + "sn_mv_mva": 25, + "sn_lv_mva": 38, + "vn_hv_kv": 110, + "vn_mv_kv": 10, + "vn_lv_kv": 10, + "vk_hv_percent": 10.4, + "vk_mv_percent": 10.4, + "vk_lv_percent": 10.4, + "vkr_hv_percent": 0.28, + "vkr_mv_percent": 0.32, + "vkr_lv_percent": 0.35, + "pfe_kw": 35, + "i0_percent": 0.89, + "shift_mv_degree": 0, + "shift_lv_degree": 0, + "vector_group": "YN0yn0yn0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -10, + "tap_max": 10, + "tap_step_percent": 1.2 + } + } + }, + "res_bus": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_pu\",\"va_degree\",\"p_mw\",\"q_mvar\"],\"index\":[0,1,2,3,4],\"data\":[[1.02,-0.845445168673926,0.0,-111.791243672370911],[1.02,0.0,-21.729831330858325,116.839935541152954],[1.019214100496144,-0.409103297622625,0.0,0.0],[1.018637116919488,-0.503470352662766,10.0,7.0],[1.017983079721402,-0.653497665026562,10.0,7.0]]}", + "dtype": { + "vm_pu": "float64", + "va_degree": "float64", + "p_mw": "float64", + "q_mvar": "float64" + }, + "orient": "split" + }, + "res_line": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\",\"i_ka\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\",\"loading_percent\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[-7.167647147657727,57.480079867900443,8.03525639977348,-60.113463233922118,0.867609252115754,-2.633383366021676,0.327874112511858,0.343286326507116,0.343286326507116,1.02,-0.845445168673926,1.02,0.0,57.214387751185988],[-0.657313913963437,25.969126903729045,0.866078469150186,-29.007927174007612,0.208764555186749,-3.038800270278568,0.147040043868819,0.164393305610081,0.164393305610081,1.02,-0.845445168673926,1.019214100496144,-0.409103297622625,74.724229822763931],[1.64566972119938,15.370129751576128,-1.540268914180618,-19.229415550834709,0.105400807018762,-3.859285799258581,0.087496748884432,0.109338903896103,0.109338903896103,1.02,-0.845445168673926,1.018637116919488,-0.503470352662766,68.336814935064211],[6.179291340421495,12.971907266349552,-6.119076735247816,-15.70424981919658,0.060214605173678,-2.732342552847028,0.081330018729726,0.095589209712924,0.095589209712924,1.02,-0.845445168673926,1.017983079721402,-0.653497665026562,59.743256070577175],[13.694574931085771,-56.726472302863066,-13.283848894885464,55.407854241119566,0.410726036200307,-1.3186180617435,0.330312825878128,0.322760996590474,0.330312825878128,1.02,0.0,1.019214100496144,-0.409103297622625,55.052137646354595],[6.208885212872048,-13.199963533555254,-6.184761786109662,11.833197159642042,0.024123426762386,-1.366766373913212,0.082632108556076,0.075677384410291,0.082632108556076,1.019214100496144,-0.409103297622625,1.018637116919488,-0.503470352662766,27.544036185358689],[6.208885212872048,-13.199963533555254,-6.184761786109662,11.833197159642042,0.024123426762386,-1.366766373913212,0.082632108556076,0.075677384410291,0.082632108556076,1.019214100496144,-0.409103297622625,1.018637116919488,-0.503470352662766,27.544036185358689],[3.909792486391969,-11.436978768449999,-3.88092326475316,8.704249819196738,0.028869221638809,-2.732728949253261,0.068506463438984,0.054050881891821,0.068506463438984,1.018637116919488,-0.503470352662766,1.017983079721402,-0.653497665026562,42.816539649365005]]}", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64", + "i_ka": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64", + "loading_percent": "float64" + }, + "orient": "split" + }, + "res_trafo": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "loading_percent": "float64" + }, + "orient": "split" + }, + "res_trafo3w": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_mv_mw\",\"q_mv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_mv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_mv_pu\",\"va_mv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"va_internal_degree\",\"vm_internal_pu\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_mv_mw": "float64", + "q_mv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_mv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_mv_pu": "float64", + "va_mv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64", + "loading_percent": "float64" + }, + "orient": "split" + }, + "res_impedance": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64" + }, + "orient": "split" + }, + "res_ext_grid": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + }, + "orient": "split" + }, + "res_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[0,1,2],\"data\":[[10.0,7.0],[10.0,7.0],[10.0,7.0]]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + }, + "orient": "split" + }, + "res_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + }, + "orient": "split" + }, + "res_storage": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + }, + "orient": "split" + }, + "res_shunt": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64" + }, + "orient": "split" + }, + "res_gen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"va_degree\",\"vm_pu\"],\"index\":[0,1],\"data\":[[10.0,118.791243672370911,-0.845445168673926,1.02],[21.729831330858325,-116.839935541152954,0.0,1.02]]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "va_degree": "float64", + "vm_pu": "float64" + }, + "orient": "split" + }, + "res_ward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64" + }, + "orient": "split" + }, + "res_xward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\",\"va_internal_degree\",\"vm_internal_pu\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64" + }, + "orient": "split" + }, + "res_dcline": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\"],\"index\":[],\"data\":[]}", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64" + }, + "orient": "split" + }, + "user_pf_options": {}, + "OPF_converged": false + } +} diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/maintenance_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/hazards.csv.bz2 similarity index 100% rename from grid2op/data_test/5bus_example_diff_name/chronics/0/maintenance_forecasted.csv.bz2 rename to grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/hazards.csv.bz2 diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/init_state.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/init_state.json new file mode 100644 index 000000000..e822a78b8 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/init_state.json @@ -0,0 +1,3 @@ +{ + "set_bus": {"lines_or_id": [["0_2_1", 2], ["0_3_2", 1]], "loads_id": [["load_0_0", 2]]} +} \ No newline at end of file diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_p.csv.bz2 new file mode 100644 index 000000000..df894b76b Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_p_forecasted.csv.bz2 new file mode 100644 index 000000000..d91236226 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_q.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_q.csv.bz2 new file mode 100644 index 000000000..d401840ad Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_q.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_q_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_q_forecasted.csv.bz2 new file mode 100644 index 000000000..494dcd68f Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/load_q_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance_forecasted.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance_meta.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance_meta.json new file mode 100644 index 000000000..e6faa8ec2 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/maintenance_meta.json @@ -0,0 +1,7 @@ +{ + "maintenance_starting_hour": 9 , + "maintenance_ending_hour": 17, + "line_to_maintenance": ["0_1_0", "2_3_5"], + "daily_proba_per_month_maintenance": [0.0, 0.0, 0.0, 0.02, 0.02, 0.03, 0.05, 0.06, 0.03, 0.02, 0.0, 0.0], + "max_daily_number_per_month_maintenance": [0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0] +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_p.csv.bz2 new file mode 100644 index 000000000..45ae98a8e Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_p_forecasted.csv.bz2 new file mode 100644 index 000000000..555c0fdde Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_v.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_v.csv.bz2 new file mode 100644 index 000000000..9fde13d8a Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_v_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_v_forecasted.csv.bz2 new file mode 100644 index 000000000..c685c39c0 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/0/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/hazards.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/hazards.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/hazards.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/init_state.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/init_state.json new file mode 100644 index 000000000..9a4c0b72f --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/init_state.json @@ -0,0 +1,3 @@ +{ + "set_line_status": [["0_2_1", -1]] +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_p.csv.bz2 new file mode 100644 index 000000000..99b3b13cc Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_p_forecasted.csv.bz2 new file mode 100644 index 000000000..26dfdb6cd Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_q.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_q.csv.bz2 new file mode 100644 index 000000000..baa780869 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_q.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_q_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_q_forecasted.csv.bz2 new file mode 100644 index 000000000..8953fd7a1 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/load_q_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance_forecasted.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance_meta.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance_meta.json new file mode 100644 index 000000000..e6faa8ec2 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/maintenance_meta.json @@ -0,0 +1,7 @@ +{ + "maintenance_starting_hour": 9 , + "maintenance_ending_hour": 17, + "line_to_maintenance": ["0_1_0", "2_3_5"], + "daily_proba_per_month_maintenance": [0.0, 0.0, 0.0, 0.02, 0.02, 0.03, 0.05, 0.06, 0.03, 0.02, 0.0, 0.0], + "max_daily_number_per_month_maintenance": [0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0] +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_p.csv.bz2 new file mode 100644 index 000000000..180acf18e Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_p_forecasted.csv.bz2 new file mode 100644 index 000000000..5cb7f768c Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_v.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_v.csv.bz2 new file mode 100644 index 000000000..9fde13d8a Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_v_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_v_forecasted.csv.bz2 new file mode 100644 index 000000000..c685c39c0 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/1/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/hazards.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/hazards.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/hazards.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/init_state.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/init_state.json new file mode 100644 index 000000000..f244dbf6f --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/init_state.json @@ -0,0 +1,3 @@ +{ + "set_bus": {"lines_or_id": [["0_2_1", 2]], "lines_ex_id": [["2_3_5", 2]]} +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_p.csv.bz2 new file mode 100644 index 000000000..36a5a5db2 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_p_forecasted.csv.bz2 new file mode 100644 index 000000000..c639bd528 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_q.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_q.csv.bz2 new file mode 100644 index 000000000..ad307afaf Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_q.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_q_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_q_forecasted.csv.bz2 new file mode 100644 index 000000000..4106e2b6b Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/load_q_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance_forecasted.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance_meta.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance_meta.json new file mode 100644 index 000000000..e6faa8ec2 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/maintenance_meta.json @@ -0,0 +1,7 @@ +{ + "maintenance_starting_hour": 9 , + "maintenance_ending_hour": 17, + "line_to_maintenance": ["0_1_0", "2_3_5"], + "daily_proba_per_month_maintenance": [0.0, 0.0, 0.0, 0.02, 0.02, 0.03, 0.05, 0.06, 0.03, 0.02, 0.0, 0.0], + "max_daily_number_per_month_maintenance": [0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0] +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_p.csv.bz2 new file mode 100644 index 000000000..ce04dcfee Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_p_forecasted.csv.bz2 new file mode 100644 index 000000000..b191b116d Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_v.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_v.csv.bz2 new file mode 100644 index 000000000..9fde13d8a Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_v_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_v_forecasted.csv.bz2 new file mode 100644 index 000000000..c685c39c0 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/start_datetime.info b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/start_datetime.info new file mode 100644 index 000000000..bd8d52ab4 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/start_datetime.info @@ -0,0 +1 @@ +2019-01-05 23:55 diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/time_interval.info b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/time_interval.info new file mode 100644 index 000000000..beb9b9011 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/2/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/hazards.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/hazards.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/hazards.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/init_state.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/init_state.json new file mode 100644 index 000000000..d46062e0d --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/init_state.json @@ -0,0 +1,4 @@ +{ + "change_bus": {"lines_or_id": ["0_2_1", "0_3_2"], "loads_id": ["load_0_0"]}, + "change_line_status": ["2_3_5"] +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_p.csv.bz2 new file mode 100644 index 000000000..c4dcdc721 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_p_forecasted.csv.bz2 new file mode 100644 index 000000000..f9a76fa2b Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_q.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_q.csv.bz2 new file mode 100644 index 000000000..75859b6ff Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_q.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_q_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_q_forecasted.csv.bz2 new file mode 100644 index 000000000..079926332 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/load_q_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance_forecasted.csv.bz2 new file mode 100644 index 000000000..5257b64d8 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance_meta.json b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance_meta.json new file mode 100644 index 000000000..e6faa8ec2 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/maintenance_meta.json @@ -0,0 +1,7 @@ +{ + "maintenance_starting_hour": 9 , + "maintenance_ending_hour": 17, + "line_to_maintenance": ["0_1_0", "2_3_5"], + "daily_proba_per_month_maintenance": [0.0, 0.0, 0.0, 0.02, 0.02, 0.03, 0.05, 0.06, 0.03, 0.02, 0.0, 0.0], + "max_daily_number_per_month_maintenance": [0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0] +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_p.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_p.csv.bz2 new file mode 100644 index 000000000..b5b19653c Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_p_forecasted.csv.bz2 new file mode 100644 index 000000000..7e5d59968 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_v.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_v.csv.bz2 new file mode 100644 index 000000000..9fde13d8a Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_v_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_v_forecasted.csv.bz2 new file mode 100644 index 000000000..c685c39c0 Binary files /dev/null and b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/start_datetime.info b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/start_datetime.info new file mode 100644 index 000000000..bd8d52ab4 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/start_datetime.info @@ -0,0 +1 @@ +2019-01-05 23:55 diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/time_interval.info b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/time_interval.info new file mode 100644 index 000000000..beb9b9011 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/chronics/3/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/config.py b/grid2op/data_test/5bus_example_act_topo_set_init/config.py new file mode 100644 index 000000000..1ec901a06 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/config.py @@ -0,0 +1,19 @@ +from grid2op.Action import TopologyAction +from grid2op.Reward import L2RPNReward +from grid2op.Rules import DefaultRules +from grid2op.Chronics import Multifolder +from grid2op.Chronics import GridStateFromFileWithForecasts +from grid2op.Backend import PandaPowerBackend + +config = { + "backend": PandaPowerBackend, + "action_class": TopologyAction, + "observation_class": None, + "reward_class": L2RPNReward, + "gamerules_class": DefaultRules, + "chronics_class": Multifolder, + "grid_value_class": GridStateFromFileWithForecasts, + "volagecontroler_class": None, + "thermal_limits": None, + "names_chronics_to_grid": None, +} diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/grid.json b/grid2op/data_test/5bus_example_act_topo_set_init/grid.json new file mode 100644 index 000000000..b94667b9c --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/grid.json @@ -0,0 +1,1772 @@ +{ + "_module": "pandapower.auxiliary", + "_class": "pandapowerNet", + "_object": { + "bus": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"vn_kv\",\"type\",\"zone\",\"in_service\"],\"index\":[0,1,2,3,4],\"data\":[[\"substation_1\",100.0,\"b\",null,true],[\"substation_2\",100.0,\"b\",null,true],[\"substation_3\",100.0,\"b\",null,true],[\"substation_4\",100.0,\"b\",null,true],[\"substation_5\",100.0,\"b\",null,true]]}", + "orient": "split", + "dtype": { + "name": "object", + "vn_kv": "float64", + "type": "object", + "zone": "object", + "in_service": "bool" + } + }, + "load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"const_z_percent\",\"const_i_percent\",\"sn_mva\",\"scaling\",\"in_service\",\"type\"],\"index\":[0,1,2],\"data\":[[\"load_0_0\",0,10.0,7.0,0.0,0.0,null,1.0,true,null],[\"load_3_1\",3,10.0,7.0,0.0,0.0,null,1.0,true,null],[\"load_4_2\",4,10.0,7.0,0.0,0.0,null,1.0,true,null]]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "p_mw": "float64", + "q_mvar": "float64", + "const_z_percent": "float64", + "const_i_percent": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + } + }, + "sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\",\"current_source\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "p_mw": "float64", + "q_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object", + "current_source": "bool" + } + }, + "motor": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"pn_mech_mw\",\"loading_percent\",\"cos_phi\",\"cos_phi_n\",\"efficiency_percent\",\"efficiency_n_percent\",\"lrc_pu\",\"vn_kv\",\"scaling\",\"in_service\",\"rx\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "pn_mech_mw": "float64", + "loading_percent": "float64", + "cos_phi": "float64", + "cos_phi_n": "float64", + "efficiency_percent": "float64", + "efficiency_n_percent": "float64", + "lrc_pu": "float64", + "vn_kv": "float64", + "scaling": "float64", + "in_service": "bool", + "rx": "float64" + } + }, + "asymmetric_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + } + }, + "asymmetric_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\",\"current_source\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object", + "current_source": "bool" + } + }, + "storage": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"sn_mva\",\"soc_percent\",\"min_e_mwh\",\"max_e_mwh\",\"scaling\",\"in_service\",\"type\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "p_mw": "float64", + "q_mvar": "float64", + "sn_mva": "float64", + "soc_percent": "float64", + "min_e_mwh": "float64", + "max_e_mwh": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + } + }, + "gen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"vm_pu\",\"sn_mva\",\"min_q_mvar\",\"max_q_mvar\",\"scaling\",\"slack\",\"in_service\",\"type\",\"slack_weight\"],\"index\":[0,1],\"data\":[[\"gen_0_0\",0,10.0,1.02,null,null,null,1.0,false,true,null,0.0],[\"gen_1_1\",1,20.0,1.02,null,null,null,1.0,true,true,null,1.0]]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "p_mw": "float64", + "vm_pu": "float64", + "sn_mva": "float64", + "min_q_mvar": "float64", + "max_q_mvar": "float64", + "scaling": "float64", + "slack": "bool", + "in_service": "bool", + "type": "object", + "slack_weight": "float64" + } + }, + "switch": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"bus\",\"element\",\"et\",\"type\",\"closed\",\"name\",\"z_ohm\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "bus": "int64", + "element": "int64", + "et": "object", + "type": "object", + "closed": "bool", + "name": "object", + "z_ohm": "float64" + } + }, + "shunt": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"bus\",\"name\",\"q_mvar\",\"p_mw\",\"vn_kv\",\"step\",\"max_step\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "bus": "uint32", + "name": "object", + "q_mvar": "float64", + "p_mw": "float64", + "vn_kv": "float64", + "step": "uint32", + "max_step": "uint32", + "in_service": "bool" + } + }, + "ext_grid": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"vm_pu\",\"va_degree\",\"in_service\",\"slack_weight\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "vm_pu": "float64", + "va_degree": "float64", + "in_service": "bool", + "slack_weight": "float64" + } + }, + "line": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"from_bus\",\"to_bus\",\"length_km\",\"r_ohm_per_km\",\"x_ohm_per_km\",\"c_nf_per_km\",\"g_us_per_km\",\"max_i_ka\",\"df\",\"parallel\",\"type\",\"in_service\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[null,\"NAYY 4x50 SE\",0,1,4.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"0_2_2\",\"NAYY 4x50 SE\",0,2,4.47,0.642,0.083,210.0,0.0,0.22,1.0,1,\"cs\",true],[\"0_3_3\",\"NAYY 4x50 SE\",0,3,5.65,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"0_4_4\",\"NAYY 4x50 SE\",0,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"1_2_5\",\"NAYY 4x50 SE\",1,2,2.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"2_3_6\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"2_3_7\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"3_4_8\",\"NAYY 4x50 SE\",3,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true]]}", + "orient": "split", + "dtype": { + "name": "object", + "std_type": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "length_km": "float64", + "r_ohm_per_km": "float64", + "x_ohm_per_km": "float64", + "c_nf_per_km": "float64", + "g_us_per_km": "float64", + "max_i_ka": "float64", + "df": "float64", + "parallel": "uint32", + "type": "object", + "in_service": "bool" + } + }, + "trafo": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"hv_bus\",\"lv_bus\",\"sn_mva\",\"vn_hv_kv\",\"vn_lv_kv\",\"vk_percent\",\"vkr_percent\",\"pfe_kw\",\"i0_percent\",\"shift_degree\",\"tap_side\",\"tap_neutral\",\"tap_min\",\"tap_max\",\"tap_step_percent\",\"tap_step_degree\",\"tap_pos\",\"tap_phase_shifter\",\"parallel\",\"df\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "std_type": "object", + "hv_bus": "uint32", + "lv_bus": "uint32", + "sn_mva": "float64", + "vn_hv_kv": "float64", + "vn_lv_kv": "float64", + "vk_percent": "float64", + "vkr_percent": "float64", + "pfe_kw": "float64", + "i0_percent": "float64", + "shift_degree": "float64", + "tap_side": "object", + "tap_neutral": "int32", + "tap_min": "int32", + "tap_max": "int32", + "tap_step_percent": "float64", + "tap_step_degree": "float64", + "tap_pos": "int32", + "tap_phase_shifter": "bool", + "parallel": "uint32", + "df": "float64", + "in_service": "bool" + } + }, + "trafo3w": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"hv_bus\",\"mv_bus\",\"lv_bus\",\"sn_hv_mva\",\"sn_mv_mva\",\"sn_lv_mva\",\"vn_hv_kv\",\"vn_mv_kv\",\"vn_lv_kv\",\"vk_hv_percent\",\"vk_mv_percent\",\"vk_lv_percent\",\"vkr_hv_percent\",\"vkr_mv_percent\",\"vkr_lv_percent\",\"pfe_kw\",\"i0_percent\",\"shift_mv_degree\",\"shift_lv_degree\",\"tap_side\",\"tap_neutral\",\"tap_min\",\"tap_max\",\"tap_step_percent\",\"tap_step_degree\",\"tap_pos\",\"tap_at_star_point\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "std_type": "object", + "hv_bus": "uint32", + "mv_bus": "uint32", + "lv_bus": "uint32", + "sn_hv_mva": "float64", + "sn_mv_mva": "float64", + "sn_lv_mva": "float64", + "vn_hv_kv": "float64", + "vn_mv_kv": "float64", + "vn_lv_kv": "float64", + "vk_hv_percent": "float64", + "vk_mv_percent": "float64", + "vk_lv_percent": "float64", + "vkr_hv_percent": "float64", + "vkr_mv_percent": "float64", + "vkr_lv_percent": "float64", + "pfe_kw": "float64", + "i0_percent": "float64", + "shift_mv_degree": "float64", + "shift_lv_degree": "float64", + "tap_side": "object", + "tap_neutral": "int32", + "tap_min": "int32", + "tap_max": "int32", + "tap_step_percent": "float64", + "tap_step_degree": "float64", + "tap_pos": "int32", + "tap_at_star_point": "bool", + "in_service": "bool" + } + }, + "impedance": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"from_bus\",\"to_bus\",\"rft_pu\",\"xft_pu\",\"rtf_pu\",\"xtf_pu\",\"sn_mva\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "rft_pu": "float64", + "xft_pu": "float64", + "rtf_pu": "float64", + "xtf_pu": "float64", + "sn_mva": "float64", + "in_service": "bool" + } + }, + "dcline": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"from_bus\",\"to_bus\",\"p_mw\",\"loss_percent\",\"loss_mw\",\"vm_from_pu\",\"vm_to_pu\",\"max_p_mw\",\"min_q_from_mvar\",\"min_q_to_mvar\",\"max_q_from_mvar\",\"max_q_to_mvar\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "p_mw": "float64", + "loss_percent": "float64", + "loss_mw": "float64", + "vm_from_pu": "float64", + "vm_to_pu": "float64", + "max_p_mw": "float64", + "min_q_from_mvar": "float64", + "min_q_to_mvar": "float64", + "max_q_from_mvar": "float64", + "max_q_to_mvar": "float64", + "in_service": "bool" + } + }, + "ward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"ps_mw\",\"qs_mvar\",\"qz_mvar\",\"pz_mw\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "ps_mw": "float64", + "qs_mvar": "float64", + "qz_mvar": "float64", + "pz_mw": "float64", + "in_service": "bool" + } + }, + "xward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"ps_mw\",\"qs_mvar\",\"qz_mvar\",\"pz_mw\",\"r_ohm\",\"x_ohm\",\"vm_pu\",\"in_service\",\"slack_weight\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "ps_mw": "float64", + "qs_mvar": "float64", + "qz_mvar": "float64", + "pz_mw": "float64", + "r_ohm": "float64", + "x_ohm": "float64", + "vm_pu": "float64", + "in_service": "bool", + "slack_weight": "float64" + } + }, + "measurement": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"measurement_type\",\"element_type\",\"element\",\"value\",\"std_dev\",\"side\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "measurement_type": "object", + "element_type": "object", + "element": "uint32", + "value": "float64", + "std_dev": "float64", + "side": "object" + } + }, + "pwl_cost": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"power_type\",\"element\",\"et\",\"points\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "power_type": "object", + "element": "uint32", + "et": "object", + "points": "object" + } + }, + "poly_cost": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"element\",\"et\",\"cp0_eur\",\"cp1_eur_per_mw\",\"cp2_eur_per_mw2\",\"cq0_eur\",\"cq1_eur_per_mvar\",\"cq2_eur_per_mvar2\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "element": "uint32", + "et": "object", + "cp0_eur": "float64", + "cp1_eur_per_mw": "float64", + "cp2_eur_per_mw2": "float64", + "cq0_eur": "float64", + "cq1_eur_per_mvar": "float64", + "cq2_eur_per_mvar2": "float64" + } + }, + "characteristic": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"object\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "object": "object" + } + }, + "controller": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"object\",\"in_service\",\"order\",\"level\",\"initial_run\",\"recycle\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "object": "object", + "in_service": "bool", + "order": "float64", + "level": "object", + "initial_run": "bool", + "recycle": "object" + } + }, + "line_geodata": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"coords\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[[[0,0],[0,4]]],[[[0,0],[2,4]]],[[[0,0],[4,4]]],[[[0,0],[4,0]]],[[[0,4],[2,4]]],[[[2,4],[3,4.2],[4,4]]],[[[2,4],[3,3.8],[4,4]]],[[[4,4],[4,0]]]]}", + "orient": "split", + "dtype": { + "coords": "object" + } + }, + "bus_geodata": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"x\",\"y\",\"coords\"],\"index\":[0,1,2,3,4],\"data\":[[0.0,0.0,null],[0.0,4.0,null],[2.0,4.0,null],[4.0,4.0,null],[4.0,0.0,null]]}", + "orient": "split", + "dtype": { + "x": "float64", + "y": "float64", + "coords": "object" + } + }, + "version": "2.8.0", + "converged": true, + "name": "5bus", + "f_hz": 50.0, + "sn_mva": 1, + "std_types": { + "line": { + "NAYY 4x50 SE": { + "c_nf_per_km": 210, + "r_ohm_per_km": 0.642, + "x_ohm_per_km": 0.083, + "max_i_ka": 0.142, + "type": "cs", + "q_mm2": 50, + "alpha": 0.00403 + }, + "NAYY 4x120 SE": { + "c_nf_per_km": 264, + "r_ohm_per_km": 0.225, + "x_ohm_per_km": 0.08, + "max_i_ka": 0.242, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NAYY 4x150 SE": { + "c_nf_per_km": 261, + "r_ohm_per_km": 0.208, + "x_ohm_per_km": 0.08, + "max_i_ka": 0.27, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x95 RM/25 12/20 kV": { + "c_nf_per_km": 216, + "r_ohm_per_km": 0.313, + "x_ohm_per_km": 0.132, + "max_i_ka": 0.252, + "type": "cs", + "q_mm2": 95, + "alpha": 0.00403 + }, + "NA2XS2Y 1x185 RM/25 12/20 kV": { + "c_nf_per_km": 273, + "r_ohm_per_km": 0.161, + "x_ohm_per_km": 0.117, + "max_i_ka": 0.362, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00403 + }, + "NA2XS2Y 1x240 RM/25 12/20 kV": { + "c_nf_per_km": 304, + "r_ohm_per_km": 0.122, + "x_ohm_per_km": 0.112, + "max_i_ka": 0.421, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00403 + }, + "NA2XS2Y 1x95 RM/25 6/10 kV": { + "c_nf_per_km": 315, + "r_ohm_per_km": 0.313, + "x_ohm_per_km": 0.123, + "max_i_ka": 0.249, + "type": "cs", + "q_mm2": 95, + "alpha": 0.00403 + }, + "NA2XS2Y 1x185 RM/25 6/10 kV": { + "c_nf_per_km": 406, + "r_ohm_per_km": 0.161, + "x_ohm_per_km": 0.11, + "max_i_ka": 0.358, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00403 + }, + "NA2XS2Y 1x240 RM/25 6/10 kV": { + "c_nf_per_km": 456, + "r_ohm_per_km": 0.122, + "x_ohm_per_km": 0.105, + "max_i_ka": 0.416, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00403 + }, + "NA2XS2Y 1x150 RM/25 12/20 kV": { + "c_nf_per_km": 250, + "r_ohm_per_km": 0.206, + "x_ohm_per_km": 0.116, + "max_i_ka": 0.319, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x120 RM/25 12/20 kV": { + "c_nf_per_km": 230, + "r_ohm_per_km": 0.253, + "x_ohm_per_km": 0.119, + "max_i_ka": 0.283, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NA2XS2Y 1x70 RM/25 12/20 kV": { + "c_nf_per_km": 190, + "r_ohm_per_km": 0.443, + "x_ohm_per_km": 0.132, + "max_i_ka": 0.22, + "type": "cs", + "q_mm2": 70, + "alpha": 0.00403 + }, + "NA2XS2Y 1x150 RM/25 6/10 kV": { + "c_nf_per_km": 360, + "r_ohm_per_km": 0.206, + "x_ohm_per_km": 0.11, + "max_i_ka": 0.315, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x120 RM/25 6/10 kV": { + "c_nf_per_km": 340, + "r_ohm_per_km": 0.253, + "x_ohm_per_km": 0.113, + "max_i_ka": 0.28, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NA2XS2Y 1x70 RM/25 6/10 kV": { + "c_nf_per_km": 280, + "r_ohm_per_km": 0.443, + "x_ohm_per_km": 0.123, + "max_i_ka": 0.217, + "type": "cs", + "q_mm2": 70, + "alpha": 0.00403 + }, + "N2XS(FL)2Y 1x120 RM/35 64/110 kV": { + "c_nf_per_km": 112, + "r_ohm_per_km": 0.153, + "x_ohm_per_km": 0.166, + "max_i_ka": 0.366, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x185 RM/35 64/110 kV": { + "c_nf_per_km": 125, + "r_ohm_per_km": 0.099, + "x_ohm_per_km": 0.156, + "max_i_ka": 0.457, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x240 RM/35 64/110 kV": { + "c_nf_per_km": 135, + "r_ohm_per_km": 0.075, + "x_ohm_per_km": 0.149, + "max_i_ka": 0.526, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x300 RM/35 64/110 kV": { + "c_nf_per_km": 144, + "r_ohm_per_km": 0.06, + "x_ohm_per_km": 0.144, + "max_i_ka": 0.588, + "type": "cs", + "q_mm2": 300, + "alpha": 0.00393 + }, + "15-AL1/3-ST1A 0.4": { + "c_nf_per_km": 11, + "r_ohm_per_km": 1.8769, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.105, + "type": "ol", + "q_mm2": 16, + "alpha": 0.00403 + }, + "24-AL1/4-ST1A 0.4": { + "c_nf_per_km": 11.25, + "r_ohm_per_km": 1.2012, + "x_ohm_per_km": 0.335, + "max_i_ka": 0.14, + "type": "ol", + "q_mm2": 24, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 0.4": { + "c_nf_per_km": 12.2, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.3, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 0.4": { + "c_nf_per_km": 13.2, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.29, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "34-AL1/6-ST1A 10.0": { + "c_nf_per_km": 9.7, + "r_ohm_per_km": 0.8342, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.17, + "type": "ol", + "q_mm2": 34, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 10.0": { + "c_nf_per_km": 10.1, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 10.0": { + "c_nf_per_km": 10.4, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.339, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 10.0": { + "c_nf_per_km": 10.75, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.33, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 10.0": { + "c_nf_per_km": 11.1, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.323, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 10.0": { + "c_nf_per_km": 11.25, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.315, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "34-AL1/6-ST1A 20.0": { + "c_nf_per_km": 9.15, + "r_ohm_per_km": 0.8342, + "x_ohm_per_km": 0.382, + "max_i_ka": 0.17, + "type": "ol", + "q_mm2": 34, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 20.0": { + "c_nf_per_km": 9.5, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.372, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 20.0": { + "c_nf_per_km": 9.7, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 20.0": { + "c_nf_per_km": 10, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 20.0": { + "c_nf_per_km": 10.3, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.344, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 20.0": { + "c_nf_per_km": 10.5, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.337, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "184-AL1/30-ST1A 20.0": { + "c_nf_per_km": 10.75, + "r_ohm_per_km": 0.1571, + "x_ohm_per_km": 0.33, + "max_i_ka": 0.535, + "type": "ol", + "q_mm2": 184, + "alpha": 0.00403 + }, + "243-AL1/39-ST1A 20.0": { + "c_nf_per_km": 11, + "r_ohm_per_km": 0.1188, + "x_ohm_per_km": 0.32, + "max_i_ka": 0.645, + "type": "ol", + "q_mm2": 243, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 110.0": { + "c_nf_per_km": 8, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.46, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 110.0": { + "c_nf_per_km": 8.4, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.45, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 110.0": { + "c_nf_per_km": 8.65, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.44, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 110.0": { + "c_nf_per_km": 8.5, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.43, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 110.0": { + "c_nf_per_km": 8.75, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.41, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "184-AL1/30-ST1A 110.0": { + "c_nf_per_km": 8.8, + "r_ohm_per_km": 0.1571, + "x_ohm_per_km": 0.4, + "max_i_ka": 0.535, + "type": "ol", + "q_mm2": 184, + "alpha": 0.00403 + }, + "243-AL1/39-ST1A 110.0": { + "c_nf_per_km": 9, + "r_ohm_per_km": 0.1188, + "x_ohm_per_km": 0.39, + "max_i_ka": 0.645, + "type": "ol", + "q_mm2": 243, + "alpha": 0.00403 + }, + "305-AL1/39-ST1A 110.0": { + "c_nf_per_km": 9.2, + "r_ohm_per_km": 0.0949, + "x_ohm_per_km": 0.38, + "max_i_ka": 0.74, + "type": "ol", + "q_mm2": 305, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 110.0": { + "c_nf_per_km": 9.75, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.37, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 110.0": { + "c_nf_per_km": 9.95, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.115, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 220.0": { + "c_nf_per_km": 10, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.285, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 220.0": { + "c_nf_per_km": 11.7, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.275, + "max_i_ka": 0.115, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 380.0": { + "c_nf_per_km": 11, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.253, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 380.0": { + "c_nf_per_km": 14.6, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.25, + "max_i_ka": 0.115, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + } + }, + "trafo": { + "160 MVA 380/110 kV": { + "i0_percent": 0.06, + "pfe_kw": 60, + "vkr_percent": 0.25, + "sn_mva": 160, + "vn_lv_kv": 110.0, + "vn_hv_kv": 380.0, + "vk_percent": 12.2, + "shift_degree": 0, + "vector_group": "Yy0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "100 MVA 220/110 kV": { + "i0_percent": 0.06, + "pfe_kw": 55, + "vkr_percent": 0.26, + "sn_mva": 100, + "vn_lv_kv": 110.0, + "vn_hv_kv": 220.0, + "vk_percent": 12.0, + "shift_degree": 0, + "vector_group": "Yy0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "63 MVA 110/20 kV": { + "i0_percent": 0.04, + "pfe_kw": 22, + "vkr_percent": 0.32, + "sn_mva": 63, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 18, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "40 MVA 110/20 kV": { + "i0_percent": 0.05, + "pfe_kw": 18, + "vkr_percent": 0.34, + "sn_mva": 40, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 16.2, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "25 MVA 110/20 kV": { + "i0_percent": 0.07, + "pfe_kw": 14, + "vkr_percent": 0.41, + "sn_mva": 25, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 12, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "63 MVA 110/10 kV": { + "sn_mva": 63, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 18, + "vkr_percent": 0.32, + "pfe_kw": 22, + "i0_percent": 0.04, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "40 MVA 110/10 kV": { + "sn_mva": 40, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 16.2, + "vkr_percent": 0.34, + "pfe_kw": 18, + "i0_percent": 0.05, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "25 MVA 110/10 kV": { + "sn_mva": 25, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 12, + "vkr_percent": 0.41, + "pfe_kw": 14, + "i0_percent": 0.07, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "0.25 MVA 20/0.4 kV": { + "sn_mva": 0.25, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.44, + "pfe_kw": 0.8, + "i0_percent": 0.32, + "shift_degree": 150, + "vector_group": "Yzn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.4 MVA 20/0.4 kV": { + "sn_mva": 0.4, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.425, + "pfe_kw": 1.35, + "i0_percent": 0.3375, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.63 MVA 20/0.4 kV": { + "sn_mva": 0.63, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.206, + "pfe_kw": 1.65, + "i0_percent": 0.2619, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.25 MVA 10/0.4 kV": { + "sn_mva": 0.25, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.2, + "pfe_kw": 0.6, + "i0_percent": 0.24, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.4 MVA 10/0.4 kV": { + "sn_mva": 0.4, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.325, + "pfe_kw": 0.95, + "i0_percent": 0.2375, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.63 MVA 10/0.4 kV": { + "sn_mva": 0.63, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.0794, + "pfe_kw": 1.18, + "i0_percent": 0.1873, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + } + }, + "trafo3w": { + "63/25/38 MVA 110/20/10 kV": { + "sn_hv_mva": 63, + "sn_mv_mva": 25, + "sn_lv_mva": 38, + "vn_hv_kv": 110, + "vn_mv_kv": 20, + "vn_lv_kv": 10, + "vk_hv_percent": 10.4, + "vk_mv_percent": 10.4, + "vk_lv_percent": 10.4, + "vkr_hv_percent": 0.28, + "vkr_mv_percent": 0.32, + "vkr_lv_percent": 0.35, + "pfe_kw": 35, + "i0_percent": 0.89, + "shift_mv_degree": 0, + "shift_lv_degree": 0, + "vector_group": "YN0yn0yn0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -10, + "tap_max": 10, + "tap_step_percent": 1.2 + }, + "63/25/38 MVA 110/10/10 kV": { + "sn_hv_mva": 63, + "sn_mv_mva": 25, + "sn_lv_mva": 38, + "vn_hv_kv": 110, + "vn_mv_kv": 10, + "vn_lv_kv": 10, + "vk_hv_percent": 10.4, + "vk_mv_percent": 10.4, + "vk_lv_percent": 10.4, + "vkr_hv_percent": 0.28, + "vkr_mv_percent": 0.32, + "vkr_lv_percent": 0.35, + "pfe_kw": 35, + "i0_percent": 0.89, + "shift_mv_degree": 0, + "shift_lv_degree": 0, + "vector_group": "YN0yn0yn0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -10, + "tap_max": 10, + "tap_step_percent": 1.2 + } + } + }, + "res_bus": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_pu\",\"va_degree\",\"p_mw\",\"q_mvar\"],\"index\":[0,1,2,3,4],\"data\":[[1.02,-0.845445168673926,0.0,-111.791243672370911],[1.02,0.0,-21.729831330858325,116.839935541152954],[1.019214100496144,-0.409103297622625,0.0,0.0],[1.018637116919488,-0.503470352662766,10.0,7.0],[1.017983079721402,-0.653497665026562,10.0,7.0]]}", + "orient": "split", + "dtype": { + "vm_pu": "float64", + "va_degree": "float64", + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_line": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\",\"i_ka\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\",\"loading_percent\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[-7.167647147657727,57.480079867900443,8.03525639977348,-60.113463233922118,0.867609252115754,-2.633383366021676,0.327874112511858,0.343286326507116,0.343286326507116,1.02,-0.845445168673926,1.02,0.0,57.214387751185988],[-0.657313913963437,25.969126903729045,0.866078469150186,-29.007927174007612,0.208764555186749,-3.038800270278568,0.147040043868819,0.164393305610081,0.164393305610081,1.02,-0.845445168673926,1.019214100496144,-0.409103297622625,74.724229822763931],[1.64566972119938,15.370129751576128,-1.540268914180618,-19.229415550834709,0.105400807018762,-3.859285799258581,0.087496748884432,0.109338903896103,0.109338903896103,1.02,-0.845445168673926,1.018637116919488,-0.503470352662766,68.336814935064211],[6.179291340421495,12.971907266349552,-6.119076735247816,-15.70424981919658,0.060214605173678,-2.732342552847028,0.081330018729726,0.095589209712924,0.095589209712924,1.02,-0.845445168673926,1.017983079721402,-0.653497665026562,59.743256070577175],[13.694574931085771,-56.726472302863066,-13.283848894885464,55.407854241119566,0.410726036200307,-1.3186180617435,0.330312825878128,0.322760996590474,0.330312825878128,1.02,0.0,1.019214100496144,-0.409103297622625,55.052137646354595],[6.208885212872048,-13.199963533555254,-6.184761786109662,11.833197159642042,0.024123426762386,-1.366766373913212,0.082632108556076,0.075677384410291,0.082632108556076,1.019214100496144,-0.409103297622625,1.018637116919488,-0.503470352662766,27.544036185358689],[6.208885212872048,-13.199963533555254,-6.184761786109662,11.833197159642042,0.024123426762386,-1.366766373913212,0.082632108556076,0.075677384410291,0.082632108556076,1.019214100496144,-0.409103297622625,1.018637116919488,-0.503470352662766,27.544036185358689],[3.909792486391969,-11.436978768449999,-3.88092326475316,8.704249819196738,0.028869221638809,-2.732728949253261,0.068506463438984,0.054050881891821,0.068506463438984,1.018637116919488,-0.503470352662766,1.017983079721402,-0.653497665026562,42.816539649365005]]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64", + "i_ka": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo3w": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_mv_mw\",\"q_mv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_mv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_mv_pu\",\"va_mv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"va_internal_degree\",\"vm_internal_pu\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_mv_mw": "float64", + "q_mv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_mv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_mv_pu": "float64", + "va_mv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64", + "loading_percent": "float64" + } + }, + "res_impedance": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64" + } + }, + "res_ext_grid": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[0,1,2],\"data\":[[10.0,7.0],[10.0,7.0],[10.0,7.0]]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_motor": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_storage": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_shunt": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64" + } + }, + "res_gen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"va_degree\",\"vm_pu\"],\"index\":[0,1],\"data\":[[10.0,118.791243672370911,-0.845445168673926,1.02],[21.729831330858325,-116.839935541152954,0.0,1.02]]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "va_degree": "float64", + "vm_pu": "float64" + } + }, + "res_ward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64" + } + }, + "res_xward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\",\"va_internal_degree\",\"vm_internal_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64" + } + }, + "res_dcline": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64" + } + }, + "res_asymmetric_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_asymmetric_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_bus_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_pu\",\"va_degree\",\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "vm_pu": "float64", + "va_degree": "float64", + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_line_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\",\"i_ka\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64", + "i_ka": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo3w_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_mv_mw\",\"q_mv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_mv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_mv_pu\",\"va_mv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"va_internal_degree\",\"vm_internal_pu\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_mv_mw": "float64", + "q_mv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_mv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_mv_pu": "float64", + "va_mv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64", + "loading_percent": "float64" + } + }, + "res_impedance_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64" + } + }, + "res_bus_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_line_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_trafo_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_trafo3w_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_ext_grid_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_gen_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_sgen_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_bus_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_a_pu\",\"va_a_degree\",\"vm_b_pu\",\"va_b_degree\",\"vm_c_pu\",\"va_c_degree\",\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "vm_a_pu": "float64", + "va_a_degree": "float64", + "vm_b_pu": "float64", + "va_b_degree": "float64", + "vm_c_pu": "float64", + "va_c_degree": "float64", + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "res_line_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_from_mw\",\"q_a_from_mvar\",\"p_b_from_mw\",\"q_b_from_mvar\",\"q_c_from_mvar\",\"p_a_to_mw\",\"q_a_to_mvar\",\"p_b_to_mw\",\"q_b_to_mvar\",\"p_c_to_mw\",\"q_c_to_mvar\",\"p_a_l_mw\",\"q_a_l_mvar\",\"p_b_l_mw\",\"q_b_l_mvar\",\"p_c_l_mw\",\"q_c_l_mvar\",\"i_a_from_ka\",\"i_a_to_ka\",\"i_b_from_ka\",\"i_b_to_ka\",\"i_c_from_ka\",\"i_c_to_ka\",\"i_a_ka\",\"i_b_ka\",\"i_c_ka\",\"i_n_from_ka\",\"i_n_to_ka\",\"i_n_ka\",\"loading_a_percent\",\"loading_b_percent\",\"loading_c_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_from_mw": "float64", + "q_a_from_mvar": "float64", + "p_b_from_mw": "float64", + "q_b_from_mvar": "float64", + "q_c_from_mvar": "float64", + "p_a_to_mw": "float64", + "q_a_to_mvar": "float64", + "p_b_to_mw": "float64", + "q_b_to_mvar": "float64", + "p_c_to_mw": "float64", + "q_c_to_mvar": "float64", + "p_a_l_mw": "float64", + "q_a_l_mvar": "float64", + "p_b_l_mw": "float64", + "q_b_l_mvar": "float64", + "p_c_l_mw": "float64", + "q_c_l_mvar": "float64", + "i_a_from_ka": "float64", + "i_a_to_ka": "float64", + "i_b_from_ka": "float64", + "i_b_to_ka": "float64", + "i_c_from_ka": "float64", + "i_c_to_ka": "float64", + "i_a_ka": "float64", + "i_b_ka": "float64", + "i_c_ka": "float64", + "i_n_from_ka": "float64", + "i_n_to_ka": "float64", + "i_n_ka": "float64", + "loading_a_percent": "float64", + "loading_b_percent": "float64", + "loading_c_percent": "float64" + } + }, + "res_trafo_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_hv_mw\",\"q_a_hv_mvar\",\"p_b_hv_mw\",\"q_b_hv_mvar\",\"p_c_hv_mw\",\"q_c_hv_mvar\",\"p_a_lv_mw\",\"q_a_lv_mvar\",\"p_b_lv_mw\",\"q_b_lv_mvar\",\"p_c_lv_mw\",\"q_c_lv_mvar\",\"p_a_l_mw\",\"q_a_l_mvar\",\"p_b_l_mw\",\"q_b_l_mvar\",\"p_c_l_mw\",\"q_c_l_mvar\",\"i_a_hv_ka\",\"i_a_lv_ka\",\"i_b_hv_ka\",\"i_b_lv_ka\",\"i_c_hv_ka\",\"i_c_lv_ka\",\"loading_a_percent\",\"loading_b_percent\",\"loading_c_percent\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_hv_mw": "float64", + "q_a_hv_mvar": "float64", + "p_b_hv_mw": "float64", + "q_b_hv_mvar": "float64", + "p_c_hv_mw": "float64", + "q_c_hv_mvar": "float64", + "p_a_lv_mw": "float64", + "q_a_lv_mvar": "float64", + "p_b_lv_mw": "float64", + "q_b_lv_mvar": "float64", + "p_c_lv_mw": "float64", + "q_c_lv_mvar": "float64", + "p_a_l_mw": "float64", + "q_a_l_mvar": "float64", + "p_b_l_mw": "float64", + "q_b_l_mvar": "float64", + "p_c_l_mw": "float64", + "q_c_l_mvar": "float64", + "i_a_hv_ka": "float64", + "i_a_lv_ka": "float64", + "i_b_hv_ka": "float64", + "i_b_lv_ka": "float64", + "i_c_hv_ka": "float64", + "i_c_lv_ka": "float64", + "loading_a_percent": "float64", + "loading_b_percent": "float64", + "loading_c_percent": "float64", + "loading_percent": "float64" + } + }, + "res_ext_grid_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "res_shunt_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_load_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_sgen_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_storage_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_asymmetric_load_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "res_asymmetric_sgen_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "user_pf_options": {}, + "OPF_converged": false + } +} \ No newline at end of file diff --git a/grid2op/data_test/5bus_example_act_topo_set_init/prods_charac.csv b/grid2op/data_test/5bus_example_act_topo_set_init/prods_charac.csv new file mode 100644 index 000000000..f47a90595 --- /dev/null +++ b/grid2op/data_test/5bus_example_act_topo_set_init/prods_charac.csv @@ -0,0 +1,3 @@ +Pmax,Pmin,name,type,bus,max_ramp_up,max_ramp_down,min_up_time,min_down_time,marginal_cost,shut_down_cost,start_cost,x,y,V +15,0.0,gen_0_0,wind,5,0,0,0,0,0,0,0,0,0,102. +35,0.0,gen_1_1,thermal,0,15,15,4,4,70,1,2,0,400,102. \ No newline at end of file diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/hazards.csv.bz2 b/grid2op/data_test/5bus_example_diff_name/chronics/0/hazards.csv.bz2 index 5257b64d8..738ccee0e 100644 Binary files a/grid2op/data_test/5bus_example_diff_name/chronics/0/hazards.csv.bz2 and b/grid2op/data_test/5bus_example_diff_name/chronics/0/hazards.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/maintenance.csv.bz2 b/grid2op/data_test/5bus_example_diff_name/chronics/0/maintenance.csv.bz2 index 5257b64d8..738ccee0e 100644 Binary files a/grid2op/data_test/5bus_example_diff_name/chronics/0/maintenance.csv.bz2 and b/grid2op/data_test/5bus_example_diff_name/chronics/0/maintenance.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p.csv.bz2 b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p.csv.bz2 index 45ae98a8e..f3169fa0a 100644 Binary files a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p.csv.bz2 and b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p_forecasted.csv.bz2 index 555c0fdde..21416f274 100644 Binary files a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p_forecasted.csv.bz2 and b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v.csv.bz2 b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v.csv.bz2 index 9fde13d8a..63209b7aa 100644 Binary files a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v.csv.bz2 and b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v_forecasted.csv.bz2 b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v_forecasted.csv.bz2 index c685c39c0..43614717a 100644 Binary files a/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v_forecasted.csv.bz2 and b/grid2op/data_test/5bus_example_diff_name/chronics/0/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/5bus_example_diff_name/grid.json b/grid2op/data_test/5bus_example_diff_name/grid.json index 4ed416f32..427f1b756 100644 --- a/grid2op/data_test/5bus_example_diff_name/grid.json +++ b/grid2op/data_test/5bus_example_diff_name/grid.json @@ -72,7 +72,7 @@ "gen": { "_module": "pandas.core.frame", "_class": "DataFrame", - "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"vm_pu\",\"sn_mva\",\"min_q_mvar\",\"max_q_mvar\",\"scaling\",\"slack\",\"in_service\",\"type\"],\"index\":[0,1],\"data\":[[\"gen_0_0\",0,10.0,1.02,null,null,null,1.0,false,true,null],[\"gen_1_1\",1,20.0,1.02,null,null,null,1.0,true,true,null]]}", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"vm_pu\",\"sn_mva\",\"min_q_mvar\",\"max_q_mvar\",\"scaling\",\"slack\",\"in_service\",\"type\"],\"index\":[0,1],\"data\":[[\"othername_0_0\",0,10.0,1.02,null,null,null,1.0,false,true,null],[\"othername_1_1\",1,20.0,1.02,null,null,null,1.0,true,true,null]]}", "dtype": { "name": "object", "bus": "uint32", @@ -135,7 +135,7 @@ "line": { "_module": "pandas.core.frame", "_class": "DataFrame", - "_object": "{\"columns\":[\"name\",\"std_type\",\"from_bus\",\"to_bus\",\"length_km\",\"r_ohm_per_km\",\"x_ohm_per_km\",\"c_nf_per_km\",\"g_us_per_km\",\"max_i_ka\",\"df\",\"parallel\",\"type\",\"in_service\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[null,\"NAYY 4x50 SE\",0,1,4.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"0_2_2\",\"NAYY 4x50 SE\",0,2,4.47,0.642,0.083,210.0,0.0,0.22,1.0,1,\"cs\",true],[\"0_3_3\",\"NAYY 4x50 SE\",0,3,5.65,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"0_4_4\",\"NAYY 4x50 SE\",0,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"1_2_5\",\"NAYY 4x50 SE\",1,2,2.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"2_3_6\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"2_3_7\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"3_4_8\",\"NAYY 4x50 SE\",3,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true]]}", + "_object": "{\"columns\":[\"name\",\"std_type\",\"from_bus\",\"to_bus\",\"length_km\",\"r_ohm_per_km\",\"x_ohm_per_km\",\"c_nf_per_km\",\"g_us_per_km\",\"max_i_ka\",\"df\",\"parallel\",\"type\",\"in_service\"],\"index\":[0,1,2,3,4,5,6,7],\"data\":[[\"l_0_1_0\",\"NAYY 4x50 SE\",0,1,4.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"l_0_2_1\",\"NAYY 4x50 SE\",0,2,4.47,0.642,0.083,210.0,0.0,0.22,1.0,1,\"cs\",true],[\"l_0_3_2\",\"NAYY 4x50 SE\",0,3,5.65,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"l_0_4_3\",\"NAYY 4x50 SE\",0,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true],[\"l_1_2_4\",\"NAYY 4x50 SE\",1,2,2.0,0.642,0.083,210.0,0.0,0.6,1.0,1,\"cs\",true],[\"l_2_3_5\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"l_2_3_6\",\"NAYY 4x50 SE\",2,3,2.0,0.642,0.083,210.0,0.0,0.3,1.0,1,\"cs\",true],[\"l_3_4_7\",\"NAYY 4x50 SE\",3,4,4.0,0.642,0.083,210.0,0.0,0.16,1.0,1,\"cs\",true]]}", "dtype": { "name": "object", "std_type": "object", diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/init_state.json b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/init_state.json new file mode 100644 index 000000000..3b9b42ef9 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/init_state.json @@ -0,0 +1,4 @@ +{ + "shunt": {"shunt_q": [[0, 0.0]]} +} + diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_p.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_p.csv.bz2 new file mode 100644 index 000000000..cb68d0275 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_p.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_p_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_p_forecasted.csv.bz2 new file mode 100644 index 000000000..19c21de8b Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_q.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_q.csv.bz2 new file mode 100644 index 000000000..35d398131 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_q.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_q_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_q_forecasted.csv.bz2 new file mode 100644 index 000000000..6c262e2d8 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/load_q_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_p.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_p.csv.bz2 new file mode 100644 index 000000000..c2f9e0442 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_p_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_p_forecasted.csv.bz2 new file mode 100644 index 000000000..c79351441 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_v.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_v.csv.bz2 new file mode 100644 index 000000000..79c31540f Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_v_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_v_forecasted.csv.bz2 new file mode 100644 index 000000000..79c31540f Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/start_datetime.info b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/start_datetime.info new file mode 100644 index 000000000..5e520426f --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/start_datetime.info @@ -0,0 +1 @@ +2019-01-11 23:55 diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/time_interval.info b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/time_interval.info new file mode 100644 index 000000000..beb9b9011 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-12/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/init_state.json b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/init_state.json new file mode 100644 index 000000000..1993de4f4 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/init_state.json @@ -0,0 +1,4 @@ +{ + "set_storage": [[0, 5]] +} + diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_p.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_p.csv.bz2 new file mode 100644 index 000000000..488a31b2a Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_p.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_p_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_p_forecasted.csv.bz2 new file mode 100644 index 000000000..8e341212e Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_q.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_q.csv.bz2 new file mode 100644 index 000000000..887ff90b5 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_q.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_q_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_q_forecasted.csv.bz2 new file mode 100644 index 000000000..74f6595f6 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/load_q_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_p.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_p.csv.bz2 new file mode 100644 index 000000000..a8f9567a0 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_p.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_p_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_p_forecasted.csv.bz2 new file mode 100644 index 000000000..8a5119858 Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_p_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_v.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_v.csv.bz2 new file mode 100644 index 000000000..79c31540f Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_v.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_v_forecasted.csv.bz2 b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_v_forecasted.csv.bz2 new file mode 100644 index 000000000..79c31540f Binary files /dev/null and b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/prod_v_forecasted.csv.bz2 differ diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/start_datetime.info b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/start_datetime.info new file mode 100644 index 000000000..d1822dcde --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/start_datetime.info @@ -0,0 +1 @@ +2019-01-12 23:55 diff --git a/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/time_interval.info b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/time_interval.info new file mode 100644 index 000000000..beb9b9011 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/chronics/2019-01-13/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/grid2op/data_test/educ_case14_storage_init_state/config.py b/grid2op/data_test/educ_case14_storage_init_state/config.py new file mode 100644 index 000000000..afefe03d4 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/config.py @@ -0,0 +1,40 @@ +from grid2op.Action import PowerlineChangeDispatchAndStorageAction +from grid2op.Reward import L2RPNReward +from grid2op.Rules import DefaultRules +from grid2op.Chronics import Multifolder +from grid2op.Chronics import GridStateFromFileWithForecasts +from grid2op.Backend import PandaPowerBackend + +config = { + "backend": PandaPowerBackend, + "action_class": PowerlineChangeDispatchAndStorageAction, + "observation_class": None, + "reward_class": L2RPNReward, + "gamerules_class": DefaultRules, + "chronics_class": Multifolder, + "grid_value_class": GridStateFromFileWithForecasts, + "volagecontroler_class": None, + "thermal_limits": [ + 541.0, + 450.0, + 375.0, + 636.0, + 175.0, + 285.0, + 335.0, + 657.0, + 496.0, + 827.0, + 442.0, + 641.0, + 840.0, + 156.0, + 664.0, + 235.0, + 119.0, + 179.0, + 1986.0, + 1572.0, + ], + "names_chronics_to_grid": None, +} diff --git a/grid2op/data_test/educ_case14_storage_init_state/difficulty_levels.json b/grid2op/data_test/educ_case14_storage_init_state/difficulty_levels.json new file mode 100644 index 000000000..da8317445 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/difficulty_levels.json @@ -0,0 +1,58 @@ +{ + "0": { + "NO_OVERFLOW_DISCONNECTION": true, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 9999, + "NB_TIMESTEP_COOLDOWN_SUB": 0, + "NB_TIMESTEP_COOLDOWN_LINE": 0, + "HARD_OVERFLOW_THRESHOLD": 9999, + "NB_TIMESTEP_RECONNECTION": 0, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + }, + "1": { + "NO_OVERFLOW_DISCONNECTION": false, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 6, + "NB_TIMESTEP_COOLDOWN_SUB": 0, + "NB_TIMESTEP_COOLDOWN_LINE": 0, + "HARD_OVERFLOW_THRESHOLD": 3.0, + "NB_TIMESTEP_RECONNECTION": 1, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + }, + "2": { + "NO_OVERFLOW_DISCONNECTION": false, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 3, + "NB_TIMESTEP_COOLDOWN_SUB": 1, + "NB_TIMESTEP_COOLDOWN_LINE": 1, + "HARD_OVERFLOW_THRESHOLD": 2.5, + "NB_TIMESTEP_RECONNECTION": 6, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + }, + "competition": { + "NO_OVERFLOW_DISCONNECTION": false, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 3, + "NB_TIMESTEP_COOLDOWN_SUB": 3, + "NB_TIMESTEP_COOLDOWN_LINE": 3, + "HARD_OVERFLOW_THRESHOLD": 2.0, + "NB_TIMESTEP_RECONNECTION": 12, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + } +} diff --git a/grid2op/data_test/educ_case14_storage_init_state/grid.json b/grid2op/data_test/educ_case14_storage_init_state/grid.json new file mode 100644 index 000000000..2f27b3528 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/grid.json @@ -0,0 +1,1766 @@ +{ + "_module": "pandapower.auxiliary", + "_class": "pandapowerNet", + "_object": { + "bus": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"vn_kv\",\"type\",\"zone\",\"in_service\",\"min_vm_pu\",\"max_vm_pu\"],\"index\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],\"data\":[[1,138.0,\"b\",1.0,true,0.94,1.06],[2,138.0,\"b\",1.0,true,0.94,1.06],[3,138.0,\"b\",1.0,true,0.94,1.06],[4,138.0,\"b\",1.0,true,0.94,1.06],[5,138.0,\"b\",1.0,true,0.94,1.06],[6,20.0,\"b\",1.0,true,0.94,1.06],[7,14.0,\"b\",1.0,true,0.94,1.06],[8,12.0,\"b\",1.0,true,0.94,1.06],[9,20.0,\"b\",1.0,true,0.94,1.06],[10,20.0,\"b\",1.0,true,0.94,1.06],[11,20.0,\"b\",1.0,true,0.94,1.06],[12,20.0,\"b\",1.0,true,0.94,1.06],[13,20.0,\"b\",1.0,true,0.94,1.06],[14,20.0,\"b\",1.0,true,0.94,1.06]]}", + "orient": "split", + "dtype": { + "name": "object", + "vn_kv": "float64", + "type": "object", + "zone": "object", + "in_service": "bool", + "min_vm_pu": "float64", + "max_vm_pu": "float64" + } + }, + "load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"const_z_percent\",\"const_i_percent\",\"sn_mva\",\"scaling\",\"in_service\",\"type\",\"controllable\"],\"index\":[0,1,2,3,4,5,6,7,8,9,10],\"data\":[[null,1,21.699999999999999,12.699999999999999,0.0,0.0,null,1.0,true,null,false],[null,2,94.200000000000003,19.0,0.0,0.0,null,1.0,true,null,false],[null,3,47.799999999999997,-3.9,0.0,0.0,null,1.0,true,null,false],[null,4,7.6,1.6,0.0,0.0,null,1.0,true,null,false],[null,5,11.199999999999999,7.5,0.0,0.0,null,1.0,true,null,false],[null,8,29.5,16.600000000000001,0.0,0.0,null,1.0,true,null,false],[null,9,9.0,5.8,0.0,0.0,null,1.0,true,null,false],[null,10,3.5,1.8,0.0,0.0,null,1.0,true,null,false],[null,11,6.1,1.6,0.0,0.0,null,1.0,true,null,false],[null,12,13.5,5.8,0.0,0.0,null,1.0,true,null,false],[null,13,14.9,5.0,0.0,0.0,null,1.0,true,null,false]]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "p_mw": "float64", + "q_mvar": "float64", + "const_z_percent": "float64", + "const_i_percent": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object", + "controllable": "object" + } + }, + "sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\",\"current_source\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "p_mw": "float64", + "q_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object", + "current_source": "bool" + } + }, + "motor": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"pn_mech_mw\",\"loading_percent\",\"cos_phi\",\"cos_phi_n\",\"efficiency_percent\",\"efficiency_n_percent\",\"lrc_pu\",\"vn_kv\",\"scaling\",\"in_service\",\"rx\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "pn_mech_mw": "float64", + "loading_percent": "float64", + "cos_phi": "float64", + "cos_phi_n": "float64", + "efficiency_percent": "float64", + "efficiency_n_percent": "float64", + "lrc_pu": "float64", + "vn_kv": "float64", + "scaling": "float64", + "in_service": "bool", + "rx": "float64" + } + }, + "asymmetric_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + } + }, + "asymmetric_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\",\"sn_mva\",\"scaling\",\"in_service\",\"type\",\"current_source\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64", + "sn_mva": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object", + "current_source": "bool" + } + }, + "storage": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"q_mvar\",\"sn_mva\",\"soc_percent\",\"min_e_mwh\",\"max_e_mwh\",\"scaling\",\"in_service\",\"type\"],\"index\":[0,1],\"data\":[[null,5,0.0,0.0,null,null,0.0,15.0,1.0,true,null],[null,7,0.0,0.0,null,null,0.0,7.0,1.0,true,null]]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "int64", + "p_mw": "float64", + "q_mvar": "float64", + "sn_mva": "float64", + "soc_percent": "float64", + "min_e_mwh": "float64", + "max_e_mwh": "float64", + "scaling": "float64", + "in_service": "bool", + "type": "object" + } + }, + "gen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"p_mw\",\"vm_pu\",\"sn_mva\",\"min_q_mvar\",\"max_q_mvar\",\"scaling\",\"slack\",\"in_service\",\"type\",\"controllable\",\"min_p_mw\",\"max_p_mw\",\"slack_weight\",\"power_station_trafo\"],\"index\":[0,1,2,3,4,5],\"data\":[[null,1,40.0,1.045,null,-40.0,50.0,1.0,false,true,null,true,0.0,140.0,0.0,null],[null,2,0.0,1.01,null,0.0,40.0,1.0,false,true,null,true,0.0,100.0,0.0,null],[null,5,0.0,1.07,null,-6.0,24.0,1.0,false,true,null,true,0.0,100.0,0.0,null],[null,5,0.0,1.07,null,-6.0,24.0,1.0,false,true,null,true,0.0,100.0,0.0,null],[null,7,0.0,1.09,null,-6.0,24.0,1.0,false,true,null,true,0.0,100.0,0.0,null],[\"gen_0_5\",0,-219.0,1.06,null,-9999.0,9999.0,1.0,true,true,null,true,null,null,1.0,null]]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "p_mw": "float64", + "vm_pu": "float64", + "sn_mva": "float64", + "min_q_mvar": "float64", + "max_q_mvar": "float64", + "scaling": "float64", + "slack": "bool", + "in_service": "bool", + "type": "object", + "controllable": "object", + "min_p_mw": "float64", + "max_p_mw": "float64", + "slack_weight": "float64", + "power_station_trafo": "float64" + } + }, + "switch": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"bus\",\"element\",\"et\",\"type\",\"closed\",\"name\",\"z_ohm\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "bus": "int64", + "element": "int64", + "et": "object", + "type": "object", + "closed": "bool", + "name": "object", + "z_ohm": "float64" + } + }, + "shunt": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"bus\",\"name\",\"q_mvar\",\"p_mw\",\"vn_kv\",\"step\",\"max_step\",\"in_service\"],\"index\":[0],\"data\":[[8,null,-19.0,0.0,20.0,1,1,true]]}", + "orient": "split", + "dtype": { + "bus": "uint32", + "name": "object", + "q_mvar": "float64", + "p_mw": "float64", + "vn_kv": "float64", + "step": "uint32", + "max_step": "uint32", + "in_service": "bool" + } + }, + "line": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"from_bus\",\"to_bus\",\"length_km\",\"r_ohm_per_km\",\"x_ohm_per_km\",\"c_nf_per_km\",\"g_us_per_km\",\"max_i_ka\",\"df\",\"parallel\",\"type\",\"in_service\",\"max_loading_percent\"],\"index\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],\"data\":[[null,null,0,1,1.0,3.6907272,11.2683348,882.522683811391971,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,0,4,1.0,10.2894732,42.475737599999995,822.350682642433412,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,1,2,1.0,8.948775599999999,37.701406800000001,732.092680888995574,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,1,3,1.0,11.0664684,33.578380799999998,568.29112215127509,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,1,4,1.0,10.845558,33.1137072,578.319789012768069,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,2,3,1.0,12.761384400000001,32.570953199999998,213.94489304518595,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,3,4,1.0,2.542374,8.019428400000001,0.0,0.0,41.418606267951418,1.0,1,\"ol\",true,100.0],[null,null,5,10,1.0,0.37992,0.7956,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,5,11,1.0,0.49164,1.02324,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,5,12,1.0,0.2646,0.52108,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,8,9,1.0,0.12724,0.338,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,8,13,1.0,0.50844,1.08152,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,9,10,1.0,0.3282,0.76828,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,11,12,1.0,0.88368,0.79952,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0],[null,null,12,13,1.0,0.68372,1.39208,0.0,0.0,285.788383248864761,1.0,1,\"ol\",true,100.0]]}", + "orient": "split", + "dtype": { + "name": "object", + "std_type": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "length_km": "float64", + "r_ohm_per_km": "float64", + "x_ohm_per_km": "float64", + "c_nf_per_km": "float64", + "g_us_per_km": "float64", + "max_i_ka": "float64", + "df": "float64", + "parallel": "uint32", + "type": "object", + "in_service": "bool", + "max_loading_percent": "float64" + } + }, + "trafo": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"hv_bus\",\"lv_bus\",\"sn_mva\",\"vn_hv_kv\",\"vn_lv_kv\",\"vk_percent\",\"vkr_percent\",\"pfe_kw\",\"i0_percent\",\"shift_degree\",\"tap_side\",\"tap_neutral\",\"tap_min\",\"tap_max\",\"tap_step_percent\",\"tap_step_degree\",\"tap_pos\",\"tap_phase_shifter\",\"parallel\",\"df\",\"in_service\",\"max_loading_percent\"],\"index\":[0,1,2,3,4],\"data\":[[null,null,3,6,9900.0,138.0,14.0,2070.288000000000011,0.0,0.0,0.0,0.0,\"hv\",0,null,null,2.200000000000002,0.0,-1,false,1,1.0,true,100.0],[null,null,3,8,9900.0,138.0,20.0,5506.181999999999789,0.0,0.0,0.0,0.0,\"hv\",0,null,null,3.100000000000003,0.0,-1,false,1,1.0,true,100.0],[null,null,4,5,9900.0,138.0,20.0,2494.998000000000047,0.0,0.0,0.0,0.0,\"hv\",0,null,null,6.799999999999995,0.0,-1,false,1,1.0,true,100.0],[null,null,6,7,9900.0,14.0,12.0,1743.884999999999991,0.0,0.0,0.0,0.0,false,0,null,null,0.0,0.0,0,false,1,1.0,true,100.0],[null,null,8,6,9900.0,20.0,14.0,1089.098999999999933,0.0,0.0,0.0,0.0,false,0,null,null,0.0,0.0,0,false,1,1.0,true,100.0]]}", + "orient": "split", + "dtype": { + "name": "object", + "std_type": "object", + "hv_bus": "uint32", + "lv_bus": "uint32", + "sn_mva": "float64", + "vn_hv_kv": "float64", + "vn_lv_kv": "float64", + "vk_percent": "float64", + "vkr_percent": "float64", + "pfe_kw": "float64", + "i0_percent": "float64", + "shift_degree": "float64", + "tap_side": "object", + "tap_neutral": "int32", + "tap_min": "float64", + "tap_max": "float64", + "tap_step_percent": "float64", + "tap_step_degree": "float64", + "tap_pos": "int32", + "tap_phase_shifter": "bool", + "parallel": "uint32", + "df": "float64", + "in_service": "bool", + "max_loading_percent": "float64" + } + }, + "trafo3w": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"std_type\",\"hv_bus\",\"mv_bus\",\"lv_bus\",\"sn_hv_mva\",\"sn_mv_mva\",\"sn_lv_mva\",\"vn_hv_kv\",\"vn_mv_kv\",\"vn_lv_kv\",\"vk_hv_percent\",\"vk_mv_percent\",\"vk_lv_percent\",\"vkr_hv_percent\",\"vkr_mv_percent\",\"vkr_lv_percent\",\"pfe_kw\",\"i0_percent\",\"shift_mv_degree\",\"shift_lv_degree\",\"tap_side\",\"tap_neutral\",\"tap_min\",\"tap_max\",\"tap_step_percent\",\"tap_step_degree\",\"tap_pos\",\"tap_at_star_point\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "std_type": "object", + "hv_bus": "uint32", + "mv_bus": "uint32", + "lv_bus": "uint32", + "sn_hv_mva": "float64", + "sn_mv_mva": "float64", + "sn_lv_mva": "float64", + "vn_hv_kv": "float64", + "vn_mv_kv": "float64", + "vn_lv_kv": "float64", + "vk_hv_percent": "float64", + "vk_mv_percent": "float64", + "vk_lv_percent": "float64", + "vkr_hv_percent": "float64", + "vkr_mv_percent": "float64", + "vkr_lv_percent": "float64", + "pfe_kw": "float64", + "i0_percent": "float64", + "shift_mv_degree": "float64", + "shift_lv_degree": "float64", + "tap_side": "object", + "tap_neutral": "int32", + "tap_min": "int32", + "tap_max": "int32", + "tap_step_percent": "float64", + "tap_step_degree": "float64", + "tap_pos": "int32", + "tap_at_star_point": "bool", + "in_service": "bool" + } + }, + "impedance": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"from_bus\",\"to_bus\",\"rft_pu\",\"xft_pu\",\"rtf_pu\",\"xtf_pu\",\"sn_mva\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "rft_pu": "float64", + "xft_pu": "float64", + "rtf_pu": "float64", + "xtf_pu": "float64", + "sn_mva": "float64", + "in_service": "bool" + } + }, + "dcline": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"from_bus\",\"to_bus\",\"p_mw\",\"loss_percent\",\"loss_mw\",\"vm_from_pu\",\"vm_to_pu\",\"max_p_mw\",\"min_q_from_mvar\",\"min_q_to_mvar\",\"max_q_from_mvar\",\"max_q_to_mvar\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "from_bus": "uint32", + "to_bus": "uint32", + "p_mw": "float64", + "loss_percent": "float64", + "loss_mw": "float64", + "vm_from_pu": "float64", + "vm_to_pu": "float64", + "max_p_mw": "float64", + "min_q_from_mvar": "float64", + "min_q_to_mvar": "float64", + "max_q_from_mvar": "float64", + "max_q_to_mvar": "float64", + "in_service": "bool" + } + }, + "ward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"ps_mw\",\"qs_mvar\",\"qz_mvar\",\"pz_mw\",\"in_service\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "ps_mw": "float64", + "qs_mvar": "float64", + "qz_mvar": "float64", + "pz_mw": "float64", + "in_service": "bool" + } + }, + "xward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"bus\",\"ps_mw\",\"qs_mvar\",\"qz_mvar\",\"pz_mw\",\"r_ohm\",\"x_ohm\",\"vm_pu\",\"in_service\",\"slack_weight\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "bus": "uint32", + "ps_mw": "float64", + "qs_mvar": "float64", + "qz_mvar": "float64", + "pz_mw": "float64", + "r_ohm": "float64", + "x_ohm": "float64", + "vm_pu": "float64", + "in_service": "bool", + "slack_weight": "float64" + } + }, + "measurement": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"name\",\"measurement_type\",\"element_type\",\"element\",\"value\",\"std_dev\",\"side\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "name": "object", + "measurement_type": "object", + "element_type": "object", + "element": "uint32", + "value": "float64", + "std_dev": "float64", + "side": "object" + } + }, + "pwl_cost": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"power_type\",\"element\",\"et\",\"points\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "power_type": "object", + "element": "uint32", + "et": "object", + "points": "object" + } + }, + "poly_cost": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"element\",\"et\",\"cp0_eur\",\"cp1_eur_per_mw\",\"cp2_eur_per_mw2\",\"cq0_eur\",\"cq1_eur_per_mvar\",\"cq2_eur_per_mvar2\"],\"index\":[0,1,2,3,4],\"data\":[[0,\"ext_grid\",0.0,20.0,0.0430293,0.0,0.0,0.0],[0,\"gen\",0.0,20.0,0.25,0.0,0.0,0.0],[1,\"gen\",0.0,40.0,0.01,0.0,0.0,0.0],[2,\"gen\",0.0,40.0,0.01,0.0,0.0,0.0],[3,\"gen\",0.0,40.0,0.01,0.0,0.0,0.0]]}", + "orient": "split", + "dtype": { + "element": "uint32", + "et": "object", + "cp0_eur": "float64", + "cp1_eur_per_mw": "float64", + "cp2_eur_per_mw2": "float64", + "cq0_eur": "float64", + "cq1_eur_per_mvar": "float64", + "cq2_eur_per_mvar2": "float64" + } + }, + "characteristic": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"object\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "object": "object" + } + }, + "controller": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"object\",\"in_service\",\"order\",\"level\",\"initial_run\",\"recycle\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "object": "object", + "in_service": "bool", + "order": "float64", + "level": "object", + "initial_run": "bool", + "recycle": "object" + } + }, + "line_geodata": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"coords\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "coords": "object" + } + }, + "bus_geodata": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"x\",\"y\",\"coords\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "x": "float64", + "y": "float64", + "coords": "object" + } + }, + "version": "2.8.0", + "converged": false, + "name": "", + "f_hz": 50, + "sn_mva": 1.0, + "std_types": { + "line": { + "NAYY 4x50 SE": { + "c_nf_per_km": 210, + "r_ohm_per_km": 0.642, + "x_ohm_per_km": 0.083, + "max_i_ka": 0.142, + "type": "cs", + "q_mm2": 50, + "alpha": 0.00403 + }, + "NAYY 4x120 SE": { + "c_nf_per_km": 264, + "r_ohm_per_km": 0.225, + "x_ohm_per_km": 0.08, + "max_i_ka": 0.242, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NAYY 4x150 SE": { + "c_nf_per_km": 261, + "r_ohm_per_km": 0.208, + "x_ohm_per_km": 0.08, + "max_i_ka": 0.27, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x95 RM/25 12/20 kV": { + "c_nf_per_km": 216, + "r_ohm_per_km": 0.313, + "x_ohm_per_km": 0.132, + "max_i_ka": 0.252, + "type": "cs", + "q_mm2": 95, + "alpha": 0.00403 + }, + "NA2XS2Y 1x185 RM/25 12/20 kV": { + "c_nf_per_km": 273, + "r_ohm_per_km": 0.161, + "x_ohm_per_km": 0.117, + "max_i_ka": 0.362, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00403 + }, + "NA2XS2Y 1x240 RM/25 12/20 kV": { + "c_nf_per_km": 304, + "r_ohm_per_km": 0.122, + "x_ohm_per_km": 0.112, + "max_i_ka": 0.421, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00403 + }, + "NA2XS2Y 1x95 RM/25 6/10 kV": { + "c_nf_per_km": 315, + "r_ohm_per_km": 0.313, + "x_ohm_per_km": 0.123, + "max_i_ka": 0.249, + "type": "cs", + "q_mm2": 95, + "alpha": 0.00403 + }, + "NA2XS2Y 1x185 RM/25 6/10 kV": { + "c_nf_per_km": 406, + "r_ohm_per_km": 0.161, + "x_ohm_per_km": 0.11, + "max_i_ka": 0.358, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00403 + }, + "NA2XS2Y 1x240 RM/25 6/10 kV": { + "c_nf_per_km": 456, + "r_ohm_per_km": 0.122, + "x_ohm_per_km": 0.105, + "max_i_ka": 0.416, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00403 + }, + "NA2XS2Y 1x150 RM/25 12/20 kV": { + "c_nf_per_km": 250, + "r_ohm_per_km": 0.206, + "x_ohm_per_km": 0.116, + "max_i_ka": 0.319, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x120 RM/25 12/20 kV": { + "c_nf_per_km": 230, + "r_ohm_per_km": 0.253, + "x_ohm_per_km": 0.119, + "max_i_ka": 0.283, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NA2XS2Y 1x70 RM/25 12/20 kV": { + "c_nf_per_km": 190, + "r_ohm_per_km": 0.443, + "x_ohm_per_km": 0.132, + "max_i_ka": 0.22, + "type": "cs", + "q_mm2": 70, + "alpha": 0.00403 + }, + "NA2XS2Y 1x150 RM/25 6/10 kV": { + "c_nf_per_km": 360, + "r_ohm_per_km": 0.206, + "x_ohm_per_km": 0.11, + "max_i_ka": 0.315, + "type": "cs", + "q_mm2": 150, + "alpha": 0.00403 + }, + "NA2XS2Y 1x120 RM/25 6/10 kV": { + "c_nf_per_km": 340, + "r_ohm_per_km": 0.253, + "x_ohm_per_km": 0.113, + "max_i_ka": 0.28, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00403 + }, + "NA2XS2Y 1x70 RM/25 6/10 kV": { + "c_nf_per_km": 280, + "r_ohm_per_km": 0.443, + "x_ohm_per_km": 0.123, + "max_i_ka": 0.217, + "type": "cs", + "q_mm2": 70, + "alpha": 0.00403 + }, + "N2XS(FL)2Y 1x120 RM/35 64/110 kV": { + "c_nf_per_km": 112, + "r_ohm_per_km": 0.153, + "x_ohm_per_km": 0.166, + "max_i_ka": 0.366, + "type": "cs", + "q_mm2": 120, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x185 RM/35 64/110 kV": { + "c_nf_per_km": 125, + "r_ohm_per_km": 0.099, + "x_ohm_per_km": 0.156, + "max_i_ka": 0.457, + "type": "cs", + "q_mm2": 185, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x240 RM/35 64/110 kV": { + "c_nf_per_km": 135, + "r_ohm_per_km": 0.075, + "x_ohm_per_km": 0.149, + "max_i_ka": 0.526, + "type": "cs", + "q_mm2": 240, + "alpha": 0.00393 + }, + "N2XS(FL)2Y 1x300 RM/35 64/110 kV": { + "c_nf_per_km": 144, + "r_ohm_per_km": 0.06, + "x_ohm_per_km": 0.144, + "max_i_ka": 0.588, + "type": "cs", + "q_mm2": 300, + "alpha": 0.00393 + }, + "15-AL1/3-ST1A 0.4": { + "c_nf_per_km": 11, + "r_ohm_per_km": 1.8769, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.105, + "type": "ol", + "q_mm2": 16, + "alpha": 0.00403 + }, + "24-AL1/4-ST1A 0.4": { + "c_nf_per_km": 11.25, + "r_ohm_per_km": 1.2012, + "x_ohm_per_km": 0.335, + "max_i_ka": 0.14, + "type": "ol", + "q_mm2": 24, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 0.4": { + "c_nf_per_km": 12.2, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.3, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 0.4": { + "c_nf_per_km": 13.2, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.29, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "34-AL1/6-ST1A 10.0": { + "c_nf_per_km": 9.7, + "r_ohm_per_km": 0.8342, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.17, + "type": "ol", + "q_mm2": 34, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 10.0": { + "c_nf_per_km": 10.1, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 10.0": { + "c_nf_per_km": 10.4, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.339, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 10.0": { + "c_nf_per_km": 10.75, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.33, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 10.0": { + "c_nf_per_km": 11.1, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.323, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 10.0": { + "c_nf_per_km": 11.25, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.315, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "34-AL1/6-ST1A 20.0": { + "c_nf_per_km": 9.15, + "r_ohm_per_km": 0.8342, + "x_ohm_per_km": 0.382, + "max_i_ka": 0.17, + "type": "ol", + "q_mm2": 34, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 20.0": { + "c_nf_per_km": 9.5, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.372, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 20.0": { + "c_nf_per_km": 9.7, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.36, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 20.0": { + "c_nf_per_km": 10, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.35, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 20.0": { + "c_nf_per_km": 10.3, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.344, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 20.0": { + "c_nf_per_km": 10.5, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.337, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "184-AL1/30-ST1A 20.0": { + "c_nf_per_km": 10.75, + "r_ohm_per_km": 0.1571, + "x_ohm_per_km": 0.33, + "max_i_ka": 0.535, + "type": "ol", + "q_mm2": 184, + "alpha": 0.00403 + }, + "243-AL1/39-ST1A 20.0": { + "c_nf_per_km": 11, + "r_ohm_per_km": 0.1188, + "x_ohm_per_km": 0.32, + "max_i_ka": 0.645, + "type": "ol", + "q_mm2": 243, + "alpha": 0.00403 + }, + "48-AL1/8-ST1A 110.0": { + "c_nf_per_km": 8, + "r_ohm_per_km": 0.5939, + "x_ohm_per_km": 0.46, + "max_i_ka": 0.21, + "type": "ol", + "q_mm2": 48, + "alpha": 0.00403 + }, + "70-AL1/11-ST1A 110.0": { + "c_nf_per_km": 8.4, + "r_ohm_per_km": 0.4132, + "x_ohm_per_km": 0.45, + "max_i_ka": 0.29, + "type": "ol", + "q_mm2": 70, + "alpha": 0.00403 + }, + "94-AL1/15-ST1A 110.0": { + "c_nf_per_km": 8.65, + "r_ohm_per_km": 0.306, + "x_ohm_per_km": 0.44, + "max_i_ka": 0.35, + "type": "ol", + "q_mm2": 94, + "alpha": 0.00403 + }, + "122-AL1/20-ST1A 110.0": { + "c_nf_per_km": 8.5, + "r_ohm_per_km": 0.2376, + "x_ohm_per_km": 0.43, + "max_i_ka": 0.41, + "type": "ol", + "q_mm2": 122, + "alpha": 0.00403 + }, + "149-AL1/24-ST1A 110.0": { + "c_nf_per_km": 8.75, + "r_ohm_per_km": 0.194, + "x_ohm_per_km": 0.41, + "max_i_ka": 0.47, + "type": "ol", + "q_mm2": 149, + "alpha": 0.00403 + }, + "184-AL1/30-ST1A 110.0": { + "c_nf_per_km": 8.8, + "r_ohm_per_km": 0.1571, + "x_ohm_per_km": 0.4, + "max_i_ka": 0.535, + "type": "ol", + "q_mm2": 184, + "alpha": 0.00403 + }, + "243-AL1/39-ST1A 110.0": { + "c_nf_per_km": 9, + "r_ohm_per_km": 0.1188, + "x_ohm_per_km": 0.39, + "max_i_ka": 0.645, + "type": "ol", + "q_mm2": 243, + "alpha": 0.00403 + }, + "305-AL1/39-ST1A 110.0": { + "c_nf_per_km": 9.2, + "r_ohm_per_km": 0.0949, + "x_ohm_per_km": 0.38, + "max_i_ka": 0.74, + "type": "ol", + "q_mm2": 305, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 110.0": { + "c_nf_per_km": 9.75, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.37, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 110.0": { + "c_nf_per_km": 9.95, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.36, + "max_i_ka": 1.15, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 220.0": { + "c_nf_per_km": 10, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.285, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 220.0": { + "c_nf_per_km": 11.7, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.275, + "max_i_ka": 1.15, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + }, + "490-AL1/64-ST1A 380.0": { + "c_nf_per_km": 11, + "r_ohm_per_km": 0.059, + "x_ohm_per_km": 0.253, + "max_i_ka": 0.96, + "type": "ol", + "q_mm2": 490, + "alpha": 0.00403 + }, + "679-AL1/86-ST1A 380.0": { + "c_nf_per_km": 14.6, + "r_ohm_per_km": 0.042, + "x_ohm_per_km": 0.25, + "max_i_ka": 1.15, + "type": "ol", + "q_mm2": 679, + "alpha": 0.00403 + } + }, + "trafo": { + "160 MVA 380/110 kV": { + "i0_percent": 0.06, + "pfe_kw": 60, + "vkr_percent": 0.25, + "sn_mva": 160, + "vn_lv_kv": 110.0, + "vn_hv_kv": 380.0, + "vk_percent": 12.2, + "shift_degree": 0, + "vector_group": "Yy0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "100 MVA 220/110 kV": { + "i0_percent": 0.06, + "pfe_kw": 55, + "vkr_percent": 0.26, + "sn_mva": 100, + "vn_lv_kv": 110.0, + "vn_hv_kv": 220.0, + "vk_percent": 12.0, + "shift_degree": 0, + "vector_group": "Yy0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "63 MVA 110/20 kV": { + "i0_percent": 0.04, + "pfe_kw": 22, + "vkr_percent": 0.32, + "sn_mva": 63, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 18, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "40 MVA 110/20 kV": { + "i0_percent": 0.05, + "pfe_kw": 18, + "vkr_percent": 0.34, + "sn_mva": 40, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 16.2, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "25 MVA 110/20 kV": { + "i0_percent": 0.07, + "pfe_kw": 14, + "vkr_percent": 0.41, + "sn_mva": 25, + "vn_lv_kv": 20.0, + "vn_hv_kv": 110.0, + "vk_percent": 12, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "63 MVA 110/10 kV": { + "sn_mva": 63, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 18, + "vkr_percent": 0.32, + "pfe_kw": 22, + "i0_percent": 0.04, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "40 MVA 110/10 kV": { + "sn_mva": 40, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 16.2, + "vkr_percent": 0.34, + "pfe_kw": 18, + "i0_percent": 0.05, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "25 MVA 110/10 kV": { + "sn_mva": 25, + "vn_hv_kv": 110, + "vn_lv_kv": 10, + "vk_percent": 12, + "vkr_percent": 0.41, + "pfe_kw": 14, + "i0_percent": 0.07, + "shift_degree": 150, + "vector_group": "YNd5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -9, + "tap_max": 9, + "tap_step_degree": 0, + "tap_step_percent": 1.5, + "tap_phase_shifter": false + }, + "0.25 MVA 20/0.4 kV": { + "sn_mva": 0.25, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.44, + "pfe_kw": 0.8, + "i0_percent": 0.32, + "shift_degree": 150, + "vector_group": "Yzn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.4 MVA 20/0.4 kV": { + "sn_mva": 0.4, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.425, + "pfe_kw": 1.35, + "i0_percent": 0.3375, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.63 MVA 20/0.4 kV": { + "sn_mva": 0.63, + "vn_hv_kv": 20, + "vn_lv_kv": 0.4, + "vk_percent": 6, + "vkr_percent": 1.206, + "pfe_kw": 1.65, + "i0_percent": 0.2619, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.25 MVA 10/0.4 kV": { + "sn_mva": 0.25, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.2, + "pfe_kw": 0.6, + "i0_percent": 0.24, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.4 MVA 10/0.4 kV": { + "sn_mva": 0.4, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.325, + "pfe_kw": 0.95, + "i0_percent": 0.2375, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + }, + "0.63 MVA 10/0.4 kV": { + "sn_mva": 0.63, + "vn_hv_kv": 10, + "vn_lv_kv": 0.4, + "vk_percent": 4, + "vkr_percent": 1.0794, + "pfe_kw": 1.18, + "i0_percent": 0.1873, + "shift_degree": 150, + "vector_group": "Dyn5", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -2, + "tap_max": 2, + "tap_step_degree": 0, + "tap_step_percent": 2.5, + "tap_phase_shifter": false + } + }, + "trafo3w": { + "63/25/38 MVA 110/20/10 kV": { + "sn_hv_mva": 63, + "sn_mv_mva": 25, + "sn_lv_mva": 38, + "vn_hv_kv": 110, + "vn_mv_kv": 20, + "vn_lv_kv": 10, + "vk_hv_percent": 10.4, + "vk_mv_percent": 10.4, + "vk_lv_percent": 10.4, + "vkr_hv_percent": 0.28, + "vkr_mv_percent": 0.32, + "vkr_lv_percent": 0.35, + "pfe_kw": 35, + "i0_percent": 0.89, + "shift_mv_degree": 0, + "shift_lv_degree": 0, + "vector_group": "YN0yn0yn0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -10, + "tap_max": 10, + "tap_step_percent": 1.2 + }, + "63/25/38 MVA 110/10/10 kV": { + "sn_hv_mva": 63, + "sn_mv_mva": 25, + "sn_lv_mva": 38, + "vn_hv_kv": 110, + "vn_mv_kv": 10, + "vn_lv_kv": 10, + "vk_hv_percent": 10.4, + "vk_mv_percent": 10.4, + "vk_lv_percent": 10.4, + "vkr_hv_percent": 0.28, + "vkr_mv_percent": 0.32, + "vkr_lv_percent": 0.35, + "pfe_kw": 35, + "i0_percent": 0.89, + "shift_mv_degree": 0, + "shift_lv_degree": 0, + "vector_group": "YN0yn0yn0", + "tap_side": "hv", + "tap_neutral": 0, + "tap_min": -10, + "tap_max": 10, + "tap_step_percent": 1.2 + } + } + }, + "res_bus": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_pu\",\"va_degree\",\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "vm_pu": "float64", + "va_degree": "float64", + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_line": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\",\"i_ka\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64", + "i_ka": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo3w": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_mv_mw\",\"q_mv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_mv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_mv_pu\",\"va_mv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"va_internal_degree\",\"vm_internal_pu\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_mv_mw": "float64", + "q_mv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_mv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_mv_pu": "float64", + "va_mv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64", + "loading_percent": "float64" + } + }, + "res_impedance": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64" + } + }, + "res_ext_grid": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_motor": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_storage": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_shunt": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64" + } + }, + "res_gen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"va_degree\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "va_degree": "float64", + "vm_pu": "float64" + } + }, + "res_ward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64" + } + }, + "res_xward": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\",\"vm_pu\",\"va_internal_degree\",\"vm_internal_pu\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64", + "vm_pu": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64" + } + }, + "res_dcline": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64" + } + }, + "res_asymmetric_load": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_asymmetric_sgen": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_bus_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_pu\",\"va_degree\",\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "vm_pu": "float64", + "va_degree": "float64", + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_line_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\",\"i_ka\",\"vm_from_pu\",\"va_from_degree\",\"vm_to_pu\",\"va_to_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64", + "i_ka": "float64", + "vm_from_pu": "float64", + "va_from_degree": "float64", + "vm_to_pu": "float64", + "va_to_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "loading_percent": "float64" + } + }, + "res_trafo3w_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_hv_mw\",\"q_hv_mvar\",\"p_mv_mw\",\"q_mv_mvar\",\"p_lv_mw\",\"q_lv_mvar\",\"pl_mw\",\"ql_mvar\",\"i_hv_ka\",\"i_mv_ka\",\"i_lv_ka\",\"vm_hv_pu\",\"va_hv_degree\",\"vm_mv_pu\",\"va_mv_degree\",\"vm_lv_pu\",\"va_lv_degree\",\"va_internal_degree\",\"vm_internal_pu\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_hv_mw": "float64", + "q_hv_mvar": "float64", + "p_mv_mw": "float64", + "q_mv_mvar": "float64", + "p_lv_mw": "float64", + "q_lv_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_hv_ka": "float64", + "i_mv_ka": "float64", + "i_lv_ka": "float64", + "vm_hv_pu": "float64", + "va_hv_degree": "float64", + "vm_mv_pu": "float64", + "va_mv_degree": "float64", + "vm_lv_pu": "float64", + "va_lv_degree": "float64", + "va_internal_degree": "float64", + "vm_internal_pu": "float64", + "loading_percent": "float64" + } + }, + "res_impedance_est": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_from_mw\",\"q_from_mvar\",\"p_to_mw\",\"q_to_mvar\",\"pl_mw\",\"ql_mvar\",\"i_from_ka\",\"i_to_ka\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_from_mw": "float64", + "q_from_mvar": "float64", + "p_to_mw": "float64", + "q_to_mvar": "float64", + "pl_mw": "float64", + "ql_mvar": "float64", + "i_from_ka": "float64", + "i_to_ka": "float64" + } + }, + "res_bus_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_line_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_trafo_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_trafo3w_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_ext_grid_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_gen_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_sgen_sc": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_bus_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"vm_a_pu\",\"va_a_degree\",\"vm_b_pu\",\"va_b_degree\",\"vm_c_pu\",\"va_c_degree\",\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "vm_a_pu": "float64", + "va_a_degree": "float64", + "vm_b_pu": "float64", + "va_b_degree": "float64", + "vm_c_pu": "float64", + "va_c_degree": "float64", + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "res_line_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_from_mw\",\"q_a_from_mvar\",\"p_b_from_mw\",\"q_b_from_mvar\",\"q_c_from_mvar\",\"p_a_to_mw\",\"q_a_to_mvar\",\"p_b_to_mw\",\"q_b_to_mvar\",\"p_c_to_mw\",\"q_c_to_mvar\",\"p_a_l_mw\",\"q_a_l_mvar\",\"p_b_l_mw\",\"q_b_l_mvar\",\"p_c_l_mw\",\"q_c_l_mvar\",\"i_a_from_ka\",\"i_a_to_ka\",\"i_b_from_ka\",\"i_b_to_ka\",\"i_c_from_ka\",\"i_c_to_ka\",\"i_a_ka\",\"i_b_ka\",\"i_c_ka\",\"i_n_from_ka\",\"i_n_to_ka\",\"i_n_ka\",\"loading_a_percent\",\"loading_b_percent\",\"loading_c_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_from_mw": "float64", + "q_a_from_mvar": "float64", + "p_b_from_mw": "float64", + "q_b_from_mvar": "float64", + "q_c_from_mvar": "float64", + "p_a_to_mw": "float64", + "q_a_to_mvar": "float64", + "p_b_to_mw": "float64", + "q_b_to_mvar": "float64", + "p_c_to_mw": "float64", + "q_c_to_mvar": "float64", + "p_a_l_mw": "float64", + "q_a_l_mvar": "float64", + "p_b_l_mw": "float64", + "q_b_l_mvar": "float64", + "p_c_l_mw": "float64", + "q_c_l_mvar": "float64", + "i_a_from_ka": "float64", + "i_a_to_ka": "float64", + "i_b_from_ka": "float64", + "i_b_to_ka": "float64", + "i_c_from_ka": "float64", + "i_c_to_ka": "float64", + "i_a_ka": "float64", + "i_b_ka": "float64", + "i_c_ka": "float64", + "i_n_from_ka": "float64", + "i_n_to_ka": "float64", + "i_n_ka": "float64", + "loading_a_percent": "float64", + "loading_b_percent": "float64", + "loading_c_percent": "float64" + } + }, + "res_trafo_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_hv_mw\",\"q_a_hv_mvar\",\"p_b_hv_mw\",\"q_b_hv_mvar\",\"p_c_hv_mw\",\"q_c_hv_mvar\",\"p_a_lv_mw\",\"q_a_lv_mvar\",\"p_b_lv_mw\",\"q_b_lv_mvar\",\"p_c_lv_mw\",\"q_c_lv_mvar\",\"p_a_l_mw\",\"q_a_l_mvar\",\"p_b_l_mw\",\"q_b_l_mvar\",\"p_c_l_mw\",\"q_c_l_mvar\",\"i_a_hv_ka\",\"i_a_lv_ka\",\"i_b_hv_ka\",\"i_b_lv_ka\",\"i_c_hv_ka\",\"i_c_lv_ka\",\"loading_a_percent\",\"loading_b_percent\",\"loading_c_percent\",\"loading_percent\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_hv_mw": "float64", + "q_a_hv_mvar": "float64", + "p_b_hv_mw": "float64", + "q_b_hv_mvar": "float64", + "p_c_hv_mw": "float64", + "q_c_hv_mvar": "float64", + "p_a_lv_mw": "float64", + "q_a_lv_mvar": "float64", + "p_b_lv_mw": "float64", + "q_b_lv_mvar": "float64", + "p_c_lv_mw": "float64", + "q_c_lv_mvar": "float64", + "p_a_l_mw": "float64", + "q_a_l_mvar": "float64", + "p_b_l_mw": "float64", + "q_b_l_mvar": "float64", + "p_c_l_mw": "float64", + "q_c_l_mvar": "float64", + "i_a_hv_ka": "float64", + "i_a_lv_ka": "float64", + "i_b_hv_ka": "float64", + "i_b_lv_ka": "float64", + "i_c_hv_ka": "float64", + "i_c_lv_ka": "float64", + "loading_a_percent": "float64", + "loading_b_percent": "float64", + "loading_c_percent": "float64", + "loading_percent": "float64" + } + }, + "res_ext_grid_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "res_shunt_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[],\"index\":[],\"data\":[]}", + "orient": "split" + }, + "res_load_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_sgen_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_storage_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_mw\",\"q_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_mw": "float64", + "q_mvar": "float64" + } + }, + "res_asymmetric_load_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "res_asymmetric_sgen_3ph": { + "_module": "pandas.core.frame", + "_class": "DataFrame", + "_object": "{\"columns\":[\"p_a_mw\",\"q_a_mvar\",\"p_b_mw\",\"q_b_mvar\",\"p_c_mw\",\"q_c_mvar\"],\"index\":[],\"data\":[]}", + "orient": "split", + "dtype": { + "p_a_mw": "float64", + "q_a_mvar": "float64", + "p_b_mw": "float64", + "q_b_mvar": "float64", + "p_c_mw": "float64", + "q_c_mvar": "float64" + } + }, + "user_pf_options": {} + } +} \ No newline at end of file diff --git a/grid2op/data_test/educ_case14_storage_init_state/grid_layout.json b/grid2op/data_test/educ_case14_storage_init_state/grid_layout.json new file mode 100644 index 000000000..e1534647f --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/grid_layout.json @@ -0,0 +1,58 @@ +{ + "sub_0": [ + -280.0, + -81.0 + ], + "sub_1": [ + -100.0, + -270.0 + ], + "sub_2": [ + 366.0, + -270.0 + ], + "sub_3": [ + 366.0, + -54.0 + ], + "sub_4": [ + -64.0, + -54.0 + ], + "sub_5": [ + -64.0, + 54.0 + ], + "sub_6": [ + 450.0, + 0.0 + ], + "sub_7": [ + 550.0, + 0.0 + ], + "sub_8": [ + 326.0, + 54.0 + ], + "sub_9": [ + 222.0, + 108.0 + ], + "sub_10": [ + 79.0, + 162.0 + ], + "sub_11": [ + -170.0, + 270.0 + ], + "sub_12": [ + -64.0, + 270.0 + ], + "sub_13": [ + 222.0, + 216.0 + ] +} diff --git a/grid2op/data_test/educ_case14_storage_init_state/prods_charac.csv b/grid2op/data_test/educ_case14_storage_init_state/prods_charac.csv new file mode 100644 index 000000000..0c1159a06 --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/prods_charac.csv @@ -0,0 +1,7 @@ +Pmax,Pmin,name,type,bus,max_ramp_up,max_ramp_down,min_up_time,min_down_time,marginal_cost,shut_down_cost,start_cost,x,y,V +140,0.0,gen_1_0,nuclear,1,5,5,96,96,40,10,20,180,10,142.1 +120,0.0,gen_2_1,thermal,2,10,10,4,4,70,1,2,646,10,142.1 +70,0.0,gen_5_2,wind,5,0,0,0,0,0,0,0,216,334,22.0 +70,0.0,gen_5_3,solar,5,0,0,0,0,0,0,0,216,334,22.0 +40,0.0,gen_7_4,solar,7,0,0,0,0,0,0,0,718,280,13.2 +100,0.0,gen_0_5,hydro,0,15,15,4,4,70,1,2,0,199,142.1 diff --git a/grid2op/data_test/educ_case14_storage_init_state/storage_units_charac.csv b/grid2op/data_test/educ_case14_storage_init_state/storage_units_charac.csv new file mode 100644 index 000000000..0bb5168fb --- /dev/null +++ b/grid2op/data_test/educ_case14_storage_init_state/storage_units_charac.csv @@ -0,0 +1,3 @@ +Emax,Emin,name,type,max_p_prod,max_p_absorb,marginal_cost,power_loss,charging_efficiency,discharging_efficiency +15,0,storage_5_0,battery,5,5,20,0.1,0.95,1 +7,0,storage_7_1,battery,10,10,20,0.1,1,0.9 diff --git a/grid2op/gym_compat/gymenv.py b/grid2op/gym_compat/gymenv.py index b0325d797..15446e6b8 100644 --- a/grid2op/gym_compat/gymenv.py +++ b/grid2op/gym_compat/gymenv.py @@ -108,10 +108,11 @@ def __init__(self, env_init: Environment, shuffle_chronics:Optional[bool]=True, render_mode: Literal["rgb_array"]="rgb_array"): - check_gym_version(type(self)._gymnasium) + cls = type(self) + check_gym_version(cls._gymnasium) self.init_env = env_init.copy() - self.action_space = type(self)._ActionSpaceType(self.init_env) - self.observation_space = type(self)._ObservationSpaceType(self.init_env) + self.action_space = cls._ActionSpaceType(self.init_env) + self.observation_space = cls._ObservationSpaceType(self.init_env) self.reward_range = self.init_env.reward_range self.metadata = self.init_env.metadata self.init_env.render_mode = render_mode @@ -141,7 +142,7 @@ def _aux_step_new(self, gym_action: ActType) -> Tuple[ObsType, float, bool, bool def _aux_reset(self, seed: Optional[int]=None, return_info: Optional[bool]=None, - options: Optional[Dict[Any, Any]]=None) -> Union[ObsType, Tuple[ObsType, RESET_INFO_GYM_TYPING]]: + options: RESET_OPTIONS_TYPING=None) -> Union[ObsType, Tuple[ObsType, RESET_INFO_GYM_TYPING]]: # used for gym < 0.26 if self._shuffle_chronics and isinstance( self.init_env.chronics_handler.real_data, Multifolder @@ -151,7 +152,7 @@ def _aux_reset(self, if seed is not None: seed_, next_seed, underlying_env_seeds = self._aux_seed(seed) - g2op_obs = self.init_env.reset() + g2op_obs = self.init_env.reset(options=options) gym_obs = self.observation_space.to_gym(g2op_obs) if return_info: @@ -300,6 +301,23 @@ def reset(self, ObsType, RESET_INFO_GYM_TYPING ]: + """This function will reset the underlying grid2op environment + and return the next state of the grid (as the gymnasium observation) + and some other information. + + Parameters + ---------- + seed : Optional[int], optional + The seed for this new environment, by default None + options : RESET_OPTIONS_TYPING, optional + See the documentation of :func:`grid2op.Environment.Environment.reset` + for more information about it, by default None + + Returns + ------- + Tuple[ ObsType, RESET_INFO_GYM_TYPING ] + _description_ + """ return self._aux_reset_new(seed, options) def step(self, action: ActType) -> Tuple[ObsType, float, bool, bool, STEP_INFO_TYPING]: diff --git a/grid2op/tests/BaseBackendTest.py b/grid2op/tests/BaseBackendTest.py index 710db907e..10c8b7e87 100644 --- a/grid2op/tests/BaseBackendTest.py +++ b/grid2op/tests/BaseBackendTest.py @@ -63,7 +63,7 @@ def comb(n, k): from grid2op.Rules import RulesChecker from grid2op.Rules import AlwaysLegal from grid2op.Action._backendAction import _BackendAction -from grid2op.Backend import Backend, PandaPowerBackend +from grid2op.Backend import PandaPowerBackend import pdb @@ -855,7 +855,6 @@ def _check_kirchoff(self): assert ( np.max(np.abs(p_bus.flatten())) <= self.tolvect ), "problem with active values, at a bus" - if self.backend.shunts_data_available: assert ( np.max(np.abs(q_subs)) <= self.tolvect diff --git a/grid2op/tests/aaa_test_backend_interface.py b/grid2op/tests/aaa_test_backend_interface.py index 2648a5476..b45bd3796 100644 --- a/grid2op/tests/aaa_test_backend_interface.py +++ b/grid2op/tests/aaa_test_backend_interface.py @@ -11,6 +11,7 @@ import warnings import grid2op from grid2op.Backend import Backend +from grid2op.dtypes import dt_int from grid2op.tests.helper_path_test import HelperTests, MakeBackend, PATH_DATA from grid2op.Exceptions import BackendError, Grid2OpException @@ -86,17 +87,45 @@ def test_01load_grid(self): backend.close() backend = self.make_backend() + backend.env_name = "BasicTest_load_grid2_" + type(self).__name__ backend.load_grid(os.path.join(self.get_path(), self.get_casefile())) # first argument filled, second None backend.load_redispacthing_data(self.get_path()) backend.load_storage_data(self.get_path()) - backend.env_name = "BasicTest_load_grid2_" + type(self).__name__ backend.assert_grid_correct() backend.close() backend = self.make_backend() with self.assertRaises(Exception): backend.load_grid() # should raise if nothing is loaded + + if backend.shunts_data_available and not cls.shunts_data_available: + raise RuntimeError("You backend object inform grid2op that it supports shunt, but the class apparently does not. " + "Have you called `self._compute_pos_big_topo()` at the end of `load_grid` implementation ?") + if not backend.shunts_data_available and cls.shunts_data_available: + raise RuntimeError("You backend object inform grid2op that it does not support shunt, but the class apparently does. " + "Have you called `self._compute_pos_big_topo()` at the end of `load_grid` implementation ?") + if not backend.shunts_data_available: + # object does not support shunts + assert not cls.shunts_data_available + assert cls.n_shunt is None, f"Your backend does not support shunt, the class should not define `n_shunt` (cls.n_shunt should be None and not {cls.n_shunt})" + assert cls.name_shunt is None, f"Your backend does not support shunt, the class should not define `name_shunt` (cls.name_shunt should be None and not {cls.name_shunt})" + assert cls.shunt_to_subid is None, f"Your backend does not support shunt, the class should not define `shunt_to_subid` (cls.shunt_to_subid should be None and not {cls.shunt_to_subid})" + assert backend.n_shunt is None, f"Your backend does not support shunt, backend.n_shunt should be None and not {backend.n_shunt}" + assert backend.name_shunt is None, f"Your backend does not support shunt, backend.name_shunt should be None {backend.name_shunt}" + assert backend.shunt_to_subid is None, f"Your backend does not support shunt, backend.shunt_to_subid should be None {backend.shunt_to_subid}" + else: + # object does support shunts + assert cls.shunts_data_available + assert isinstance(cls.n_shunt, (int, dt_int)), f"Your backend does not support shunt, the class should define `n_shunt`as an int, found {cls.n_shunt} ({type(cls.n_shunt)})" + assert cls.name_shunt is not None, f"Your backend does not support shunt, the class should define `name_shunt` (cls.name_shunt should not be None)" + assert cls.shunt_to_subid is not None, f"Your backend does not support shunt, the class should define `shunt_to_subid` (cls.shunt_to_subid should not be None)" + # these attributes are "deleted" from the backend instance + # and only stored in the class + # assert isinstance(backend.n_shunt, (int, dt_int)), f"Your backend does support shunt, `backend.n_shunt` should be an int, found {backend.n_shunt} ({type(backend.n_shunt)})" + # assert backend.name_shunt is not None, f"Your backend does not support shunt, backend.name_shunt should not be None" + # assert backend.shunt_to_subid is not None, f"Your backend does not support shunt, backend.shunt_to_subid should not be None" + def test_02modify_load(self): """Tests the loads can be modified diff --git a/grid2op/tests/test_Action.py b/grid2op/tests/test_Action.py index b45a810a9..059686f08 100644 --- a/grid2op/tests/test_Action.py +++ b/grid2op/tests/test_Action.py @@ -28,6 +28,7 @@ def _get_action_grid_class(): + GridObjects._clear_class_attribute() GridObjects.env_name = "test_action_env" GridObjects.n_busbar_per_sub = 2 GridObjects.n_gen = 5 @@ -101,7 +102,7 @@ def _get_action_grid_class(): np.arange(GridObjects.n_sub), repeats=GridObjects.sub_info ) GridObjects.glop_version = grid2op.__version__ - GridObjects._PATH_ENV = None + GridObjects._PATH_GRID_CLASSES = None json_ = { "glop_version": grid2op.__version__, @@ -333,11 +334,12 @@ def _get_action_grid_class(): "dim_alerts": 0, "alertable_line_names": [], "alertable_line_ids": [], - "_PATH_ENV": None, + "_PATH_GRID_CLASSES": None, "assistant_warning_type": None } GridObjects.shunts_data_available = False my_cls = GridObjects.init_grid(GridObjects, force=True) + GridObjects._clear_class_attribute() return my_cls, json_ diff --git a/grid2op/tests/test_Action_iadd.py b/grid2op/tests/test_Action_iadd.py index 10f203cab..1de5fe8fa 100644 --- a/grid2op/tests/test_Action_iadd.py +++ b/grid2op/tests/test_Action_iadd.py @@ -1,3 +1,12 @@ +# Copyright (c) 2024, 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. + + import unittest import warnings from abc import ABC, abstractmethod diff --git a/grid2op/tests/test_CompactEpisodeData.py b/grid2op/tests/test_CompactEpisodeData.py index e3dc8713a..5fcdeeeae 100644 --- a/grid2op/tests/test_CompactEpisodeData.py +++ b/grid2op/tests/test_CompactEpisodeData.py @@ -144,6 +144,7 @@ def act(self, observation, reward, done=False): def test_one_episode_with_saving(self): f = tempfile.mkdtemp() ( + ep_id, episode_name, cum_reward, timestep, @@ -152,7 +153,6 @@ def test_one_episode_with_saving(self): episode_data = CompactEpisodeData.from_disk(path=f, ep_id=episode_name) assert int(episode_data.meta["chronics_max_timestep"]) == self.max_iter assert len(episode_data.other_rewards) == self.max_iter - print("\n\nOther Rewards:", episode_data.other_reward_names) other_reward_idx = episode_data.other_reward_names.index("test") other_reward = episode_data.other_rewards[:, other_reward_idx] assert np.all(np.abs(other_reward - episode_data.rewards) <= self.tol_one) @@ -178,7 +178,7 @@ def test_collection_wrapper_after_run(self): agentClass=OneChange, use_compact_episode_data=True, ) - _, cum_reward, timestep, max_ts, episode_data = runner.run_one_episode( + ep_id, ep_name, cum_reward, timestep, max_ts, episode_data = runner.run_one_episode( max_iter=self.max_iter, detailed_output=True ) # Check that the type of first action is set bus @@ -188,6 +188,7 @@ def test_len(self): """test i can use the function "len" of the episode data""" f = tempfile.mkdtemp() ( + ep_id, episode_name, cum_reward, timestep, @@ -206,12 +207,11 @@ def test_3_episode_with_saving(self): def test_3_episode_3process_with_saving(self): f = tempfile.mkdtemp() - nb_episode = 2 + nb_episode = 2 res = self.runner._run_parrallel( nb_episode=nb_episode, nb_process=2, path_save=f, ) assert len(res) == nb_episode - print(f"\n\n{f}\n",'\n'.join([str(elt) for elt in Path(f).glob('*')])) for i, episode_name, cum_reward, timestep, total_ts in res: episode_data = CompactEpisodeData.from_disk(path=f, ep_id=episode_name) assert int(episode_data.meta["chronics_max_timestep"]) == self.max_iter diff --git a/grid2op/tests/test_Environment.py b/grid2op/tests/test_Environment.py index 055f3e865..61106baae 100644 --- a/grid2op/tests/test_Environment.py +++ b/grid2op/tests/test_Environment.py @@ -266,13 +266,15 @@ def test_reward(self): i = 0 self.chronics_handler.next_chronics() + ch = copy.deepcopy(self.chronics_handler) + ch.cleanup_action_space() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.get_backend(), init_env_path=os.path.join(self.path_matpower, self.case_file), - chronics_handler=self.chronics_handler, + chronics_handler=ch, parameters=self.env_params, rewardClass=L2RPNReward, names_chronics_to_backend=self.names_chronics_to_backend, diff --git a/grid2op/tests/test_EpisodeData.py b/grid2op/tests/test_EpisodeData.py index 15f231979..1b1e29535 100644 --- a/grid2op/tests/test_EpisodeData.py +++ b/grid2op/tests/test_EpisodeData.py @@ -143,6 +143,7 @@ def act(self, observation, reward, done=False): def test_one_episode_with_saving(self): f = tempfile.mkdtemp() ( + ep_id, episode_name, cum_reward, timestep, @@ -176,7 +177,7 @@ def test_collection_wrapper_after_run(self): name_env="test_episodedata_env", agentClass=OneChange, ) - _, cum_reward, timestep, max_ts, episode_data = runner.run_one_episode( + _, _, cum_reward, timestep, max_ts, episode_data = runner.run_one_episode( max_iter=self.max_iter, detailed_output=True ) # Check that the type of first action is set bus @@ -186,6 +187,7 @@ def test_len(self): """test i can use the function "len" of the episode data""" f = tempfile.mkdtemp() ( + ep_id, episode_name, cum_reward, timestep, diff --git a/grid2op/tests/test_GridObjects.py b/grid2op/tests/test_GridObjects.py index 5de75ab8b..62c6ace6b 100644 --- a/grid2op/tests/test_GridObjects.py +++ b/grid2op/tests/test_GridObjects.py @@ -15,7 +15,7 @@ import grid2op from grid2op.Backend.educPandaPowerBackend import EducPandaPowerBackend -from grid2op.Exceptions import EnvError +from grid2op.Exceptions import Grid2OpException class TestAuxFunctions(unittest.TestCase): @@ -72,8 +72,8 @@ def test_auxilliary_func(self): bk_cls.line_or_pos_topo_vect = None bk_cls.line_ex_pos_topo_vect = None - # test that the grid is not correct now - with self.assertRaises(EnvError): + # test that the grid should not be correct at this stage + with self.assertRaises(Grid2OpException): bk_cls.assert_grid_correct_cls() # fill the _compute_sub_elements diff --git a/grid2op/tests/test_Observation.py b/grid2op/tests/test_Observation.py index 1742ae4e3..dff0b2051 100644 --- a/grid2op/tests/test_Observation.py +++ b/grid2op/tests/test_Observation.py @@ -298,7 +298,7 @@ def setUp(self): "alertable_line_names": [], "alertable_line_ids": [], "assistant_warning_type": None, - "_PATH_ENV": None, + "_PATH_GRID_CLASSES": None, } self.json_ref = { @@ -2200,9 +2200,9 @@ def test_space_to_dict(self): val = dict_[el] val_res = self.dict_[el] if val is None and val_res is not None: - raise AssertionError(f"val is None and val_res is not None: val_res: {val_res}") + raise AssertionError(f"{el}: val is None and val_res is not None: val_res: {val_res}") if val is not None and val_res is None: - raise AssertionError(f"val is not None and val_res is None: val {val}") + raise AssertionError(f"{el}: val is not None and val_res is None: val {val}") if val is None and val_res is None: continue @@ -2975,9 +2975,13 @@ def setUp(self): "educ_case14_storage", test=True, action_class=PlayableAction, _add_to_name=type(self).__name__ ) + self.env.reset(seed=0, options={"time serie id": 0}) self.obs = self._make_forecast_perfect(self.env) self.sim_obs = None self.step_obs = None + + def tearDown(self): + self.env.close() def test_storage_act(self): """test i can do storage actions in simulate""" diff --git a/grid2op/tests/test_Runner.py b/grid2op/tests/test_Runner.py index db2f3fe6a..71446f2ba 100644 --- a/grid2op/tests/test_Runner.py +++ b/grid2op/tests/test_Runner.py @@ -401,9 +401,8 @@ def test_nomaxiter(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore") with grid2op.make("rte_case14_test", test=True, _add_to_name=type(self).__name__) as env: + env.set_max_iter(2 * self.max_iter) runner = Runner(**env.get_params_for_runner()) - runner.gridStateclass_kwargs["max_iter"] = 2 * self.max_iter - runner.chronics_handler.set_max_iter(2 * self.max_iter) res = runner.run(nb_episode=1) for i, _, cum_reward, timestep, total_ts in res: assert int(timestep) == 2 * self.max_iter @@ -518,6 +517,7 @@ def test_backward_compatibility(self): "1.9.7", "1.9.8", "1.10.0", + "1.10.1", ] curr_version = "test_version" assert ( diff --git a/grid2op/tests/test_RunnerFast.py b/grid2op/tests/test_RunnerFast.py index 1da9d05f4..b82a2017a 100644 --- a/grid2op/tests/test_RunnerFast.py +++ b/grid2op/tests/test_RunnerFast.py @@ -20,8 +20,6 @@ from grid2op.Runner import Runner from grid2op.dtypes import dt_float -warnings.simplefilter("error") - class TestRunner(HelperTests, unittest.TestCase): def setUp(self): @@ -56,7 +54,7 @@ def setUp(self): def test_one_episode(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore") - _, cum_reward, timestep, max_ts = self.runner.run_one_episode( + _, _, cum_reward, timestep, max_ts = self.runner.run_one_episode( max_iter=self.max_iter ) assert int(timestep) == self.max_iter @@ -65,7 +63,7 @@ def test_one_episode(self): def test_one_episode_detailed(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore") - _, cum_reward, timestep, max_ts, episode_data = self.runner.run_one_episode( + _, _, cum_reward, timestep, max_ts, episode_data = self.runner.run_one_episode( max_iter=self.max_iter, detailed_output=True ) assert int(timestep) == self.max_iter diff --git a/grid2op/tests/test_act_as_serializable_dict.py b/grid2op/tests/test_act_as_serializable_dict.py index f15f6fae1..3ac3df599 100644 --- a/grid2op/tests/test_act_as_serializable_dict.py +++ b/grid2op/tests/test_act_as_serializable_dict.py @@ -24,6 +24,7 @@ def _get_action_grid_class(): + GridObjects._clear_class_attribute() GridObjects.env_name = "test_action_serial_dict" GridObjects.n_gen = 5 GridObjects.name_gen = np.array(["gen_{}".format(i) for i in range(5)]) @@ -96,7 +97,7 @@ def _get_action_grid_class(): np.arange(GridObjects.n_sub), repeats=GridObjects.sub_info ) GridObjects.glop_version = grid2op.__version__ - GridObjects._PATH_ENV = None + GridObjects._PATH_GRID_CLASSES = None GridObjects.shunts_data_available = True GridObjects.n_shunt = 2 @@ -108,6 +109,7 @@ def _get_action_grid_class(): GridObjects.alarms_lines_area = {el: ["all"] for el in GridObjects.name_line} GridObjects.dim_alarms = 1 my_cls = GridObjects.init_grid(GridObjects, force=True) + GridObjects._clear_class_attribute() return my_cls diff --git a/grid2op/tests/test_action_set_orig_state.py b/grid2op/tests/test_action_set_orig_state.py new file mode 100644 index 000000000..228320227 --- /dev/null +++ b/grid2op/tests/test_action_set_orig_state.py @@ -0,0 +1,704 @@ +# Copyright (c) 2019-2020, 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. + +import tempfile +import numpy as np +import warnings +import unittest +try: + from lightsim2grid import LightSimBackend + LS_AVAIL = True +except ImportError: + LS_AVAIL = False + +import grid2op +from grid2op.Environment import (TimedOutEnvironment, + MaskedEnvironment, + SingleEnvMultiProcess) +from grid2op.Backend import PandaPowerBackend +from grid2op.Episode import EpisodeData +from grid2op.Opponent import FromEpisodeDataOpponent +from grid2op.Runner import Runner +from grid2op.Action import TopologyAction, DispatchAction +from grid2op.tests.helper_path_test import * +from grid2op.Chronics import (FromHandlers, + Multifolder, + MultifolderWithCache, + GridStateFromFileWithForecasts, + GridStateFromFile, + GridStateFromFileWithForecastsWithMaintenance, + GridStateFromFileWithForecastsWithoutMaintenance, + FromOneEpisodeData, + FromMultiEpisodeData, + FromNPY) +from grid2op.Chronics.handlers import CSVHandler, JSONInitStateHandler + + +# TODO test "change" is deactivated +# TODO test with "names_orig_to_backend" + + +class TestSetActOrigDefault(unittest.TestCase): + def _get_act_cls(self): + return TopologyAction + + def _get_ch_cls(self): + return Multifolder + + def _get_c_cls(self): + return GridStateFromFileWithForecasts + + def _env_path(self): + return os.path.join( + PATH_DATA_TEST, "5bus_example_act_topo_set_init" + ) + + def _names_ch_to_bk(self): + return None + + def _get_backend(self): + return PandaPowerBackend() + + def _get_gridpath(self): + return None + + def _get_envparams(self, env): + return None + + def setUp(self) -> None: + self.env_nm = self._env_path() + tmp_path = self._get_gridpath() + env_params = dict(test=True, + backend=self._get_backend(), + action_class=self._get_act_cls(), + chronics_class=self._get_ch_cls(), + data_feeding_kwargs={"gridvalueClass": self._get_c_cls()}, + _add_to_name=type(self).__name__ + ) + if tmp_path is not None: + env_params["grid_path"] = tmp_path + ch_to_bk = self._names_ch_to_bk() + if ch_to_bk is not None: + env_params["names_chronics_to_grid"] = ch_to_bk + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.env_nm, **env_params) + env_params = self._get_envparams(self.env) + if env_params is not None: + self.env.change_parameters(env_params) + self.env.change_forecast_parameters(env_params) + if issubclass(self._get_ch_cls(), MultifolderWithCache): + self.env.chronics_handler.set_filter(lambda x: True) + self.env.chronics_handler.reset() + self.env.reset(seed=0) + # some test to make sure the tests are correct + assert issubclass(self.env.action_space.subtype, self._get_act_cls()) + assert isinstance(self.env.chronics_handler.real_data, self._get_ch_cls()) + assert isinstance(self.env.chronics_handler.real_data.data, self._get_c_cls()) + assert isinstance(self.env.backend, type(self._get_backend())) + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def _aux_reset_env(self, seed, ep_id): + obs = self.env.reset(seed=seed, options={"time serie id": ep_id}) + return obs + + def _aux_make_step(self, act=None): + if act is None: + act = self.env.action_space() + return self.env.step(act) + + def _aux_get_init_act(self): + return self.env.chronics_handler.get_init_action(names_chronics_to_backend=self._names_ch_to_bk()) + + def _aux_get_act_valid(self): + # check the action in the time series folder is valid + with warnings.catch_warnings(): + warnings.filterwarnings("error") + act_init = self._aux_get_init_act() + + def test_working_setbus(self): + # ts id 0 => set_bus + self.obs = self._aux_reset_env(seed=0, ep_id=0) + + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 2 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 2 + assert (self.obs.time_before_cooldown_line == 0).all() + assert (self.obs.time_before_cooldown_sub == 0).all() + + obs, reward, done, info = self._aux_make_step() + assert not done + assert obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 2 + assert obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 2 + assert (obs.time_before_cooldown_line == 0).all() + assert (obs.time_before_cooldown_sub == 0).all() + # check the action in the time series folder is valid + self._aux_get_act_valid() + + def test_working_setstatus(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1) + + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == -1 + assert not self.obs.line_status[1] + assert (self.obs.time_before_cooldown_line == 0).all() + assert (self.obs.time_before_cooldown_sub == 0).all() + + obs, reward, done, info = self._aux_make_step() + assert not done + assert obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == -1 + assert obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == -1 + assert not obs.line_status[1] + assert (obs.time_before_cooldown_line == 0).all() + assert (obs.time_before_cooldown_sub == 0).all() + # check the action in the time series folder is valid + self._aux_get_act_valid() + + def test_rules_ok(self): + """test that even if the action to set is illegal, it works (case of ts id 2)""" + self.obs = self._aux_reset_env(seed=0, ep_id=2) + + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 2 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == 2 + assert (self.obs.time_before_cooldown_line == 0).all() + assert (self.obs.time_before_cooldown_sub == 0).all() + act_init = self._aux_get_init_act() + if act_init is None: + # test not correct for multiprocessing, I stop here + return + obs, reward, done, info = self._aux_make_step(act_init) + assert info["exception"] is not None + assert info["is_illegal"] + assert obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 2 + assert obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == 2 + assert (obs.time_before_cooldown_line == 0).all() + assert (obs.time_before_cooldown_sub == 0).all() + # check the action in the time series folder is valid + self._aux_get_act_valid() + + def test_change_bus_ignored(self, catch_warning=True): + """test that if the action to set uses change_bus then nothing is done""" + if catch_warning: + with self.assertWarns(UserWarning): + # it raises the warning "be carefull, change stuff are ignored" + self.obs = self._aux_reset_env(seed=0, ep_id=3) + else: + # no warning in the main process in multiprocessing + self.obs = self._aux_reset_env(seed=0, ep_id=3) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[2]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[2]] == 1 + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == 1 + assert self.obs.line_status[1] == 1 + assert self.obs.line_status[2] == 1 + assert self.obs.line_status[5] == 1 + # check the action in the time series folder is valid + self._aux_get_act_valid() + + +class TestSetActOrigDifferentActionCLS(TestSetActOrigDefault): + def _get_act_cls(self): + return DispatchAction + + +class TestSetAcOrigtMultiFolderWithCache(TestSetActOrigDefault): + def _get_ch_cls(self): + return MultifolderWithCache + + def test_two_reset_same(self): + """test it does not crash when the same time series is used twice""" + self.test_working_setstatus() + obs, reward, done, info = self.env.step(self.env.action_space()) + self.test_working_setstatus() + obs, reward, done, info = self.env.step(self.env.action_space()) + + +class TestSetActOrigGridStateFromFile(TestSetActOrigDefault): + def _get_c_cls(self): + return GridStateFromFile + + +class TestSetActOrigGSFFWFWM(TestSetActOrigDefault): + def _get_c_cls(self): + return GridStateFromFileWithForecastsWithMaintenance + + +class TestSetActOrigGSFFWFWoM(TestSetActOrigDefault): + def _get_c_cls(self): + return GridStateFromFileWithForecastsWithoutMaintenance + + +class TestSetActOrigFromOneEpisodeData(TestSetActOrigDefault): + def _aux_make_ep_data(self, ep_id): + runner = Runner(**self.env.get_params_for_runner()) + runner.run(nb_episode=1, + episode_id=[ep_id], + path_save=self.fn.name, + max_iter=10) + self.env.close() + + li_episode = EpisodeData.list_episode(self.fn.name) + ep_data = li_episode[0] + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self._env_path(), + chronics_class=FromOneEpisodeData, + data_feeding_kwargs={"ep_data": ep_data}, + opponent_class=FromEpisodeDataOpponent, + opponent_attack_cooldown=1, + _add_to_name=type(self).__name__ + ) + + def setUp(self) -> None: + self.fn = tempfile.TemporaryDirectory() + super().setUp() + + def tearDown(self) -> None: + self.fn.cleanup() + return super().tearDown() + + def test_working_setbus(self): + self._aux_make_ep_data(0) # episode id 0 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_working_setbus() + + def test_working_setstatus(self): + self._aux_make_ep_data(1) # episode id 1 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_working_setstatus() + + def test_rules_ok(self): + self._aux_make_ep_data(2) # episode id 2 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_rules_ok() + + def test_change_bus_ignored(self): + self.skipTest("This make no sense for this class (change is not used internally)") + + +class TestSetActOrigFromMultiEpisodeData(TestSetActOrigDefault): + def setUp(self) -> None: + super().setUp() + self.fn = tempfile.TemporaryDirectory() + runner = Runner(**self.env.get_params_for_runner()) + runner.run(nb_episode=3, + episode_id=[0, 1, 2], + path_save=self.fn.name, + max_iter=10) + self.env.close() + + li_episode = EpisodeData.list_episode(self.fn.name) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self._env_path(), + chronics_class=FromMultiEpisodeData, + data_feeding_kwargs={"li_ep_data": li_episode}, + opponent_class=FromEpisodeDataOpponent, + opponent_attack_cooldown=1, + _add_to_name=type(self).__name__ + ) + + + def tearDown(self) -> None: + self.fn.cleanup() + return super().tearDown() + + def test_two_reset_same(self): + """test it does not crash when the same time series is used twice""" + self.test_working_setstatus() + obs, reward, done, info = self.env.step(self.env.action_space()) + self.test_working_setstatus() + obs, reward, done, info = self.env.step(self.env.action_space()) + + def test_change_bus_ignored(self): + self.skipTest("This make no sense for this class (change is not used internally)") + + +class TestSetActOrigFromNPY(TestSetActOrigDefault): + def _aux_make_env(self, ch_id): + self.obs = self.env.reset(seed=0, options={"time serie id": ch_id}) + load_p = 1.0 * self.env.chronics_handler._real_data.data.load_p[:self.max_iter,:] + load_q = 1.0 * self.env.chronics_handler._real_data.data.load_q[:self.max_iter,:] + gen_p = 1.0 * self.env.chronics_handler._real_data.data.prod_p[:self.max_iter,:] + gen_v = np.repeat(self.obs.gen_v.reshape(1, -1), self.max_iter, axis=0) + act = self.env.action_space({"set_bus": self.obs.topo_vect}) + self.env.close() + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self._env_path(), + chronics_class=FromNPY, + data_feeding_kwargs={"load_p": load_p, + "load_q": load_q, + "prod_p": gen_p, + "prod_v": gen_v, + "init_state": act + }, + _add_to_name=type(self).__name__) + def setUp(self) -> None: + self.max_iter = 5 + super().setUp() + + def tearDown(self) -> None: + return super().tearDown() + + def test_working_setbus(self): + self._aux_make_env(0) # episode id 0 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_working_setbus() + + def test_working_setstatus(self): + self._aux_make_env(1) # episode id 1 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_working_setstatus() + + def test_rules_ok(self): + self._aux_make_env(2) # episode id 2 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_rules_ok() + + def test_change_bus_ignored(self): + self._aux_make_env(3) # episode id 3 is used for this test + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + super().test_change_bus_ignored() + + +class TestSetActOrigEnvCopy(TestSetActOrigDefault): + def setUp(self) -> None: + super().setUp() + env_cpy = self.env.copy() + self.env.close() + self.env = env_cpy + + +class TestSetActOrigFromHandlers(TestSetActOrigDefault): + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self._env_path(), + data_feeding_kwargs={"gridvalueClass": FromHandlers, + "gen_p_handler": CSVHandler("prod_p"), + "load_p_handler": CSVHandler("load_p"), + "gen_v_handler": CSVHandler("prod_v"), + "load_q_handler": CSVHandler("load_q"), + "init_state_handler": JSONInitStateHandler("init_state_handler") + }, + _add_to_name=type(self).__name__ + ) + + +class TestSetActOrigLightsim(TestSetActOrigDefault): + def _get_backend(self): + if not LS_AVAIL: + self.skipTest("LightSimBackend is not available") + return LightSimBackend() + + +class TestSetActOrigDiffNames(TestSetActOrigDefault): + def _get_gridpath(self): + # just to have a grid with different names + return os.path.join(PATH_DATA_TEST, "5bus_example_diff_name", "grid.json") + + def _names_ch_to_bk(self): + res = {"loads": {'load_0_0': 'tutu', 'load_3_1': 'toto', 'load_4_2': 'tata'}, + "prods": {"gen_0_0": "othername_0_0", "gen_1_1": "othername_1_1"}, + "lines": {"0_1_0": 'l_0_1_0', + "0_2_1": 'l_0_2_1', + "0_3_2": 'l_0_3_2', + "0_4_3": 'l_0_4_3', + "1_2_4": 'l_1_2_4', + "2_3_5": 'l_2_3_5', + "2_3_6": 'l_2_3_6', + "3_4_7": 'l_3_4_7'} + } + return res + + +class TestSetActOrigTOEnv(TestSetActOrigDefault): + def setUp(self) -> None: + super().setUp() + env_init = self.env + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = TimedOutEnvironment(self.env) + env_init.close() + return LightSimBackend() + + +class TestSetActOrigMaskedEnv(TestSetActOrigDefault): + def setUp(self) -> None: + super().setUp() + env_init = self.env + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = MaskedEnvironment(self.env, + lines_of_interest=np.array([1, 1, 1, 1, 0, 0, 0, 0])) + env_init.close() + + +def always_true(x): + # I can't use lambda in set_filter (lambda cannot be pickled) + return True + + +class TestSetActOrigMultiProcEnv(TestSetActOrigDefault): + def _aux_reset_env(self, seed, ep_id): + # self.env.seed(seed) + self.env.set_id(ep_id) + obs = self.env.reset() + return obs[0] + + def _aux_get_init_act(self): + return None + + def _aux_make_step(self): + obs, reward, done, info = self.env.step([self.env_init.action_space(), self.env_init.action_space()]) + return obs[0], reward[0], done[0], info[0] + + def setUp(self) -> None: + super().setUp() + self.env_init = self.env + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = SingleEnvMultiProcess(self.env, 2) + self.env.set_filter(always_true) + + def tearDown(self) -> None: + self.env_init.close() + return super().tearDown() + + def test_change_bus_ignored(self): + super().test_change_bus_ignored(catch_warning=False) + + +class TestSetActOrigForcastEnv(TestSetActOrigDefault): + def test_working_setbus(self): + super().test_working_setbus() + for_env = self.env.get_obs().get_forecast_env() + obs, reward, done, info = for_env.step(self.env.action_space()) + + def test_working_setstatus(self): + super().test_working_setstatus() + for_env = self.env.get_obs().get_forecast_env() + obs, reward, done, info = for_env.step(self.env.action_space()) + + def test_rules_ok(self): + super().test_rules_ok() + for_env = self.env.get_obs().get_forecast_env() + obs, reward, done, info = for_env.step(self.env.action_space()) + + def test_change_bus_ignored(self): + super().test_change_bus_ignored() + for_env = self.env.get_obs().get_forecast_env() + obs, reward, done, info = for_env.step(self.env.action_space()) + + +class TestSetActOrigRunner(unittest.TestCase): + def _env_path(self): + return TestSetActOrigDefault._env_path(self) + + def setUp(self) -> None: + self.env_nm = self._env_path() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.env_nm, + test=True, + _add_to_name=type(self).__name__ + ) + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def test_right_init_act(self): + runner = Runner(**self.env.get_params_for_runner()) + res = runner.run(nb_episode=3, + episode_id=[0, 1, 2], + max_iter=10, + add_detailed_output=True) + for i, el in enumerate(res): + ep_data = el[-1] + init_obs = ep_data.observations[0] + if i == 0: + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 2 + assert init_obs.topo_vect[init_obs.load_pos_topo_vect[0]] == 2 + assert (init_obs.time_before_cooldown_line == 0).all() + assert (init_obs.time_before_cooldown_sub == 0).all() + elif i == 1: + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == -1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == -1 + assert not init_obs.line_status[1] + assert (init_obs.time_before_cooldown_line == 0).all() + assert (init_obs.time_before_cooldown_sub == 0).all() + elif i == 2: + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 2 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[5]] == 2 + assert (init_obs.time_before_cooldown_line == 0).all() + assert (init_obs.time_before_cooldown_sub == 0).all() + else: + raise RuntimeError("Test is coded correctly") + + +class _PPNoShunt_Test(PandaPowerBackend): + shunts_data_available = False + + +class TestSetSuntState(unittest.TestCase): + def _env_path(self): + return os.path.join( + PATH_DATA_TEST, "educ_case14_storage_init_state" + ) + + def _get_backend(self): + return PandaPowerBackend() + + def setUp(self) -> None: + self.env_nm = self._env_path() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.env_nm, + test=True, + _add_to_name=type(self).__name__ + ) + self.env_noshunt = grid2op.make(self.env_nm, + test=True, + backend=_PPNoShunt_Test(), + _add_to_name=type(self).__name__ + ) + self.env_nostor = grid2op.make(self.env_nm, + test=True, + _add_to_name=type(self).__name__, + _compat_glop_version="neurips_2020_compat" + ) + assert type(self.env_noshunt).shunts_data_available is False + assert type(self.env_nostor).n_storage == 0 + assert type(self.env).n_storage == 2 + + def test_set_shunt_state(self): + """test that the action that acts on the shunt works (when shunt are supported) + or has no impact if the backend does not support shunts""" + obs_shunt = self.env.reset(seed=0, options={"time serie id": 0}) + obs_noshunt = self.env_noshunt.reset(seed=0, options={"time serie id": 0}) + assert obs_shunt._shunt_q[0] == 0. # the action put the shunt to 0. + # in the backend with no shunt, the shunt is active and generator + # does not produce same q + assert abs(obs_shunt.gen_q[4] - obs_noshunt.gen_q[4]) > 5. + + def test_set_storage_state(self): + obs_stor = self.env.reset(seed=0, options={"time serie id": 1}) + obs_nostor = self.env_nostor.reset(seed=0, options={"time serie id": 1}) + slack_id = -1 + # the storage action is taken into account + assert obs_stor.storage_power[0] == 5. # the action set this + + # the original grid (withtout storage) + # and the grid with storage action have the same "gen_p" + # if I remove the impact of the storage unit + deltagen_p_th = ((obs_stor.gen_p - obs_stor.actual_dispatch) - obs_nostor.gen_p) + assert (np.abs(deltagen_p_th[:slack_id]) <= 1e-6).all() + + +class TestSetActOrigIgnoredParams(TestSetActOrigDefault): + """This class test that the new feature (setting the initial state in the time series + is properly ignored if the parameter says so)""" + + def _get_envparams(self, env): + param = env.parameters + param.IGNORE_INITIAL_STATE_TIME_SERIE = True + return param + + def test_working_setbus(self): + """test that it's ignored even if the action is set_status""" + self.obs = self._aux_reset_env(seed=0, ep_id=0) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + assert (self.obs.time_before_cooldown_line == 0).all() + assert (self.obs.time_before_cooldown_sub == 0).all() + + obs, reward, done, info = self._aux_make_step() + assert not done + assert obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + assert (obs.time_before_cooldown_line == 0).all() + assert (obs.time_before_cooldown_sub == 0).all() + # check the action in the time series folder is valid + self._aux_get_act_valid() + + def test_working_setstatus(self): + """test that it's ignored even if the action is set_status""" + self.obs = self._aux_reset_env(seed=0, ep_id=1) + + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + assert (self.obs.time_before_cooldown_line == 0).all() + assert (self.obs.time_before_cooldown_sub == 0).all() + + obs, reward, done, info = self._aux_make_step() + assert not done + assert obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert obs.line_status[1] + assert (obs.time_before_cooldown_line == 0).all() + assert (obs.time_before_cooldown_sub == 0).all() + # check the action in the time series folder is valid + self._aux_get_act_valid() + + def test_rules_ok(self): + """that it's ignored even if the action is illegal""" + self.obs = self._aux_reset_env(seed=0, ep_id=2) + + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == 1 + assert (self.obs.time_before_cooldown_line == 0).all() + assert (self.obs.time_before_cooldown_sub == 0).all() + act_init = self._aux_get_init_act() + if act_init is None: + # test not correct for multiprocessing, I stop here + return + obs, reward, done, info = self._aux_make_step(act_init) + assert info["exception"] is not None + assert info["is_illegal"] + assert obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == 1 + assert (obs.time_before_cooldown_line == 0).all() + assert (obs.time_before_cooldown_sub == 0).all() + # check the action in the time series folder is valid + self._aux_get_act_valid() + + def test_change_bus_ignored(self, catch_warning=True): + """test that if the action to set uses change_bus then nothing is done""" + # no warning in the main process in multiprocessing + self.obs = self._aux_reset_env(seed=0, ep_id=3) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[2]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[2]] == 1 + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == 1 + assert self.obs.line_status[1] == 1 + assert self.obs.line_status[2] == 1 + assert self.obs.line_status[5] == 1 + # check the action in the time series folder is valid + self._aux_get_act_valid() + + +if __name__ == "__main__": + unittest.main() diff --git a/grid2op/tests/test_action_set_orig_state_options.py b/grid2op/tests/test_action_set_orig_state_options.py new file mode 100644 index 000000000..8e142a302 --- /dev/null +++ b/grid2op/tests/test_action_set_orig_state_options.py @@ -0,0 +1,513 @@ +# Copyright (c) 2019-2020, 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. + + +import warnings +import unittest + +import grid2op +from grid2op.Runner import Runner +from grid2op.tests.helper_path_test import * + + +class TestSetActOptionDefault(unittest.TestCase): + def _env_path(self): + return os.path.join( + PATH_DATA_TEST, "5bus_example_act_topo_set_init" + ) + + def setUp(self) -> None: + self.env_nm = self._env_path() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.env_nm, + test=True + ) + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def _aux_reset_env(self, seed, ep_id, init_state): + obs = self.env.reset(seed=seed, options={"time serie id": ep_id, + "init state": init_state}) + return obs + + def _aux_make_step(self, act=None): + if act is None: + act = self.env.action_space() + return self.env.step(act) + + def _aux_get_init_act(self): + return self.env.chronics_handler.get_init_action() + + def test_combine_ts_set_bus_opt_setbus_nopb(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, init_state={"set_bus": {"lines_or_id": [(0, 2)]}}) + # in the time series + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 2 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 2 + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[0]] == 2 + + def test_combine_ts_set_bus_opt_setbus_collision(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, init_state={"set_bus": {"lines_or_id": [(1, 1)], + "loads_id": [(0, 1)]}}) + + # in the option (totally erase the time series) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + + def test_combine_ts_set_bus_opt_setstat_nopb(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, + init_state={"set_line_status": [(5, -1)]}) + + # in the time series + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 2 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 2 + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == -1 + + def test_combine_ts_set_bus_opt_setstat_collision(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, + init_state={"set_bus": {"loads_id": [(0, 1)]}, + "set_line_status": [(1, -1)]}) + # in the act + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == -1 + assert not self.obs.line_status[1] + # in the time series + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + + def test_combine_ts_set_status_opt_setbus_nopb(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_bus": {"lines_or_id": [(5, 2)]}}) + + # in the time series + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == -1 + assert not self.obs.line_status[1] + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == 2 + + def test_combine_ts_set_status_opt_setbus_collision(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_bus": {"lines_or_id": [(1, 1)]}}) + # in the time series (erased by the action, or side) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_bus": {"lines_ex_id": [(1, 2)]}}) + # in the time series (erased by the action, ex side) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 2 + assert self.obs.line_status[1] + + def test_combine_ts_set_status_opt_setstat_nopb(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_line_status": [(5, -1)]}) + + # in the time series + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == -1 + assert not self.obs.line_status[1] + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == -1 + assert not self.obs.line_status[5] + + def test_combine_ts_set_status_opt_setstat_collision(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_line_status": [(1, 1)]}) + + # in the time series (bus overriden by the action) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + return self.env.chronics_handler.get_init_action() + + def test_ignore_ts_set_bus_opt_setbus_nopb(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, init_state={"set_bus": {"lines_or_id": [(5, 2)]}, "method": "ignore"}) + + # in the time series + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == 2 + + def test_ignore_ts_set_bus_opt_setbus_collision(self): + # TODO not tested for method = ignore (because action here totally erased action in ts) + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, init_state={"set_bus": {"lines_or_id": [(1, 1)], + "loads_id": [(0, 1)]}, + "method": "ignore"}) + + # in the option (totally erase the time series) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + + def test_ignore_ts_set_bus_opt_setstat_nopb(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, + init_state={"set_line_status": [(5, -1)], + "method": "ignore"}) + + # in the time series + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == -1 + assert not self.obs.line_status[5] + + def test_ignore_ts_set_bus_opt_setstat_collision(self): + # ts id 0 => set_bus (in the time series) + self.obs = self._aux_reset_env(seed=0, ep_id=0, + init_state={"set_bus": {"loads_id": [(0, 1)]}, + "set_line_status": [(1, -1)], + "method": "ignore"}) + # in the act + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == -1 + assert not self.obs.line_status[1] + # in the time series (ignored) + assert self.obs.topo_vect[self.obs.load_pos_topo_vect[0]] == 1 + + def test_ignore_ts_set_status_opt_setbus_nopb(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_bus": {"lines_or_id": [(5, 2)]}, + "method": "ignore"}) + + # in the time series (ignored) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == 2 + + def test_ignore_ts_set_status_opt_setbus_collision(self): + # TODO not tested for method = ignore (because action here totally erased action in ts) + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_bus": {"lines_or_id": [(1, 1)]}, + "method": "ignore"}) + # in the time series (erased by the action, or side) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_bus": {"lines_ex_id": [(1, 2)]}, + "method": "ignore"}) + # in the time series (erased by the action, ex side) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 2 + assert self.obs.line_status[1] + + def test_ignore_ts_set_status_opt_setstat_nopb(self): + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_line_status": [(5, -1)], + "method": "ignore"}) + + # in the time series (ignored) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + # in the action + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[5]] == -1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[5]] == -1 + assert not self.obs.line_status[5] + + def test_ignore_ts_set_status_opt_setstat_collision(self): + # TODO not tested for method = ignore (because action here totally erased action in ts) + + # ts id 1 => set_status + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state={"set_line_status": [(1, 1)], + "method": "ignore"}) + + # in the time series (bus overriden by the action) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + + def test_byact(self): + # ts id 1 => set_status + act = self.env.action_space({"set_line_status": [(1, 1)]}) + self.obs = self._aux_reset_env(seed=0, ep_id=1, init_state=act) + + # in the time series (bus overriden by the action) + assert self.obs.topo_vect[self.obs.line_or_pos_topo_vect[1]] == 1 + assert self.obs.topo_vect[self.obs.line_ex_pos_topo_vect[1]] == 1 + assert self.obs.line_status[1] + return self.env.chronics_handler.get_init_action() + + +class TestSetInitRunner(unittest.TestCase): + def _env_path(self): + return os.path.join( + PATH_DATA_TEST, "5bus_example_act_topo_set_init" + ) + + def setUp(self) -> None: + self.env_nm = self._env_path() + self.max_iter = 5 + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.env_nm, + test=True + ) + self.runner = Runner(**self.env.get_params_for_runner()) + + def tearDown(self) -> None: + self.env.close() + self.runner._clean_up() + return super().tearDown() + + def test_run_one_episode(self): + res = self.runner.run_one_episode(init_state={"set_line_status": [(1, 1)], "method": "ignore"}, + episode_id=1, + max_iter=self.max_iter, + detailed_output=True + ) + ep_data = res[-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + def test_run_onesingle_ep_onesingle_act(self): + # one action + res = self.runner.run(nb_episode=1, + init_states={"set_line_status": [(1, 1)], "method": "ignore"}, + episode_id=[1], + max_iter=self.max_iter, + add_detailed_output=True + ) + ep_data = res[0][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + # one list (of one element here) + res = self.runner.run(nb_episode=1, + init_states=[{"set_line_status": [(1, 1)], "method": "ignore"}], + episode_id=[1], + max_iter=self.max_iter, + add_detailed_output=True + ) + ep_data = res[0][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + # one tuple (of one element here) + res = self.runner.run(nb_episode=1, + init_states=({"set_line_status": [(1, 1)], "method": "ignore"}, ), + episode_id=[1], + max_iter=self.max_iter, + add_detailed_output=True + ) + ep_data = res[0][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + def test_run_two_eps_seq_onesingle_act(self, nb_process=1): + # one action + res = self.runner.run(nb_episode=2, + init_states={"set_line_status": [(1, 1)], "method": "ignore"}, + episode_id=[1, 1], + max_iter=self.max_iter, + add_detailed_output=True, + nb_process=nb_process + ) + for el in res: + ep_data = el[-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + # one list + res = self.runner.run(nb_episode=2, + init_states=[{"set_line_status": [(1, 1)], "method": "ignore"}, + {"set_line_status": [(1, 1)], "method": "ignore"}], + episode_id=[1, 1], + max_iter=self.max_iter, + add_detailed_output=True, + nb_process=nb_process + ) + for el in res: + ep_data = el[-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + # one tuple + res = self.runner.run(nb_episode=2, + init_states=({"set_line_status": [(1, 1)], "method": "ignore"}, + {"set_line_status": [(1, 1)], "method": "ignore"}), + episode_id=[1, 1], + max_iter=self.max_iter, + add_detailed_output=True, + nb_process=nb_process + ) + for el in res: + ep_data = el[-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + def test_run_two_eps_seq_two_acts(self, nb_process=1): + # given as list + res = self.runner.run(nb_episode=2, + init_states=[{"set_bus": {"loads_id": [(0, 1)]}, "set_line_status": [(1, -1)], "method": "ignore"}, + {"set_line_status": [(1, 1)], "method": "ignore"}], + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + nb_process=nb_process + ) + + # check for ep 0 + ep_data = res[0][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == -1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == -1 + assert not init_obs.line_status[1] + assert init_obs.topo_vect[init_obs.load_pos_topo_vect[0]] == 1 + # check for ep 1 + ep_data = res[1][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + # one tuple + res = self.runner.run(nb_episode=2, + init_states=({"set_bus": {"loads_id": [(0, 1)]}, "set_line_status": [(1, -1)], "method": "ignore"}, + {"set_line_status": [(1, 1)], "method": "ignore"}), + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + nb_process=nb_process + ) + # check for ep 0 + ep_data = res[0][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == -1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == -1 + assert not init_obs.line_status[1] + assert init_obs.topo_vect[init_obs.load_pos_topo_vect[0]] == 1 + # check for ep 1 + ep_data = res[1][-1] + init_obs = ep_data.observations[0] + assert init_obs.topo_vect[init_obs.line_or_pos_topo_vect[1]] == 1 + assert init_obs.topo_vect[init_obs.line_ex_pos_topo_vect[1]] == 1 + assert init_obs.line_status[1] + + def test_run_two_eps_par_onesingle_act(self): + self.test_run_two_eps_seq_onesingle_act(nb_process=2) + + def test_run_two_eps_par_two_acts(self): + self.test_run_two_eps_seq_two_acts(nb_process=2) + + def test_fail_when_needed(self): + # wrong type + with self.assertRaises(RuntimeError): + res = self.runner.run(nb_episode=2, + init_states=1, + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + ) + with self.assertRaises(RuntimeError): + res = self.runner.run(nb_episode=2, + init_states=[1, {"set_line_status": [(1, 1)], "method": "ignore"}], + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + ) + with self.assertRaises(RuntimeError): + res = self.runner.run(nb_episode=2, + init_states=[{"set_line_status": [(1, 1)], "method": "ignore"}, 1], + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + ) + + # wrong size (too big) + with self.assertRaises(RuntimeError): + res = self.runner.run(nb_episode=2, + init_states=[{"set_line_status": [(1, 1)], "method": "ignore"}, + {"set_line_status": [(1, 1)], "method": "ignore"}, + {"set_line_status": [(1, 1)], "method": "ignore"}], + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + ) + # wrong size (too small) + with self.assertRaises(RuntimeError): + res = self.runner.run(nb_episode=2, + init_states=[{"set_line_status": [(1, 1)], "method": "ignore"}], + episode_id=[0, 1], + max_iter=self.max_iter, + add_detailed_output=True, + ) + + +class TestSetActOptionDefaultComplexAction(unittest.TestCase): + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make("educ_case14_storage", test=True, _add_to_name=type(self).__name__) + return super().setUp() + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def test_storage(self): + obs = self.env.reset(seed=0, + options={"time serie id": 0, + "init state": {"set_storage": [(0, 5.)]}}) + assert abs(obs.storage_power[0] - 5.) <= 1e-6 + obs, reward, done, info = self.env.step(self.env.action_space()) + assert abs(obs.storage_power[0] - 0.) <= 1e-6 + + def test_curtail(self): + obs = self.env.reset(seed=0, + options={"time serie id": 0, + "init state": {"curtail": [(3, 0.1)]}}) + assert abs(obs.curtailment_limit[3] - 0.1) <= 1e-6 + obs, reward, done, info = self.env.step(self.env.action_space()) + assert abs(obs.curtailment_limit[3] - 0.1) <= 1e-6 + + def test_redispatching(self): + obs = self.env.reset(seed=0, + options={"time serie id": 0, + "init state": {"redispatch": [(0, -1)]}}) + assert abs(obs.target_dispatch[0] - -1.) <= 1e-6 + assert abs(obs.actual_dispatch[0] - -1.) <= 1e-6 + obs, reward, done, info = self.env.step(self.env.action_space()) + assert abs(obs.target_dispatch[0] - -1.) <= 1e-6 + assert abs(obs.actual_dispatch[0] - -1.) <= 1e-6 + + +if __name__ == "__main__": + unittest.main() diff --git a/grid2op/tests/test_alert_trust_score.py b/grid2op/tests/test_alert_trust_score.py index 58a18e48d..32e0494f1 100644 --- a/grid2op/tests/test_alert_trust_score.py +++ b/grid2op/tests/test_alert_trust_score.py @@ -14,7 +14,7 @@ from grid2op.Observation import BaseObservation from grid2op.tests.helper_path_test import * -from grid2op import make +import grid2op from grid2op.Reward import _AlertTrustScore from grid2op.Parameters import Parameters from grid2op.Exceptions import Grid2OpException @@ -68,7 +68,7 @@ def test_assistant_trust_score_no_blackout_no_attack_no_alert(self) -> None : Raises: Grid2OpException: raise an exception if an attack occur """ - with make( + with grid2op.make( self.env_nm, test=True, difficulty="1", @@ -111,7 +111,7 @@ def test_assistant_trust_score_no_blackout_attack_alert(self) -> None : kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[2]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -173,7 +173,7 @@ def test_assistant_trust_score_no_blackout_2_attack_same_time_1_alert(self) -> N kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE] + ['48_53_141'], duration=3, steps_attack=[2]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", reward_class=_AlertTrustScore(**DEFAULT_PARAMS_TRUSTSCORE), @@ -237,7 +237,7 @@ def test_assistant_trust_score_no_blackout_no_attack_alert(self) -> None: Raises: Grid2OpException: raise an exception if an attack occur """ - with make( + with grid2op.make( self.env_nm, test=True, difficulty="1", @@ -289,7 +289,7 @@ def test_assistant_trust_score_no_blackout_attack_no_alert(self) -> None: kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[1]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -342,7 +342,7 @@ def test_assistant_trust_score_no_blackout_attack_alert_too_late(self) -> None : kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[2]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", reward_class=_AlertTrustScore(**DEFAULT_PARAMS_TRUSTSCORE), @@ -398,7 +398,7 @@ def test_assistant_trust_score_no_blackout_attack_alert_too_early(self)-> None : kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[2]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", reward_class=_AlertTrustScore(**DEFAULT_PARAMS_TRUSTSCORE), @@ -458,7 +458,7 @@ def test_assistant_trust_score_no_blackout_2_attack_same_time_no_alert(self) -> kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=3, steps_attack=[1]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -512,7 +512,7 @@ def test_assistant_trust_score_no_blackout_2_attack_same_time_2_alert(self) -> N kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=3, steps_attack=[2]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", reward_class=_AlertTrustScore(**DEFAULT_PARAMS_TRUSTSCORE), @@ -575,7 +575,7 @@ def test_assistant_trust_score_no_blackout_2_attack_diff_time_no_alert(self) -> kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1, 1], steps_attack=[1, 2]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -629,7 +629,7 @@ def test_assistant_trust_score_no_blackout_2_attack_diff_time_2_alert(self) -> N kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1,1], steps_attack=[2, 3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -693,7 +693,7 @@ def test_assistant_trust_score_no_blackout_2_attack_diff_time_alert_first_attack kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1,1], steps_attack=[2, 3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -760,7 +760,7 @@ def test_assistant_trust_score_no_blackout_2_attack_diff_time_alert_second_attac kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1,1], steps_attack=[2, 3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -842,7 +842,7 @@ def test_assistant_trust_score_blackout_attack_nocause_blackout_no_alert(self) - kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -904,7 +904,7 @@ def test_assistant_trust_score_blackout_attack_nocause_blackout_raise_alert(self kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -966,7 +966,7 @@ def test_assistant_trust_score_blackout_no_attack_alert(self) -> None: """Even if there is a blackout, an we raise an alert we expect a score of 0 because there is no attack and we don't finish the scenario""" - with make( + with grid2op.make( self.env_nm, test=True, difficulty="1", @@ -1012,7 +1012,7 @@ def test_assistant_trust_score_blackout_no_attack_alert(self) -> None: def test_assistant_trust_score_blackout_no_attack_no_alert(self) -> None: """Even if there is a blackout, an we don't raise an alert we expect a score of 0 because there is no attack and we don't finish the scenario""" - with make( + with grid2op.make( self.env_nm, test=True, difficulty="1", @@ -1056,7 +1056,7 @@ def test_assistant_trust_score_blackout_no_attack_no_alert(self) -> None: def test_assistant_trust_score_blackout_no_attack_before_window_alert(self) -> None: """Even if there is a blackout, an we raise an alert too early we expect a score of 0 because there is no attack and we don't finish the scenario""" - with make( + with grid2op.make( self.env_nm, test=True, difficulty="1", @@ -1102,7 +1102,7 @@ def test_assistant_trust_score_blackout_no_attack_before_window_alert(self) -> N def test_assistant_trust_score_blackout_no_attack_before_window_no_alert(self) -> None: """Even if there is a blackout, an we raise an alert too late we expect a score of 0 because there is no attack and we don't finish the scenario""" - with make( + with grid2op.make( self.env_nm, test=True, difficulty="1", @@ -1174,7 +1174,7 @@ def test_assistant_trust_score_blackout_attack_raise_good_alert(self) -> None : kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1239,7 +1239,7 @@ def test_assistant_trust_score_blackout_attack_raise_alert_just_before_blackout( kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1304,7 +1304,7 @@ def test_assistant_trust_score_blackout_2_lines_attacked_simulaneous_only_1_aler kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE] + ['48_53_141'], duration=3, steps_attack=[3, 3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", reward_class=_AlertTrustScore(**DEFAULT_PARAMS_TRUSTSCORE), @@ -1376,7 +1376,7 @@ def test_assistant_trust_score_blackout_attack_no_alert(self) -> None: kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1437,7 +1437,7 @@ def test_assistant_trust_score_blackout_attack_raise_alert_too_early(self) -> No kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE], duration=3, steps_attack=[3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1502,7 +1502,7 @@ def test_assistant_trust_score_blackout_2_lines_same_step_in_window_good_alerts kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=3, steps_attack=[3, 3]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1570,7 +1570,7 @@ def test_assistant_trust_score_blackout_2_lines_different_step_in_window_good_a kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1,1], steps_attack=[3, 4]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1641,7 +1641,7 @@ def test_assistant_trust_score_blackout_2_lines_attacked_different_step_in_windo kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1,1], steps_attack=[3, 4]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1706,7 +1706,7 @@ def test_assistant_trust_score_blackout_2_lines_attacked_different_step_in_windo kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1,1], steps_attack=[3, 4]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1770,7 +1770,7 @@ def test_assistant_trust_score_blackout_2_lines_attacked_different_1_in_window_1 kwargs_opponent = dict(lines_attacked=[ATTACKED_LINE]+['48_53_141'], duration=[1, 1], steps_attack=[3, 6]) - with make(self.env_nm, + with grid2op.make(self.env_nm, test=True, difficulty="1", opponent_attack_cooldown=0, @@ -1831,7 +1831,7 @@ def setUp(self) -> None: self.env_nm = os.path.join( PATH_DATA_TEST, "l2rpn_idf_2023_with_alert" ) - self.env = make(self.env_nm, test=True, difficulty="1", + self.env = grid2op.make(self.env_nm, test=True, difficulty="1", reward_class=_AlertTrustScore(**DEFAULT_PARAMS_TRUSTSCORE)) self.env.seed(0) return super().setUp() diff --git a/grid2op/tests/test_attached_envs.py b/grid2op/tests/test_attached_envs.py index d9c0742bc..0451dfb52 100644 --- a/grid2op/tests/test_attached_envs.py +++ b/grid2op/tests/test_attached_envs.py @@ -12,11 +12,12 @@ from grid2op.Action import (PowerlineSetAction, PlayableAction, DontAct) from grid2op.Observation import CompleteObservation -from grid2op.Opponent import GeometricOpponent +from grid2op.Opponent import GeometricOpponent, GeometricOpponentMultiArea import pdb # TODO refactor to have 1 base class, maybe +# TODO: test runner, gym_compat and EpisodeData class TestL2RPNNEURIPS2020_Track1(unittest.TestCase): @@ -28,11 +29,11 @@ def setUp(self) -> None: _ = self.env.reset() def test_elements(self): - assert self.env.n_sub == 36 - assert self.env.n_line == 59 - assert self.env.n_load == 37 - assert self.env.n_gen == 22 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 36 + assert type(self.env).n_line == 59 + assert type(self.env).n_load == 37 + assert type(self.env).n_gen == 22 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, PowerlineSetAction) @@ -72,11 +73,11 @@ def setUp(self) -> None: _ = self.env.reset() def test_elements(self): - assert self.env.n_sub == 36 - assert self.env.n_line == 59 - assert self.env.n_load == 37 - assert self.env.n_gen == 22 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 36 + assert type(self.env).n_line == 59 + assert type(self.env).n_load == 37 + assert type(self.env).n_gen == 22 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, PowerlineSetAction) @@ -121,11 +122,11 @@ def setUp(self) -> None: _ = self.env.reset() def test_elements(self): - assert self.env.n_sub == 118 - assert self.env.n_line == 186 - assert self.env.n_load == 99 - assert self.env.n_gen == 62 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 118 + assert type(self.env).n_line == 186 + assert type(self.env).n_load == 99 + assert type(self.env).n_gen == 62 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -165,11 +166,11 @@ def setUp(self) -> None: _ = self.env.reset() def test_elements(self): - assert self.env.n_sub == 14 - assert self.env.n_line == 20 - assert self.env.n_load == 11 - assert self.env.n_gen == 6 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 14 + assert type(self.env).n_line == 20 + assert type(self.env).n_load == 11 + assert type(self.env).n_gen == 6 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -209,11 +210,11 @@ def setUp(self) -> None: _ = self.env.reset() def test_elements(self): - assert self.env.n_sub == 14 - assert self.env.n_line == 20 - assert self.env.n_load == 11 - assert self.env.n_gen == 6 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 14 + assert type(self.env).n_line == 20 + assert type(self.env).n_load == 11 + assert type(self.env).n_gen == 6 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -253,11 +254,11 @@ def setUp(self) -> None: _ = self.env.reset() def test_elements(self): - assert self.env.n_sub == 14 - assert self.env.n_line == 20 - assert self.env.n_load == 11 - assert self.env.n_gen == 6 - assert self.env.n_storage == 2 + assert type(self.env).n_sub == 14 + assert type(self.env).n_line == 20 + assert type(self.env).n_load == 11 + assert type(self.env).n_gen == 6 + assert type(self.env).n_storage == 2 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -288,5 +289,102 @@ def test_random_action(self): ) + +class TestL2RPNWCCI2022(unittest.TestCase): + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make("l2rpn_wcci_2022", test=True, _add_to_name=type(self).__name__) + _ = self.env.reset(seed=0) + + def test_elements(self): + assert type(self.env).n_sub == 118, f"{type(self.env).n_sub} vs 118" + assert type(self.env).n_line == 186, f"{type(self.env).n_line} vs 186" + assert type(self.env).n_load == 91, f"{type(self.env).n_load} vs 91" + assert type(self.env).n_gen == 62, f"{type(self.env).n_gen} vs 62" + assert type(self.env).n_storage == 7, f"{type(self.env).n_storage} vs 7" + + def test_opponent(self): + assert issubclass(self.env._opponent_action_class, PowerlineSetAction) + assert isinstance(self.env._opponent, GeometricOpponent) + assert self.env._opponent_action_space.n == type(self.env).n_line + + def test_action_space(self): + assert issubclass(self.env.action_space.subtype, PlayableAction) + assert self.env.action_space.n == 1567, ( + f"act space size is {self.env.action_space.n}, should be {1567}" + ) + + def test_observation_space(self): + assert issubclass(self.env.observation_space.subtype, CompleteObservation) + size_th = 4295 + assert self.env.observation_space.n == size_th, ( + f"obs space size is " + f"{self.env.observation_space.n}, " + f"should be {size_th}" + ) + + def test_random_action(self): + """test i can perform some step (random)""" + i = 0 + for i in range(10): + act = self.env.action_space.sample() + obs, reward, done, info = self.env.step(act) + if done: + break + assert i >= 1, ( + "could not perform the random action test because it games over first time step. " + "Please fix the test and try again" + ) + + +class TestL2RPNIDF2023(unittest.TestCase): + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make("l2rpn_idf_2023", test=True, _add_to_name=type(self).__name__) + _ = self.env.reset(seed=0) + + def test_elements(self): + assert type(self.env).n_sub == 118, f"{type(self.env).n_sub} vs 118" + assert type(self.env).n_line == 186, f"{type(self.env).n_line} vs 186" + assert type(self.env).n_load == 99, f"{type(self.env).n_load} vs 99" + assert type(self.env).n_gen == 62, f"{type(self.env).n_gen} vs 62" + assert type(self.env).n_storage == 7, f"{type(self.env).n_storage} vs 7" + + def test_opponent(self): + assert issubclass(self.env._opponent_action_class, PowerlineSetAction) + assert isinstance(self.env._opponent, GeometricOpponentMultiArea) + assert self.env._opponent_action_space.n == type(self.env).n_line + + def test_action_space(self): + assert issubclass(self.env.action_space.subtype, PlayableAction) + assert self.env.action_space.n == 1605, ( + f"act space size is {self.env.action_space.n}, should be {1605}" + ) + + def test_observation_space(self): + assert issubclass(self.env.observation_space.subtype, CompleteObservation) + size_th = 4460 + assert self.env.observation_space.n == size_th, ( + f"obs space size is " + f"{self.env.observation_space.n}, " + f"should be {size_th}" + ) + + def test_random_action(self): + """test i can perform some step (random)""" + i = 0 + for i in range(10): + act = self.env.action_space.sample() + obs, reward, done, info = self.env.step(act) + if done: + break + assert i >= 1, ( + "could not perform the random action test because it games over first time step. " + "Please fix the test and try again" + ) + + if __name__ == "__main__": unittest.main() diff --git a/grid2op/tests/test_attached_envs_compat.py b/grid2op/tests/test_attached_envs_compat.py index 65b170cd2..9b7904970 100644 --- a/grid2op/tests/test_attached_envs_compat.py +++ b/grid2op/tests/test_attached_envs_compat.py @@ -34,15 +34,15 @@ def setUp(self) -> None: self.env.seed(0) def test_elements(self): - assert self.env.n_sub == 36 - assert self.env.n_line == 59 - assert self.env.n_load == 37 - assert self.env.n_gen == 22 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 36 + assert type(self.env).n_line == 59 + assert type(self.env).n_load == 37 + assert type(self.env).n_gen == 22 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, PowerlineSetAction) - assert self.env._opponent_action_space.n == self.env.n_line + assert self.env._opponent_action_space.n == type(self.env).n_line def test_action_space(self): assert issubclass(self.env.action_space.subtype, PlayableAction) @@ -79,11 +79,11 @@ def setUp(self) -> None: self.env.seed(0) def test_elements(self): - assert self.env.n_sub == 118 - assert self.env.n_line == 186 - assert self.env.n_load == 99 - assert self.env.n_gen == 62 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 118 + assert type(self.env).n_line == 186 + assert type(self.env).n_load == 99 + assert type(self.env).n_gen == 62 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -127,11 +127,11 @@ def setUp(self) -> None: self.env.seed(42) def test_elements(self): - assert self.env.n_sub == 14 - assert self.env.n_line == 20 - assert self.env.n_load == 11 - assert self.env.n_gen == 6 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 14 + assert type(self.env).n_line == 20 + assert type(self.env).n_load == 11 + assert type(self.env).n_gen == 6 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -172,11 +172,11 @@ def setUp(self) -> None: self.env.seed(0) def test_elements(self): - assert self.env.n_sub == 14 - assert self.env.n_line == 20 - assert self.env.n_load == 11 - assert self.env.n_gen == 6 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 14 + assert type(self.env).n_line == 20 + assert type(self.env).n_load == 11 + assert type(self.env).n_gen == 6 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -214,14 +214,14 @@ def setUp(self) -> None: _compat_glop_version=GridObjects.BEFORE_COMPAT_VERSION, _add_to_name=type(self).__name__+"test_attached_compat_4", ) - self.env.seed(0) + self.env.seed(3) # 0, 1 and 2 leads to "wrong action" (games over) def test_elements(self): - assert self.env.n_sub == 14 - assert self.env.n_line == 20 - assert self.env.n_load == 11 - assert self.env.n_gen == 6 - assert self.env.n_storage == 0 + assert type(self.env).n_sub == 14 + assert type(self.env).n_line == 20 + assert type(self.env).n_load == 11 + assert type(self.env).n_gen == 6 + assert type(self.env).n_storage == 0 def test_opponent(self): assert issubclass(self.env._opponent_action_class, DontAct) @@ -239,7 +239,9 @@ def test_same_env_as_no_storage(self): res = 0 with warnings.catch_warnings(): warnings.filterwarnings("ignore") - env = grid2op.make("educ_case14_redisp", test=True, _add_to_name=type(self).__name__+"test_same_env_as_no_storage") + env = grid2op.make("educ_case14_redisp", + test=True, + _add_to_name=type(self).__name__+"test_same_env_as_no_storage") for attr in self.env.observation_space.attr_list_vect: tmp = getattr(self.env.observation_space._template_obj, attr).shape tmp2 = getattr(env.observation_space._template_obj, attr).shape @@ -272,7 +274,6 @@ def test_random_action(self): act = self.env.action_space.sample() obs, reward, done, info = self.env.step(act) if done: - pdb.set_trace() break assert i >= 1, ( "could not perform the random action test because it games over first time step. " diff --git a/grid2op/tests/test_backend_shunt_deactivated.py b/grid2op/tests/test_backend_shunt_deactivated.py new file mode 100644 index 000000000..c9db09253 --- /dev/null +++ b/grid2op/tests/test_backend_shunt_deactivated.py @@ -0,0 +1,25 @@ +# Copyright (c) 2024, 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. + +import unittest + +from grid2op.Backend import PandaPowerBackend + +class PandaPowerNoShunt(PandaPowerBackend): + shunts_data_available = False + +from grid2op._create_test_suite import create_test_suite +from grid2op.tests.aaa_test_backend_interface import AAATestBackendAPI +class TestBackendAPI_PPNoShuntTester(AAATestBackendAPI, unittest.TestCase): + def make_backend(self, detailed_infos_for_cascading_failures=False): + return PandaPowerNoShunt(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) + + +# and run it with `python -m unittest gridcal_backend_tests.py` +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/grid2op/tests/test_basic_env_ls.py b/grid2op/tests/test_basic_env_ls.py index 3b4b0b2ab..c3214a26f 100644 --- a/grid2op/tests/test_basic_env_ls.py +++ b/grid2op/tests/test_basic_env_ls.py @@ -9,14 +9,13 @@ import warnings import unittest import numpy as np +import tempfile +import os +import json +import packaging +from packaging import version import grid2op -try: - from lightsim2grid import LightSimBackend - LS_AVAIL = True -except ImportError: - LS_AVAIL = False - pass from grid2op.Environment import Environment from grid2op.Runner import Runner from grid2op.gym_compat import (GymEnv, @@ -24,6 +23,19 @@ BoxGymObsSpace, DiscreteActSpace, MultiDiscreteActSpace) +from grid2op.Action import PlayableAction +from grid2op.Parameters import Parameters +from grid2op.Observation import CompleteObservation +from grid2op.Agent import RandomAgent +from grid2op.tests.helper_path_test import data_test_dir +from grid2op.Episode import EpisodeData + +try: + from lightsim2grid import LightSimBackend + LS_AVAIL = True +except ImportError: + LS_AVAIL = False + pass class TestEnvironmentBasic(unittest.TestCase): @@ -73,7 +85,24 @@ def test_reset(self): obs = self.env.reset() assert obs.timestep_overflow[self.line_id] == 0 assert obs.rho[self.line_id] > 1. + + def test_can_make_2_envs(self): + env_name = "l2rpn_case14_sandbox" + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env = grid2op.make(env_name, test=True, backend=LightSimBackend()) + + param = Parameters() + param.NO_OVERFLOW_DISCONNECTION = True + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env1 = grid2op.make("educ_case14_storage", + test=True, + action_class=PlayableAction, + param=param, + backend=LightSimBackend()) + class TestEnvironmentBasicCpy(TestEnvironmentBasic): def setUp(self) -> None: @@ -119,7 +148,143 @@ def test_runner(self): # - it disconnect stuff in `self.env_in` # - it does not affect anything in `self.env_out` assert not obs_in.line_status[self.line_id], f"error for step {i}: line is not disconnected" - + + def test_backward_compatibility(self): + # TODO copy paste from test_Runner + backward_comp_version = [ + "1.6.4", # minimum version for lightsim2grid + "1.6.5", + "1.7.0", + "1.7.1", + "1.7.2", + "1.8.1", + # "1.9.0", # this one is bugy I don"t know why + "1.9.1", + "1.9.2", + "1.9.3", + "1.9.4", + "1.9.5", + "1.9.6", + "1.9.7", + "1.9.8", + "1.10.0", + "1.10.1", + ] + # first check a normal run + curr_version = "test_version" + PATH_PREVIOUS_RUNNER = os.path.join(data_test_dir, "runner_data") + assert ( + "curtailment" in CompleteObservation.attr_list_vect + ), "error at the beginning" + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + with grid2op.make( + "rte_case5_example", test=True, + _add_to_name=type(self).__name__, + backend=LightSimBackend() + ) as env, tempfile.TemporaryDirectory() as path: + runner = Runner(**env.get_params_for_runner(), agentClass=RandomAgent) + runner.run( + nb_episode=2, + path_save=os.path.join(path, curr_version), + pbar=False, + max_iter=100, + env_seeds=[1, 0], + agent_seeds=[42, 69], + ) + # check that i can read this data generate for this runner + try: + self._aux_backward(path, curr_version, curr_version) + except Exception as exc_: + raise RuntimeError(f"error for {curr_version}") from exc_ + assert ( + "curtailment" in CompleteObservation.attr_list_vect + ), "error after the first runner" + + # check that it raises a warning if loaded on the compatibility version + grid2op_version = backward_comp_version[0] + with self.assertWarns(UserWarning, msg=f"error for {grid2op_version}"): + self._aux_backward( + PATH_PREVIOUS_RUNNER, f"res_agent_{grid2op_version}", grid2op_version + ) + + # now check the compat versions + for grid2op_version in backward_comp_version: + # check that i can read previous data stored from previous grid2Op version + # can be loaded properly + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + try: + self._aux_backward( + PATH_PREVIOUS_RUNNER, + f"res_agent_{grid2op_version}", + grid2op_version, + ) + except Exception as exc_: + raise RuntimeError(f"error for {grid2op_version}") from exc_ + assert "curtailment" in CompleteObservation.attr_list_vect, ( + f"error after the legacy version " f"{grid2op_version}" + ) + + def _aux_backward(self, base_path, g2op_version_txt, g2op_version): + # TODO copy paste from test_Runner + episode_studied = EpisodeData.list_episode( + os.path.join(base_path, g2op_version_txt) + ) + for base_path, episode_path in episode_studied: + assert "curtailment" in CompleteObservation.attr_list_vect, ( + f"error after the legacy version " f"{g2op_version}" + ) + this_episode = EpisodeData.from_disk(base_path, episode_path) + assert "curtailment" in CompleteObservation.attr_list_vect, ( + f"error after the legacy version " f"{g2op_version}" + ) + full_episode_path = os.path.join(base_path, episode_path) + with open( + os.path.join(full_episode_path, "episode_meta.json"), + "r", + encoding="utf-8", + ) as f: + meta_data = json.load(f) + nb_ts = int(meta_data["nb_timestep_played"]) + try: + assert len(this_episode.actions) == nb_ts, ( + f"wrong number of elements for actions for version " + f"{g2op_version_txt}: {len(this_episode.actions)} vs {nb_ts}" + ) + assert len(this_episode.observations) == nb_ts + 1, ( + f"wrong number of elements for observations " + f"for version {g2op_version_txt}: " + f"{len(this_episode.observations)} vs {nb_ts}" + ) + assert len(this_episode.env_actions) == nb_ts, ( + f"wrong number of elements for env_actions for " + f"version {g2op_version_txt}: " + f"{len(this_episode.env_actions)} vs {nb_ts}" + ) + except Exception as exc_: + raise exc_ + g2op_ver = "" + try: + g2op_ver = version.parse(g2op_version) + except packaging.version.InvalidVersion: + if g2op_version != "test_version": + g2op_ver = version.parse("0.0.1") + else: + g2op_ver = version.parse("1.4.1") + if g2op_ver <= version.parse("1.4.0"): + assert ( + EpisodeData.get_grid2op_version(full_episode_path) == "<=1.4.0" + ), "wrong grid2op version stored (grid2op version <= 1.4.0)" + elif g2op_version == "test_version": + assert ( + EpisodeData.get_grid2op_version(full_episode_path) + == grid2op.__version__ + ), "wrong grid2op version stored (test_version)" + else: + assert ( + EpisodeData.get_grid2op_version(full_episode_path) == g2op_version + ), "wrong grid2op version stored (>=1.5.0)" class TestBasicEnvironmentGym(unittest.TestCase): def setUp(self) -> None: diff --git a/grid2op/tests/test_gym_asynch_env.py b/grid2op/tests/test_gym_asynch_env.py new file mode 100644 index 000000000..e0cca4c75 --- /dev/null +++ b/grid2op/tests/test_gym_asynch_env.py @@ -0,0 +1,185 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# See AUTHORS.txt and https://github.com/rte-france/Grid2Op/pull/319 +# 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. + +import unittest +from gymnasium.spaces import Box, Discrete, MultiDiscrete, Dict +from gymnasium.vector import AsyncVectorEnv +import warnings +import numpy as np +from multiprocessing import set_start_method + +import grid2op +from grid2op.Action import PlayableAction +from grid2op.gym_compat import GymEnv, BoxGymActSpace, BoxGymObsSpace, DiscreteActSpace, MultiDiscreteActSpace + + +class AsyncGymEnvTester_Fork(unittest.TestCase): + def _aux_start_method(self): + return "fork" + + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + # this needs to be tested with pandapower backend + self.env = grid2op.make("educ_case14_storage", + test=True, + _add_to_name=type(self).__name__, + action_class=PlayableAction, + experimental_read_from_local_dir=False) + obs = self.env.reset(seed=0, options={"time serie id": 0}) + return super().setUp() + + def test_default_space_obs_act(self): + template_env = GymEnv(self.env) + template_env.action_space.seed(0) + obs = template_env.reset(seed=0, options={"time serie id": 0}) + async_vect_env = AsyncVectorEnv((lambda: GymEnv(self.env), lambda: GymEnv(self.env)), + context=self._aux_start_method()) + assert isinstance(async_vect_env.action_space, Dict) + assert isinstance(async_vect_env.observation_space, Dict) + obs, infos = async_vect_env.reset(seed=[0, 1], + options={"time serie id": 0}) + + dn_act_single = template_env.action_space.sample() + for k, v in dn_act_single.items(): + v[:] = 0 + dn_acts = {k: np.tile(v, reps=[2, 1]) for k, v in dn_act_single.items()} + obs2 = async_vect_env.step(dn_acts) + + rnd_acts_li = [template_env.action_space.sample(), template_env.action_space.sample()] + rnd_acts = {k: np.concatenate((rnd_acts_li[0][k], rnd_acts_li[1][k])) for k in rnd_acts_li[0].keys()} + obs3 = async_vect_env.step(rnd_acts) + + obs, infos = async_vect_env.reset(seed=[2, 3], + options={"time serie id": 0}, + ) + + def _aux_obs_act_vect(self, ts_id=0): + gym_env = GymEnv(self.env) + gym_env.action_space.close() + gym_env.action_space = BoxGymActSpace(self.env.action_space, attr_to_keep=["redispatch", "curtail"]) + gym_env.observation_space.close() + gym_env.observation_space = BoxGymObsSpace(self.env.observation_space, attr_to_keep=["rho"]) + gym_env.action_space.seed(0) + _ = gym_env.reset(seed=0, options={"time serie id": ts_id}) + return gym_env + + def test_space_obs_act_vect(self): + template_env = self._aux_obs_act_vect(0) + async_vect_env = AsyncVectorEnv((lambda: self._aux_obs_act_vect(1), + lambda: self._aux_obs_act_vect(2)), + context=self._aux_start_method()) + try: + assert isinstance(async_vect_env.action_space, Box) + assert isinstance(async_vect_env.observation_space, Box) + obs, infos = async_vect_env.reset(seed=[0, 1], + options={"time serie id": 0}) + + dn_act_single = template_env.action_space.sample() + dn_act_single[:] = 0 + dn_acts = np.tile(dn_act_single, reps=[2, 1]) + obs2 = async_vect_env.step(dn_acts) + + rnd_acts_li = [template_env.action_space.sample().reshape(1,-1), template_env.action_space.sample().reshape(1,-1)] + rnd_acts = np.concatenate(rnd_acts_li) + obs3 = async_vect_env.step(rnd_acts) + + obs, infos = async_vect_env.reset(seed=[2, 3], + options={"time serie id": 0}, + ) + finally: + async_vect_env.close() + template_env.close() + + def _aux_obs_vect_act_discrete(self, ts_id=0): + gym_env = GymEnv(self.env) + gym_env.observation_space.close() + gym_env.observation_space = BoxGymObsSpace(self.env.observation_space, attr_to_keep=["rho"]) + gym_env.action_space.close() + gym_env.action_space = DiscreteActSpace(self.env.action_space, attr_to_keep=["set_bus"]) + gym_env.action_space.seed(0) + _ = gym_env.reset(seed=0, options={"time serie id": ts_id}) + return gym_env + + def test_space_obs_vect_act_discrete(self): + template_env = self._aux_obs_vect_act_discrete(0) + assert isinstance(template_env.action_space, Discrete) + async_vect_env = AsyncVectorEnv((lambda: self._aux_obs_vect_act_discrete(1), + lambda: self._aux_obs_vect_act_discrete(2)), + context=self._aux_start_method()) + try: + assert isinstance(async_vect_env.action_space, MultiDiscrete) # converted to MultiDiscrete by gymnasium + assert isinstance(async_vect_env.observation_space, Box) + obs, infos = async_vect_env.reset(seed=[0, 1], + options={"time serie id": 0}) + + dn_act_single = 0 + dn_acts = np.tile(dn_act_single, reps=[2, 1]) + obs2 = async_vect_env.step(dn_acts) + + rnd_acts_li = [template_env.action_space.sample().reshape(1,-1), template_env.action_space.sample().reshape(1,-1)] + rnd_acts = np.concatenate(rnd_acts_li) + obs3 = async_vect_env.step(rnd_acts) + + obs, infos = async_vect_env.reset(seed=[2, 3], + options={"time serie id": 0}, + ) + finally: + async_vect_env.close() + template_env.close() + + def _aux_obs_vect_act_multidiscrete(self, ts_id=0): + gym_env = GymEnv(self.env) + gym_env.observation_space.close() + gym_env.observation_space = BoxGymObsSpace(self.env.observation_space, attr_to_keep=["rho"]) + gym_env.action_space.close() + gym_env.action_space = MultiDiscreteActSpace(self.env.action_space, attr_to_keep=["one_sub_set", "one_line_set"]) + gym_env.action_space.seed(0) + _ = gym_env.reset(seed=0, options={"time serie id": ts_id}) + return gym_env + + def test_space_obs_vect_act_multidiscrete(self): + template_env = self._aux_obs_vect_act_multidiscrete(0) + assert isinstance(template_env.action_space, MultiDiscrete) + async_vect_env = AsyncVectorEnv((lambda: self._aux_obs_vect_act_multidiscrete(1), + lambda: self._aux_obs_vect_act_multidiscrete(2)), + context=self._aux_start_method()) + try: + assert isinstance(async_vect_env.action_space, Box) # converted to Box by gymnasium + assert isinstance(async_vect_env.observation_space, Box) + obs, infos = async_vect_env.reset(seed=[0, 1], + options={"time serie id": 0}) + + dn_act_single = template_env.action_space.sample() + dn_act_single[:] = 0 + dn_acts = np.tile(dn_act_single, reps=[2, 1]) + obs2 = async_vect_env.step(dn_acts) + + rnd_acts_li = [template_env.action_space.sample().reshape(1,-1), template_env.action_space.sample().reshape(1,-1)] + rnd_acts = np.concatenate(rnd_acts_li) + obs3 = async_vect_env.step(rnd_acts) + + obs, infos = async_vect_env.reset(seed=[2, 3], + options={"time serie id": 0}, + ) + finally: + async_vect_env.close() + template_env.close() + + +class AsyncGymEnvTester_Spawn(AsyncGymEnvTester_Fork): + # Will be working when branch class_in_files will be merged + def _aux_start_method(self): + return "spawn" + + def setUp(self) -> None: + self.skipTest("Not handled at the moment") + return super().setUp() + +if __name__ == "__main__": + unittest.main() diff --git a/grid2op/tests/test_issue_446.py b/grid2op/tests/test_issue_446.py index dd0278a0b..a194b1caa 100644 --- a/grid2op/tests/test_issue_446.py +++ b/grid2op/tests/test_issue_446.py @@ -11,12 +11,15 @@ from grid2op.gym_compat import BoxGymObsSpace import numpy as np import unittest +import warnings class Issue446Tester(unittest.TestCase): def test_box_action_space(self): # We considers only redispatching actions - env = grid2op.make("l2rpn_case14_sandbox", test=True, _add_to_name=type(self).__name__) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env = grid2op.make("l2rpn_case14_sandbox", test=True, _add_to_name=type(self).__name__) divide = {"hour_of_day": np.ones(1)} subtract = {"hour_of_day": np.zeros(1)} diff --git a/grid2op/tests/test_issue_511.py b/grid2op/tests/test_issue_511.py index 5ff3db8de..4d5f558bf 100644 --- a/grid2op/tests/test_issue_511.py +++ b/grid2op/tests/test_issue_511.py @@ -36,7 +36,6 @@ def test_issue_set_bus(self): topo_action = self.env.action_space(act) as_dict = topo_action.as_dict() - print(as_dict) assert len(as_dict['set_bus_vect']['0']) == 2 # two objects modified def test_issue_change_bus(self): diff --git a/grid2op/tests/test_issue_598.py b/grid2op/tests/test_issue_598.py new file mode 100644 index 000000000..7260e8c4e --- /dev/null +++ b/grid2op/tests/test_issue_598.py @@ -0,0 +1,162 @@ +# Copyright (c) 2024, 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. + +import numpy as np +import warnings + +import grid2op +import unittest +from grid2op.Chronics import FromHandlers +from grid2op.Chronics.handlers import PerfectForecastHandler, CSVHandler + + +class Issue598Tester(unittest.TestCase): + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make("educ_case14_storage", + test=True, + data_feeding_kwargs={"gridvalueClass": FromHandlers, + "gen_p_handler": CSVHandler("prod_p"), + "load_p_handler": CSVHandler("load_p"), + "gen_v_handler": CSVHandler("prod_v"), + "load_q_handler": CSVHandler("load_q"), + "gen_p_for_handler": PerfectForecastHandler("prod_p_forecasted"), + "load_p_for_handler": PerfectForecastHandler("load_p_forecasted"), + "load_q_for_handler": PerfectForecastHandler("load_q_forecasted"), + "h_forecast": (5, 10, 15, 20, 25, 30), + } + ) + params = self.env.parameters + params.ACTIVATE_STORAGE_LOSS = False + self.env.change_parameters(params) + self.env.change_forecast_parameters(params) + self.env.reset(seed=0, options={"time serie id": 0}) + self.dn = self.env.action_space() + return super().setUp() + + def test_issue_598_dn_same_res(self): + """no redisp: simu = step""" + obs, *_ = self.env.step(self.dn) + obs_simulate, *_ = obs.simulate(self.dn, time_step=0) + #no redispatch action yet, the productions are the same after simulation on the same state + assert (np.abs(obs_simulate.prod_p - obs.prod_p) <= 1e-6).all() + + def test_issue_598_dn_redisp(self, redisp_amout=2., storage_amount=None): + """one small redispatch action before simulation and then a single simulation of do nothing + on current step (default args)""" + self.skipTest("Not sure it should be equal, waiting for the model in the issue 598") + act = self.env.action_space() + if redisp_amout is not None: + act.redispatch = {"gen_2_1": redisp_amout} + if storage_amount is not None: + act.storage_p = [(1, 10.)] + + obs, reward, done, info = self.env.step(act) + obs, reward, done, info = self.env.step(self.dn) + assert not done + assert not info["is_ambiguous"] + assert not info["is_illegal"] + print("here here here") + obs_simulate, *_ = obs.simulate(self.dn, time_step=0) + # obs.gen_p.sum() no redisp: 262.86395 + assert (np.abs(obs_simulate.prod_p - obs.prod_p) <= 1e-6).all(), f"{obs_simulate.prod_p} vs {obs.prod_p}" + assert (np.abs(obs_simulate.storage_charge - obs.storage_charge) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power - obs.storage_power) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power_target - obs.storage_power_target) <= 1e-6).all() + + def test_simulate_ok_current_step_redisp_large(self): + self.skipTest("Does not pass: redisp is not 'limited' by the simulate in this case") + self.test_issue_598_dn_redisp(redisp_amout=-10.) + + def test_simulate_ok_current_step_storage(self): + """doing a storage action. Then a simulate on current step lead to the same result""" + self.skipTest("Does not pass: storage is not the same in this case (nothing in simulate, but something in the env)") + # self.skipTest("Does not pass: curtailment is not 'limited' by the simulate in this case") + self.test_issue_598_dn_redisp(redisp_amout=None, storage_amount=10.) + + def test_simulate_step_redisp_before(self, redisp_amout=2., storage_amount=None): + """one small redispatch before simulation and then lots of simulation of do nothing (default args)""" + act = self.env.action_space() + if redisp_amout is not None: + act.redispatch = {"gen_2_1": redisp_amout} + if storage_amount is not None: + act.storage_p = [(1, 10.)] + obs, *_ = self.env.step(act) + next_obs = obs + for time_step in [1, 2, 3, 4, 5, 6]: + obs_simulate, *_ = next_obs.simulate(self.dn, time_step=1) + next_obs, *_ = self.env.step(self.dn) + assert (np.abs(obs_simulate.prod_p - next_obs.prod_p) <= 1e-6).all(), f"for h={time_step}: {obs_simulate.prod_p} vs {next_obs.prod_p}" + assert (np.abs(obs_simulate.storage_charge - next_obs.storage_charge) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power - next_obs.storage_power) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power_target - next_obs.storage_power_target) <= 1e-6).all() + + def test_simulate_step_redisp_before_from_init(self, redisp_amout=2., storage_amount=None): + """one small redispatch and then lots of simulation of do nothing (increasing the forecast horizon) + (default args) + """ + act = self.env.action_space() + if redisp_amout is not None: + act.redispatch = {"gen_2_1": redisp_amout} + if storage_amount is not None: + act.storage_p = [(1, 10.)] + obs, *_ = self.env.step(act) + for time_step in [1, 2, 3, 4, 5, 6]: + obs_simulate, *_ = obs.simulate(self.dn, time_step=time_step) + next_obs, *_ = self.env.step(self.dn) + assert (np.abs(obs_simulate.prod_p - next_obs.prod_p) <= 1e-6).all(), f"for h={time_step}: {obs_simulate.prod_p} vs {next_obs.prod_p}" + assert (np.abs(obs_simulate.storage_charge - next_obs.storage_charge) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power - next_obs.storage_power) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power_target - next_obs.storage_power_target) <= 1e-6).all() + + def test_simulate_step_redisp_before_chain(self, redisp_amout=2., storage_amount=None): + """one small redispatch and then lots of simulation of do nothing (chaining simulate on the forecasts) + (default args)""" + act = self.env.action_space() + if redisp_amout is not None: + act.redispatch = {"gen_2_1": redisp_amout} + if storage_amount is not None: + act.storage_p = [(1, 10.)] + obs, *_ = self.env.step(act) + obs_simulate = obs + for time_step in [1, 2, 3, 4, 5, 6]: + obs_simulate, *_ = obs_simulate.simulate(self.dn, time_step=1) + next_obs, *_ = self.env.step(self.dn) + assert (np.abs(obs_simulate.prod_p - next_obs.prod_p) <= 1e-6).all(), f"for h={time_step}: {obs_simulate.prod_p} vs {next_obs.prod_p}" + assert (np.abs(obs_simulate.storage_charge - next_obs.storage_charge) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power - next_obs.storage_power) <= 1e-6).all() + assert (np.abs(obs_simulate.storage_power_target - next_obs.storage_power_target) <= 1e-6).all() + + def test_simulate_step_redisp_before_large(self): + """one large redispatch before simulation and then lots of simulation of do nothing""" + self.test_simulate_step_redisp_before(redisp_amout=-10.) # -10. so that redisp cannot be satisfied at first + + def test_simulate_step_redisp_before_from_init_large(self): + """one large redispatch and then lots of simulation of do nothing (increasing the forecast horizon)""" + self.test_simulate_step_redisp_before_from_init(redisp_amout=-10.) # -10. so that redisp cannot be satisfied at first + + def test_simulate_step_redisp_before_chain_large(self): + """one large redispatch and then lots of simulation of do nothing (increasing the forecast horizon)""" + self.test_simulate_step_redisp_before_chain(redisp_amout=-10.) # -10. so that redisp cannot be satisfied at first + + def test_simulate_step_storage_before_large(self): + """one action on storage unit before simulation and then lots of simulation of do nothing""" + self.test_simulate_step_redisp_before(redisp_amout=None, storage_amount=10.) + + def test_simulate_step_redisp_storage_from_init_large(self): + """one action on storage unit and then lots of simulation of do nothing (increasing the forecast horizon)""" + self.test_simulate_step_redisp_before_from_init(redisp_amout=None, storage_amount=10.) + + def test_simulate_step_redisp_storage_chain_large(self): + """one action on storage unit and then lots of simulation of do nothing (increasing the forecast horizon)""" + self.test_simulate_step_redisp_before_chain(redisp_amout=None, storage_amount=10.) + + +# TODO when a redisp is done in the forecast +# TODO with curtailment \ No newline at end of file diff --git a/grid2op/typing_variables.py b/grid2op/typing_variables.py index aa5c55c4d..463d9adb7 100644 --- a/grid2op/typing_variables.py +++ b/grid2op/typing_variables.py @@ -6,7 +6,8 @@ # 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 typing import Dict, Literal, Any, Union +from typing import Dict, Literal, Any, Union, List +import numpy as np #: type hints corresponding to the "info" part of the env.step return value STEP_INFO_TYPING = Dict[Literal["disc_lines", @@ -24,5 +25,32 @@ "time_series_id"], Any] +#: Dict representing an action +DICT_ACT_TYPING = Dict[Literal["set_line_status", + "change_line_status", + "set_bus", + "change_bus", + "redispatch", + "set_storage", + "curtail", + "raise_alarm", + "raise_alert", + "injection", + "hazards", + "maintenance", + "shunt"], + Any] +# TODO improve that (especially the Any part) + #: type hints for the "options" flag of reset function -RESET_OPTIONS_TYPING = Union[Dict[Union[str, Literal["time serie id"]], Union[int, str]], None] +RESET_OPTIONS_TYPING = Union[Dict[Literal["time serie id"], int], + Dict[Literal["init state"], DICT_ACT_TYPING], + None] + +#: type hints for a "GridObject" when converted to a dictionary +CLS_AS_DICT_TYPING = Dict[str, + Union[int, # eg n_sub, or n_line + str, # eg name_shunt, name_load + np.ndarray, # eg load_to_subid, gen_pos_topo_vect + List[Union[int, str, float, bool]]] + ] diff --git a/setup.py b/setup.py index 57e002376..ec6d9f963 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def my_test_suite(): pkgs = { "required": [ - "numpy>=1.20", + "numpy>=1.20,<2", # disable numpy 2 for now "scipy>=1.4.1", "pandas>=1.0.3", "pandapower>=2.2.2",