Grid2op / grid2op

Grid2Op a testbed platform to model sequential decision making in power systems.
https://grid2op.readthedocs.io/
Mozilla Public License 2.0
299 stars 117 forks source link

Fetching the 'True' Chronics with obs.simulate(...) #657

Open DEUCE1957 opened 2 weeks ago

DEUCE1957 commented 2 weeks ago

Environment

Bug description

A normal, fully initialized, Environment has a chronics_handler - from which you can get the name / ID of the current episode:

import grid2op
env = grid2op.make("l2rpn_case14_sandbox")
obs = env.reset()
print(f"Current Episode '{env.chronics_handler.get_name()}'")

The call to the Reward Function has the following signature (from BaseReward):

def __call__(self, action:BaseAction, env:BaseEnv, has_error:bool, is_done:bool, is_illegal:bool, is_ambigious:bool):

Hence it should be possible to use env.chronics_handler.get_name() inside a custom reward function, since BaseEnv includes the chronics handler. This is useful for, for instance, fetching the load profiles.

However, this will throw an error if we use obs.simulate():

  File ".../L2RPN/Rewards/EnergyNotSuppliedReward.py", line 22, in __call__
    ep_id = env.chronics_handler.get_name()
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: '_ObsCH' object has no attribute 'get_name'

Since the _ObsEnv class is used in place of the normal BaseEnv, the normal chronics handler does not exist.

My current workaround is to use:

expected_load = env.get_obs(_update_state=False).load_p

Minimal Example

from logging import Logger
import grid2op
from grid2op.Action import BaseAction
from grid2op.Reward import BaseReward
from grid2op.Environment import BaseEnv
from grid2op.Environment._obsEnv import _ObsEnv

internal_env = None
class WeirdReward(BaseReward):

    def __init__(self, logger: Logger = None):
        super().__init__(logger)

    def __call__(self, action: BaseAction, env:BaseEnv, has_error: bool, is_done: bool, is_illegal: bool, is_ambiguous: bool) -> float:
        global internal_env
        if isinstance(env, _ObsEnv):
            print(env)
            internal_env = env
        print(env.chronics_handler.get_name())

        return super().__call__(action, env, has_error, is_done, is_illegal, is_ambiguous)

env = grid2op.make("l2rpn_case14_sandbox", reward_class=WeirdReward)
obs = env.reset()

act = env.action_space({})
sim_obs, sim_reward, sim_done, sim_info = obs.simulate(act)
BDonnot commented 2 weeks ago

Hello,

I will fix it ASAP.

In the mean time (the grid2op release with this fix will wait for some other modifications) I suggest you use:

class WeirdReward(BaseReward):

    def __init__(self, logger: Logger = None):
        super().__init__(logger)

    def __call__(self, action: BaseAction, env:BaseEnv, has_error: bool, is_done: bool, is_illegal: bool, is_ambiguous: bool) -> float:
        if self.is_simulated_env(env):
            return 0. # the data for the simulation does not mean anything, better not use it
        # now you can use env.chronics_handler.get_name()
        return super().__call__(action, env, has_error, is_done, is_illegal, is_ambiguous)
BDonnot commented 2 weeks ago

Oh and by the way, the "True" chronics will never be fed to "obs.simulate" this is a design choice to ensure that a user cannot access the "future" data, in the same way that accessing a real simulator will not provide you any data about the future.