From aef9a87cf694e4a9794f30d0b96533287a9fe561 Mon Sep 17 00:00:00 2001 From: Vincent Renault Date: Wed, 19 Feb 2020 17:29:25 +0100 Subject: [PATCH] Initial working version --- .gitignore | 3 + grid2kpi/__init__.py | 0 .../episode_analytics/EpisodeAnalytics.py | 280 ++++++++++++++++++ grid2kpi/episode_analytics/EpisodeTrace.py | 209 +++++++++++++ grid2kpi/episode_analytics/__init__.py | 0 grid2kpi/episode_analytics/actions_model.py | 23 ++ .../episode_analytics/consumption_profiles.py | 85 ++++++ grid2kpi/episode_analytics/env_actions.py | 30 ++ grid2kpi/episode_analytics/maintenances.py | 14 + .../episode_analytics/observation_model.py | 82 +++++ requirements.txt | 16 + setup.cfg | 2 + setup.py | 26 ++ 13 files changed, 770 insertions(+) create mode 100644 grid2kpi/__init__.py create mode 100644 grid2kpi/episode_analytics/EpisodeAnalytics.py create mode 100644 grid2kpi/episode_analytics/EpisodeTrace.py create mode 100644 grid2kpi/episode_analytics/__init__.py create mode 100644 grid2kpi/episode_analytics/actions_model.py create mode 100644 grid2kpi/episode_analytics/consumption_profiles.py create mode 100644 grid2kpi/episode_analytics/env_actions.py create mode 100644 grid2kpi/episode_analytics/maintenances.py create mode 100644 grid2kpi/episode_analytics/observation_model.py create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index b6e4761..04e8791 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# IDEs +.vscode + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/grid2kpi/__init__.py b/grid2kpi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grid2kpi/episode_analytics/EpisodeAnalytics.py b/grid2kpi/episode_analytics/EpisodeAnalytics.py new file mode 100644 index 0000000..75b2505 --- /dev/null +++ b/grid2kpi/episode_analytics/EpisodeAnalytics.py @@ -0,0 +1,280 @@ +import datetime as dt +import time + +from .env_actions import env_actions +from grid2op.EpisodeData import EpisodeData +import numpy as np +import pandas as pd +from tqdm import tqdm + +from . import EpisodeTrace, maintenances, consumption_profiles + + +class EpisodeAnalytics: + def __init__(self, episode_data, episode_name, agent): + self.episode_name = episode_name + self.agent = agent + + # Add EpisodeData attributes to EpisodeAnalytics + for attribute in [elem for elem in dir(episode_data) if + not (elem.startswith("__") or callable(getattr(episode_data, elem)))]: + setattr(self, attribute, getattr(episode_data, attribute)) + + self.timesteps = list(range(len(self.actions))) + print("computing df") + beg = time.time() + print("Environment") + self.load, self.production, self.rho, self.action_data_table, self.computed_reward, self.flow_and_voltage_line = self._make_df_from_data() + print("Hazards-Maintenances") + self.hazards, self.maintenances = self._env_actions_as_df() + print("Big TS") + self.total_overflow_trace = EpisodeTrace.get_total_overflow_trace(self) + self.usage_rate_trace = EpisodeTrace.get_usage_rate_trace(self) + self.reward_trace = EpisodeTrace.get_df_rewards_trace(self) + self.total_overflow_ts = EpisodeTrace.get_total_overflow_ts(self) + self.profile_traces = consumption_profiles.profiles_traces(self) + self.total_maintenance_duration = maintenances.total_duration_maintenance(self) + self.nb_hazards = env_actions(self, which="hazards", kind="nb", aggr=True) + self.nb_maintenances = env_actions(self, which="maintenances", kind="nb", aggr=True) + + end = time.time() + print(f"end computing df: {end - beg}") + + @staticmethod + def timestamp(obs): + return dt.datetime(obs.year[0], obs.month[0], obs.day[0], obs.hour_of_day[0], + obs.minute_of_hour[0]) + + # @jit(forceobj=True) + def _make_df_from_data(self): + """ + Convert all episode's data into comprehensible dataframes usable by + the application. + + The generated dataframes are: + - loads + - production + - rho + - action data table + - instant and cumulated rewards + - flow and voltage by line + + Returns + ------- + res: :class:`tuple` + generated dataframes + """ + size = len(self.actions) + timesteps = list(range(size)) + load_size = size * len(self.observations[0].load_p) + prod_size = size * len(self.observations[0].prod_p) + n_rho = len(self.observations[0].rho) + rho_size = size * n_rho + + load_data = pd.DataFrame(index=range(load_size), + columns=["timestamp", "value"]) + load_data.loc[:, "value"] = load_data.loc[:, "value"].astype(float) + + production = pd.DataFrame(index=range(prod_size), + columns=["value"]) + + rho = pd.DataFrame(index=range(rho_size), columns=['value']) + + cols_loop_action_data_table = [ + 'action_line', 'action_subs', 'line_action', 'sub_name', + 'objects_changed', 'distance' + ] + action_data_table = pd.DataFrame( + index=range(size), + columns=[ + 'timestep', 'timestamp', 'timestep_reward', 'action_line', + 'action_subs', 'line_action', 'sub_name', 'objects_changed', + 'distance' + ] + ) + + computed_rewards = pd.DataFrame(index=range(size), + columns=['timestep', 'rewards', 'cum_rewards']) + flow_voltage_cols = pd.MultiIndex.from_product( + [['or', 'ex'], ['active', 'reactive', 'current', 'voltage'], self.line_names]) + flow_voltage_line_table = pd.DataFrame(index=range(size), columns=flow_voltage_cols) + + topo_list = [] + bus_list = [] + for (time_step, (obs, act)) in tqdm(enumerate(zip(self.observations[:-1], self.actions)), + total=size): + time_stamp = self.timestamp(obs) + line_impact, sub_impact = act.get_topological_impact() + sub_action = act.name_sub[sub_impact] + line_action = self.line_names[line_impact] + + if not len(sub_action): + sub_action = None + else: + sub_action = " - ".join(sub_action) + if not len(line_action): + line_action = None + else: + line_action = " - ".join(line_action) + + # Building load DF + begin = time_step * self.n_loads + end = (time_step + 1) * self.n_loads - 1 + load_data.loc[begin:end, "value"] = obs.load_p.astype(float) + load_data.loc[begin:end, "timestamp"] = time_stamp + # Building prod DF + begin = time_step * self.n_prods + end = (time_step + 1) * self.n_prods - 1 + production.loc[begin:end, "value"] = obs.prod_p.astype(float) + # Building RHO DF + begin = time_step * n_rho + end = (time_step + 1) * n_rho - 1 + rho.loc[begin:end, "value"] = obs.rho.astype(float) + + pos = time_step + # TODO : change with benjamin's count of actions + action_line = np.sum(act._switch_line_status) + + # TODO: change with benjamin's count of actions + action_subs = int(np.any(act._change_bus_vect)) + + object_changed_set = self.get_object_changed( + act._set_topo_vect, topo_list) + if object_changed_set is not None: + object_changed = object_changed_set + else: + object_changed = self.get_object_changed( + act._change_bus_vect, bus_list) + + action_data_table.loc[pos, cols_loop_action_data_table] = [ + action_line, + action_subs, + line_action, + sub_action, + object_changed, + self.get_distance_from_obs(obs)] + + computed_rewards.loc[time_step, :] = [ + time_stamp, + self.rewards[time_step], + self.rewards.cumsum(axis=0)[time_step] + ] + + flow_voltage_line_table.loc[time_step, :] = np.array([ + obs.p_ex, + obs.q_ex, + obs.a_ex, + obs.v_ex, + obs.p_or, + obs.q_or, + obs.a_or, + obs.v_or + ]).flatten() + + load_data["timestep"] = np.repeat(timesteps, self.n_loads) + load_data["equipment_name"] = np.tile(self.load_names, size).astype(str) + load_data["equipement_id"] = np.tile(range(self.n_loads), size) + + self.timestamps = sorted(load_data.timestamp.dropna().unique()) + self.timesteps = sorted(load_data.timestep.unique()) + + production["timestep"] = np.repeat(timesteps, self.n_prods) + production["timestamp"] = np.repeat(self.timestamps, self.n_prods) + production.loc[:, "equipment_name"] = np.tile(self.prod_names, size) + production.loc[:, "equipement_id"] = np.tile(range(self.n_prods), size) + + rho["time"] = np.repeat(timesteps, n_rho) + rho["timestamp"] = np.repeat(self.timestamps, n_rho) + rho["equipment"] = np.tile(range(n_rho), size) + + action_data_table["timestep"] = self.timesteps + action_data_table["timestamp"] = self.timestamps + action_data_table["timestep_reward"] = self.rewards[:size] + + load_data["value"] = load_data["value"].astype(float) + production["value"] = production["value"].astype(float) + rho["value"] = rho["value"].astype(float) + return load_data, production, rho, action_data_table, computed_rewards, flow_voltage_line_table + + def get_object_changed(self, vect, list_topo): + if np.count_nonzero(vect) is 0: + return None + for idx, topo_array in enumerate(list_topo): + if not np.array_equal(topo_array, vect): + return idx + # if we havnt found the vect... + list_topo.append(vect) + return len(list_topo) - 1 + + def get_sub_action(self, act, obs): + for sub in range(len(obs.sub_info)): + effect = act.effect_on(substation_id=sub) + if np.any(effect["change_bus"] is True): + return self.name_sub[sub] + if np.any(effect["set_bus"] is 1) or np.any(effect["set_bus"] is -1): + return self.name_sub[sub] + return None + + def get_distance_from_obs(self, obs): + return len(obs.topo_vect) - np.count_nonzero(obs.topo_vect == 1) + + # @jit(forceobj=True) + def _env_actions_as_df(self): + hazards_size = (len(self.observations) - 1) * self.n_lines + cols = ["timestep", "timestamp", "line_id", "line_name", "value"] + hazards = pd.DataFrame(index=range(hazards_size), + columns=["value"], dtype=int) + maintenances = hazards.copy() + + for (time_step, env_act) in tqdm(enumerate(self.env_actions), total=len(self.env_actions)): + if env_act is None: + continue + + time_stamp = self.timestamp(self.observations[time_step]) + + begin = time_step * self.n_lines + end = (time_step + 1) * self.n_lines - 1 + hazards.loc[begin:end, "value"] = env_act._hazards.astype(int) + + begin = time_step * self.n_lines + end = (time_step + 1) * self.n_lines - 1 + maintenances.loc[begin:end, "value"] = env_act._maintenance.astype(int) + + # iter_haz_maint = zip(env_act._hazards, env_act._maintenance) + # for line_id, (haz, maint) in enumerate(iter_haz_maint): + # pos = time_step * self.n_lines + line_id + # hazards.loc[pos, :] = [ + # time_step, time_stamp, line_id, self.line_names[line_id], + # int(haz) + # ] + # maintenances.loc[pos, :] = [ + # time_step, time_stamp, line_id, self.line_names[line_id], + # int(maint) + # # ] + # hazards["value"] = hazards["value"].fillna(0).astype(int) + # maintenances["value"] = maintenances["value"].fillna(0).astype(int) + + hazards["timestep"] = np.repeat(self.timesteps, self.n_lines) + maintenances["timestep"] = hazards["timestep"] + hazards["timestamp"] = np.repeat(self.timestamps, self.n_lines) + maintenances["timestamp"] = hazards["timestamp"] + hazards["line_name"] = np.tile(self.line_names, len(self.timesteps)) + maintenances["line_name"] = hazards["line_name"] + hazards["line_id"] = np.tile(range(self.n_lines), len(self.timesteps)) + maintenances["line_id"] = hazards["line_id"] + + return hazards, maintenances + + +class Test(): + def __init__(self): + self.foo = 2 + self.bar = 3 + + +if __name__ == "__main__": + test = Test() + path_agent = "nodisc_badagent" + episode = EpisodeData.from_disk( + "D:/Projects/RTE - Grid2Viz/20200127_data_scripts/20200127_agents_log/" + path_agent, "3_with_hazards") + print(dir(EpisodeAnalytics(episode))) diff --git a/grid2kpi/episode_analytics/EpisodeTrace.py b/grid2kpi/episode_analytics/EpisodeTrace.py new file mode 100644 index 0000000..3839b65 --- /dev/null +++ b/grid2kpi/episode_analytics/EpisodeTrace.py @@ -0,0 +1,209 @@ +from . import observation_model +from .env_actions import env_actions +import plotly.graph_objects as go +import pandas as pd +import numpy as np + + +def get_total_overflow_trace(episode): + df = get_total_overflow_ts(episode) + return [go.Scatter( + x=df["time"], + y=df["value"], + name="Nb of overflows" + )] + + +def get_total_overflow_ts(episode): + # TODO: This :-1 probably has to change + df = pd.DataFrame(index=range(len(episode.observations[:-1])), + columns=["time", "value"]) + for (time_step, obs) in enumerate(episode.observations[:-1]): + tstamp = episode.timestamps[time_step] + df.loc[time_step, :] = [tstamp, (obs.timestep_overflow > 0).sum()] + return df + + +def get_prod_share_trace(episode, prod_types): + prod_type_values = list(prod_types.values()) if len( + prod_types.values()) > 0 else [] + + share_prod = observation_model.get_prod(episode) + df = share_prod.groupby("equipment_name")["value"].sum() + unique_prod_types = np.unique(prod_type_values) + + labels = [*df.index.values, *np.unique(prod_type_values)] + + parents = [prod_types.get(name) for name in df.index.values] + values = list(df) + + for prod_type in unique_prod_types: + parents.append("") + value = 0 + for gen in df.index.values: + if prod_types.get(gen) == prod_type: + value = value + df.get(gen) + values.append(value) + + return [go.Sunburst(labels=labels, values=values, + parents=parents, branchvalues="total")] + + +def get_hazard_trace(episode, equipments=None): + ts_hazards_by_line = env_actions( + episode, which="hazards", kind="ts", aggr=False) + + if 'total' in equipments: + ts_hazards_by_line = ts_hazards_by_line.assign( + total=episode.hazards.groupby('timestamp', as_index=True)[ + 'value'].sum() + ) + + if equipments is not None: + ts_hazards_by_line = ts_hazards_by_line.loc[:, equipments] + + return [go.Scatter(x=ts_hazards_by_line.index, + y=ts_hazards_by_line[line], + name=line) + for line in ts_hazards_by_line.columns] + + +def get_maintenance_trace(episode, equipments=None): + ts_maintenances_by_line = env_actions( + episode, which="maintenances", kind="ts", aggr=False) + + if 'total' in equipments: + ts_maintenances_by_line = ts_maintenances_by_line.assign( + total=episode.maintenances.groupby( + 'timestamp', as_index=True)['value'].sum() + ) + + if equipments is not None: + ts_maintenances_by_line = ts_maintenances_by_line.loc[:, equipments] + + return [go.Scatter(x=ts_maintenances_by_line.index, + y=ts_maintenances_by_line[line], + name=line) + for line in ts_maintenances_by_line.columns] + + +def get_all_prod_trace(episode, prod_types, selection): + prod_with_type = observation_model.get_prod(episode).assign( + prod_type=[prod_types.get(equipment_name) + for equipment_name in observation_model.get_prod(episode)['equipment_name']] + ) + prod_type_names = prod_types.values() + trace = [] + if 'total' in selection: + trace.append( + go.Scatter( + x=prod_with_type['timestamp'].unique(), + y=prod_with_type.groupby('timestamp')['value'].sum(), + name='total' + ) + ) + for name in prod_type_names: + if name in selection: + trace.append(go.Scatter( + x=prod_with_type[prod_with_type.prod_type.values == + name]['timestamp'].unique(), + y=prod_with_type[prod_with_type.prod_type.values == name].groupby(['timestamp'])[ + 'value'].sum(), + name=name + )) + selection.remove( + name) # remove prod type from selection to avoid misunderstanding in get_def_trace_per_equipment() + + return [*trace, *get_df_trace_per_equipment(observation_model.get_prod(episode, selection))] + + +def get_load_trace_per_equipment(episode, equipements): + all_equipements = observation_model.get_load(episode) + load_equipments = observation_model.get_load(episode, equipements) + + if 'total' in equipements: + load_equipments = load_equipments.append(pd.DataFrame({ + 'equipement_id': ['nan' for i in all_equipements.groupby('timestep').size()], + 'equipment_name': ['total' for i in all_equipements.groupby('timestep').size()], + 'timestamp': [timestamp for timestamp in all_equipements['timestamp'].unique()], + 'timestep': [timestep for timestep in all_equipements['timestep'].unique()], + 'value': [value for value in all_equipements.groupby('timestep')['value'].sum()] + })) + + return get_df_trace_per_equipment(load_equipments) + + +def get_usage_rate_trace(episode): + df = observation_model.get_usage_rate(episode) + line = { + "shape": "spline", + "width": 0, + "smoothing": 1 + } + trace = [ + go.Scatter( + x=df["timestamp"], + y=df["value"]["quantile10"], + name="quantile 10", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["value"]["quantile25"], + name="quantile 25", + fill="tonexty", + fillcolor="rgba(159, 197, 232, 0.63)", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["value"]["median"], + name="median", + fill="tonexty", + fillcolor="rgba(31, 119, 180, 0.5)", + line={ + "color": "rgb(31, 119, 180)", + "shape": "spline", + "smoothing": 1 + } + ), go.Scatter( + x=df["timestamp"], + y=df["value"]["quantile75"], + name="quantile 75", + fill="tonexty", + fillcolor="rgba(31, 119, 180, 0.5)", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["value"]["quantile90"], + name="quantile 90", + fill="tonexty", + fillcolor="rgba(159, 197, 232, 0.63)", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["value"]["max"], + name="Max", + line={ + "shape": "spline", + "smoothing": 1, + "color": "rgba(255,0,0,0.5)" + } + )] + return trace + + +def get_df_trace_per_equipment(df): + return [go.Scatter( + x=df.loc[df["equipment_name"] == equipment, :]["timestamp"], + y=df.loc[df["equipment_name"] == equipment, :]["value"], + name=equipment + ) for equipment in df["equipment_name"].drop_duplicates()] + + +def get_df_rewards_trace(episode): + df = observation_model.get_df_computed_reward(episode) + return [ + go.Scatter(x=df["timestep"], y=df["rewards"], + name=episode.agent + "_reward"), + go.Scatter(x=df["timestep"], y=df["cum_rewards"], + name=episode.agent + "cum_rewards", yaxis='y2') + ] \ No newline at end of file diff --git a/grid2kpi/episode_analytics/__init__.py b/grid2kpi/episode_analytics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grid2kpi/episode_analytics/actions_model.py b/grid2kpi/episode_analytics/actions_model.py new file mode 100644 index 0000000..8cbc7a4 --- /dev/null +++ b/grid2kpi/episode_analytics/actions_model.py @@ -0,0 +1,23 @@ +import plotly.graph_objects as go + + +def get_action_per_line(new_episode): + data = get_action_table_data(new_episode) + count = data[(data["action_line"] > 0)]["line_action"].map(lambda x: " ".join(x)).value_counts() + return [go.Bar(x=new_episode.line_names, y=count.values)] + + +def get_action_table_data(new_episode): + return new_episode.action_data_table + + +def get_action_per_sub(new_episode): + data = get_action_table_data(new_episode) + count = data[(data["action_subs"] > 0)]["sub_name"].map(lambda x: " ".join(x)).value_counts() + return [go.Bar(x=new_episode.name_sub, y=count.values)] + + +def get_actions_sum(new_episode): + return new_episode.action_data_table.set_index("timestamp")[[ + 'action_line', 'action_subs' + ]].sum(axis=1).to_frame(name="Nb Actions") diff --git a/grid2kpi/episode_analytics/consumption_profiles.py b/grid2kpi/episode_analytics/consumption_profiles.py new file mode 100644 index 0000000..32a8fc9 --- /dev/null +++ b/grid2kpi/episode_analytics/consumption_profiles.py @@ -0,0 +1,85 @@ +import pandas as pd +import plotly.graph_objects as go + +from .observation_model import quantile10, quantile25, quantile75, quantile90 + + +def consumption_profiles(episode, freq="30T"): + + load = pd.pivot_table( + episode.load, index="timestamp", columns=["equipment_name"], values="value" + ).sum(axis=1).resample(freq).mean().to_frame(name="load").reset_index() + + if freq == "H": + load["timestamp"] = load.timestamp.dt.hour + elif "T" in freq: + load["timestamp"] = load.timestamp.dt.time.map( + lambda x: x.strftime("%H:%M") + ) + else: + raise ValueError("Only hourly (\"H\") or minutely (\"xT\") freqs are " + f"supported. Passed: {freq}") + + daily_load_distrib = load.groupby(["timestamp"]).aggregate( + ["median", quantile10, quantile25, quantile75, quantile90, "max"] + )[["load"]].reset_index() + + return daily_load_distrib + + +def profiles_traces(episode, freq="30T"): + episode_data = episode + df = consumption_profiles(episode_data, freq) + line = { + "shape": "spline", + "width": 0, + "smoothing": 1 + } + trace = [go.Scatter( + x=df["timestamp"], + y=df["load"]["quantile10"], + name="quantile 10", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["load"]["quantile25"], + name="quantile 25", + fill="tonexty", + fillcolor="rgba(159, 197, 232, 0.63)", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["load"]["median"], + name="median", + fill="tonexty", + fillcolor="rgba(31, 119, 180, 0.5)", + line={ + "color": "rgb(31, 119, 180)", + "shape": "spline", + "smoothing": 1 + } + ), go.Scatter( + x=df["timestamp"], + y=df["load"]["quantile75"], + name="quantile 75", + fill="tonexty", + fillcolor="rgba(31, 119, 180, 0.5)", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["load"]["quantile90"], + name="quantile 90", + fill="tonexty", + fillcolor="rgba(159, 197, 232, 0.63)", + line=line + ), go.Scatter( + x=df["timestamp"], + y=df["load"]["max"], + name="Max", + line={ + "shape": "spline", + "smoothing": 1, + "color": "rgba(255,0,0,0.5)" + } + )] + return trace diff --git a/grid2kpi/episode_analytics/env_actions.py b/grid2kpi/episode_analytics/env_actions.py new file mode 100644 index 0000000..61c4798 --- /dev/null +++ b/grid2kpi/episode_analytics/env_actions.py @@ -0,0 +1,30 @@ +import pandas as pd + + +def env_actions(episode, which="hazards", kind="ts", aggr=True): + if kind not in ("ts", "nb", "dur"): + raise ValueError("kind argument can only be either ts, nb or dur.\n" + f"{kind} passed") + if which not in ("hazards", "maintenances"): + raise ValueError("which argument can only be either hazards or " + f"maintenances. {which} passed") + env_acts = getattr(episode, which) + env_acts = pd.pivot_table( + env_acts, index="timestamp", columns=["line_name"], values="value" + ) + + if kind == "dur": + env_acts = env_acts.sum() + if kind == "nb": + equipments = env_acts.columns + for col in env_acts.columns: + env_acts["temp"] = env_acts[col].shift(1).fillna(0) + env_acts[col] = env_acts[[col, "temp"]].apply( + lambda row: 1 if row[0] == 1 and row[1] == 0 else 0, axis=1 + ) + env_acts = env_acts[equipments].sum() + if aggr: + env_acts = env_acts.sum() + if kind == "ts" and aggr: + env_acts = env_acts.sum(axis=1).to_frame(name=which) + return env_acts diff --git a/grid2kpi/episode_analytics/maintenances.py b/grid2kpi/episode_analytics/maintenances.py new file mode 100644 index 0000000..ac27319 --- /dev/null +++ b/grid2kpi/episode_analytics/maintenances.py @@ -0,0 +1,14 @@ +from .env_actions import env_actions + + +def total_duration_maintenance(episode): + timestep_duration = (episode.timestamps[1] - episode.timestamps[0]) + nb_maintenance = env_actions(episode, which="maintenances", kind="dur", aggr=False).sum() + return (timestep_duration * nb_maintenance).total_seconds() / 60.0 + + +def hist_duration_maintenances(episode): + # Suppose that there is at most one maintenance per line per episode + + return [t for t in episode.observations[0].duration_next_maintenance if t] + diff --git a/grid2kpi/episode_analytics/observation_model.py b/grid2kpi/episode_analytics/observation_model.py new file mode 100644 index 0000000..4cdd3ef --- /dev/null +++ b/grid2kpi/episode_analytics/observation_model.py @@ -0,0 +1,82 @@ +import numpy as np +import pandas as pd +import plotly.graph_objects as go +from .env_actions import env_actions + + +def get_prod_and_conso(episode): + episode_data = episode + productions = pd.pivot_table( + episode_data.production, index="timestamp", values="value", + columns=["equipment_name"] + ) + + loads = pd.pivot_table( + episode_data.load, index="timestamp", values="value", + columns=["equipment_name"] + ) + + prods_and_loads = productions.merge( + loads, left_index=True, right_index=True) + return prods_and_loads + + +def get_episode_active_consumption_ts(episode): + return [sum(obs.load_p) for obs in episode.observations] + + +def get_prod(episode, equipments=None): + if equipments is None: + return episode.production + else: + return episode.production.loc[episode.production.equipment_name.isin(equipments), :] + + +def get_load(episode, equipments=None): + if equipments is None: + return episode.load + else: + return episode.load.loc[episode.load.equipment_name.isin(equipments), :] + + +def get_rho(episode): + return episode.rho + + +def get_df_computed_reward(episode): + return episode.computed_reward + + +# quantiles utilities +def quantile10(df): + return df.quantile(0.1) + + +def quantile25(df): + return df.quantile(0.25) + + +def quantile75(df): + return df.quantile(0.75) + + +def quantile90(df): + return df.quantile(0.90) + + +def get_usage_rate(episode): + rho = get_rho(episode) + return rho.groupby("timestamp").aggregate([ + "median", quantile10, quantile25, quantile75, quantile90, "max" + ])[["value"]].reset_index() + + +def init_table_inspection_data(episode): + ts_hazards = env_actions(episode, which="hazards", kind="ts", aggr=True) + ts_maintenances = env_actions( + episode, which="maintenances", kind="ts", aggr=True) + table = ts_hazards.merge( + ts_maintenances, left_index=True, right_index=True) + table = table.reset_index() + table["IsWorkingDay"] = table["timestamp"].dt.weekday < 5 + return table diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ebfed24 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +decorator==4.4.1 +grid2kpi==0.1.0 +Grid2Op==0.5.5 +networkx==2.4 +numpy==1.18.1 +packaging==20.1 +pandapower==2.2.1 +pandas==1.0.1 +plotly==4.5.0 +pyparsing==2.4.6 +python-dateutil==2.8.1 +pytz==2019.3 +retrying==1.3.3 +scipy==1.4.1 +six==1.14.0 +tqdm==4.43.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..224a779 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..50fde48 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup + + +setup(name='Grid2Kpi', + version='0.1.0', + description='Toto', + long_description='Tata', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Natural Language :: English" + ], + # keywords='ML powergrid optmization RL power-systems', + # author='Benjamin DONNOT', + # author_email='benjamin.donnot@rte-france.com', + # url="https://github.com/rte-france/Grid2Op", + license='MPL', + packages=['grid2kpi'], + include_package_data=True, + install_requires=["numpy", "pandas", "pandapower", "Grid2Op"], + zip_safe=False)