Source code for powersimdata.scenario.analyze

import os
import pickle

import pandas as pd

from powersimdata.input.input_data import InputData
from powersimdata.input.transform_profile import TransformProfile
from powersimdata.output.output_data import OutputData, construct_load_shed
from powersimdata.scenario.ready import Ready
from powersimdata.utility import server_setup


[docs]class Analyze(Ready): """Scenario is in a state of being analyzed. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. """ name = "analyze" allowed = ["delete"] exported_methods = { "get_averaged_cong", "get_congl", "get_congu", "get_dcline_pf", "get_lmp", "get_load_shed", "get_load_shift_up", "get_load_shift_dn", "get_pf", "get_pg", "get_storage_e", "get_storage_pg", "print_infeasibilities", } | Ready.exported_methods def __init__(self, scenario): """Constructor.""" super().__init__(scenario) self.refresh(scenario) self._output_data = OutputData()
[docs] def refresh(self, scenario): print( "SCENARIO: %s | %s\n" % (self._scenario_info["plan"], self._scenario_info["name"]) ) print("--> State\n%s" % self.name) self._set_allowed_state() self._set_ct_and_grid()
def _set_allowed_state(self): """Sets allowed state.""" if self._scenario_status == "extracted": self.allowed.append("move") def _set_ct_and_grid(self): """Sets change table and grid.""" input_data = InputData() self.grid = input_data.get_data(self._scenario_info, "grid") if self._scenario_info["change_table"] == "Yes": self.ct = input_data.get_data(self._scenario_info, "ct") else: self.ct = {} def _parse_infeasibilities(self): """Parses infeasibilities. When the optimizer cannot find a solution in a time interval, the remedy is to decrease demand by some amount until a solution is found. The purpose of this function is to get the interval number(s) and the associated decrease(s). :return: (*dict*) -- keys are the interval number and the values are the decrease in percent (%) applied to the original demand profile. """ field = self._scenario_info["infeasibilities"] if field == "": return None else: infeasibilities = {} for entry in field.split("_"): item = entry.split(":") infeasibilities[int(item[0])] = int(item[1]) return infeasibilities
[docs] def print_infeasibilities(self): """Prints infeasibilities.""" infeasibilities = self._parse_infeasibilities() if infeasibilities is None: print("There are no infeasibilities.") else: dates = pd.date_range( start=self._scenario_info["start_date"], end=self._scenario_info["end_date"], freq=self._scenario_info["interval"], ) for key, value in infeasibilities.items(): print( "demand in %s - %s interval has been reduced by %d%%" % ( dates[key], dates[key] + pd.Timedelta(self._scenario_info["interval"]), value, ) )
def _get_data(self, field): return self._output_data.get_data(self._scenario_info["id"], field)
[docs] def get_pg(self): """Returns PG data frame. :return: (*pandas.DataFrame*) -- data frame of power generated. """ return self._get_data("PG")
[docs] def get_pf(self): """Returns PF data frame. :return: (*pandas.DataFrame*) -- data frame of power flow. """ return self._get_data("PF")
[docs] def get_dcline_pf(self): """Returns PF_DCLINE data frame. :return: (*pandas.DataFrame*) -- data frame of power flow on DC line(s). """ return self._get_data("PF_DCLINE")
[docs] def get_lmp(self): """Returns LMP data frame. LMP = locational marginal price :return: (*pandas.DataFrame*) -- data frame of nodal prices. """ return self._get_data("LMP")
[docs] def get_congu(self): """Returns CONGU data frame. CONGU = Congestion, Upper flow limit :return: (*pandas.DataFrame*) -- data frame of branch flow mu (upper). """ return self._get_data("CONGU")
[docs] def get_congl(self): """Returns CONGL data frame. CONGL = Congestion, Lower flow limit :return: (*pandas.DataFrame*) -- data frame of branch flow mu (lower). """ return self._get_data("CONGL")
[docs] def get_averaged_cong(self): """Returns averaged CONGL and CONGU. :return: (*pandas.DataFrame*) -- data frame of averaged congestion with the branch id as indices an the averaged CONGL and CONGU as columns. """ return self._get_data("AVERAGED_CONG")
[docs] def get_storage_pg(self): """Returns STORAGE_PG data frame. :return: (*pandas.DataFrame*) -- data frame of power generated by storage units. """ return self._get_data("STORAGE_PG")
[docs] def get_storage_e(self): """Returns STORAGE_E data frame. Energy state of charge. :return: (*pandas.DataFrame*) -- data frame of energy state of charge. """ return self._get_data("STORAGE_E")
[docs] def get_load_shed(self): """Returns LOAD_SHED data frame, either via loading or calculating. :return: (*pandas.DataFrame*) -- data frame of load shed (hour x bus). """ try: # It's either on the server or in our local ScenarioData folder load_shed = self._get_data("LOAD_SHED") except OSError: # The scenario was run without load_shed, and we must construct it grid = self.get_grid() infeasibilities = self._parse_infeasibilities() load_shed = construct_load_shed(self._scenario_info, grid, infeasibilities) scenario_id = self._scenario_info["id"] filename = scenario_id + "_LOAD_SHED.pkl" output_dir = server_setup.OUTPUT_DIR filepath = os.path.join(server_setup.LOCAL_DIR, *output_dir, filename) with open(filepath, "wb") as f: pickle.dump(load_shed, f) return load_shed
[docs] def get_load_shift_up(self): """Returns LOAD_SHIFT_UP data frame. This is the amount that flexible demand deviates above (e.g., recovers) the base demand. :return: (*pandas.DataFrame*) -- data frame of load shifted up (hour x bus). """ return self._get_data("LOAD_SHIFT_UP")
[docs] def get_load_shift_dn(self): """Returns LOAD_SHIFT_DN data frame. This is the amount that flexible demand deviates below (e.g., curtails) the base demand. :return: (*pandas.DataFrame*) -- data frame of load shifted down (hour x bus). """ return self._get_data("LOAD_SHIFT_DN")
[docs] def get_demand(self, original=True): """Returns demand profiles. :param bool original: should the original demand profile or the potentially modified one be returned. :return: (*pandas.DataFrame*) -- data frame of demand (hour, zone). """ profile = TransformProfile(self._scenario_info, self.get_grid(), self.get_ct()) demand = profile.get_profile("demand") if original: return demand else: dates = pd.date_range( start=self._scenario_info["start_date"], end=self._scenario_info["end_date"], freq=self._scenario_info["interval"], ) infeasibilities = self._parse_infeasibilities() if infeasibilities is None: print("No infeasibilities. Return original profile.") return demand else: for key, value in infeasibilities.items(): start = dates[key] end = ( dates[key] + pd.Timedelta(self._scenario_info["interval"]) - pd.Timedelta("1H") ) demand[start:end] *= 1.0 - value / 100.0 return demand
def _leave(self): """Cleans when leaving state.""" del self.grid del self.ct