def test_reward(self): done = False i = 0 self.chronics_handler.next_chronics() self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, rewardClass=L2RPNReward, names_chronics_to_backend=self.names_chronics_to_backend, name="test_env_env2") if PROFILE_CODE: cp = cProfile.Profile() cp.enable() beg_ = time.time() cum_reward = dt_float(0.0) do_nothing = self.env.action_space({}) while not done: obs, reward, done, info = self.env.step(do_nothing) # should load the first time stamp cum_reward += reward i += 1 end_ = time.time() if DEBUG: msg_ = "\nEnv: {:.2f}s\n\t - apply act {:.2f}s\n\t - run pf: {:.2f}s\n" \ "\t - env update + observation: {:.2f}s\nTotal time: {:.2f}\nCumulative reward: {:1f}" print(msg_.format( self.env._time_apply_act+self.env._time_powerflow+self.env._time_extract_obs, self.env._time_apply_act, self.env._time_powerflow, self.env._time_extract_obs, end_-beg_, cum_reward)) if PROFILE_CODE: cp.disable() cp.print_stats(sort="tottime") assert i == 287, "Wrong number of timesteps" expected_reward = dt_float(5739.9336) assert dt_float(np.abs(cum_reward - expected_reward)) <= self.tol_one, "Wrong reward"
def init_env(self): """ INTERNAL .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ Initialize the environment that will perform all the computation of this process. Remember the environment only lives in this process. It cannot be transfer to / from the main process. This function also makes sure the chronics are read in different order accross all processes. This is done by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function is provided in :func:`grid2op.Chronics.Multifolder.shuffle`. """ self.space_prng = np.random.RandomState() self.space_prng.seed(seed=self.seed_used) self.backend = self.env_params["_raw_backend_class"]() with warnings.catch_warnings(): # warnings have bee already sent in the main process, no need to resend them warnings.filterwarnings("ignore") self.env = Environment(**self.env_params, backend=self.backend) env_seed = self.space_prng.randint(np.iinfo(dt_int).max) self.all_seeds = self.env.seed(env_seed) self.env.chronics_handler.shuffle(shuffler=lambda x: x[ self.space_prng.choice(len(x), size=len(x), replace=False)])
def init_env(self): """ Initialize the environment that will perform all the computation of this process. Remember the environment only lives in this process. It cannot be transfer to / from the main process. This function also makes sure the chronics are read in different order accross all processes. This is done by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function is provided in :func:`grid2op.Chronics.Multifolder.shuffle`. """ # TODO documentation # TODO seed of the environment. self.space_prng = np.random.RandomState() self.space_prng.seed(seed=self.seed_used) self.backend = self.env_params["backendClass"]() del self.env_params["backendClass"] chronics_handler = self.env_params["chronics_handler"] self.env = Environment(**self.env_params, backend=self.backend) env_seed = self.space_prng.randint(np.iinfo(dt_int).max) self.all_seeds = self.env.seed(env_seed) self.env.chronics_handler.shuffle(shuffler=lambda x: x[self.space_prng.choice(len(x), size=len(x), replace=False)]) # forecast are not forwarded with this method anyway self.env.deactivate_forecast()
def load(self, backend, agent_path=None, name=None, data=None, env_kwargs={}): if data is None: if agent_path is not None and name is not None: self.episode_data = EpisodeData.from_disk(agent_path, name) else: raise Grid2OpException( "To replay an episode you need at least to provide an EpisodeData " "(using the keyword argument \"data=...\") or provide the path and name where " "the " "episode is stored (keyword arguments \"agent_path\" and \"name\")." ) else: self.episode_data = copy.deepcopy(data) self.episode_data.reboot() self.chronics_handler = ChronicsHandler(chronicsClass=_GridFromLog, episode_data=self.episode_data) if "chronics_handler" in env_kwargs: del env_kwargs["chronics_handler"] if "backend" in env_kwargs: del env_kwargs["backend"] if "opponent_class" in env_kwargs: del env_kwargs["opponent_class"] if "name" in env_kwargs: del env_kwargs["name"] nm = "unknonwn" seed = None with open(os.path.join(agent_path, name, "episode_meta.json")) as f: dict_ = json.load(f) nm = re.sub("Environment_", "", dict_["env_type"]) if dict_["env_seed"] is not None: seed = int(dict_["env_seed"]) self.env = Environment(**env_kwargs, backend=backend, chronics_handler=self.chronics_handler, opponent_class=OpponentFromLog, name=nm) if seed is not None: self.env.seed(seed) tmp = self.env.reset() # always have the two bellow synch ! otherwise it messes up the "chronics" # in the env, when calling "env.step" self.current_time_step = 0 self.env.chronics_handler.real_data.curr_iter = 0 # first observation of the scenario current_obs = self.episode_data.observations[self.current_time_step] self._assign_state(current_obs) return self.env.get_obs()
def test_kwargs(self): """test the get_kwargs function properly foward the attention budget""" env2 = Environment(**self.env.get_kwargs()) assert env2._has_attention_budget assert env2._kwargs_attention_budget == self.default_kwargs_att_budget assert env2._attention_budget_cls == LinearAttentionBudget obs = env2.reset() assert obs.attention_budget == 3 obs, reward, done, info = env2.step(env2.action_space()) assert obs.attention_budget == 3 + 1. / (12. * 8.)
def test_copy_env(self): cpy = Environment(**self.env.get_kwargs()) obs1 = cpy.reset() obs2 = self.env.reset() assert obs1 == obs2 obs1, reward1, done1, info1 = cpy.step(self.env.action_space()) obs2, reward2, done2, info2 = self.env.step(self.env.action_space()) assert abs(reward1 - reward2) <= self.tol_one assert done1 == done2 assert info1.keys() == info2.keys() for kk in info1.keys(): assert np.all(info1[kk] == info2[kk]) assert obs1 == obs2
def test_copy_env(self): # first copying method with warnings.catch_warnings(): warnings.filterwarnings("ignore") cpy = Environment(**self.env.get_kwargs()) obs1 = cpy.reset() obs2 = self.env.reset() assert obs1 == obs2 # test both coppy and not copy behave the same if we do the same obs1, reward1, done1, info1 = cpy.step(self.env.action_space()) obs2, reward2, done2, info2 = self.env.step(self.env.action_space()) assert abs(reward1 - reward2) <= self.tol_one assert done1 == done2 assert info1.keys() == info2.keys() for kk in info1.keys(): assert np.all(info1[kk] == info2[kk]) assert obs1 == obs2 # test they are different if we do different stuff obs2, reward2, done2, info2 = self.env.step( self.env.action_space({"set_line_status": [(0, -1)]})) obs1, reward1, done1, info1 = cpy.step(self.env.action_space()) assert obs1.line_status[0] assert not obs2.line_status[0] assert obs1 != obs2 # second copying method self.env.reset() env2 = self.env.copy() # test both coppy and not copy behave the same if we do the same obs1, reward1, done1, info1 = env2.step(self.env.action_space()) obs2, reward2, done2, info2 = self.env.step(self.env.action_space()) assert abs(reward1 - reward2) <= self.tol_one assert done1 == done2 assert info1.keys() == info2.keys() for kk in info1.keys(): assert np.all(info1[kk] == info2[kk]) assert obs1 == obs2 # test they are different if we do different stuff obs2, reward2, done2, info2 = self.env.step( self.env.action_space({"set_line_status": [(0, -1)]})) obs1, reward1, done1, info1 = env2.step(self.env.action_space()) assert obs1.line_status[0] assert not obs2.line_status[0] assert obs1 != obs2 # new "same obs" again after reset obs1 = self.env.reset() obs2 = env2.reset() assert obs1 == obs2
def setUp(self): # powergrid self.backend = self.make_backend() self.path_matpower = self.get_path() self.case_file = self.get_casefile() # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_chron) self.id_chron_to_back_load = np.array([0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = {"loads": {"2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10'}, "lines": {'1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19'}, "prods": {"1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3"}, } # _parameters for the environment self.env_params = Parameters() self.env_params.ALLOW_DISPATCH_GEN_SWITCH_OFF = False with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=BaseAction, name="test_redisp_env1") self.array_double_dispatch = np.array([0., 10., 20., 0., -30.]) # self.array_double_dispatch = np.array([0., 11.208119, 12.846733, 0., -24.054852]) self.tol_one = self.env._tol_poly
def setUp(self): # powergrid self.adn_backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # data self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_chron) self.tolvect = 1e-2 self.tol_one = 1e-5 # force the verbose backend self.adn_backend.detailed_infos_for_cascading_failures = True # _parameters for the environment self.env_params = Parameters() self.names_chronics_to_backend = {"loads": {"2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10'}, "lines": {'1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19'}, "prods": {"1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3"}, } with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.adn_backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, name="test_rules_env1") self.helper_action = self.env._helper_action_env
def setUp(self): # powergrid self.backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_chron) self.id_chron_to_back_load = np.array([0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = {"loads": {"2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10'}, "lines": {'1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19'}, "prods": {"1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3"}, } # _parameters for the environment self.env_params = Parameters() self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=BaseAction) self.array_double_dispatch = np.array([0., 10., 20., 0., -30.])
def make_multi_env(env_init, nb_env): """ This function creates a multi environment compatible with what is expected in the baselines. In particular, it adds the observation_space, the action_space and the reward_range attribute. The way this function works is explained in the getting_started of grid2op. Attributes ----------- env_init: :class:`grid2op.Environment.Environment` The environment to duplicates nb_env: ``int`` The number of environment on with which you want to interact at the same time Returns ------- res: :class:`grid2op.Environment.MultiEnvironment` or :class:`grid2op.Environment.Environment` A copy of the initial environment (if nb_env = 1) or a MultiEnvironment based on the initial environment if nb_env >= 2. """ res = None nb_env = int(nb_env) if nb_env <= 0: raise RuntimeError( "Impossible to create a negative number of environments") if nb_env == 1: warnings.warn( "You asked to create 1 environment. We didn't use the MultiEnvironment for that. We instead " "created a copy of your initial environment.") res = Environment(**env_init.get_kwargs()) else: res = MultiEnvironment(nb_env=nb_env, env=env_init) res.observation_space = env_init.observation_space res.action_space = env_init.action_space res.reward_range = env_init.reward_range return res
def setUp(self): """ The case file is a representation of the case14 as found in the ieee14 powergrid. :return: """ # from ADNBackend import ADNBackend # self.backend = ADNBackend() # self.path_matpower = "/home/donnotben/Documents/RL4Grid/RL4Grid/data" # self.case_file = "ieee14_ADN.xml" # self.backend.load_grid(self.path_matpower, self.case_file) self.tolvect = 1e-2 self.tol_one = 1e-5 self.game_rules = RulesChecker() # pdb.set_trace() self.rewardClass = L2RPNReward self.reward_helper = self.rewardClass() self.obsClass = CompleteObservation self.parameters = Parameters() # powergrid self.backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics_with_hazards") self.chronics_handler = ChronicsHandler( chronicsClass=GridStateFromFile, path=self.path_chron) self.tolvect = 1e-2 self.tol_one = 1e-5 self.id_chron_to_back_load = np.array( [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } # _parameters for the environment self.env_params = Parameters() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, rewardClass=self.rewardClass, name="test_obs_env1")
class TestObservationMaintenance(unittest.TestCase): def setUp(self): """ The case file is a representation of the case14 as found in the ieee14 powergrid. :return: """ self.tolvect = 1e-2 self.tol_one = 1e-5 self.game_rules = RulesChecker() # pdb.set_trace() self.rewardClass = L2RPNReward self.reward_helper = self.rewardClass() self.obsClass = CompleteObservation self.parameters = Parameters() # powergrid self.backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics_with_maintenance") self.chronics_handler = ChronicsHandler( chronicsClass=GridStateFromFileWithForecasts, path=self.path_chron) self.tolvect = 1e-2 self.tol_one = 1e-5 self.id_chron_to_back_load = np.array( [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } # _parameters for the environment self.env_params = Parameters() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, rewardClass=self.rewardClass, name="test_obs_env2", legalActClass=DefaultRules) def tearDown(self) -> None: self.env.close() def test_1_generating_obs_withmaintenance(self): # test that helper_obs is abl to generate a valid observation obs = self.env.get_obs() assert np.all(obs.time_next_maintenance == np.array([ -1, -1, -1, -1, 1, -1, 276, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ])) assert np.all(obs.duration_next_maintenance == np.array( [0, 0, 0, 0, 12, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) action = self.env.action_space({}) _ = self.env.step(action) obs = self.env.get_obs() assert np.all(obs.time_next_maintenance == np.array([ -1, -1, -1, -1, 0, -1, 275, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ])) assert np.all(obs.duration_next_maintenance == np.array( [0, 0, 0, 0, 12, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) _ = self.env.step(action) obs = self.env.get_obs() assert np.all(obs.time_next_maintenance == np.array([ -1, -1, -1, -1, 0, -1, 274, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ])) assert np.all(obs.duration_next_maintenance == np.array( [0, 0, 0, 0, 11, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) def test_simulate_disco_planned_maintenance(self): obs = self.env.get_obs() assert obs.line_status[4] assert obs.time_next_maintenance[4] == 1 assert obs.duration_next_maintenance[4] == 12 # line will be disconnected next time step sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=1) assert not sim_obs.line_status[4] assert sim_obs.time_next_maintenance[4] == 0 assert sim_obs.duration_next_maintenance[4] == 11 # simulation at current step sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=0) assert sim_obs.line_status[4] assert sim_obs.time_next_maintenance[4] == 1 assert sim_obs.duration_next_maintenance[4] == 12 # line will be disconnected next time step sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=1) assert not sim_obs.line_status[4] assert sim_obs.time_next_maintenance[4] == 0 assert sim_obs.duration_next_maintenance[4] == 11 for ts in range(12): obs, reward, done, info = self.env.step(self.env.action_space()) # maintenance will be over next time step assert not obs.line_status[4] assert obs.time_next_maintenance[4] == 0 assert obs.duration_next_maintenance[4] == 1 # if i don't do anything, it's updated properly sim_obs, *_ = obs.simulate(self.env.action_space(), time_step=1) assert not sim_obs.line_status[4] assert sim_obs.time_next_maintenance[4] == -1 assert sim_obs.duration_next_maintenance[4] == 0 # i have the right to reconnect it (if i simulate in the future) act = self.env.action_space() act.line_set_status = [(4, +1)] sim_obs, reward, done, info = obs.simulate(act, time_step=1) assert not info["is_illegal"] assert sim_obs.line_status[4] assert sim_obs.time_next_maintenance[4] == -1 assert sim_obs.duration_next_maintenance[4] == 0 # i don't have the right to reconnect it if i don't simulate in the future sim_obs, reward, done, info = obs.simulate(act, time_step=0) assert info["is_illegal"] assert not sim_obs.line_status[4] assert sim_obs.time_next_maintenance[4] == 0 assert sim_obs.duration_next_maintenance[4] == 1
class RemoteEnv(Process): """ This class represent the environment that is executed on a remote process. Note that the environment is only created in the subprocess, and is not available in the main process. Once created it is not possible to access anything directly from it in the main process, where the BaseAgent lives. Only the :class:`grid2op.Observation.BaseObservation` are forwarded to the agent. """ def __init__(self, env_params, remote, parent_remote, seed, name=None): Process.__init__(self, group=None, target=None, name=name) self.backend = None self.env = None self.env_params = env_params self.remote = remote self.parent_remote = parent_remote self.seed_used = seed self.space_prng = None def init_env(self): """ Initialize the environment that will perform all the computation of this process. Remember the environment only lives in this process. It cannot be transfer to / from the main process. This function also makes sure the chronics are read in different order accross all processes. This is done by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function is provided in :func:`grid2op.Chronics.Multifolder.shuffle`. """ # TODO documentation # TODO seed of the environment. self.space_prng = np.random.RandomState() self.space_prng.seed(seed=self.seed_used) self.backend = self.env_params["backendClass"]() del self.env_params["backendClass"] self.env = Environment(**self.env_params, backend=self.backend) self.env.chronics_handler.shuffle(shuffler=lambda x: x[ self.space_prng.choice(len(x), size=len(x), replace=False)]) def _clean_observation(self, obs): obs._forecasted_grid = [] obs._forecasted_inj = [] obs._obs_env = None obs.action_helper = None def get_obs_ifnotconv(self): # TODO dirty hack because of wrong chronics # need to check!!! conv = False obs = None while not conv: try: obs = self.env.reset() conv = True except: pass return obs def run(self): if self.env is None: self.init_env() while True: cmd, data = self.remote.recv() if cmd == 'get_spaces': self.remote.send( (self.env.observation_space, self.env.action_space)) elif cmd == 's': # perform a step data = self.env.action_space.from_vect(data) obs, reward, done, info = self.env.step(data) if done: # if done do a reset obs = self.get_obs_ifnotconv() self._clean_observation(obs) self.remote.send((obs.to_vect(), reward, done, info)) elif cmd == 'r': # perfom a reset obs = self.get_obs_ifnotconv() self._clean_observation(obs) self.remote.send(obs.to_vect()) elif cmd == 'c': # close everything self.env.close() self.remote.close() break elif cmd == 'z': # adapt the chunk size self.env.set_chunk_size(data) else: raise NotImplementedError
def make_old(name_env="case14_realistic", **kwargs): """ .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ (DEPRECATED) This function is a shortcut to rapidly create some (pre defined) environments within the grid2op Framework. For now, only the environment corresponding to the IEEE "case14" powergrid, with some pre defined chronics is available. Other environments, with different powergrids will be made available in the future. It mimic the ``gym.make`` function. Parameters ---------- name_env: ``str`` Name of the environment to create. param: ``grid2op.Parameters.Parameters``, optional Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an markov decision process, and some internal backend: ``grid2op.Backend.Backend``, optional The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`. action_class: ``type``, optional Type of BaseAction the BaseAgent will be able to perform. If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction` observation_class: ``type``, optional Type of BaseObservation the BaseAgent will receive. If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation` reward_class: ``type``, optional Type of reward signal the BaseAgent will receive. If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward` gamerules_class: ``type``, optional Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints. If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules` grid_path: ``str``, optional The path where the powergrid is located. If provided it must be a string, and point to a valid file present on the hard drive. data_feeding_kwargs: ``dict``, optional Dictionnary that is used to build the `data_feeding` (chronics) objects. chronics_class: ``type``, optional The type of chronics that represents the dynamics of the Environment created. Usually they come from different folders. data_feeding: ``type``, optional The type of chronics handler you want to use. chronics_path: ``str`` Path where to look for the chronics dataset. volagecontroler_class: ``type``, optional The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to other_rewards: ``dict``, optional Dictionnary with other rewards we might want to look at at during training. It is given as a dictionnary with keys the name of the reward, and the values a class representing the new variables. Returns ------- env: :class:`grid2op.Environment.Environment` The created environment. """ warnings.warn("make_old is deprecated. Please consider using make instead") for el in kwargs: if not el in ALLOWED_KWARGS_MAKE: raise EnvError( "Unknown keyword argument \"{}\" used to create an Environement. " "No Environment will be created. " "Accepted keyword arguments are {}".format( el, sorted(ALLOWED_KWARGS_MAKE))) # first extract parameters that doesn't not depend on the powergrid ## the parameters of the game, thermal limits threshold, simulate cascading failure, powerflow mode etc. (the gamification of the game) msg_error = "The parameters of the environment (keyword \"param\") must be an instance of grid2op.Parameters" param = _get_default_aux('param', kwargs, defaultClass=Parameters, defaultClassApp=Parameters, msg_error=msg_error) ## the backend use, to compute the powerflow msg_error = "The backend of the environment (keyword \"backend\") must be an instance of grid2op.Backend" backend = _get_default_aux("backend", kwargs, defaultClass=PandaPowerBackend, defaultClassApp=Backend, msg_error=msg_error) ## type of observation the agent will receive msg_error = "The type of observation of the environment (keyword \"observation_class\")" msg_error += " must be a subclass of grid2op.BaseObservation" observation_class = _get_default_aux("observation_class", kwargs, defaultClass=CompleteObservation, defaultClassApp=BaseObservation, msg_error=msg_error, isclass=True) ## type of rules of the game (mimic the operationnal constraints) msg_error = "The path where the data is located (keyword \"chronics_path\") should be a string." chronics_path = _get_default_aux("chronics_path", kwargs, defaultClassApp=str, defaultinstance='', msg_error=msg_error) # bulid the default parameters for each case file data_feeding_default_class = ChronicsHandler gamerules_class = AlwaysLegal defaultinstance_chronics_kwargs = {} if name_env.lower() == "case14_fromfile": default_grid_path = CASE_14_FILE if chronics_path == '': chronics_path = CHRONICS_MLUTIEPISODE defaultinstance_chronics_kwargs = { "chronicsClass": Multifolder, "path": chronics_path, "gridvalueClass": GridStateFromFileWithForecasts } default_name_converter = {} default_action_class = TopologyAction default_reward_class = L2RPNReward elif name_env.lower() == "l2rpn_2019": warnings.warn( "You are using the \"l2rpn_2019\" environmnet, which will be remove from this package in " "future versions. Please use \"make_new\" to download the real l2rpn dataset." ) if chronics_path == '': msg_error = "Default chronics (provided in this package) cannot be used with the environment " msg_error += "\"l2rpn_2019\". Please download the training data using either the method described in" \ "Grid2Op/l2rpn_2019/README.md (if you downloaded the github repository) or\n" \ "running the command line script (in a terminal):\n" \ "python -m grid2op.download --name \"l2rpn_2019\" --path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD" raise EnvError(msg_error) default_grid_path = L2RPN2019_CASEFILE defaultinstance_chronics_kwargs = { "chronicsClass": Multifolder, "path": chronics_path, "gridvalueClass": ReadPypowNetData } default_name_converter = L2RPN2019_DICT_NAMES default_action_class = TopologyAction default_reward_class = L2RPNReward gamerules_class = DefaultRules elif name_env.lower() == "case5_example": if chronics_path == '': chronics_path = EXAMPLE_CHRONICSPATH default_grid_path = EXAMPLE_CASEFILE defaultinstance_chronics_kwargs = { "chronicsClass": Multifolder, "path": chronics_path, "gridvalueClass": GridStateFromFileWithForecasts } default_name_converter = {} default_action_class = TopologyAction default_reward_class = L2RPNReward gamerules_class = DefaultRules elif name_env.lower() == "case14_test": if chronics_path == '': chronics_path = case14_test_CHRONICSPATH warnings.warn( "Your are using a case designed for testing purpose. Consider using the \"case14_redisp\" " "environment instead.") default_grid_path = case14_test_CASEFILE defaultinstance_chronics_kwargs = { "chronicsClass": Multifolder, "path": chronics_path, "gridvalueClass": GridStateFromFileWithForecasts } default_name_converter = {} default_action_class = TopologyAndDispatchAction default_reward_class = RedispReward gamerules_class = DefaultRules elif name_env.lower() == "case14_redisp": if chronics_path == '': chronics_path = case14_redisp_CHRONICSPATH warnings.warn( "Your are using only 2 chronics for this environment. More can be download by running, " "from a command line:\n" "python -m grid2op.download --name \"case14_redisp\" " "--path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD\DATA") default_grid_path = case14_redisp_CASEFILE defaultinstance_chronics_kwargs = { "chronicsClass": Multifolder, "path": chronics_path, "gridvalueClass": GridStateFromFileWithForecasts } default_name_converter = {} default_action_class = TopologyAndDispatchAction default_reward_class = RedispReward gamerules_class = DefaultRules elif name_env.lower() == "case14_realistic": if chronics_path == '': chronics_path = case14_real_CHRONICSPATH warnings.warn( "Your are using only 2 chronics for this environment. More can be download by running, " "from a command line:\n" "python -m grid2op.download --name \"case14_realistic\" " "--path_save PATH\WHERE\YOU\WANT\TO\DOWNLOAD\DATA") default_grid_path = case14_real_CASEFILE defaultinstance_chronics_kwargs = { "chronicsClass": Multifolder, "path": chronics_path, "gridvalueClass": GridStateFromFileWithForecasts } default_name_converter = {} default_action_class = TopologyAndDispatchAction default_reward_class = RedispReward gamerules_class = DefaultRules elif name_env.lower() == "blank": default_name_converter = {} default_grid_path = "" default_action_class = TopologyAction default_reward_class = L2RPNReward gamerules_class = AlwaysLegal else: raise UnknownEnv( "Unknown Environment named \"{}\". Current known environments are \"case14_fromfile\" " "(default), \"case5_example\", \"case14_redisp\", \"case14_realistic\" " "and \"l2rpn_2019\"".format(name_env)) if "chronicsClass" not in defaultinstance_chronics_kwargs: defaultinstance_chronics_kwargs["chronicsClass"] = ChangeNothing # extract powergrid dependant parameters ## type of rules of the game (mimic the operationnal constraints) msg_error = "The type of rules of the environment (keyword \"gamerules_class\")" msg_error += " must be a subclass of grid2op.BaseRules" gamerules_class = _get_default_aux("gamerules_class", kwargs, defaultClass=gamerules_class, defaultClassApp=BaseRules, msg_error=msg_error, isclass=True) ## type of reward the agent will receive msg_error = "The type of observation of the environment (keyword \"reward_class\")" msg_error += " must be a subclass of grid2op.BaseReward" reward_class = _get_default_aux("reward_class", kwargs, defaultClass=default_reward_class, defaultClassApp=BaseReward, msg_error=msg_error, isclass=True) ## type of action the BaseAgent can perform msg_error = "The type of action of the environment (keyword \"action_class\") must be a subclass of grid2op.BaseAction" action_class = _get_default_aux("action_class", kwargs, defaultClass=default_action_class, defaultClassApp=BaseAction, msg_error=msg_error, isclass=True) ## the powergrid path to use msg_error = "The path where the grid is located (keyword \"grid_path\") should be a string." grid_path = _get_default_aux("grid_path", kwargs, defaultClassApp=str, defaultinstance=default_grid_path, msg_error=msg_error) ## msg_error = "The converter between names (keyword \"names_chronics_to_backend\") should be a dictionnary." names_chronics_to_backend = _get_default_aux( "names_chronics_to_backend", kwargs, defaultClassApp=dict, defaultinstance=default_name_converter, msg_error=msg_error) ## the chronics to use ### the arguments used to build the data, note that the arguments must be compatible with the chronics class msg_error = "The argument to build the data generation process [chronics] (keyword \"data_feeding_kwargs\")" msg_error += " should be a dictionnary." data_feeding_kwargs = _get_default_aux( "data_feeding_kwargs", kwargs, defaultClassApp=dict, defaultinstance=defaultinstance_chronics_kwargs, msg_error=msg_error) for el in defaultinstance_chronics_kwargs: if not el in data_feeding_kwargs: data_feeding_kwargs[el] = defaultinstance_chronics_kwargs[el] ### the chronics generator msg_error = "The argument to build the data generation process [chronics] (keyword \"chronics_class\")" msg_error += " should be a class that inherit grid2op.ChronicsHandler.GridValue." chronics_class_used = _get_default_aux( "chronics_class", kwargs, defaultClassApp=GridValue, defaultClass=data_feeding_kwargs["chronicsClass"], msg_error=msg_error, isclass=True) data_feeding_kwargs["chronicsClass"] = chronics_class_used ### the chronics generator msg_error = "The argument to build the data generation process [chronics] (keyword \"data_feeding\")" msg_error += " should be a class that inherit grid2op.ChronicsHandler.ChronicsHandler." data_feeding = _get_default_aux("data_feeding", kwargs, defaultClassApp=ChronicsHandler, defaultClass=data_feeding_default_class, build_kwargs=data_feeding_kwargs, msg_error=msg_error) ### controler for voltages msg_error = "The argument to build the online controler for chronics (keyword \"volagecontroler_class\")" msg_error += " should be a class that inherit grid2op.VoltageControler.ControlVoltageFromFile." volagecontroler_class = _get_default_aux( "volagecontroler_class", kwargs, defaultClassApp=ControlVoltageFromFile, defaultClass=ControlVoltageFromFile, msg_error=msg_error, isclass=True) ### other rewards msg_error = "The argument to build the online controler for chronics (keyword \"other_rewards\")" msg_error += " should be dictionnary." other_rewards = _get_default_aux("other_rewards", kwargs, defaultClassApp=dict, defaultinstance={}, msg_error=msg_error, isclass=False) # Opponent opponent_action_class = _get_default_aux( "opponent_action_class", kwargs, defaultClassApp=BaseAction, defaultClass=DontAct, msg_error=ERR_MSG_KWARGS["opponent_action_class"], isclass=True) opponent_class = _get_default_aux( "opponent_class", kwargs, defaultClassApp=BaseOpponent, defaultClass=BaseOpponent, msg_error=ERR_MSG_KWARGS["opponent_class"], isclass=True) opponent_init_budget = _get_default_aux( "opponent_init_budget", kwargs, defaultClassApp=float, defaultinstance=0., msg_error=ERR_MSG_KWARGS["opponent_init_budget"], isclass=False) if not os.path.exists(grid_path): raise EnvError( "There is noting at \"{}\" where the powergrid should be located". format(os.path.abspath(grid_path))) env = Environment(init_grid_path=grid_path, chronics_handler=data_feeding, backend=backend, parameters=param, names_chronics_to_backend=names_chronics_to_backend, actionClass=action_class, observationClass=observation_class, rewardClass=reward_class, legalActClass=gamerules_class, voltagecontrolerClass=volagecontroler_class, other_rewards=other_rewards, opponent_action_class=opponent_action_class, opponent_class=opponent_class, opponent_init_budget=opponent_init_budget, name=name_env) # update the thermal limit if any if name_env.lower() == "case14_test": env.set_thermal_limit(case14_test_TH_LIM) env.attach_layout(CASE_14_L2RPN2019_LAYOUT) elif name_env.lower() == "case14_redisp": env.set_thermal_limit(case14_redisp_TH_LIM) env.attach_layout(CASE_14_L2RPN2019_LAYOUT) elif name_env.lower() == "case14_realistic": env.set_thermal_limit(case14_real_TH_LIM) env.attach_layout(CASE_14_L2RPN2019_LAYOUT) elif name_env.lower() == "l2rpn_2019": env.attach_layout(CASE_14_L2RPN2019_LAYOUT) elif name_env.lower() == "case5_example": env.attach_layout(CASE_5_GRAPH_LAYOUT) return env
class BaseTestRedispatch(MakeBackend): def setUp(self): # powergrid self.backend = self.make_backend() self.path_matpower = self.get_path() self.case_file = self.get_casefile() # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler( chronicsClass=GridStateFromFile, path=self.path_chron) self.id_chron_to_back_load = np.array( [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } # _parameters for the environment self.env_params = Parameters() self.env_params.ALLOW_DISPATCH_GEN_SWITCH_OFF = False with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=BaseAction, name="test_redisp_env1") self.array_double_dispatch = np.array([0., 10., 20., 0., -30.]) # self.array_double_dispatch = np.array([0., 11.208119, 12.846733, 0., -24.054852]) self.tol_one = self.env._tol_poly def tearDown(self): self.env.close() def test_negative_dispatch(self): self.skip_if_needed() act = self.env.action_space({"redispatch": [(1, -10)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one def test_no_impact_env(self): # perform a valid redispatching action self.skip_if_needed() obs_init = self.env.reset() # reset the environment act = self.env.action_space() for i in range( 1 ): # number cherry picked to introduce explain the behaviour in the cells bellow obsinit, rewardinit, doneinit, infoinit = self.env.step( self.env.action_space()) ref_data = copy.deepcopy(obsinit.prod_p) act = self.env.action_space({"redispatch": [(0, -10)]}) # act = env.action_space({"redispatch": [(4,0)]}) obs, reward, done, info = self.env.step(act) assert self.compare_vect(obsinit.prod_p, ref_data) target_val = obs.prod_p + self.env._actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) assert np.all(target_val <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - obsinit.prod_p <= self.env.gen_max_ramp_up) assert np.all( obsinit.prod_p - obs.prod_p <= self.env.gen_max_ramp_down) def test_basic_redispatch_act(self): # test of the implementation of a simple case redispatching on one generator, bellow ramp min and ramp max self.skip_if_needed() act = self.env.action_space({"redispatch": (2, 5)}) obs, reward, done, info = self.env.step(act) assert np.abs(np.sum(self.env._actual_dispatch)) <= self.tol_one th_dispatch = np.array([0., -2.5, 5., 0., -2.5]) th_dispatch = np.array([0., -1.4814819, 5., 0., -3.518518]) assert self.compare_vect(self.env._actual_dispatch, th_dispatch) target_val = self.chronics_handler.real_data.prod_p[ 1, :] + self.env._actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) assert np.all(target_val <= self.env.gen_pmax + self.tol_one) # check that the redispatching is apply in the right direction indx_ok = self.env._target_dispatch != 0. assert np.all( np.sign(self.env._actual_dispatch[indx_ok]) == np.sign( self.env._target_dispatch[indx_ok])) def test_redispatch_act_above_pmax(self): # in this test, the asked redispatching for generator 2 would make it above pmax, so the environment # need to "cut" it automatically, without invalidating the action self.skip_if_needed() act = self.env.action_space({"redispatch": (2, 60)}) obs, reward, done, info = self.env.step(act) assert np.abs(np.sum(self.env._actual_dispatch)) <= self.tol_one th_dispatch = np.array([0., -23.2999, 50.899902, 0., -27.600002]) th_dispatch = np.array([0., -20., 40., 0., -20.]) th_dispatch = np.array([0., -13.227808, 50.90005, 0., -37.67224]) assert self.compare_vect(self.env._actual_dispatch, th_dispatch) target_val = self.chronics_handler.real_data.prod_p[ 1, :] + self.env._actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) assert np.all(target_val <= self.env.gen_pmax + self.tol_one) def test_two_redispatch_act(self): self.skip_if_needed() act = self.env.action_space({"redispatch": (2, 20)}) obs_first, reward, done, info = self.env.step(act) act = self.env.action_space({"redispatch": (1, 10)}) obs, reward, done, info = self.env.step(act) th_dispatch = np.array([0., 10, 20., 0., 0.]) th_dispatch[1] += obs_first.actual_dispatch[1] assert self.compare_vect(self.env._target_dispatch, th_dispatch) # check that the redispatching is apply in the right direction indx_ok = self.env._target_dispatch != 0. assert np.all( np.sign(self.env._actual_dispatch[indx_ok]) == np.sign( self.env._target_dispatch[indx_ok])) th_dispatch = np.array([0., 10., 20., 0., -30.]) th_dispatch = np.array([0., 4.0765514, 20.004545, 0., -24.081097]) assert self.compare_vect(self.env._actual_dispatch, th_dispatch) target_val = self.chronics_handler.real_data.prod_p[ 2, :] + self.env._actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.abs(np.sum(self.env._actual_dispatch)) <= self.tol_one assert np.all(target_val <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) def test_redispacth_two_gen(self): self.skip_if_needed() act = self.env.action_space({"redispatch": [(2, 20), (1, 10)]}) obs, reward, done, info = self.env.step(act) assert not done th_dispatch = np.array([0., 10, 20., 0., 0.]) assert self.compare_vect(self.env._target_dispatch, th_dispatch) assert self.compare_vect(self.env._actual_dispatch, self.array_double_dispatch) # check that the redispatching is apply in the right direction indx_ok = self.env._target_dispatch != 0. assert np.all( np.sign(self.env._actual_dispatch[indx_ok]) == np.sign( self.env._target_dispatch[indx_ok])) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) def test_redispacth_all_gen(self): # this should be exactly the same as the previous one self.skip_if_needed() act = self.env.action_space( {"redispatch": [(2, 20.), (1, 10.), (4, -30.)]}) obs, reward, done, info = self.env.step(act) th_dispatch = np.array([0., 10, 20., 0., -30.]) assert self.compare_vect(self.env._target_dispatch, th_dispatch) assert self.compare_vect(self.env._actual_dispatch, self.array_double_dispatch) # check that the redispatching is apply in the right direction indx_ok = self.env._target_dispatch != 0. assert np.all( np.sign(self.env._actual_dispatch[indx_ok]) == np.sign( self.env._target_dispatch[indx_ok])) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) def test_count_turned_on(self): self.skip_if_needed() act = self.env.action_space() # recoded it: it's the normal behavior to call "env.reset()" to get the first time step obs = self.env.reset() assert np.all(self.env._gen_uptime == np.array([0, 1, 1, 0, 1])) assert np.all(self.env._gen_downtime == np.array([1, 0, 0, 1, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) obs, reward, done, info = self.env.step(act) assert np.all(self.env._gen_uptime == np.array([0, 2, 2, 0, 2])) assert np.all(self.env._gen_downtime == np.array([2, 0, 0, 2, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) for i in range(64): obs, reward, done, info = self.env.step(act) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) obs, reward, done, info = self.env.step(act) assert np.all(self.env._gen_uptime == np.array([0, 67, 67, 1, 67])) assert np.all(self.env._gen_downtime == np.array([67, 0, 0, 0, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) obs, reward, done, info = self.env.step(act) assert np.all(self.env._gen_uptime == np.array([1, 68, 68, 2, 68])) assert np.all(self.env._gen_downtime == np.array([0, 0, 0, 0, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) def test_redispacth_twice_same(self): self.skip_if_needed() # this should be exactly the same as the previous one act = self.env.action_space({"redispatch": [(2, 5.)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.target_dispatch == np.array([0., 0., 5., 0., 0.])) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one th_disp = np.array([0., -2.5, 5., 0., -2.5]) th_disp = np.array([0., -1.4814819, 5., 0., -3.518518]) assert self.compare_vect(obs.actual_dispatch, th_disp) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) act = self.env.action_space({"redispatch": [(2, 5.)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.target_dispatch == np.array([0., 0., 10., 0., 0.])) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one th_disp = np.array([0., -5., 10., 0., -5.]) assert self.compare_vect(obs.actual_dispatch, th_disp) assert np.all(obs.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs.prod_p - self.env.gen_pmin >= -self.tol_one) def test_redispacth_secondabovepmax(self): self.skip_if_needed() act = self.env.action_space({"redispatch": [(2, 20.)]}) obs0, reward, done, info = self.env.step(act) assert np.all(obs0.target_dispatch == np.array([0., 0., 20., 0., 0.])) assert np.abs(np.sum(obs0.actual_dispatch)) <= self.tol_one th_disp = np.array([0., -10., 20., 0., -10.]) th_disp = np.array([0., -5.9259276, 20., 0., -14.074072]) assert self.compare_vect(obs0.actual_dispatch, th_disp) assert np.all(obs0.prod_p <= self.env.gen_pmax + self.tol_one) assert np.all(obs0.prod_p >= self.env.gen_pmin - self.tol_one) act = self.env.action_space({"redispatch": [(2, 40.)]}) obs, reward, done, info = self.env.step(act) assert not info["is_dispatching_illegal"] assert np.all(obs.target_dispatch == np.array([0., 0., 60., 0., 0.])) th_disp = np.array([0., -23.5, 50.4, 0., -26.900002]) assert self.compare_vect(obs.actual_dispatch, th_disp) assert np.all(obs.prod_p[:-1] <= self.env.gen_pmax[:-1] + self.tol_one) assert np.all(obs.prod_p[:-1] >= self.env.gen_pmin[:-1] - self.tol_one) assert np.all(obs.prod_p[:-1] - obs0.prod_p[:-1] >= -self.env.gen_max_ramp_down[:-1]) assert np.all(obs.prod_p[:-1] - obs0.prod_p[:-1] <= self.env.gen_max_ramp_up[:-1]) def test_redispacth_non_dispatchable_generator(self): """ Dispatch a non redispatchable generator is ambiguous """ self.skip_if_needed() act = self.env.action_space() obs, reward, done, info = self.env.step(act) # Check that generator 0 isn't redispatchable assert self.env.gen_redispatchable[0] == False # Check that generator 0 is off assert self.env._gen_downtime[0] >= 1 # Try to redispatch redispatch_act = self.env.action_space({"redispatch": [(0, 5.)]}) obs, reward, done, info = self.env.step(redispatch_act) assert info['is_ambiguous']
class TestLoadingBackendPandaPower(unittest.TestCase): def setUp(self): # powergrid self.backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler( chronicsClass=GridStateFromFile, path=self.path_chron) self.tolvect = dt_float(1e-2) self.tol_one = dt_float(1e-5) self.id_chron_to_back_load = np.array( [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } # _parameters for the environment self.env_params = Parameters() self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, name="test_env_env1") def tearDown(self): pass def compare_vect(self, pred, true): return dt_float(np.max(np.abs(pred - true))) <= self.tolvect def test_step_doesnt_change_action(self): act = self.env.action_space() act_init = copy.deepcopy(act) res = self.env.step(act) assert act == act_init def test_load_env(self): """ Just executes the SetUp and tearDown functions. :return: """ if DEBUG: if PROFILE_CODE: cp = cProfile.Profile() cp.enable() import pandapower as pp nb_powerflow = 5000 beg_ = time.time() for i in range(nb_powerflow): pp.runpp(self.backend._grid) end_ = time.time() print("Time to compute {} powerflows: {:.2f}".format( nb_powerflow, end_ - beg_)) if PROFILE_CODE: cp.disable() cp.print_stats(sort="tottime") pass def test_proper_injection_at_first(self): injs_act, *_ = self.env.backend.loads_info() # below: row as found in the file vect = np.array( [18.8, 86.5, 44.5, 7.1, 10.4, 27.6, 8.1, 3.2, 5.6, 11.9, 13.6]) # now it's in the "backend" order (ie properly reordered) vect = vect[self.id_chron_to_back_load] # and now i make sure everything is working as intentended assert self.compare_vect(injs_act, vect) def test_proper_voltage_modification(self): do_nothing = self.env.helper_action_player({}) obs, reward, done, info = self.env.step( do_nothing) # should load the first time stamp vect = np.array([143.9, 139.1, 0.2, 13.3, 146.]) assert self.compare_vect( obs.prod_v, vect ), "Production voltages setpoint have not changed at first time step" obs, reward, done, info = self.env.step( do_nothing) # should load the first time stamp vect = np.array([145.3, 140.4, 0.2, 13.5, 147.4]) assert self.compare_vect( obs.prod_v, vect ), "Production voltages setpoint have not changed at second time step" def test_number_of_timesteps(self): for i in range(287): do_nothing = self.env.helper_action_player({}) obs, reward, done, info = self.env.step( do_nothing) # should load the first time stamp injs_act, *_ = self.env.backend.loads_info() vect = np.array( [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6]) vect = vect[self.id_chron_to_back_load] assert self.compare_vect(injs_act, vect) def test_stop_right_time(self): done = False i = 0 while not done: do_nothing = self.env.helper_action_player({}) obs, reward, done, info = self.env.step( do_nothing) # should load the first time stamp i += 1 assert i == 287 def test_reward(self): done = False i = 0 self.chronics_handler.next_chronics() self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, rewardClass=L2RPNReward, names_chronics_to_backend=self.names_chronics_to_backend, name="test_env_env2") if PROFILE_CODE: cp = cProfile.Profile() cp.enable() beg_ = time.time() cum_reward = dt_float(0.0) while not done: do_nothing = self.env.helper_action_player({}) obs, reward, done, info = self.env.step( do_nothing) # should load the first time stamp cum_reward += reward i += 1 end_ = time.time() if DEBUG: msg_ = "\nEnv: {:.2f}s\n\t - apply act {:.2f}s\n\t - run pf: {:.2f}s\n\t - env update + observation: {:.2f}s\nTotal time: {:.2f}\nCumulative reward: {:1f}" print( msg_.format( self.env._time_apply_act + self.env._time_powerflow + self.env._time_extract_obs, self.env._time_apply_act, self.env._time_powerflow, self.env._time_extract_obs, end_ - beg_, cum_reward)) if PROFILE_CODE: cp.disable() cp.print_stats(sort="tottime") assert i == 287, "Wrong number of timesteps" expected_reward = dt_float(5739.9336) assert dt_float( np.abs(cum_reward - expected_reward)) <= self.tol_one, "Wrong reward"
class RemoteEnv(Process): """ INTERNAL .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ This class represent the environment that is executed on a remote process. Note that the environment is only created in the subprocess, and is not available in the main process. Once created it is not possible to access anything directly from it in the main process, where the BaseAgent lives. Only the :class:`grid2op.Observation.BaseObservation` are forwarded to the agent. """ def __init__(self, env_params, remote, parent_remote, seed, name=None, return_info=True, _obs_to_vect=True): Process.__init__(self, group=None, target=None, name=name) self.backend = None self.env = None self.env_params = env_params self.remote = remote self.parent_remote = parent_remote self.seed_used = seed self.space_prng = None self.fast_forward = 0 self.all_seeds = [] # internal do not modify # Do not work (in the sens that is it less efficient) self.return_info = return_info self._obs_to_vect = _obs_to_vect self._comp_time = 0. def init_env(self): """ INTERNAL .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ Initialize the environment that will perform all the computation of this process. Remember the environment only lives in this process. It cannot be transfer to / from the main process. This function also makes sure the chronics are read in different order accross all processes. This is done by calling the :func:`grid2op.Chronics.GridValue.shuffle` method. An example of how to use this function is provided in :func:`grid2op.Chronics.Multifolder.shuffle`. """ self.space_prng = np.random.RandomState() self.space_prng.seed(seed=self.seed_used) self.backend = self.env_params["_raw_backend_class"]() with warnings.catch_warnings(): # warnings have bee already sent in the main process, no need to resend them warnings.filterwarnings("ignore") self.env = Environment(**self.env_params, backend=self.backend) env_seed = self.space_prng.randint(np.iinfo(dt_int).max) self.all_seeds = self.env.seed(env_seed) self.env.chronics_handler.shuffle(shuffler=lambda x: x[ self.space_prng.choice(len(x), size=len(x), replace=False)]) def _clean_observation(self, obs): obs._forecasted_grid = [] obs._forecasted_inj = [] obs._obs_env = None obs.action_helper = None return obs def get_obs_ifnotconv(self): # warnings.warn(f"get_obs_ifnotconv is used") # TODO dirty hack because of wrong chronics # need to check!!! conv = False obs_v = None obs = None while not conv: try: self.env.reset() if self.fast_forward > 0: self.env.fast_forward_chronics( self.space_prng.randint(0, self.fast_forward)) obs = self.env.get_obs() obs_v = obs.to_vect() if np.all(np.isfinite(obs_v)): # i make sure that everything is not Nan # other i consider it's "divergence" so "game over" conv = True except Exception as exc_: pass if self._obs_to_vect: res = obs_v else: res = obs return res def run(self): if self.env is None: self.init_env() while True: cmd, data = self.remote.recv() if cmd == 'get_spaces': self.remote.send( (self.env.observation_space, self.env.action_space)) elif cmd == 's': # perform a step beg_ = time.time() if data is None: data = self.env.action_space() else: data = self.env.action_space.from_vect(data) obs, reward, done, info = self.env.step(data) obs_v = obs.to_vect() if done or np.any(~np.isfinite(obs_v)): # if done do a reset res_obs = self.get_obs_ifnotconv() elif self._obs_to_vect: res_obs = obs.to_vect() else: res_obs = self._clean_observation(obs) if not self.return_info: info = None end_ = time.time() self._comp_time += end_ - beg_ self.remote.send((res_obs, reward, done, info)) elif cmd == 'r': # perfom a reset obs_v = self.get_obs_ifnotconv() self.remote.send(obs_v) elif cmd == 'c': # close everything self.env.close() self.remote.close() break elif cmd == 'z': # adapt the chunk size self.env.set_chunk_size(data) elif cmd == 'o': # get_obs tmp = self.env.get_obs() if self._obs_to_vect: res_obs = tmp.to_vect() else: res_obs = self._clean_observation(tmp) self.remote.send(res_obs) elif cmd == "f": # fast forward the chronics when restart self.fast_forward = int(data) elif cmd == "seed": self.remote.send((self.seed_used, self.all_seeds)) elif cmd == "params": self.remote.send(self.env.parameters) elif cmd == "comp_time": self.remote.send(self._comp_time) elif cmd == "powerflow_time": self.remote.send(self.env.backend.comp_time) elif cmd == "step_time": self.remote.send(self.env._time_step) elif cmd == "set_filter": self.env.chronics_handler.set_filter(data) self.remote.send(None) elif cmd == "set_id": self.env.set_id(data) self.remote.send(None) elif hasattr(self.env, cmd): tmp = getattr(self.env, cmd) self.remote.send(tmp) else: raise NotImplementedError
class TestRedispatchChangeNothingEnvironment(HelperTests): def setUp(self): # powergrid self.backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler(chronicsClass=ChangeNothing) self.id_chron_to_back_load = np.array( [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } # _parameters for the environment self.env_params = Parameters() self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=BaseAction) def tearDown(self): self.env.close() def test_redispatch_generator_off(self): """ Redispatch a turned off generator is illegal """ # Step into simulation once nothing_act = self.env.action_space() obs, reward, done, info = self.env.step(nothing_act) # Check that generator 1 is redispatchable assert self.env.gen_redispatchable[1] == True # Check that generator 1 is off assert obs.prod_p[1] == 0 assert self.env.gen_downtime[1] >= 1 # Try to redispatch generator 1 redispatch_act = self.env.action_space({"redispatch": [(1, 5.)]}) obs, reward, done, info = self.env.step(redispatch_act) assert info['is_dispatching_illegal'] == True
class EpisodeReboot: """ INTERNAL .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ This is a first implementation to serve as "what can be done". It is a beta feature """ def __init__(self): self.episode_data = None self.env = None self.chronics_handler = None self.current_time_step = None self.action = None # the last action played warnings.warn("EpisodeReboot is a beta feature, it will likely be renamed, methods will be adapted " "and it has probably some bugs. Use with care!") def load(self, backend, agent_path=None, name=None, data=None, env_kwargs={}): if data is None: if agent_path is not None and name is not None: self.episode_data = EpisodeData.from_disk(agent_path, name) else: raise Grid2OpException("To replay an episode you need at least to provide an EpisodeData " "(using the keyword argument \"data=...\") or provide the path and name where " "the " "episode is stored (keyword arguments \"agent_path\" and \"name\").") else: self.episode_data = copy.deepcopy(data) self.episode_data.reboot() if self.env is not None: self.env.close() self.env = None self.chronics_handler = ChronicsHandler(chronicsClass=_GridFromLog, episode_data=self.episode_data) if "chronics_handler" in env_kwargs: del env_kwargs["chronics_handler"] if "backend" in env_kwargs: del env_kwargs["backend"] if "opponent_class" in env_kwargs: del env_kwargs["opponent_class"] if "name" in env_kwargs: del env_kwargs["name"] seed = None with open(os.path.join(agent_path, name, "episode_meta.json")) as f: dict_ = json.load(f) nm = re.sub("Environment_", "", dict_["env_type"]) if dict_["env_seed"] is not None: seed = int(dict_["env_seed"]) self.env = Environment(**env_kwargs, backend=backend, chronics_handler=self.chronics_handler, opponent_class=OpponentFromLog, name=nm) if seed is not None: self.env.seed(seed) tmp = self.env.reset() # always have the two bellow synch ! otherwise it messes up the "chronics" # in the env, when calling "env.step" self.current_time_step = 0 self.env.chronics_handler.real_data.curr_iter = 0 # first observation of the scenario current_obs = self.episode_data.observations[self.current_time_step] self._assign_state(current_obs) return self.env.get_obs() def _assign_state(self, obs): """ works only if observation store the complete state of the grid... """ if self.env.done: # if there has been a game over previously i reset it self.env.chronics_handler.real_data.curr_iter = self.current_time_step self.env.reset() self.env._gen_activeprod_t[:] = obs.prod_p.astype(dt_float) self.env._actual_dispatch[:] = obs.actual_dispatch.astype(dt_float) self.env._target_dispatch[:] = obs.target_dispatch.astype(dt_float) self.env._gen_activeprod_t_redisp[:] = obs.prod_p.astype(dt_float) + obs.actual_dispatch.astype(dt_float) self.env.current_obs = obs self.env._timestep_overflow[:] = obs.timestep_overflow.astype(dt_int) self.env._times_before_line_status_actionable[:] = obs.time_before_cooldown_line.astype(dt_int) self.env._times_before_topology_actionable[:] = obs.time_before_cooldown_sub.astype(dt_int) self.env._duration_next_maintenance[:] = obs.duration_next_maintenance.astype(dt_int) self.env._time_next_maintenance[:] = obs.time_next_maintenance.astype(dt_int) # # TODO check that the "stored" "last bus for when the powerline were connected" are # # kept there (I might need to do a for loop) self.env.backend.update_from_obs(obs) disc_lines, detailed_info, conv_ = self.env.backend.next_grid_state(env=self.env) if conv_ is None: self.env._backend_action.update_state(disc_lines) self.env._backend_action.reset() def next(self, _sentinel=None, _update=False): """ go to next time step if "update" then i reuse the observation stored to go to this time step, otherwise not do as if the environment will execute the action the stored agent did at the next time step (compared to the time step the environment is currently at) Parameters ---------- _sentinel: ``None`` Used to prevent positional parameters. Internal, do not use. _update: ``bool`` Internal, you should not use it. # TODO split self._next (called by both self.next and self.go_to that has the `_update` kwargs """ if _sentinel is not None: raise Grid2OpException("You should not use reboot.next() with any argument.") if self.current_time_step is None: raise Grid2OpException("Impossible to go to the next time step with an episode not loaded. " "Call \"EpisodeReboot.load\" before.") if _update: # I put myself at the observation just before the next time step obs = self.episode_data.observations[self.current_time_step] self.env._backend_action = self.env._backend_action_class() # update the "previous topological state" to the right value self._update_bk_act_topo(obs) # assign the right state of the grid self._assign_state(obs) self.action = self.episode_data.actions[self.current_time_step] self.env.chronics_handler.real_data.curr_iter = self.current_time_step new_obs, new_reward, new_done, new_info = self.env.step(self.action) self.current_time_step += 1 # the chronics handler handled the "self.env.chronics_handler.curr_iter += 1" return new_obs, new_reward, new_done, new_info def _update_bk_act_topo(self, obs): """update the "previous topological state" to the right value""" self.env._backend_action.current_topo.values[:] = obs.topo_vect self.env._backend_action.current_topo.changed[:] = True if obs.shunts_data_available: self.env._backend_action.shunt_bus.values[:] = obs._shunt_bus self.env._backend_action.shunt_bus.changed[:] = True # TODO previous update self.env._backend_action.last_topo_registered too ! def go_to(self, time_step): """ goes to the step number "time_step". So if you go_to timestep 10 then you retrieve the 10th observation and its as if the agent did the 9th action (just before) """ if time_step > len(self.episode_data.actions): raise Grid2OpException("The stored episode counts only {} time steps. You cannot go " "at time step {}" "".format(len(self.episode_data.actions), time_step)) if time_step <= 0: raise Grid2OpException("You cannot go to timestep <= 0, it does not make sense (as there is not \"-1th\"" "action). If you want to load the data, please use \"EpisodeReboot.load\".") self.current_time_step = time_step - 1 return self.next(_update=True)
class TestRedispatch(HelperTests): def setUp(self): # powergrid self.backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # chronics self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler( chronicsClass=GridStateFromFile, path=self.path_chron) self.id_chron_to_back_load = np.array( [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) # force the verbose backend self.backend.detailed_infos_for_cascading_failures = True self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } # _parameters for the environment self.env_params = Parameters() self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=BaseAction) self.array_double_dispatch = np.array([0., 10., 20., 0., -30.]) def tearDown(self): self.env.close() def test_negative_dispatch(self): act = self.env.action_space({"redispatch": [(1, -10)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.prod_p >= self.env.gen_pmin) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one def test_no_impact_env(self): # perform a valid redispatching action obs_init = self.env.reset() # reset the environment act = self.env.action_space() for i in range( 1 ): # number cherry picked to introduce explain the behaviour in the cells bellow obsinit, rewardinit, doneinit, infoinit = self.env.step( self.env.action_space()) ref_data = copy.deepcopy(obsinit.prod_p) act = self.env.action_space({"redispatch": [(0, -10)]}) # act = env.action_space({"redispatch": [(4,0)]}) obs, reward, done, info = self.env.step(act) assert self.compare_vect(obsinit.prod_p, ref_data) target_val = obs.prod_p + self.env.actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.all(obs.prod_p >= self.env.gen_pmin) assert np.all(target_val <= self.env.gen_pmax) assert np.all(obs.prod_p - obsinit.prod_p <= self.env.gen_max_ramp_up) assert np.all( obsinit.prod_p - obs.prod_p <= self.env.gen_max_ramp_down) def test_basic_redispatch_act(self): # test of the implementation of a simple case redispatching on one generator, bellow ramp min and ramp max act = self.env.action_space({"redispatch": [2, 5]}) obs, reward, done, info = self.env.step(act) assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one th_dispatch = np.array([0., -1.44301856, 5., 0., -3.55698144]) assert self.compare_vect(self.env.actual_dispatch, th_dispatch) target_val = self.chronics_handler.real_data.prod_p[ 1, :] + self.env.actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.all(obs.prod_p >= self.env.gen_pmin) assert np.all(target_val <= self.env.gen_pmax) # check that the redispatching is apply in the right direction indx_ok = self.env.target_dispatch != 0. assert np.all( np.sign(self.env.actual_dispatch[indx_ok]) == np.sign( self.env.target_dispatch[indx_ok])) def test_redispatch_act_above_pmax(self): # in this test, the asked redispatching for generator 2 would make it above pmax, so the environment # need to "cut" it automatically, without invalidating the action act = self.env.action_space({"redispatch": [2, 60]}) obs, reward, done, info = self.env.step(act) assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one th_dispatch = np.array( [0., -10.57042905, 50.89066718, 0., -40.32023813]) assert self.compare_vect(self.env.actual_dispatch, th_dispatch) target_val = self.chronics_handler.real_data.prod_p[ 1, :] + self.env.actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.all(obs.prod_p >= self.env.gen_pmin) assert np.all(target_val <= self.env.gen_pmax) def test_two_redispatch_act(self): act = self.env.action_space({"redispatch": [2, 20]}) obs, reward, done, info = self.env.step(act) act = self.env.action_space({"redispatch": [1, 10]}) obs, reward, done, info = self.env.step(act) th_dispatch = np.array([0., 10, 20., 0., 0.]) assert self.compare_vect(self.env.target_dispatch, th_dispatch) # check that the redispatching is apply in the right direction indx_ok = self.env.target_dispatch != 0. assert np.all( np.sign(self.env.actual_dispatch[indx_ok]) == np.sign( self.env.target_dispatch[indx_ok])) th_dispatch = np.array([0., 10., 20., 0., -30.]) assert self.compare_vect(self.env.actual_dispatch, th_dispatch) target_val = self.chronics_handler.real_data.prod_p[ 2, :] + self.env.actual_dispatch assert self.compare_vect( obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one assert np.all(target_val <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) def test_redispacth_two_gen(self): act = self.env.action_space({"redispatch": [(2, 20), (1, 10)]}) obs, reward, done, info = self.env.step(act) th_dispatch = np.array([0., 10, 20., 0., 0.]) assert self.compare_vect(self.env.target_dispatch, th_dispatch) assert self.compare_vect(self.env.actual_dispatch, self.array_double_dispatch) # check that the redispatching is apply in the right direction indx_ok = self.env.target_dispatch != 0. assert np.all( np.sign(self.env.actual_dispatch[indx_ok]) == np.sign( self.env.target_dispatch[indx_ok])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) def test_redispacth_all_gen(self): # this should be exactly the same as the previous one act = self.env.action_space( {"redispatch": [(2, 20.), (1, 10.), (4, -30.)]}) obs, reward, done, info = self.env.step(act) th_dispatch = np.array([0., 10, 20., 0., -30.]) assert self.compare_vect(self.env.target_dispatch, th_dispatch) assert self.compare_vect(self.env.actual_dispatch, self.array_double_dispatch) # check that the redispatching is apply in the right direction indx_ok = self.env.target_dispatch != 0. assert np.all( np.sign(self.env.actual_dispatch[indx_ok]) == np.sign( self.env.target_dispatch[indx_ok])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) def test_count_turned_on(self): act = self.env.action_space() obs, reward, done, info = self.env.step(act) # pdb.set_trace() assert np.all(self.env.gen_uptime == np.array([0, 1, 1, 0, 1])) assert np.all(self.env.gen_downtime == np.array([1, 0, 0, 1, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) obs, reward, done, info = self.env.step(act) assert np.all(self.env.gen_uptime == np.array([0, 2, 2, 0, 2])) assert np.all(self.env.gen_downtime == np.array([2, 0, 0, 2, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) for i in range(63): obs, reward, done, info = self.env.step(act) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) obs, reward, done, info = self.env.step(act) assert np.all(self.env.gen_uptime == np.array([0, 66, 66, 1, 66])) assert np.all(self.env.gen_downtime == np.array([66, 0, 0, 0, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) obs, reward, done, info = self.env.step(act) assert np.all(self.env.gen_uptime == np.array([1, 67, 67, 2, 67])) assert np.all(self.env.gen_downtime == np.array([0, 0, 0, 0, 0])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) def test_redispacth_twice_same(self): # this should be exactly the same as the previous one act = self.env.action_space({"redispatch": [(2, 5.)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.target_dispatch == np.array([0., 0., 5., 0., 0.])) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one assert self.compare_vect( obs.actual_dispatch, np.array([0., -1.44301856, 5., 0., -3.55698144])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) act = self.env.action_space({"redispatch": [(2, 5.)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.target_dispatch == np.array([0., 0., 10., 0., 0.])) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one assert self.compare_vect( obs.actual_dispatch, np.array([0., -2.81339987, 10., 0., -7.18660013])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) def test_redispacth_secondabovepmax(self): act = self.env.action_space({"redispatch": [(2, 20.)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.target_dispatch == np.array([0., 0., 20., 0., 0.])) assert np.abs(np.sum(obs.actual_dispatch)) <= self.tol_one assert self.compare_vect( obs.actual_dispatch, np.array([0., -5.36765536, 20., 0., -14.63234464])) assert np.all(obs.prod_p <= self.env.gen_pmax) assert np.all(obs.prod_p >= self.env.gen_pmin) act = self.env.action_space({"redispatch": [(2, 40.)]}) obs, reward, done, info = self.env.step(act) assert np.all(obs.target_dispatch == np.array([0., 0., 60., 0., 0.])) assert self.compare_vect( obs.actual_dispatch, np.array([0., -10.3814061, 50.39070301, 0., -40.00929691])) assert np.all(obs.prod_p[:-1] <= self.env.gen_pmax[:-1]) assert np.all(obs.prod_p[:-1] >= self.env.gen_pmin[:-1]) def test_redispacth_non_dispatchable_generator(self): """ Dispatch a non redispatchable generator is ambiguous """ act = self.env.action_space() obs, reward, done, info = self.env.step(act) # Check that generator 0 isn't redispatchable assert self.env.gen_redispatchable[0] == False # Check that generator 0 is off assert self.env.gen_downtime[0] >= 1 # Try to redispatch redispatch_act = self.env.action_space({"redispatch": [(0, 5.)]}) obs, reward, done, info = self.env.step(redispatch_act) assert info['is_ambiguous']
def make_from_dataset_path(dataset_path="/", _add_to_name="", **kwargs): """ .. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\ Prefer using the :func:`grid2op.make` function. This function is a shortcut to rapidly create environments within the grid2op Framework. We don't recommend using directly this function. Prefer using the :func:`make` function. It mimic the ``gym.make`` function. .. _Parameters-make-from-path: Parameters ---------- dataset_path: ``str`` Path to the dataset folder param: ``grid2op.Parameters.Parameters``, optional Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an markov decision process, and some internal backend: ``grid2op.Backend.Backend``, optional The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`. action_class: ``type``, optional Type of BaseAction the BaseAgent will be able to perform. If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction` observation_class: ``type``, optional Type of BaseObservation the BaseAgent will receive. If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation` reward_class: ``type``, optional Type of reward signal the BaseAgent will receive. If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward` other_rewards: ``dict``, optional Used to additional information than the "info" returned value after a call to env.step. gamerules_class: ``type``, optional Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints. If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules` data_feeding_kwargs: ``dict``, optional Dictionnary that is used to build the `data_feeding` (chronics) objects. chronics_class: ``type``, optional The type of chronics that represents the dynamics of the Environment created. Usually they come from different folders. data_feeding: ``type``, optional The type of chronics handler you want to use. volagecontroler_class: ``type``, optional The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to chronics_path: ``str`` Path where to look for the chronics dataset (optional) grid_path: ``str``, optional The path where the powergrid is located. If provided it must be a string, and point to a valid file present on the hard drive. difficulty: ``str``, optional the difficulty level. If present it starts from "0" the "easiest" but least realistic mode. In the case of the dataset being used in the l2rpn competition, the level used for the competition is "competition" ("hardest" and most realistic mode). If multiple difficulty levels are available, the most realistic one (the "hardest") is the default choice. opponent_action_class: ``type``, optional The action class used for the opponent. The opponent will not be able to use action that are invalid with the given action class provided. It defaults to :class:`grid2op.Action.DontAct` which forbid any type of action possible. opponent_class: ``type``, optional The opponent class to use. The default class is :class:`grid2op.Opponent.BaseOpponent` which is a type of opponents that does nothing. opponent_init_budget: ``float``, optional The initial budget of the opponent. It defaults to 0.0 which means the opponent cannot perform any action if this is not modified. opponent_attack_duration: ``int``, optional The number of time steps an attack from the opponent lasts. opponent_attack_cooldown: ``int``, optional The number of time steps the opponent as to wait for an attack. opponent_budget_per_ts: ``float``, optional The increase of the opponent budget per time step. Each time step the opponent see its budget increase. It defaults to 0.0. opponent_budget_class: ``type``, optional defaults: :class:`grid2op.Opponent.UnlimitedBudget` _add_to_name: Internal, used for test only. Do not attempt to modify under any circumstances. Returns ------- env: :class:`grid2op.Environment.Environment` The created environment with the given properties. """ # Compute and find root folder _check_path(dataset_path, "Dataset root directory") dataset_path_abs = os.path.abspath(dataset_path) # Compute env name from directory name name_env = os.path.split(dataset_path_abs)[1] # Compute and find chronics folder chronics_path = _get_default_aux("chronics_path", kwargs, defaultClassApp=str, defaultinstance='', msg_error=ERR_MSG_KWARGS["chronics_path"]) if chronics_path == "": # if no "chronics_path" argument is provided, look into the "chronics" folder chronics_path_abs = os.path.abspath( os.path.join(dataset_path_abs, NAME_CHRONICS_FOLDER)) else: # otherwise use it chronics_path_abs = os.path.abspath(chronics_path) _check_path(chronics_path_abs, "Dataset chronics folder") # Compute and find backend/grid file grid_path = _get_default_aux("grid_path", kwargs, defaultClassApp=str, defaultinstance="", msg_error=ERR_MSG_KWARGS["grid_path"]) if grid_path == "": grid_path_abs = os.path.abspath( os.path.join(dataset_path_abs, NAME_GRID_FILE)) else: grid_path_abs = os.path.abspath(grid_path) _check_path(grid_path_abs, "Dataset power flow solver configuration") # Compute and find grid layout file grid_layout_path_abs = os.path.abspath( os.path.join(dataset_path_abs, NAME_GRID_LAYOUT_FILE)) _check_path(grid_layout_path_abs, "Dataset grid layout") # Check provided config overrides are valid _check_kwargs(kwargs) # Compute and find config file config_path_abs = os.path.abspath( os.path.join(dataset_path_abs, NAME_CONFIG_FILE)) _check_path(config_path_abs, "Dataset environment configuration") # Read config file try: spec = importlib.util.spec_from_file_location("config.config", config_path_abs) config_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(config_module) config_data = config_module.config except Exception as e: print(e) raise EnvError("Invalid dataset config file: {}".format( config_path_abs)) from None # Get graph layout try: with open(grid_layout_path_abs) as layout_fp: graph_layout = json.load(layout_fp) except Exception as e: raise EnvError("Dataset {} doesn't have a valid graph layout".format( config_path_abs)) # Get thermal limits thermal_limits = None if "thermal_limits" in config_data: thermal_limits = config_data["thermal_limits"] # Get chronics_to_backend name_converter = None if "names_chronics_to_grid" in config_data: name_converter = config_data["names_chronics_to_grid"] if name_converter is None: name_converter = {} names_chronics_to_backend = _get_default_aux( "names_chronics_to_backend", kwargs, defaultClassApp=dict, defaultinstance=name_converter, msg_error=ERR_MSG_KWARGS["names_chronics_to_grid"]) # Get default backend class backend_class_cfg = PandaPowerBackend if "backend_class" in config_data and config_data[ "backend_class"] is not None: backend_class_cfg = config_data["backend_class"] ## Create the backend, to compute the powerflow backend = _get_default_aux("backend", kwargs, defaultClass=backend_class_cfg, defaultClassApp=Backend, msg_error=ERR_MSG_KWARGS["backend"]) # Get default observation class observation_class_cfg = CompleteObservation if "observation_class" in config_data and config_data[ "observation_class"] is not None: observation_class_cfg = config_data["observation_class"] ## Setup the type of observation the agent will receive observation_class = _get_default_aux( "observation_class", kwargs, defaultClass=observation_class_cfg, isclass=True, defaultClassApp=BaseObservation, msg_error=ERR_MSG_KWARGS["observation_class"]) ## Create the parameters of the game, thermal limits threshold, # simulate cascading failure, powerflow mode etc. (the gamification of the game) if "param" in kwargs: param = _get_default_aux('param', kwargs, defaultClass=Parameters, defaultClassApp=Parameters, msg_error=ERR_MSG_KWARGS["param"]) else: # param is not in kwargs param = Parameters() json_path = os.path.join(dataset_path_abs, "difficulty_levels.json") if os.path.exists(json_path): with open(json_path, "r", encoding="utf-8") as f: dict_ = json.load(f) available_parameters = sorted(dict_.keys()) if DIFFICULTY_NAME in kwargs: # player enters a difficulty levels my_difficulty = kwargs[DIFFICULTY_NAME] try: my_difficulty = str(my_difficulty) except: raise EnvError( "Impossible to convert your difficulty into a valid string. Please make sure to " "pass a string (eg \"2\") and not something else (eg. int(2)) as a difficulty" ) if my_difficulty in dict_: param.init_from_dict(dict_[my_difficulty]) else: raise EnvError(ERR_MSG_KWARGS[DIFFICULTY_NAME].format( difficulty=my_difficulty, difficulties=available_parameters)) else: # no difficulty name provided, i need to chose the most suited one if CHALLENGE_NAME in dict_: param.init_from_dict(dict_[CHALLENGE_NAME]) else: # i chose the most difficult one available_parameters_int = {} for el in available_parameters: try: int_ = int(el) available_parameters_int[int_] = el except: pass max_ = np.max(list(available_parameters_int.keys())) keys_ = available_parameters_int[max_] param.init_from_dict(dict_[keys_]) else: json_path = os.path.join(dataset_path_abs, "parameters.json") if os.path.exists(json_path): param.init_from_json(json_path) # Get default rules class rules_class_cfg = DefaultRules if "rules_class" in config_data and config_data["rules_class"] is not None: rules_class_cfg = config_data["rules_class"] ## Create the rules of the game (mimic the operationnal constraints) gamerules_class = _get_default_aux( "gamerules_class", kwargs, defaultClass=rules_class_cfg, defaultClassApp=BaseRules, msg_error=ERR_MSG_KWARGS["gamerules_class"], isclass=True) # Get default reward class reward_class_cfg = L2RPNReward if "reward_class" in config_data and config_data[ "reward_class"] is not None: reward_class_cfg = config_data["reward_class"] ## Setup the reward the agent will receive reward_class = _get_default_aux("reward_class", kwargs, defaultClass=reward_class_cfg, defaultClassApp=BaseReward, msg_error=ERR_MSG_KWARGS["reward_class"], isclass=True) # Get default BaseAction class action_class_cfg = BaseAction if "action_class" in config_data and config_data[ "action_class"] is not None: action_class_cfg = config_data["action_class"] ## Setup the type of action the BaseAgent can perform action_class = _get_default_aux("action_class", kwargs, defaultClass=action_class_cfg, defaultClassApp=BaseAction, msg_error=ERR_MSG_KWARGS["action_class"], isclass=True) # Get default Voltage class voltage_class_cfg = ControlVoltageFromFile if "voltage_class" in config_data and config_data[ "voltage_class"] is not None: voltage_class_cfg = config_data["voltage_class"] ### Create controler for voltages volagecontroler_class = _get_default_aux( "volagecontroler_class", kwargs, defaultClassApp=voltage_class_cfg, defaultClass=ControlVoltageFromFile, msg_error=ERR_MSG_KWARGS["voltagecontroler_class"], isclass=True) # Get default Chronics class chronics_class_cfg = ChangeNothing if "chronics_class" in config_data and config_data[ "chronics_class"] is not None: chronics_class_cfg = config_data["chronics_class"] # Get default Grid class grid_value_class_cfg = GridStateFromFile if "grid_value_class" in config_data and config_data[ "grid_value_class"] is not None: grid_value_class_cfg = config_data["grid_value_class"] ## the chronics to use ### the arguments used to build the data, note that the arguments must be compatible with the chronics class default_chronics_kwargs = { "chronicsClass": chronics_class_cfg, "path": chronics_path_abs, "gridvalueClass": grid_value_class_cfg } data_feeding_kwargs = _get_default_aux( "data_feeding_kwargs", kwargs, defaultClassApp=dict, defaultinstance=default_chronics_kwargs, msg_error=ERR_MSG_KWARGS["data_feeding_kwargs"]) for el in default_chronics_kwargs: if not el in data_feeding_kwargs: data_feeding_kwargs[el] = default_chronics_kwargs[el] ### the chronics generator chronics_class_used = _get_default_aux( "chronics_class", kwargs, defaultClassApp=GridValue, defaultClass=data_feeding_kwargs["chronicsClass"], msg_error=ERR_MSG_KWARGS["chronics_class"], isclass=True) data_feeding_kwargs["chronicsClass"] = chronics_class_used data_feeding = _get_default_aux( "data_feeding", kwargs, defaultClassApp=ChronicsHandler, defaultClass=ChronicsHandler, build_kwargs=data_feeding_kwargs, msg_error=ERR_MSG_KWARGS["chronics_handler"]) ### other rewards other_rewards = _get_default_aux("other_rewards", kwargs, defaultClassApp=dict, defaultinstance={}, msg_error=ERR_MSG_KWARGS["other_rewards"], isclass=False) # Opponent chronics_class_cfg = DontAct if "opponent_action_class" in config_data and config_data[ "opponent_action_class"] is not None: chronics_class_cfg = config_data["opponent_action_class"] opponent_action_class = _get_default_aux( "opponent_action_class", kwargs, defaultClassApp=BaseAction, defaultClass=chronics_class_cfg, msg_error=ERR_MSG_KWARGS["opponent_action_class"], isclass=True) opponent_class_cfg = BaseOpponent if "opponent_class" in config_data and config_data[ "opponent_class"] is not None: opponent_class_cfg = config_data["opponent_class"] opponent_class = _get_default_aux( "opponent_class", kwargs, defaultClassApp=BaseOpponent, defaultClass=opponent_class_cfg, msg_error=ERR_MSG_KWARGS["opponent_class"], isclass=True) opponent_budget_class_cfg = NeverAttackBudget if "opponent_budget_class" in config_data and config_data[ "opponent_budget_class"] is not None: opponent_budget_class_cfg = config_data["opponent_budget_class"] opponent_budget_class = _get_default_aux( "opponent_budget_class", kwargs, defaultClassApp=BaseActionBudget, defaultClass=opponent_budget_class_cfg, msg_error=ERR_MSG_KWARGS["opponent_budget_class"], isclass=True) opponent_init_budget_cfg = 0. if "opponent_init_budget" in config_data and config_data[ "opponent_init_budget"] is not None: opponent_init_budget_cfg = config_data["opponent_init_budget"] opponent_init_budget = _get_default_aux( "opponent_init_budget", kwargs, defaultClassApp=float, defaultinstance=opponent_init_budget_cfg, msg_error=ERR_MSG_KWARGS["opponent_init_budget"], isclass=False) opponent_budget_per_ts_cfg = 0. if "opponent_budget_per_ts" in config_data and config_data[ "opponent_budget_per_ts"] is not None: opponent_budget_per_ts_cfg = config_data["opponent_budget_per_ts"] opponent_budget_per_ts = _get_default_aux( "opponent_budget_per_ts", kwargs, defaultClassApp=float, defaultinstance=opponent_budget_per_ts_cfg, msg_error=ERR_MSG_KWARGS["opponent_budget_per_ts"], isclass=False) opponent_attack_duration_cfg = 0 if "opponent_attack_duration" in config_data and config_data[ "opponent_attack_duration"] is not None: opponent_attack_duration_cfg = config_data["opponent_attack_duration"] opponent_attack_duration = _get_default_aux( "opponent_attack_duration", kwargs, defaultClassApp=int, defaultinstance=opponent_attack_duration_cfg, msg_error=ERR_MSG_KWARGS["opponent_attack_duration"], isclass=False) opponent_attack_cooldown_cfg = 99999 if "opponent_attack_cooldown" in config_data and config_data[ "opponent_attack_cooldown"] is not None: opponent_attack_cooldown_cfg = config_data["opponent_attack_cooldown"] opponent_attack_cooldown = _get_default_aux( "opponent_attack_cooldown", kwargs, defaultClassApp=int, defaultinstance=opponent_attack_cooldown_cfg, msg_error=ERR_MSG_KWARGS["opponent_attack_cooldown"], isclass=False) kwargs_opponent_cfg = {} if "kwargs_opponent" in config_data and config_data[ "kwargs_opponent"] is not None: kwargs_opponent_cfg = config_data["kwargs_opponent"] kwargs_opponent = _get_default_aux( "kwargs_opponent", kwargs, defaultClassApp=dict, defaultinstance=kwargs_opponent_cfg, msg_error=ERR_MSG_KWARGS["kwargs_opponent"], isclass=False) # Finally instanciate env from config & overrides env = Environment( init_grid_path=grid_path_abs, chronics_handler=data_feeding, backend=backend, parameters=param, name=name_env + _add_to_name, names_chronics_to_backend=names_chronics_to_backend, actionClass=action_class, observationClass=observation_class, rewardClass=reward_class, legalActClass=gamerules_class, voltagecontrolerClass=volagecontroler_class, other_rewards=other_rewards, opponent_action_class=opponent_action_class, opponent_class=opponent_class, opponent_init_budget=opponent_init_budget, opponent_attack_duration=opponent_attack_duration, opponent_attack_cooldown=opponent_attack_cooldown, opponent_budget_per_ts=opponent_budget_per_ts, opponent_budget_class=opponent_budget_class, kwargs_opponent=kwargs_opponent, ) # Update the thermal limit if any if thermal_limits is not None: env.set_thermal_limit(thermal_limits) # Set graph layout if not None and not an empty dict if graph_layout is not None and graph_layout: env.attach_layout(graph_layout) return env
class TestLoadingBackendFunc(unittest.TestCase): def setUp(self): # powergrid self.adn_backend = PandaPowerBackend() self.path_matpower = PATH_DATA_TEST_PP self.case_file = "test_case14.json" # data self.path_chron = os.path.join(PATH_CHRONICS, "chronics") self.chronics_handler = ChronicsHandler( chronicsClass=GridStateFromFile, path=self.path_chron) self.tolvect = 1e-2 self.tol_one = 1e-5 # force the verbose backend self.adn_backend.detailed_infos_for_cascading_failures = True # _parameters for the environment self.env_params = Parameters() self.names_chronics_to_backend = { "loads": { "2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', "5_C201.84": 'load_4_4', "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', "10_C228.66": 'load_9_7', "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', "13_C-13.33": 'load_12_10' }, "lines": { '1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', '9_14_17': '8_13_3', '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', '3_4_6': '2_3_10', '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', '5_6_10': '4_5_17', '7_8_14': '6_7_18', '7_9_15': '6_8_19' }, "prods": { "1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3" }, } self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.adn_backend, chronics_handler=self.chronics_handler, parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, name="test_rules_env1") self.helper_action = self.env.helper_action_env def test_AlwaysLegal(self): # build a random action acting on everything new_vect = np.random.randn(self.helper_action.n_load) new_vect2 = np.random.randn(self.helper_action.n_load) change_status_orig = np.random.randint( 0, 2, self.helper_action.n_line).astype(np.bool) set_status_orig = np.random.randint(-1, 2, self.helper_action.n_line) set_status_orig[change_status_orig] = 0 change_topo_vect_orig = np.random.randint( 0, 2, self.helper_action.dim_topo).astype(np.bool) # powerline that are set to be reconnected, can't be moved to another bus change_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[ set_status_orig == 1]] = False change_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[ set_status_orig == 1]] = False # powerline that are disconnected, can't be moved to the other bus change_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[ set_status_orig == -1]] = False change_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[ set_status_orig == -1]] = False set_topo_vect_orig = np.random.randint(0, 3, self.helper_action.dim_topo) set_topo_vect_orig[ change_topo_vect_orig] = 0 # don't both change and set # I need to make sure powerlines that are reconnected are indeed reconnected to a bus set_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[ set_status_orig == 1]] = 1 set_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[ set_status_orig == 1]] = 1 # I need to make sure powerlines that are disconnected are not assigned to a bus set_topo_vect_orig[self.helper_action.line_or_pos_topo_vect[ set_status_orig == -1]] = 0 set_topo_vect_orig[self.helper_action.line_ex_pos_topo_vect[ set_status_orig == -1]] = 0 action = self.helper_action({ "change_bus": change_topo_vect_orig, "set_bus": set_topo_vect_orig, "injection": { "load_p": new_vect, "load_q": new_vect2 }, "change_line_status": change_status_orig, "set_line_status": set_status_orig }) # game rules gr = RulesChecker() assert gr.legal_action(action, self.env) def test_LookParam(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = 2 self.helper_action.legal_action = RulesChecker( legalActClass=LookParam).legal_action self.env.parameters.MAX_SUB_CHANGED = 2 self.env.parameters.MAX_LINE_STATUS_CHANGED = 2 _ = self.helper_action( { "change_bus": { "substations_id": [(id_1, arr1)] }, "set_bus": { "substations_id": [(id_2, arr2)] }, "change_line_status": arr_line1, "set_line_status": arr_line2 }, env=self.env, check_legal=True) try: self.env.parameters.MAX_SUB_CHANGED = 1 self.env.parameters.MAX_LINE_STATUS_CHANGED = 2 _ = self.helper_action( { "change_bus": { "substations_id": [(id_1, arr1)] }, "set_bus": { "substations_id": [(id_2, arr2)] }, "change_line_status": arr_line1, "set_line_status": arr_line2 }, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass try: self.env.parameters.MAX_SUB_CHANGED = 2 self.env.parameters.MAX_LINE_STATUS_CHANGED = 1 _ = self.helper_action( { "change_bus": { "substations_id": [(id_1, arr1)] }, "set_bus": { "substations_id": [(id_2, arr2)] }, "change_line_status": arr_line1, "set_line_status": arr_line2 }, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass self.env.parameters.MAX_SUB_CHANGED = 1 self.env.parameters.MAX_LINE_STATUS_CHANGED = 1 _ = self.helper_action( { "change_bus": { "substations_id": [(id_1, arr1)] }, "set_line_status": arr_line2 }, env=self.env, check_legal=True) def test_PreventReconection(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=dt_int) arr_line2[id_line2] = 2 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action self.env.parameters.MAX_SUB_CHANGED = 1 self.env.parameters.MAX_LINE_STATUS_CHANGED = 2 act = self.helper_action( { "change_bus": { "substations_id": [(id_1, arr1)] }, "set_bus": { "substations_id": [(id_2, arr2)] }, "change_line_status": arr_line1, "set_line_status": arr_line2 }, env=self.env, check_legal=True) _ = self.env.step(act) try: self.env.parameters.MAX_SUB_CHANGED = 2 self.env.parameters.MAX_LINE_STATUS_CHANGED = 1 self.env.times_before_line_status_actionable[id_line] = 1 _ = self.helper_action( { "change_bus": { "substations": [(id_1, arr1)] }, "set_bus": { "substations_id": [(id_2, arr2)] }, "change_line_status": arr_line1, "set_line_status": arr_line2 }, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass self.env.times_before_line_status_actionable[:] = 0 self.env.parameters.MAX_SUB_CHANGED = 2 self.env.parameters.MAX_LINE_STATUS_CHANGED = 1 self.env.times_before_line_status_actionable[1] = 1 _ = self.helper_action( { "change_bus": { "substations": [(id_1, arr1)] }, "set_bus": { "substations_id": [(id_2, arr2)] }, "change_line_status": arr_line1, "set_line_status": arr_line2 }, env=self.env, check_legal=True) def test_linereactionnable_throw(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = -1 self.env.max_timestep_line_status_deactivated = 1 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action # i act a first time on powerline 15 act = self.helper_action({"set_line_status": arr_line2}, env=self.env, check_legal=True) self.env.step(act) try: # i try to react on it, it should throw an IllegalAction exception. act = self.helper_action({"set_line_status": arr_line2}, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass def test_linereactionnable_nothrow(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = -1 self.env.max_timestep_line_status_deactivated = 1 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action # i act a first time on powerline 15 act = self.helper_action({"set_line_status": arr_line2}, env=self.env, check_legal=True) self.env.step(act) # i compute another time step without doing anything self.env.step(self.helper_action({})) # i try to react on it, it should NOT throw an IllegalAction exception, but act = self.helper_action({"set_line_status": arr_line2}, env=self.env, check_legal=True) def test_linereactionnable_throw_longerperiod(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = -1 self.env.max_timestep_line_status_deactivated = 2 self.env.parameters.NB_TIMESTEP_LINE_STATUS_REMODIF = 2 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action # i act a first time on powerline 15 act = self.helper_action({"set_line_status": arr_line2}, env=self.env, check_legal=True) _ = self.env.step(act) # i compute another time step without doing anything _ = self.env.step(self.helper_action({})) # i try to react on it, it should throw an IllegalAction exception because we ask the environment to wait # at least 2 time steps try: # i try to react on it, it should throw an IllegalAction exception. act = self.helper_action({"set_line_status": arr_line2}, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass def test_toporeactionnable_throw(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = -1 self.env.max_timestep_topology_deactivated = 1 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action # i act a first time on powerline 15 act = self.helper_action( {"set_bus": { "substations_id": [(id_2, arr2)] }}, env=self.env, check_legal=True) self.env.step(act) try: # i try to react on it, it should throw an IllegalAction exception. act = self.helper_action( {"set_bus": { "substations_id": [(id_2, arr2)] }}, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass def test_toporeactionnable_nothrow(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = -1 self.env.max_timestep_topology_deactivated = 1 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action # i act a first time on powerline 15 act = self.helper_action( {"set_bus": { "substations_id": [(id_2, arr2)] }}, env=self.env, check_legal=True) self.env.step(act) # i compute another time step without doing anything self.env.step(self.helper_action({})) # i try to react on it, it should NOT throw an IllegalAction exception, but act = self.helper_action( {"set_bus": { "substations_id": [(id_2, arr2)] }}, env=self.env, check_legal=True) def test_toporeactionnable_throw_longerperiod(self): id_1 = 1 id_2 = 12 id_line = 17 id_line2 = 15 arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) arr_line1 = np.full(self.helper_action.n_line, fill_value=False, dtype=np.bool) arr_line1[id_line] = True arr_line2 = np.full(self.helper_action.n_line, fill_value=0, dtype=np.int) arr_line2[id_line2] = -1 self.env.max_timestep_topology_deactivated = 2 self.helper_action.legal_action = RulesChecker( legalActClass=PreventReconnection).legal_action # i act a first time on powerline 15 act = self.helper_action( {"set_bus": { "substations_id": [(id_2, arr2)] }}, env=self.env, check_legal=True) self.env.step(act) # i compute another time step without doing anything self.env.step(self.helper_action({})) # i try to react on it, it should throw an IllegalAction exception because we ask the environment to wait # at least 2 time steps try: # i try to react on it, it should throw an IllegalAction exception. act = self.helper_action( {"set_bus": { "substations_id": [(id_2, arr2)] }}, env=self.env, check_legal=True) raise RuntimeError("This should have thrown an IllegalException") except IllegalAction: pass
def make2(dataset_path="/", **kwargs): """ This function is a shortcut to rapidly create environments within the grid2op Framework. It mimic the ``gym.make`` function. Parameters ---------- dataset_path: ``str`` Path to the dataset folder param: ``grid2op.Parameters.Parameters``, optional Type of parameters used for the Environment. Parameters defines how the powergrid problem is cast into an markov decision process, and some internal backend: ``grid2op.Backend.Backend``, optional The backend to use for the computation. If provided, it must be an instance of :class:`grid2op.Backend.Backend`. action_class: ``type``, optional Type of BaseAction the BaseAgent will be able to perform. If provided, it must be a subclass of :class:`grid2op.BaseAction.BaseAction` observation_class: ``type``, optional Type of BaseObservation the BaseAgent will receive. If provided, It must be a subclass of :class:`grid2op.BaseAction.BaseObservation` reward_class: ``type``, optional Type of reward signal the BaseAgent will receive. If provided, It must be a subclass of :class:`grid2op.BaseReward.BaseReward` gamerules_class: ``type``, optional Type of "Rules" the BaseAgent need to comply with. Rules are here to model some operational constraints. If provided, It must be a subclass of :class:`grid2op.RulesChecker.BaseRules` data_feeding_kwargs: ``dict``, optional Dictionnary that is used to build the `data_feeding` (chronics) objects. chronics_class: ``type``, optional The type of chronics that represents the dynamics of the Environment created. Usually they come from different folders. data_feeding: ``type``, optional The type of chronics handler you want to use. volagecontroler_class: ``type``, optional The type of :class:`grid2op.VoltageControler.VoltageControler` to use, it defaults to Returns ------- env: :class:`grid2op.Environment.Environment` The created environment. """ # Compute and find root folder _check_path(dataset_path, "Dataset root directory") dataset_path_abs = os.path.abspath(dataset_path) # Compute env name from directory name name_env = os.path.split(dataset_path_abs)[1] # Compute and find chronics folder chronics_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_CHRONICS_FOLDER)) _check_path(chronics_path_abs, "Dataset chronics folder") # Compute and find backend/grid file grid_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_GRID_FILE)) _check_path(grid_path_abs, "Dataset power flow solver configuration") # Compute and find grid layout file grid_layout_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_GRID_LAYOUT_FILE)) _check_path(grid_layout_path_abs, "Dataset grid layout") # Check provided config overrides are valid _check_kwargs(kwargs) # Compute and find config file config_path_abs = os.path.abspath(os.path.join(dataset_path_abs, NAME_CONFIG_FILE)) _check_path(grid_path_abs, "Dataset environment configuration") # Read config file try: spec = importlib.util.spec_from_file_location("config.config", config_path_abs) config_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(config_module) config_data = config_module.config except Exception as e: print (e) raise EnvError("Invalid dataset config file: {}".format(config_path_abs)) from None # Get graph layout try: with open(grid_layout_path_abs) as layout_fp: graph_layout = json.load(layout_fp) except Exception as e: raise EnvError("Dataset {} doesn't have a valid graph layout".format(config_path_abs)) # Get thermal limits thermal_limits = None if "thermal_limits" in config_data: thermal_limits = config_data["thermal_limits"] # Get chronics_to_backend name_converter = None if "names_chronics_to_grid" in config_data: name_converter = config_data["names_chronics_to_grid"] if name_converter is None: name_converter = {} names_chronics_to_backend = _get_default_aux("names_chronics_to_backend", kwargs, defaultClassApp=dict, defaultinstance=name_converter, msg_error=ERR_MSG_KWARGS["names_chronics_to_grid"]) # Get default backend class backend_class_cfg = PandaPowerBackend if "backend_class" in config_data and config_data["backend_class"] is not None: backend_class_cfg = config_data["backend_class"] ## Create the backend, to compute the powerflow backend = _get_default_aux("backend", kwargs, defaultClass=backend_class_cfg, defaultClassApp=Backend, msg_error=ERR_MSG_KWARGS["backend"]) # Get default observation class observation_class_cfg = CompleteObservation if "observation_class" in config_data and config_data["observation_class"] is not None: observation_class_cfg = config_data["observation_class"] ## Setup the type of observation the agent will receive observation_class = _get_default_aux("observation_class", kwargs, defaultClass=observation_class_cfg, isclass=True, defaultClassApp=BaseObservation, msg_error=ERR_MSG_KWARGS["observation"]) ## Create the parameters of the game, thermal limits threshold, # simulate cascading failure, powerflow mode etc. (the gamification of the game) param = _get_default_aux('param', kwargs, defaultClass=Parameters, defaultClassApp=Parameters, msg_error=ERR_MSG_KWARGS["param"]) # Get default rules class rules_class_cfg = DefaultRules if "rules_class" in config_data and config_data["rules_class"] is not None: rules_class_cfg = config_data["rules_class"] ## Create the rules of the game (mimic the operationnal constraints) gamerules_class = _get_default_aux("gamerules_class", kwargs, defaultClass=rules_class_cfg, defaultClassApp=BaseRules, msg_error=ERR_MSG_KWARGS["rules"], isclass=True) # Get default reward class reward_class_cfg = L2RPNReward if "reward_class" in config_data and config_data["reward_class"] is not None: reward_class_cfg = config_data["reward_class"] ## Setup the reward the agent will receive reward_class = _get_default_aux("reward_class", kwargs, defaultClass=reward_class_cfg, defaultClassApp=BaseReward, msg_error=ERR_MSG_KWARGS["reward"], isclass=True) # Get default BaseAction class action_class_cfg = BaseAction if "action_class" in config_data and config_data["action_class"] is not None: action_class_cfg = config_data["action_class"] ## Setup the type of action the BaseAgent can perform action_class = _get_default_aux("action_class", kwargs, defaultClass=action_class_cfg, defaultClassApp=BaseAction, msg_error=ERR_MSG_KWARGS["action"], isclass=True) # Get default Voltage class voltage_class_cfg = ControlVoltageFromFile if "voltage_class" in config_data and config_data["voltage_class"] is not None: voltage_class_cfg = config_data["voltage_class"] ### Create controler for voltages volagecontroler_class = _get_default_aux("volagecontroler_class", kwargs, defaultClassApp=voltage_class_cfg, defaultClass=ControlVoltageFromFile, msg_error=ERR_MSG_KWARGS["voltage"], isclass=True) # Get default Chronics class chronics_class_cfg = ChangeNothing if "chronics_class" in config_data and config_data["chronics_class"] is not None: chronics_class_cfg = config_data["chronics_class"] # Get default Grid class grid_value_class_cfg = GridStateFromFile if "grid_value_class" in config_data and config_data["grid_value_class"] is not None: grid_value_class_cfg = config_data["grid_value_class"] ## the chronics to use ### the arguments used to build the data, note that the arguments must be compatible with the chronics class default_chronics_kwargs = { "chronicsClass": chronics_class_cfg, "path": chronics_path_abs, "gridvalueClass": grid_value_class_cfg } data_feeding_kwargs = _get_default_aux("data_feeding_kwargs", kwargs, defaultClassApp=dict, defaultinstance=default_chronics_kwargs, msg_error=ERR_MSG_KWARGS["data_feeding_kwargs"]) for el in default_chronics_kwargs: if not el in data_feeding_kwargs: data_feeding_kwargs[el] = default_chronics_kwargs[el] ### the chronics generator chronics_class_used = _get_default_aux("chronics_class", kwargs, defaultClassApp=GridValue, defaultClass=data_feeding_kwargs["chronicsClass"], msg_error=ERR_MSG_KWARGS["chronics"], isclass=True) data_feeding_kwargs["chronicsClass"] = chronics_class_used data_feeding = _get_default_aux("data_feeding", kwargs, defaultClassApp=ChronicsHandler, defaultClass=ChronicsHandler, build_kwargs=data_feeding_kwargs, msg_error=ERR_MSG_KWARGS["chronics_handler"]) ### other rewards other_rewards = _get_default_aux("other_rewards", kwargs, defaultClassApp=dict, defaultinstance={}, msg_error=ERR_MSG_KWARGS["other_rewards"], isclass=False) # Opponent # TODO make that in config file of the default environment !!! opponent_action_class = _get_default_aux("opponent_action_class", kwargs, defaultClassApp=BaseAction, defaultClass=DontAct, msg_error=ERR_MSG_KWARGS["opponent_action_class"], isclass=True) opponent_class = _get_default_aux("opponent_class", kwargs, defaultClassApp=BaseOpponent, defaultClass=BaseOpponent, msg_error=ERR_MSG_KWARGS["opponent_class"], isclass=True) opponent_init_budget = _get_default_aux("opponent_init_budget", kwargs, defaultClassApp=float, defaultinstance=0., msg_error=ERR_MSG_KWARGS["opponent_init_budget"], isclass=False) # Finally instanciate env from config & overrides env = Environment(init_grid_path=grid_path_abs, chronics_handler=data_feeding, backend=backend, parameters=param, names_chronics_to_backend=names_chronics_to_backend, actionClass=action_class, observationClass=observation_class, rewardClass=reward_class, legalActClass=gamerules_class, voltagecontrolerClass=volagecontroler_class, other_rewards=other_rewards, opponent_action_class=opponent_action_class, opponent_class=opponent_class, opponent_init_budget=opponent_init_budget) # Update the thermal limit if any if thermal_limits is not None: env.set_thermal_limit(thermal_limits) # Set graph layout env.attach_layout(graph_layout) return env