def test_loadchornics_maintenance_ok(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_maintenance) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step() assert np.sum(maintenance_duration == 0) == 18 assert maintenance_duration[17] == 12, "incorrect duration of maintenance on powerline 17" assert maintenance_duration[19] == 12, "incorrect duration of maintenance on powerline 19" assert np.sum(maintenance_time == -1) == 18 assert maintenance_time[17] == 1, "incorrect time for next maintenance on powerline 17" assert maintenance_time[19] == 276, "incorrect time for next maintenance on powerline 19" for i in range(12): current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step() assert np.sum(maintenance_duration == 0) == 18 assert int(maintenance_duration[17]) == int(12-i), "incorrect duration of maintenance on powerline 17 at iteration {}: it is {} and should be {}".format(i, maintenance_duration[17], int(12-i)) assert maintenance_duration[19] == 12, "incorrect duration of maintenance on powerline 19 at iteration {}".format(i) assert np.sum(maintenance_time == -1) == 18 assert maintenance_time[17] == 0, "incorrect time for next maintenance on powerline 17 at iteration {}".format(i) assert maintenance_time[19] == 275-i, "incorrect time for next maintenance on powerline 19 at iteration {}".format(i) current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step() assert np.sum(maintenance_duration == 0) == 19 assert maintenance_duration[19] == 12, "incorrect duration of maintenance on powerline 19 at finish" assert np.sum(maintenance_time == -1) == 19 assert maintenance_time[19] == 263, "incorrect time for next maintenance on powerline 19 at finish"
def test_chronicsloading(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) _, res = chron_handl.next_time_step() # should load the first time stamp vect = [18.8, 86.5, 44.5, 7.1, 10.4, 27.6, 8.1, 3.2, 5.6, 11.9, 13.6] assert self.compare_vect(res["injection"]['load_p'], vect)
def test_chronicsloading(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs, self.names_chronics_to_backend) _, res = chron_handl.next_time_step() # should load the first time stamp 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]) # what is written on the file backend_th = vect[self.id_chron_to_back_load] # what should be in backend assert self.compare_vect(res["injection"]['load_p'], backend_th)
def test_loadchornics_hazard_ok(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_hazard) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step() assert np.all(hazard_duration == 0) for i in range(12): current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step() assert np.sum(hazard_duration == 0) == 19 assert hazard_duration[17] == 12-i, "error at iteration {}".format(i) current_datetime, dict_, maintenance_time, maintenance_duration, hazard_duration, prod_v = chron_handl.next_time_step() assert np.all(hazard_duration == 0)
def setUp(self): self.backend = PandaPowerBackend( detailed_infos_for_cascading_failures=True) self.path_matpower = PATH_DATA_TEST self.case_file = "test_case14.json" self.backend.load_grid(self.path_matpower, self.case_file) self.tolvect = 1e-2 self.tol_one = 1e-5 self.game_rules = GameRules() self.action_env = HelperAction( gridobj=self.backend, legal_action=self.game_rules.legal_action) self.lines_flows_init = np.array([ 638.28966637, 305.05042301, 17658.9674809, 26534.04334098, 10869.23856329, 4686.71726729, 15612.65903298, 300.07915572, 229.8060832, 169.97292682, 100.40192958, 265.47505664, 21193.86923911, 21216.44452327, 49701.1565287, 124.79684388, 67.59759985, 192.19424706, 666.76961936, 1113.52773632 ]) # _parameters for the environment self.env_params = Parameters() # used for init an env too self.chronics_handler = ChronicsHandler() self.id_first_line_disco = 8 # due to hard overflow self.id_2nd_line_disco = 11 # due to soft overflow
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 = GameRules() 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_forecast") 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() 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)
def test_name_invariant(self): """ Test that the crhonics are loaded in whatever format, but the order returned is consistent with the one of the backend. :return: """ path = os.path.join(PATH_CHRONICS, "chronics_reorder") chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs, self.names_chronics_to_backend) _, res = chron_handl.next_time_step() # should load the first time stamp 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]) vect = vect[self.id_chron_to_back_load] assert self.compare_vect(res["injection"]['load_p'], vect) for i in range(287): _, res = chron_handl.next_time_step() # should load the first time stamp 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(res["injection"]['load_p'], vect) try: _, res = chron_handl.next_time_step() # should load the first time stamp raise RuntimeError("This should have thrown a StopIteration exception") except StopIteration: pass
def _one_process_parrallel(runner, episode_this_process, process_id, path_save=None): chronics_handler = ChronicsHandler(chronicsClass=runner.gridStateclass, path=runner.path_chron, **runner.gridStateclass_kwargs) parameters = copy.deepcopy(runner.parameters) backend = runner.backendClass() nb_episode_this_process = len(episode_this_process) res = [(None, None, None) for _ in range(nb_episode_this_process)] for i, p_id in enumerate(episode_this_process): env, agent = runner._new_env(chronics_handler=chronics_handler, backend=backend, parameters=parameters) name_chron, cum_reward, nb_time_step = Runner._run_one_episode( env, agent, runner.logger, p_id, path_save) id_chron = chronics_handler.get_id() max_ts = chronics_handler.max_timestep() res[i] = (id_chron, name_chron, cum_reward, nb_time_step, max_ts) return res
def test_chronicsloading_secondtimestep_chunksize(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path, chunk_size=1) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) _ = chron_handl.next_time_step() # should load the first time stamp _, res, *_ = chron_handl.next_time_step() # should load the first time stamp vect = [18.8, 85.1, 44.3, 7.1, 10.2, 27.1, 8.2, 3.2, 5.7, 11.8, 13.8] assert self.compare_vect(res["injection"]['load_p'], vect)
def test_done(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) for i in range(288): _, res = chron_handl.next_time_step() # should load the first time stamp vect = [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6] assert self.compare_vect(res["injection"]['load_p'], vect) assert chron_handl.done()
def test_check_validity(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) backend = PandaPowerBackend() path_matpower = PATH_DATA_TEST_PP case_file = "test_case14.json" backend.load_grid(path_matpower, case_file) chron_handl.check_validity(backend)
def test_chronicsloading_secondtimestep(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs, self.names_chronics_to_backend) _ = chron_handl.next_time_step() # should load the first time stamp _, res = chron_handl.next_time_step() # should load the first time stamp vect = np.array([18.8, 85.1, 44.3, 7.1, 10.2, 27.1, 8.2, 3.2, 5.7, 11.8, 13.8]) vect = vect[self.id_chron_to_back_load] assert self.compare_vect(res["injection"]['load_p'], vect)
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 = 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() 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=Action) self.array_double_dispatch = np.array([0., 12.41833569, 10.89081339, 0., -23.30914908])
def test_check_validity(self): # load a "fake" chronics with name in the correct order chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.pathfake) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs, self.names_chronics_to_backend) backend = PandaPowerBackend() path_matpower = PATH_DATA_TEST_PP case_file = "test_case14.json" backend.load_grid(path_matpower, case_file) chron_handl.check_validity(backend)
def test_done_chunk_size(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path, chunk_size=1) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs, self.names_chronics_to_backend) for i in range(288): _, res, *_ = chron_handl.next_time_step() # should load the first time stamp 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(res["injection"]['load_p'], vect) assert chron_handl.done()
def test_stopiteration(self): chron_handl = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs) for i in range(288): _, res = chron_handl.next_time_step() # should load the first time stamp vect = [19.0, 87.9, 44.4, 7.2, 10.4, 27.5, 8.4, 3.2, 5.7, 12.2, 13.6] assert self.compare_vect(res["injection"]['load_p'], vect) try: res = chron_handl.next_time_step() # should load the first time stamp raise RuntimeError("This should have thrown a StopIteration exception") except StopIteration: pass
def test_stopiteration(self): chron_handl = ChronicsHandler(chronicsClass=Multifolder, path=self.path, gridvalueClass=GridStateFromFileWithForecasts, max_iter=self.max_iter) chron_handl.initialize(self.order_backend_loads, self.order_backend_prods, self.order_backend_lines, self.order_backend_subs, self.names_chronics_to_backend) _, res, *_ = chron_handl.next_time_step() # should load the first time stamp for i in range(self.max_iter): _, res, *_ = chron_handl.next_time_step() # should load the first time stamp try: _, res, *_ = chron_handl.next_time_step() # should load the first time stamp raise RuntimeError("This should have thrown a StopIteration exception") except StopIteration: pass
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 = GameRules() # 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_forecast") 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() 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) self.dict_ = { 'name_gen': ['gen_1_0', 'gen_2_1', 'gen_5_2', 'gen_7_3', 'gen_0_4'], 'name_load': [ 'load_1_0', 'load_2_1', 'load_13_2', 'load_3_3', 'load_4_4', 'load_5_5', 'load_8_6', 'load_9_7', 'load_10_8', 'load_11_9', 'load_12_10' ], 'name_line': [ '0_1_0', '0_4_1', '8_9_2', '8_13_3', '9_10_4', '11_12_5', '12_13_6', '1_2_7', '1_3_8', '1_4_9', '2_3_10', '3_4_11', '5_10_12', '5_11_13', '5_12_14', '3_6_15', '3_8_16', '4_5_17', '6_7_18', '6_8_19' ], 'name_sub': [ 'sub_0', 'sub_1', 'sub_10', 'sub_11', 'sub_12', 'sub_13', 'sub_2', 'sub_3', 'sub_4', 'sub_5', 'sub_6', 'sub_7', 'sub_8', 'sub_9' ], 'sub_info': [3, 6, 4, 6, 5, 6, 3, 2, 5, 3, 3, 3, 4, 3], 'load_to_subid': [1, 2, 13, 3, 4, 5, 8, 9, 10, 11, 12], 'gen_to_subid': [1, 2, 5, 7, 0], 'line_or_to_subid': [0, 0, 8, 8, 9, 11, 12, 1, 1, 1, 2, 3, 5, 5, 5, 3, 3, 4, 6, 6], 'line_ex_to_subid': [ 1, 4, 9, 13, 10, 12, 13, 2, 3, 4, 3, 4, 10, 11, 12, 6, 8, 5, 7, 8 ], 'load_to_sub_pos': [5, 3, 2, 5, 4, 5, 4, 2, 2, 2, 3], 'gen_to_sub_pos': [4, 2, 4, 1, 2], 'line_or_to_sub_pos': [0, 1, 0, 1, 1, 0, 1, 1, 2, 3, 1, 2, 0, 1, 2, 3, 4, 3, 1, 2], 'line_ex_to_sub_pos': [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 2, 1, 1, 2, 0, 2, 3, 0, 3], 'load_pos_topo_vect': [8, 12, 55, 18, 23, 29, 39, 42, 45, 48, 52], 'gen_pos_topo_vect': [7, 11, 28, 34, 2], 'line_or_pos_topo_vect': [ 0, 1, 35, 36, 41, 46, 50, 4, 5, 6, 10, 15, 24, 25, 26, 16, 17, 22, 31, 32 ], 'line_ex_pos_topo_vect': [ 3, 19, 40, 53, 43, 49, 54, 9, 13, 20, 14, 21, 44, 47, 51, 30, 37, 27, 33, 38 ], 'subtype': 'Observation.CompleteObservation' } self.dtypes = np.array([ dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('bool'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64') ], dtype=object) self.shapes = np.array([ 1, 1, 1, 1, 1, 1, 5, 5, 5, 11, 11, 11, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 56, 20, 20, 14, 20, 20, 20 ])
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 = 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() 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) def tearDown(self): pass def compare_vect(self, pred, true): return 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) if PROFILE_CODE: cp = cProfile.Profile() cp.enable() beg_ = time.time() cum_reward = 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" assert np.abs(cum_reward - 5739.92911) <= self.tol_one, "Wrong reward"
def __init__( self, init_grid_path: str, # full path where grid state is located, eg "./data/test_Pandapower/case14.json" path_chron, # path where chronics of injections are stored parameters_path=None, names_chronics_to_backend=None, actionClass=TopologyAction, observationClass=CompleteObservation, rewardClass=FlatReward, legalActClass=AllwaysLegal, envClass=Environment, gridStateclass=GridStateFromFile, #type of chronics to use. For example GridStateFromFile if forecasts are not used, or GridStateFromFileWithForecasts otherwise backendClass=PandaPowerBackend, agentClass=DoNothingAgent, #class used to build the agent agentInstance=None, verbose=False, gridStateclass_kwargs={}, ): """ Initialize the Runner. Parameters ---------- init_grid_path: ``str`` Madantory, used to initialize :attr:`Runner.init_grid_path`. path_chron: ``str`` Madantory where to look for chronics data, used to initialize :attr:`Runner.path_chron`. parameters_path: ``str``, optional Used to initialize :attr:`Runner.parameters_path`. names_chronics_to_backend: ``dict``, optional Used to initialize :attr:`Runner.names_chronics_to_backend`. actionClass: ``type``, optional Used to initialize :attr:`Runner.actionClass`. observationClass: ``type``, optional Used to initialize :attr:`Runner.observationClass`. rewardClass: ``type``, optional Used to initialize :attr:`Runner.rewardClass`. Default to :class:`grid2op.ConstantReward` that *should not** be used to train or evaluate an agent, but rather as debugging purpose. legalActClass: ``type``, optional Used to initialize :attr:`Runner.legalActClass`. envClass: ``type``, optional Used to initialize :attr:`Runner.envClass`. gridStateclass: ``type``, optional Used to initialize :attr:`Runner.gridStateclass`. backendClass: ``type``, optional Used to initialize :attr:`Runner.backendClass`. agentClass: ``type``, optional Used to initialize :attr:`Runner.agentClass`. agentInstance: :class:`grid2op.Agent.Agent` Used to initialize the agent. Note that either :attr:`agentClass` or :attr:`agentInstance` is used at the same time. If both ot them are ``None`` or both of them are "not ``None``" it throw an error. verbose: ``bool``, optional Used to initialize :attr:`Runner.verbose`. """ if not isinstance(envClass, type): raise Grid2OpException( "Parameter \"envClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(envClass))) if not issubclass(envClass, Environment): raise RuntimeError( "Impossible to create a runner without an evnrionment derived from grid2op.Environement" " class. Please modify \"envClass\" paramter.") self.envClass = envClass if not isinstance(actionClass, type): raise Grid2OpException( "Parameter \"actionClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(actionClass))) if not issubclass(actionClass, Action): raise RuntimeError( "Impossible to create a runner without an action class derived from grid2op.Action. " "Please modify \"actionClass\" paramter.") self.actionClass = actionClass if not isinstance(observationClass, type): raise Grid2OpException( "Parameter \"observationClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(observationClass))) if not issubclass(observationClass, Observation): raise RuntimeError( "Impossible to create a runner without an observation class derived from " "grid2op.Observation. Please modify \"observationClass\" paramter." ) self.observationClass = observationClass if not isinstance(rewardClass, type): raise Grid2OpException( "Parameter \"rewardClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(rewardClass))) if not issubclass(rewardClass, Reward): raise RuntimeError( "Impossible to create a runner without an observation class derived from " "grid2op.Reward. Please modify \"rewardClass\" paramter.") self.rewardClass = rewardClass if not isinstance(gridStateclass, type): raise Grid2OpException( "Parameter \"gridStateclass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(gridStateclass))) if not issubclass(gridStateclass, GridValue): raise RuntimeError( "Impossible to create a runner without an chronics class derived from " "grid2op.GridValue. Please modify \"gridStateclass\" paramter." ) self.gridStateclass = gridStateclass if not isinstance(legalActClass, type): raise Grid2OpException( "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(legalActClass))) if not issubclass(legalActClass, LegalAction): raise RuntimeError( "Impossible to create a runner without a class defining legal actions derived " "from grid2op.LegalAction. Please modify \"legalActClass\" paramter." ) self.legalActClass = legalActClass if not isinstance(backendClass, type): raise Grid2OpException( "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(backendClass))) if not issubclass(backendClass, Backend): raise RuntimeError( "Impossible to create a runner without a backend class derived from grid2op.GridValue. " "Please modify \"backendClass\" paramter.") self.backendClass = backendClass if agentClass is not None: if agentInstance is not None: raise RuntimeError( "Impossible to build the backend. Only one of AgentClass or agentInstance can be " "used (both are not None).") if not isinstance(agentClass, type): raise Grid2OpException( "Parameter \"agentClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(agentClass))) if not issubclass(agentClass, Agent): raise RuntimeError( "Impossible to create a runner without an agent class derived from grid2op.Agent. " "Please modify \"agentClass\" parameter.") self.agentClass = agentClass self._useclass = True self.agent = None elif agentInstance is not None: if not isinstance(agentInstance, Agent): raise RuntimeError( "Impossible to create a runner without an agent class derived from grid2op.Agent. " "Please modify \"agentInstance\" parameter.") self.agentClass = None self._useclass = False self.agent = agentInstance else: raise RuntimeError( "Impossible to build the backend. Either AgentClass or agentInstance must be provided " "and both are None.") self.logger = ConsoleLog( DoNothingLog.INFO if verbose else DoNothingLog.ERROR) # store _parameters self.init_grid_path = init_grid_path self.names_chronics_to_backend = names_chronics_to_backend # game _parameters self.parameters_path = parameters_path self.parameters = Parameters(parameters_path) # chronics of grid state self.path_chron = path_chron self.gridStateclass_kwargs = gridStateclass_kwargs self.chronics_handler = ChronicsHandler( chronicsClass=self.gridStateclass, path=self.path_chron, **self.gridStateclass_kwargs) # the backend, used to compute powerflows self.backend = self.backendClass() # build the environment self.env = None # miscellaneous self.verbose = verbose
class Runner(object): """ A runner is a utilitary tool that allows to create environment, and run simulations more easily. This specific class as for main purpose to evaluate the performance of a trained :class:`grid2op.Agent`, rather than to train it. Of course, it is possible to adapt it for a specific training mechanisms. Examples of such will be made available in the future. Attributes ---------- envClass: ``type`` The type of the environment used for the game. The class should be given, and **not** an instance (object) of this class. The default is the :class:`grid2op.Environment`. If modified, it should derived from this class. actionClass: ``type`` The type of action that can be performed by the agent / bot / controler. The class should be given, and **not** an instance of this class. This type should derived from :class:`grid2op.Action`. The default is :class:`grid2op.TopologyAction`. observationClass: ``type`` This type represents the class that will be used to build the :class:`grid2op.Observation` visible by the :class:`grid2op.Agent`. As :attr:`Runner.actionClass`, this should be a type, and **not** and instance (object) of this type. This type should derived from :class:`grid2op.Observation`. The default is :class:`grid2op.CompleteObservation`. rewardClass: ``type`` Representes the type used to build the rewards that are given to the :class:`Agent`. As :attr:`Runner.actionClass`, this should be a type, and **not** and instance (object) of this type. This type should derived from :class:`grid2op.Reward`. The default is :class:`grid2op.ConstantReward` that **should not** be used to train or evaluate an agent, but rather as debugging purpose. gridStateclass: ``type`` This types control the mechanisms to read chronics and assign data to the powergrid. Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type. Its default is :class:`grid2op.GridStateFromFile` and it must be a subclass of :class:`grid2op.GridValue`. legalActClass: ``type`` This types control the mechanisms to assess if an :class:`grid2op.Action` is legal. Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type. Its default is :class:`grid2op.AllwaysLegal` and it must be a subclass of :class:`grid2op.LegalAction`. backendClass: ``type`` This types control the backend, *eg.* the software that computes the powerflows. Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type. Its default is :class:`grid2op.PandaPowerBackend` and it must be a subclass of :class:`grid2op.Backend`. agentClass: ``type`` This types control the type of Agent, *eg.* the bot / controler that will take :class:`grid2op.Action` and avoid cascading failures. Like every "\.*Class" attributes the type should be pass and not an intance (object) of this type. Its default is :class:`grid2op.DoNothingAgent` and it must be a subclass of :class:`grid2op.Agent`. logger: A object than can be used to log information, either in a text file, or by printing them to the command prompt. init_grid_path: ``str`` This attributes store the path where the powergrid data are located. If a relative path is given, it will be extended as an absolute path. names_chronics_to_backend: ``dict`` See description of :func:`grid2op.ChronicsHelper.initialize` for more information about this dictionnary parameters_path: ``str``, optional Where to look for the :class:`grid2op.Environment` :class:`grid2op.Parameters`. It defaults to ``None`` which corresponds to using default values. parameters: :class:`grid2op.Parameters` Type of _parameters used. This is an instance (object) of type :class:`grid2op.Parameters` initialized from :attr:`Runner.parameters_path` path_chron: ``str`` Path indicatng where to look for temporal data. chronics_handler: :class:`grid2op.ChronicsHandler` Initialized from :attr:`Runner.gridStateclass` and :attr:`Runner.path_chron` it represents the input data used to generate grid state by the :attr:`Runner.env` backend: :class:`grid2op.Backend` Used to compute the powerflow. This object has the type given by :attr:`Runner.backendClass` env: :class:`grid2op.Environment` Represents the environment which the agent / bot / control must control through action. It is initialized from the :attr:`Runner.envClass` agent: :class:`grid2op.Agent` Represents the agent / bot / controler that takes action performed on a environment (the powergrid) to maximize a certain reward. verbose: ``bool`` If ``True`` then detailed output of each steps are written. gridStateclass_kwargs: ``dict`` Additional keyword arguments used to build the :attr:`Runner.chronics_handler` """ def __init__( self, init_grid_path: str, # full path where grid state is located, eg "./data/test_Pandapower/case14.json" path_chron, # path where chronics of injections are stored parameters_path=None, names_chronics_to_backend=None, actionClass=TopologyAction, observationClass=CompleteObservation, rewardClass=FlatReward, legalActClass=AllwaysLegal, envClass=Environment, gridStateclass=GridStateFromFile, #type of chronics to use. For example GridStateFromFile if forecasts are not used, or GridStateFromFileWithForecasts otherwise backendClass=PandaPowerBackend, agentClass=DoNothingAgent, #class used to build the agent agentInstance=None, verbose=False, gridStateclass_kwargs={}, ): """ Initialize the Runner. Parameters ---------- init_grid_path: ``str`` Madantory, used to initialize :attr:`Runner.init_grid_path`. path_chron: ``str`` Madantory where to look for chronics data, used to initialize :attr:`Runner.path_chron`. parameters_path: ``str``, optional Used to initialize :attr:`Runner.parameters_path`. names_chronics_to_backend: ``dict``, optional Used to initialize :attr:`Runner.names_chronics_to_backend`. actionClass: ``type``, optional Used to initialize :attr:`Runner.actionClass`. observationClass: ``type``, optional Used to initialize :attr:`Runner.observationClass`. rewardClass: ``type``, optional Used to initialize :attr:`Runner.rewardClass`. Default to :class:`grid2op.ConstantReward` that *should not** be used to train or evaluate an agent, but rather as debugging purpose. legalActClass: ``type``, optional Used to initialize :attr:`Runner.legalActClass`. envClass: ``type``, optional Used to initialize :attr:`Runner.envClass`. gridStateclass: ``type``, optional Used to initialize :attr:`Runner.gridStateclass`. backendClass: ``type``, optional Used to initialize :attr:`Runner.backendClass`. agentClass: ``type``, optional Used to initialize :attr:`Runner.agentClass`. agentInstance: :class:`grid2op.Agent.Agent` Used to initialize the agent. Note that either :attr:`agentClass` or :attr:`agentInstance` is used at the same time. If both ot them are ``None`` or both of them are "not ``None``" it throw an error. verbose: ``bool``, optional Used to initialize :attr:`Runner.verbose`. """ if not isinstance(envClass, type): raise Grid2OpException( "Parameter \"envClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(envClass))) if not issubclass(envClass, Environment): raise RuntimeError( "Impossible to create a runner without an evnrionment derived from grid2op.Environement" " class. Please modify \"envClass\" paramter.") self.envClass = envClass if not isinstance(actionClass, type): raise Grid2OpException( "Parameter \"actionClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(actionClass))) if not issubclass(actionClass, Action): raise RuntimeError( "Impossible to create a runner without an action class derived from grid2op.Action. " "Please modify \"actionClass\" paramter.") self.actionClass = actionClass if not isinstance(observationClass, type): raise Grid2OpException( "Parameter \"observationClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(observationClass))) if not issubclass(observationClass, Observation): raise RuntimeError( "Impossible to create a runner without an observation class derived from " "grid2op.Observation. Please modify \"observationClass\" paramter." ) self.observationClass = observationClass if not isinstance(rewardClass, type): raise Grid2OpException( "Parameter \"rewardClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(rewardClass))) if not issubclass(rewardClass, Reward): raise RuntimeError( "Impossible to create a runner without an observation class derived from " "grid2op.Reward. Please modify \"rewardClass\" paramter.") self.rewardClass = rewardClass if not isinstance(gridStateclass, type): raise Grid2OpException( "Parameter \"gridStateclass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(gridStateclass))) if not issubclass(gridStateclass, GridValue): raise RuntimeError( "Impossible to create a runner without an chronics class derived from " "grid2op.GridValue. Please modify \"gridStateclass\" paramter." ) self.gridStateclass = gridStateclass if not isinstance(legalActClass, type): raise Grid2OpException( "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(legalActClass))) if not issubclass(legalActClass, LegalAction): raise RuntimeError( "Impossible to create a runner without a class defining legal actions derived " "from grid2op.LegalAction. Please modify \"legalActClass\" paramter." ) self.legalActClass = legalActClass if not isinstance(backendClass, type): raise Grid2OpException( "Parameter \"legalActClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(backendClass))) if not issubclass(backendClass, Backend): raise RuntimeError( "Impossible to create a runner without a backend class derived from grid2op.GridValue. " "Please modify \"backendClass\" paramter.") self.backendClass = backendClass if agentClass is not None: if agentInstance is not None: raise RuntimeError( "Impossible to build the backend. Only one of AgentClass or agentInstance can be " "used (both are not None).") if not isinstance(agentClass, type): raise Grid2OpException( "Parameter \"agentClass\" used to build the Runner should be a type (a class) and not an object " "(an instance of a class). It is currently \"{}\"".format( type(agentClass))) if not issubclass(agentClass, Agent): raise RuntimeError( "Impossible to create a runner without an agent class derived from grid2op.Agent. " "Please modify \"agentClass\" parameter.") self.agentClass = agentClass self._useclass = True self.agent = None elif agentInstance is not None: if not isinstance(agentInstance, Agent): raise RuntimeError( "Impossible to create a runner without an agent class derived from grid2op.Agent. " "Please modify \"agentInstance\" parameter.") self.agentClass = None self._useclass = False self.agent = agentInstance else: raise RuntimeError( "Impossible to build the backend. Either AgentClass or agentInstance must be provided " "and both are None.") self.logger = ConsoleLog( DoNothingLog.INFO if verbose else DoNothingLog.ERROR) # store _parameters self.init_grid_path = init_grid_path self.names_chronics_to_backend = names_chronics_to_backend # game _parameters self.parameters_path = parameters_path self.parameters = Parameters(parameters_path) # chronics of grid state self.path_chron = path_chron self.gridStateclass_kwargs = gridStateclass_kwargs self.chronics_handler = ChronicsHandler( chronicsClass=self.gridStateclass, path=self.path_chron, **self.gridStateclass_kwargs) # the backend, used to compute powerflows self.backend = self.backendClass() # build the environment self.env = None # miscellaneous self.verbose = verbose def _new_env(self, chronics_handler, backend, parameters): res = self.envClass( init_grid_path=self.init_grid_path, chronics_handler=chronics_handler, backend=backend, parameters=parameters, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=self.actionClass, observationClass=self.observationClass, rewardClass=self.rewardClass, legalActClass=self.legalActClass) if self._useclass: agent = self.agentClass(res.helper_action_player) else: agent = self.agent return res, agent def init_env(self): """ Function used to initialized the environment and the agent. It is called by :func:`Runner.reset`. Returns ------- ``None`` """ self.env, self.agent = self._new_env(self.chronics_handler, self.backend, self.parameters) def reset(self): """ Used to reset an environment. This method is called at the beginning of each new episode. If the environment is not initialized, then it initializes it with :func:`Runner.make_env`. Returns ------- ``None`` """ if self.env is None: self.init_env() else: self.env.reset() def run_one_episode(self, indx=0, path_save=None): """ Function used to run one episode of the :attr:`Runner.agent` and see how it performs in the :attr:`Runner.env`. Parameters ---------- indx: ``int`` The number of episode previously run path_save: ``str``, optional Path where to save the data. See the description of :mod:`grid2op.Runner` for the structure of the saved file. Returns ------- cum_reward: ``float`` The cumulative reward obtained by the agent during this episode time_step: ``int`` The number of timesteps that have been played before the end of the episode (because of a "game over" or because there were no more data) """ self.reset() return self._run_one_episode(self.env, self.agent, self.logger, indx, path_save) @staticmethod def _run_one_episode(env, agent, logger, indx, path_save=None): done = False time_step = int(0) dict_ = {} time_act = 0. cum_reward = 0. # reset the environment env.chronics_handler.tell_id(indx - 1) # the "-1" above is because the environment will be reset. So it will increase id of 1. obs = env.reset() if path_save is not None: path_save = os.path.abspath(path_save) if not os.path.exists(path_save): os.mkdir(path_save) logger.info("Creating path \"{}\" to save the runner".format( path_save)) if not os.path.exists( os.path.join(path_save, "dict_action_space.json")): dict_action_space = env.action_space.to_dict() with open(os.path.join(path_save, "dict_action_space.json"), "w", encoding='utf8') as f: json.dump(obj=dict_action_space, fp=f, indent=4, sort_keys=True) if not os.path.exists( os.path.join(path_save, "dict_observation_space.json")): dict_observation_space = env.observation_space.to_dict() with open(os.path.join(path_save, "dict_observation_space.json"), "w", encoding='utf8') as f: json.dump(obj=dict_observation_space, fp=f, indent=4, sort_keys=True) if not os.path.exists( os.path.join(path_save, "dict_env_modification_space.json")): dict_action_space = env.helper_action_env.to_dict() with open(os.path.join(path_save, "dict_env_modification_space.json"), "w", encoding='utf8') as f: json.dump(obj=dict_action_space, fp=f, indent=4, sort_keys=True) this_path = os.path.join( path_save, "{}".format(os.path.split(env.chronics_handler.get_id())[-1])) if not os.path.exists(this_path): os.mkdir(this_path) logger.info( "Creating path \"{}\" to save the episode {}".format( this_path, indx)) else: this_path = None if path_save is not None: dict_ = {} dict_["chronics_path"] = "{}".format(env.chronics_handler.get_id()) dict_["chronics_max_timestep"] = "{}".format( env.chronics_handler.max_timestep()) dict_["grid_path"] = "{}".format(env.init_grid_path) dict_["backend_type"] = "{}".format(type(env.backend).__name__) dict_["env_type"] = "{}".format(type(env).__name__) with open(os.path.join(this_path, "_parameters.json"), "w") as f: dict_params = env.parameters.to_dict() json.dump(obj=dict_params, fp=f, indent=4, sort_keys=True) # compute the size and everything if it needs to be stored nb_timestep_max = env.chronics_handler.max_timestep() efficient_storing = nb_timestep_max > 0 nb_timestep_max = max(nb_timestep_max, 0) if path_save is None: # i don't store anything on drive, so i don't need to store anything on memory nb_timestep_max = 0 times = np.full(nb_timestep_max, fill_value=np.NaN, dtype=np.float) rewards = np.full(nb_timestep_max, fill_value=np.NaN, dtype=np.float) actions = np.full((nb_timestep_max, env.action_space.n), fill_value=np.NaN, dtype=np.float) env_actions = np.full((nb_timestep_max, env.helper_action_env.n), fill_value=np.NaN, dtype=np.float) observations = np.full((nb_timestep_max + 1, env.observation_space.n), fill_value=np.NaN, dtype=np.float) disc_lines = np.full((nb_timestep_max, env.backend.n_line), fill_value=np.NaN, dtype=np.bool) disc_lines_templ = np.full((1, env.backend.n_line), fill_value=False, dtype=np.bool) beg_ = time.time() reward = env.reward_range[0] done = False if path_save is not None: # store observation at timestep 0 if efficient_storing: observations[time_step, :] = obs.to_vect() else: observations = np.concatenate((observations, obs.to_vect())) while not done: beg__ = time.time() act = agent.act(obs, reward, done) end__ = time.time() time_act += end__ - beg__ obs, reward, done, info = env.step( act) # should load the first time stamp cum_reward += reward time_step += 1 # save the results if path_save is not None: env_act = env.env_modification if efficient_storing: # efficient way of writing times[time_step - 1] = end__ - beg__ rewards[time_step - 1] = reward actions[time_step - 1, :] = act.to_vect() env_actions[time_step - 1, :] = env_act.to_vect() observations[time_step, :] = obs.to_vect() if "disc_lines" in info: arr = info["disc_lines"] if arr is not None: disc_lines[time_step - 1, :] = arr else: disc_lines[time_step - 1, :] = disc_lines_templ else: # completely inefficient way of writing times = np.concatenate((times, (end__ - beg__, ))) rewards = np.concatenate((rewards, (reward, ))) actions = np.concatenate((actions, act.to_vect())) env_actions = np.concatenate((actions, env_act.to_vect())) observations = np.concatenate( (observations, obs.to_vect())) if "disc_lines" in info: arr = info["disc_lines"] if arr is not None: disc_lines = np.concatenate((disc_lines, arr)) else: disc_lines = np.concatenate( (disc_lines, disc_lines_templ)) if path_save is not None: dict_["nb_timestep_played"] = time_step dict_["cumulative_reward"] = cum_reward end_ = time.time() if path_save is not None: with open(os.path.join(this_path, "episode_meta.json"), "w") as f: json.dump(obj=dict_, fp=f, indent=4, sort_keys=True) np.save(os.path.join(this_path, "agent_exec_times.npy"), times) np.save(os.path.join(this_path, "actions.npy"), actions) np.save(os.path.join(this_path, "env_modifications.npy"), env_actions) np.save(os.path.join(this_path, "observations.npy"), observations) np.save( os.path.join(this_path, "disc_lines_cascading_failure.npy"), disc_lines) np.save(os.path.join(this_path, "rewards.npy"), rewards) li_text = [ "Env: {:.2f}s", "\t - apply act {:.2f}s", "\t - run pf: {:.2f}s", "\t - env update + observation: {:.2f}s", "Agent: {:.2f}s", "Total time: {:.2f}s", "Cumulative reward: {:1f}" ] msg_ = "\n".join(li_text) logger.info( msg_.format( env._time_apply_act + env._time_powerflow + env._time_extract_obs, env._time_apply_act, env._time_powerflow, env._time_extract_obs, time_act, end_ - beg_, cum_reward)) if path_save is not None: with open(os.path.join(this_path, "episode_times.json"), "w") as f: dict_ = {} dict_["Env"] = {} dict_["Env"]["total"] = float(env._time_apply_act + env._time_powerflow + env._time_extract_obs) dict_["Env"]["apply_act"] = float(env._time_apply_act) dict_["Env"]["powerflow_computation"] = float( env._time_powerflow) dict_["Env"]["observation_computation"] = float( env._time_extract_obs) dict_["Agent"] = {} dict_["Agent"]["total"] = float(time_act) dict_["total"] = float(end_ - beg_) json.dump(obj=dict_, fp=f, indent=4, sort_keys=True) return cum_reward, int(time_step) def run_sequential(self, nb_episode, path_save=None): """ This method is called to see how well an agent performed on a sequence of episode. Parameters ---------- nb_episode: ``int`` Number of episode to play. path_save: ``str``, optional If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for more information Returns ------- res: ``list`` List of tuple. Each tuple having 3 elements: - "i" unique identifier of the episode - "cum_reward" the cumulative reward obtained by the :attr:`Runner.Agent` on this episode i - "nb_time_step": the number of time steps played in this episode. - "max_ts" : the maximum number of time steps of the chronics """ res = [(None, None, None) for _ in range(nb_episode)] for i in range(nb_episode): cum_reward, nb_time_step = self.run_one_episode( path_save=path_save, indx=i) id_chron = self.chronics_handler.get_id() max_ts = self.chronics_handler.max_timestep() res[i] = (id_chron, cum_reward, nb_time_step, max_ts) return res @staticmethod def _one_process_parrallel(runner, episode_this_process, process_id, path_save=None): chronics_handler = ChronicsHandler(chronicsClass=runner.gridStateclass, path=runner.path_chron, **runner.gridStateclass_kwargs) parameters = copy.deepcopy(runner.parameters) backend = runner.backendClass() nb_episode_this_process = len(episode_this_process) res = [(None, None, None) for _ in range(nb_episode_this_process)] for i, p_id in enumerate(episode_this_process): env, agent = runner._new_env(chronics_handler=chronics_handler, backend=backend, parameters=parameters) cum_reward, nb_time_step = Runner._run_one_episode( env, agent, runner.logger, p_id, path_save) id_chron = chronics_handler.get_id() max_ts = chronics_handler.max_timestep() res[i] = (id_chron, cum_reward, nb_time_step, max_ts) return res def run_parrallel(self, nb_episode, nb_process=1, path_save=None): """ This method will run in parrallel, independantly the nb_episode over nb_process. Note that it restarts completely the :attr:`Runner.backend` and :attr:`Runner.env` if the computation is actually performed with more than 1 cores (nb_process > 1) It uses the python multiprocess, and especially the :class:`multiprocess.Pool` to perform the computations. This implies that all runs are completely independant (they happen in different process) and that the memory consumption can be big. Tests may be recommended if the amount of RAM is low. It has the same return type as the :func:`Runner.run_sequential`. Parameters ---------- nb_episode: ``int`` Number of episode to simulate nb_process: ``int``, optional Number of process used to play the nb_episode. Default to 1. path_save: ``str``, optional If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for more information Returns ------- res: ``list`` List of tuple. Each tuple having 3 elements: - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the returned list are not necessarily sorted by this value) - "cum_reward" the cumulative reward obtained by the :attr:`Runner.Agent` on this episode i - "nb_time_step": the number of time steps played in this episode. - "max_ts" : the maximum number of time steps of the chronics """ if nb_process <= 0: raise RuntimeError( "Runner: you need at least 1 process to run episodes") if nb_process == 1: warnings.warn( "Runner.run_parrallel: number of process set to 1. Failing back into sequential mod." ) return [self.run_sequential(nb_episode, path_save=path_save)] else: if self.env is not None: self.env.close() self.env = None self.backend = self.backendClass() nb_process = int(nb_process) process_ids = [[] for i in range(nb_process)] for i in range(nb_episode): process_ids[i % nb_process].append(i) res = [] with Pool(nb_process) as p: tmp = p.starmap(Runner._one_process_parrallel, [(self, pn, i, path_save) for i, pn in enumerate(process_ids)]) for el in tmp: res += el return res def run(self, nb_episode, nb_process=1, path_save=None): """ Main method of the :class:`Runner` class. It will either call :func:`Runner.run_sequential` if "nb_process" is 1 or :func:`Runner.run_parrallel` if nb_process >= 2. Parameters ---------- nb_episode: ``int`` Number of episode to simulate nb_process: ``int``, optional Number of process used to play the nb_episode. Default to 1. path_save: ``str``, optional If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for more information Returns ------- res: ``list`` List of tuple. Each tuple having 3 elements: - "i" unique identifier of the episode (compared to :func:`Runner.run_sequential`, the elements of the returned list are not necessarily sorted by this value) - "cum_reward" the cumulative reward obtained by the :attr:`Runner.Agent` on this episode i - "nb_time_step": the number of time steps played in this episode. """ if nb_episode < 0: raise RuntimeError( "Impossible to run a negative number of scenarios.") if nb_episode == 0: res = [] else: if nb_process <= 0: raise RuntimeError( "Impossible to run using less than 1 process.") if nb_process == 1: self.logger.info("Sequential runner used.") res = self.run_sequential(nb_episode, path_save=path_save) else: self.logger.info("Parrallel runner used.") res = self.run_parrallel(nb_episode, nb_process=nb_process, path_save=path_save) return res