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']
class TestLoadingBackendPandaPower(unittest.TestCase): def get_backend(self): return PandaPowerBackend() def setUp(self): # powergrid self.backend = self.get_backend() 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() 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, name="test_env_env1") def tearDown(self): self.env.close() def compare_vect(self, pred, true): return dt_float(np.max(np.abs(pred - true))) <= self.tolvect 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 test_assign_action_space(self): """test that i cannot change the action_space""" with self.assertRaises(EnvError): self.env.action_space = self.env._action_space def test_assign_obs_space(self): """test that i cannot change the observation_space""" with self.assertRaises(EnvError): self.env.observation_space = self.env._observation_space 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.action_space({}) 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.action_space({}) 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.action_space({}) 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() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = Environment( init_grid_path=os.path.join(self.path_matpower, self.case_file), backend=self.get_backend(), 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(5719.9336) assert dt_float( np.abs(cum_reward - expected_reward)) <= self.tol_one, "Wrong reward"
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 TestObservationHazard(unittest.TestCase): 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") def tearDown(self) -> None: self.env.close() def test_1_generating_obs_withhazard(self): # test that helper_obs is abl to generate a valid observation obs = self.env.get_obs() assert np.all( obs.time_before_cooldown_line == [0, 0, 0, 0, 0, 0, 0, 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_before_cooldown_line == [0, 0, 0, 0, 12, 0, 0, 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_before_cooldown_line == [0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
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
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 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']