def unpack_data(path, file_name, fmt='on', types=None, models=None, dirs=None): if not os.path.exists(path): os.mkdir(path) hdf = pd.HDFStore(file_name, 'r') if not types: types = format_key()['types'] types = [t for t in types if t in restore_column()] if not models: models = {} for k in hdf.keys(): if k.startswith('/model_'): models[k[7:]] = hdf[k] if not dirs: dirs = [idx[0] for idx in hdf['/bus'].index] if '/bus' in hdf.keys() else [] indices = index_dict() for d in dirs: power = Power(fmt) for t in types: power.data[t] = hdf.get(t).loc[d] if t in models: columns = models[t].columns.difference(power.data[t].columns) power.data[t] = power.data[t].join(models[t][columns]) for i, n in enumerate(power.data[t].index.names): power.data[t][n] = power.data[t].index.get_level_values(i) if t in indices: power.data[t].set_index(indices[t], drop=False, inplace=True) power.data[t].sort_index(inplace=True) power.data['bus']['vl'] = get_vl_from_vbase(power.data['bus']['vbase']) power.generate_mdc_version_outline() power.save_power(os.path.join(path, d), fmt=fmt)
def load_actions(power, base_path, out_path, files, fmt='on', st=True, wmlf=True): """ 从文件加载并执行修改动作 :param power: Power. Power实例。 :param base_path: str. 基础数据目录。 :param out_path: str. 输出目录,输入的动作文件也在这里。 :param files: [str]. 文件列表。 :param fmt: str. 数据格式类型。 :param st: bool. 是否输出ST*文件。 :param wmlf: bool. 是否计算潮流。 :return dict. 每个文件对应目录的输出是否成功(潮流是否收敛) """ ret = {} if not power: power = Power(fmt=fmt) power.load_power(base_path, fmt=fmt, lp=False, st=st) power.set_index(idx='name') for f in files: actions = pd.read_csv(os.path.join(out_path, f), encoding='gbk', index_col=False) for _, etype, idx, dtype, value in actions.itertuples(): set_values(power.data, etype, dtype, {idx: value}) if '.' in f: name = f[:f.index('.')] path = os.path.join(out_path, name) power.save_power(path, fmt=power.fmt, lp=False, st=st) shutil.copy(os.path.join(base_path, 'LF.L0'), path) if st: shutil.copy(os.path.join(base_path, 'ST.S0'), path) if wmlf: call_wmlf(path) ret[name] = check_lfcal(path) else: ret[name] = True return ret
def test_power(real_data=False): # set_format_version({'mdc': 2.3}) path = 'dataset/wepri36' fmt = 'off' power = Power(fmt) power.load_power(path, fmt=fmt) if real_data: path = os.path.join(os.path.expanduser('~'), 'data', 'gd', '2019_12_23T12_15_00') fmt = 'on' power = Power(fmt) power.load_power(path, fmt=fmt) power.save_power(path + '/out', fmt=fmt) path = 'C:/PSASP_Pro/2020¹úµ÷Äê¶È/¶¬µÍ731' fmt = 'off' power = Power(fmt) power.load_power(path, fmt=fmt, lp=False) power.save_power(path + '/out', fmt=fmt, lp=False)
def random_generate(base_path, fmt, size, out_path, min_p=None, max_p=None, gl_ratio=0.9, random_q0=True, random_open=False, open_prob=[0.8]): power = Power(fmt=fmt) power.load_power(base_path, fmt=fmt) generators_bak = power.data['generator'].copy() loads_bak = power.data['load'].copy() if random_open: aclines_bak = power.data['acline'].copy() min_p = np.sum(generators_bak['pmin']) if not min_p else min_p max_p = np.sum(generators_bak['pmax']) if not max_p else max_p p0 = np.sum(generators_bak['p0']) shutil.rmtree(out_path, ignore_errors=True) os.mkdir(out_path) conv_count = 0 for i in range(size): generators = power.data['generator'] = generators_bak.copy() loads = power.data['load'] = loads_bak.copy() if random_open: power.data['acline'] = aclines_bak.copy() p = min_p + (max_p - min_p) * np.random.rand() distribute_generators_p(generators, p - p0, sigma=0.2) gen_p = np.sum(generators['p0']) load_p = np.sum(loads['p0']) distribute_loads_p(loads, gl_ratio * gen_p - load_p, p_sigma=0.2, keep_factor=False) if random_q0: random_load_q0(loads, sigma=None) if random_open: open_num = np.sum(np.random.rand(1) > open_prob) random_open_acline(power, num=open_num) path = os.path.join(out_path, '%08d' % i) power.save_power(path, fmt, lf=True, lp=False, st=True) shutil.copy(os.path.join(base_path, 'LF.L0'), path) shutil.copy(os.path.join(base_path, 'ST.S0'), path) call_wmlf(path) if check_lfcal(path): conv_count += 1 print('Random generate done: %d / %d' % (conv_count, size))
class OpEnv(object): def __init__(self, base_path, work_path, inputs, fmt='on'): """ 初始化。 :param base_path: str. 初始断面存放目录。 :param work_path: str. Env工作目录。 :param inputs: {etype: [str]}. 强化学习模型的输入量。 :param fmt: str. 数据格式类型。 """ self.power = Power(fmt) self.fmt = fmt self.base_path = base_path self.work_path = work_path self.episode = 0 self.step = 0 self.assessments = [] self.state0 = None self.min_max = None self.init_load_p = 0. self.inputs = inputs def get_ep_path(self, ep=None, step=None): """ 获取指定episode和step的工作目录。 :param ep: int. 指定episode; or None. 使用当前episode。 :param step: int. 指定step; or None. 返回episode对应目录。 :return: str. """ if ep is None: ep = self.episode if step is None: return os.path.join(self.work_path, 'ep%06d' % ep) return os.path.join(self.work_path, 'ep%06d' % ep, str(step)) def reset(self, random=True, load_path=None): """ 重置潮流,并进行评估。 :param random: bool. 是否随机初始化潮流。 :param load_path: str. 初始断面目录; or None. 用self.base_path作为初始断面。 :return: bool. 是否重置成果(not done) """ if load_path is None: self.power.load_power(self.base_path, fmt=self.fmt) else: self.power.load_power(load_path, fmt=self.fmt) self.power.data['generator']['p0'] = self.power.data['generator']['p'] self.episode += 1 path = self.get_ep_path() if os.path.exists(path): shutil.rmtree(path) os.mkdir(path) self.step = -1 self.assessments = [] if random: generators = self.power.data['generator'] loads = self.power.data['load'] max_p, gen_p = np.sum(generators[['pmax', 'p']]) p = max_p * 0.4 + max_p * 0.5 * np.random.rand() # 40% ~ 90% distribute_generators_p(generators, p - gen_p, sigma=0.2) generators['p0'] = np.clip(generators['p0'], generators['pmin'], generators['pmax']) gen_p = np.sum(generators['p0']) load_p = np.sum(loads['p']) distribute_loads_p(loads, 0.9 * gen_p - load_p, p_sigma=0.1, keep_factor=False) random_load_q0(loads, sigma=None) self.min_max = None self.init_load_p = 0. self.state0, _, done = self.run_step() return not done def get_state(self, normalize=True): """ 获取当前输入量数值 :param normalize: bool. True返回归一化数据;False返回原始数据。 :return: np.array. """ state = [] for etype, columns in self.inputs.items(): state.append(self.power.data[etype][columns].values.T.reshape(-1)) state = np.concatenate(state) if normalize: state = (state - self.min_max[:, 0]) \ / (self.min_max[:, 1] - self.min_max[:, 0] + EPS) return state def load_init_info(self): """ 获取初始状态,包括输入量的上下限和总负荷功率。 """ values = [] for etype, columns in self.inputs.items(): for col in columns: if col == 'p0': values.append(self.power.data[etype][['pmin', 'pmax']].values) elif col == 'q0': values.append(self.power.data[etype][['qmin', 'qmax']].values) else: continue self.min_max = np.concatenate(values) loads = self.power.data['load'] self.init_load_p = np.sum(loads.loc[loads['mark'] == 1, 'p0']) @staticmethod def make_assessment(path): """ 对断面稳定结果进行打分。 :param path: str. 指定断面目录,包含*.res结果文件。 :return: (float, bool, np.array). 评分、结束标志、稳定结果。 """ headers = {'CCTOUT': 'no desc name cct gen1 gen2 times tmp1 tmp2'} update_table_header(path, 'res', headers) iwant = {'CCTOUT': ['name', 'cct']} results = [] for file_name in os.listdir(path): if file_name.endswith('.res'): cct = read_efile(os.path.join(path, file_name), iwant.keys(), iwant) results.append(cct['CCTOUT']['cct']) results = pd.concat(results) values = results.values.reshape(-1,) values = values[~np.isnan(values)] values = values[values > 0.] if len(values) == 0 or np.min(values) < 0.1: return ST_UNSTABLE_REWARD, True, results # thrs = [0.3, 0.5] thrs = [1.0] for thr in thrs: values_lt = values[values < thr] if len(values_lt) > 0: return np.average(values_lt), False, results return thrs[-1], False, results def run_step(self, actions=None): """ 按照给定actions运行一步。 :param actions: str. “random",随机初始化,仅用于测试; or dict. 动作集合,由load_action函数执行动作。 :return: (np.array, float, bool). 状态量、回报值、结束标志。 """ self.step += 1 path = self.get_ep_path(step=self.step) if actions == 'random': # just for test distribute_generators_p(self.power.data['generator'], 1., sigma=0.1) distribute_loads_p(self.power.data['load'], 1., p_sigma=0.1, keep_factor=True, factor_sigma=0.1) elif actions is not None: self.load_action(actions) self.power.save_power(path, self.fmt, lf=True, lp=False, st=True) shutil.copy(os.path.join(self.base_path, 'LF.L0'), path) shutil.copy(os.path.join(self.base_path, 'ST.S0'), path) call_wmlf(path) if check_lfcal(path): self.power.drop_data(self.fmt, 'lp') self.power.load_power(path, self.fmt, lf=False, lp=True, st=False) self.power.data['generator']['p0'] = self.power.data['generator']['p'] if self.step == 0: self.load_init_info() state = self.get_state() if os.name != 'nt': call_psa(path) assess, done, _ = self.make_assessment(path) else: assess = np.random.rand() done = (assess < 0.1) else: state = [] assess = PF_NOTCONV_REWARD done = True self.assessments.append(assess) if self.step == 0: reward = 0. else: reward = self.assessments[-1] - self.assessments[-2] if not done: reward *= CCT_CHANGE_RATIO loads = self.power.data['load'] load_p = np.sum(loads.loc[loads['mark'] == 1, 'p0']) if abs(load_p - self.init_load_p) / self.init_load_p >= LOAD_CHANGE_THR: reward += PF_LOADFULL_REWARD done = True else: pass # reward = assess return state, reward, done def load_action(self, actions): """ 加载潮流修改动作,但不计算潮流。 :param actions: dict. 动作字典:'load_ratio_p'~按比例调整负荷有功; 'generator_ratio_p'~按比例调整机组有功。 :return: np.array. 归一化的状态量。 """ for k in actions: if k == 'load_ratio_p': set_gl_p0(self.power.data['load'], self.power.data['load']['p0'] * actions[k], keep_factor=False, clip=False) elif k == 'generator_ratio_p': set_gl_p0(self.power.data['generator'], self.power.data['generator']['p0'] * actions[k], keep_factor=False, clip=True) return self.get_state() def print_info(self, state=True, assessment=True): """ 打印每步信息。 :param state: bool. 是否打印状态量。 :param assessment: bool. 是否打印评分值。 """ print('episode = %d, step = %d' % (self.episode, self.step)) if state: print('state =', self.get_state()) if assessment: print('assessment =', self.assessments)
class OpEnv(object): def __init__(self, base_path, work_path, inputs, fmt='on'): """ 初始化。 :param base_path: str. 初始断面存放目录。 :param work_path: str. Env工作目录。 :param inputs: {etype: [str]}. 强化学习模型的输入量。 :param fmt: str. 数据格式类型。 """ self.power = Power(fmt) self.fmt = fmt self.base_path = base_path self.work_path = work_path self.episode = 0 self.step = 0 self.assessments = [] self.state0 = None self.min_max = None self.init_load_p = 0. self.inputs = inputs def get_ep_path(self, ep=None, step=None): """ 获取指定episode和step的工作目录。 :param ep: int. 指定episode; or None. 使用当前episode。 :param step: int. 指定step; or None. 返回episode对应目录。 :return: str. """ if ep is None: ep = self.episode if step is None: return os.path.join(self.work_path, 'ep%06d' % ep) return os.path.join(self.work_path, 'ep%06d' % ep, str(step)) def reset(self, random=True, load_path=None, error='raise'): """ 重置潮流,并进行评估。 :param random: bool. 是否随机初始化潮流。 :param load_path: str. 初始断面目录; or None. 用self.base_path作为初始断面。 :param errpr: str. 初始化失败则raise exception. :return: bool. 是否重置成果(not done) """ load_path = self.base_path if load_path is None else load_path self.episode += 1 self.min_max = None self.init_load_p = 0. path = self.get_ep_path() shutil.rmtree(path, ignore_errors=True) os.mkdir(path) self.step = -1 self.assessments = [] self.power.load_power(load_path, fmt=self.fmt) if random: generators = self.power.data['generator'] loads = self.power.data['load'] generators['p0'] = generators['p'] # gl_rate = np.sum(generators['p']) / np.sum(loads['p']) max_p, p0 = np.sum(generators[['pmax', 'p0']]) p = max_p * (0.4 + 0.5 * np.random.rand()) # 40% ~ 90% distribute_generators_p(generators, p - p0, sigma=0.2) # dp = np.sum(generators['p0']) - p0 gen_p = np.sum(generators['p0']) load_p = np.sum(loads['p0']) distribute_loads_p(loads, 0.9 * gen_p - load_p, p_sigma=0.1, keep_factor=False) # distribute_loads_p(loads, dp / gl_rate, p_sigma=0.1, keep_factor=False) random_load_q0(loads, sigma=None) self.state0, _, done = self.run_step() if done and error == 'raise': raise ValueError return not done def get_state(self, normalize=True): """ 获取当前输入量数值 :param normalize: bool. True返回归一化数据;False返回原始数据。 :return: np.array. """ state = [] for etype, columns in self.inputs.items(): state.append(self.power.data[etype][columns].values.T.reshape(-1)) state = np.concatenate(state) if normalize: state = (state - self.min_max[:, 0]) \ / (self.min_max[:, 1] - self.min_max[:, 0] + EPS) return state def load_init_info(self): """ 获取初始状态,包括输入量的上下限和总负荷功率。 """ values = [] for etype, columns in self.inputs.items(): for col in columns: if col == 'p0': values.append(self.power.data[etype][['pmin', 'pmax']].values) elif col == 'q0': values.append(self.power.data[etype][['qmin', 'qmax']].values) else: continue self.min_max = np.concatenate(values) loads = self.power.data['load'] self.init_load_p = np.sum(loads.loc[loads['mark'] == 1, 'p0']) @staticmethod def make_assessment(path, method='min', **kwargs): """ 对断面稳定结果进行打分。 :param path: str. 指定断面目录,包含*.res结果文件。 :param method: str. 'min'取最小值;'avg'取平均值;'grade'取分档平均值。 :param kwargs: dict. 相关参数字典。 :return: (float, bool, np.array). 评分、结束标志、稳定结果。 """ headers = {'CCTOUT': 'no desc name cct gen1 gen2 times tmp1 tmp2'} update_table_header(path, 'res', headers) iwant = {'CCTOUT': ['name', 'cct']} results = [] for file_name in os.listdir(path): if file_name.endswith('.res'): cct = read_efile(os.path.join(path, file_name), iwant.keys(), iwant) if 'CCTOUT' in cct: results.append(cct['CCTOUT']['cct']) results = pd.concat(results) values = results.values.reshape(-1, ) values = values[~np.isnan(values)] values = values[values >= 0.] if len(values) == 0 or np.min(values) < 0.1: return ST_UNSTABLE_REWARD, True, results if method == 'min': min_n = kwargs.get('min_n', 1) values.sort() return np.average(values[:min_n]), False, results elif method == 'avg': return np.average(values), False, results elif method == 'grade': thrs = kwargs.get('grades', [0.3, 0.5]) for thr in thrs: values_lt = values[values < thr] if len(values_lt) > 0: return np.average(values_lt), False, results return thrs[-1], False, results else: raise NotImplementedError('Method =', method) def run_step(self, actions=None): """ 按照给定actions运行一步。 :param actions: str. “random",随机初始化,仅用于测试; or dict. 动作集合,由load_action函数执行动作。 :return: (np.array, float, bool). 状态量、回报值、结束标志。 """ self.step += 1 path = self.get_ep_path(step=self.step) if actions == 'random': # just for test distribute_generators_p(self.power.data['generator'], 1., sigma=0.1) distribute_loads_p(self.power.data['load'], 1., p_sigma=0.1, keep_factor=True, factor_sigma=0.1) elif actions is not None: self.load_action(actions) shutil.rmtree(path, ignore_errors=True) self.power.save_power(path, self.fmt, lf=True, lp=False, st=True) shutil.copy(os.path.join(self.base_path, 'LF.L0'), path) shutil.copy(os.path.join(self.base_path, 'ST.S0'), path) call_wmlf(path) if check_lfcal(path): self.power.drop_data(self.fmt, 'lp') self.power.load_power(path, self.fmt, lf=False, lp=True, st=False) self.power.data['generator']['p0'] = self.power.data['generator']['p'] if self.step == 0: self.load_init_info() state = self.get_state() if os.name != 'nt': call_psa(path) assess, done, _ = self.make_assessment(path, method='min', min_n=3) else: assess = np.random.rand() done = (assess < 0.1) else: state = [] assess = PF_NOTCONV_REWARD done = True self.assessments.append(assess) if self.step == 0: reward = 0. else: reward = self.assessments[-1] - self.assessments[-2] + STEP_REWARD # reward = assess if not done: # reward *= CCT_CHANGE_RATIO loads = self.power.data['load'] load_p = np.sum(loads.loc[loads['mark'] == 1, 'p0']) if abs(load_p - self.init_load_p) / self.init_load_p >= LOAD_CHANGE_THR: reward += PF_LOADFULL_REWARD done = True else: reward = assess return state, reward, done def load_action(self, actions): """ 加载潮流修改动作,但不计算潮流。 :param actions: dict. 动作字典:'load_ratio_p'~按比例调整负荷有功; 'generator_ratio_p'~按比例调整机组有功。 :return: np.array. 归一化的状态量。 """ for k in actions: if k == 'load_ratio_p': set_gl_p0(self.power.data['load'], self.power.data['load']['p0'] * actions[k], keep_factor=False, clip=False) elif k == 'generator_ratio_p': set_gl_p0(self.power.data['generator'], self.power.data['generator']['p0'] * actions[k], keep_factor=False, clip=True) return self.get_state() def print_info(self, state=True, assessment=True): """ 打印每步信息。 :param state: bool. 是否打印状态量。 :param assessment: bool. 是否打印评分值。 """ print('episode = %d, step = %d' % (self.episode, self.step)) if state: print('state =', self.get_state()) if assessment: print('assessment =', self.assessments) @staticmethod def random_generate(base_path, fmt, size, out_path, min_p=None, max_p=None, gl_ratio=0.9, random_q0=True, random_open=False, open_prob=[0.8]): power = Power(fmt=fmt) power.load_power(base_path, fmt=fmt) generators_bak = power.data['generator'].copy() loads_bak = power.data['load'].copy() if random_open: aclines_bak = power.data['acline'].copy() min_p = np.sum(generators_bak['pmin']) if not min_p else min_p max_p = np.sum(generators_bak['pmax']) if not max_p else max_p p0 = np.sum(generators_bak['p0']) shutil.rmtree(out_path, ignore_errors=True) os.mkdir(out_path) conv_count = 0 for i in range(size): generators = power.data['generator'] = generators_bak.copy() loads = power.data['load'] = loads_bak.copy() if random_open: power.data['acline'] = aclines_bak.copy() p = min_p + (max_p - min_p) * np.random.rand() distribute_generators_p(generators, p - p0, sigma=0.2) gen_p = np.sum(generators['p0']) load_p = np.sum(loads['p0']) distribute_loads_p(loads, gl_ratio * gen_p - load_p, p_sigma=0.2, keep_factor=False) if random_q0: random_load_q0(loads, sigma=None) if random_open: open_num = np.sum(np.random.rand(1) > open_prob) random_open_acline(power, num=open_num) path = os.path.join(out_path, '%08d' % i) power.save_power(path, fmt, lf=True, lp=False, st=True) shutil.copy(os.path.join(base_path, 'LF.L0'), path) shutil.copy(os.path.join(base_path, 'ST.S0'), path) call_wmlf(path) if check_lfcal(path): conv_count += 1 print('Random generate done: %d / %d' % (conv_count, size))