Esempio n. 1
0
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)
Esempio n. 2
0
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))