def test_demand_is_scaled(base_grid, raw_demand): base_demand = raw_demand[base_grid.id2zone.keys()] n_zone = param["n_zone_to_scale"] ct = ChangeTable(base_grid) ct.scale_demand( zone_id={ z: f for z, f in zip( np.random.choice([i for i in base_grid.zone2id.values()], size=n_zone, replace=False), 2 * np.random.random(size=n_zone), ) }) tg = TransformGrid(base_grid, ct.ct) transformed_grid = tg.get_grid() empty_scenario_info = { } # scenario_info not needed since profile_input is mocked tp = TransformProfile(empty_scenario_info, transformed_grid, ct.ct) transformed_profile = tp.get_profile("demand") assert not base_demand.equals(transformed_profile) scaled_zone = list(ct.ct["demand"]["zone_id"].keys()) unscaled_zone = set(base_grid.id2zone.keys()) - set(scaled_zone) factor = list(ct.ct["demand"]["zone_id"].values()) assert transformed_profile[scaled_zone].equals( base_demand[scaled_zone].multiply(factor, axis=1)) if unscaled_zone: assert transformed_profile[list(unscaled_zone)].equals( base_demand[list(unscaled_zone)])
def prepare_profile(self, kind, profile_as=None): """Prepares profile for simulation. :param kind: one of *demand*, *'hydro'*, *'solar'* or *'wind'*. :param int/str/None profile_as: if given, copy profile from this scenario. """ if profile_as is None: tp = TransformProfile(self._scenario_info, self.grid, self.ct) profile = tp.get_profile(kind) print( f"Writing scaled {kind} profile in {server_setup.LOCAL_DIR} on local machine" ) file_name = "%s_%s.csv" % (self._scenario_info["id"], kind) profile.to_csv(os.path.join(server_setup.LOCAL_DIR, file_name)) self._data_access.move_to( file_name, self.REL_TMP_DIR, change_name_to=f"{kind}.csv" ) else: from_dir = posixpath.join( self.server_config.execute_dir(), f"scenario_{profile_as}", ) to_dir = posixpath.join( self.server_config.execute_dir(), self.scenario_folder ) _, _, stderr = self._data_access.copy(f"{from_dir}/{kind}.csv", to_dir) if len(stderr.readlines()) != 0: raise IOError(f"Failed to copy {kind}.csv on server")
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 get_wind(self): """Returns wind profile :return: (*pandas.DataFrame*) -- data frame of wind energy output. """ profile = TransformProfile(self._scenario_info, self.get_grid(), self.get_ct()) return profile.get_profile("wind")
def get_solar(self): """Returns solar profile :return: (*pandas.DataFrame*) -- data frame of solar energy output. """ profile = TransformProfile(self._scenario_info, self.get_grid(), self.get_ct()) return profile.get_profile("solar")
def get_hydro(self): """Returns hydro profile :return: (*pandas.DataFrame*) -- data frame of hydro energy output. """ profile = TransformProfile(self._scenario_info, self.get_grid(), self.get_ct()) return profile.get_profile("hydro")
def get_profile(self, kind): """Returns demand, hydro, solar or wind profile. :param str kind: either *'demand'*, *'hydro'*, *'solar'*, *'wind'*. :return: (*pandas.DataFrame*) -- profile. """ profile = TransformProfile(self._scenario_info, self.get_grid(), self.get_ct()) return profile.get_profile(kind)
def get_profile(self, kind): """Returns demand, hydro, solar or wind profile. :param str kind: either *'demand'*, *'hydro'*, *'solar'*, *'wind'*. :return: (*pandas.DataFrame*) -- profile. """ if getattr(self, kind): profile = TransformProfile( { "grid_model": self.grid_model, "base_%s" % kind: getattr(self, kind), }, self.get_grid(), self.get_ct(), ) return profile.get_profile(kind) else: raise Exception("%s profile version not set" % kind)
def _check_plants_are_scaled(ct, base_grid, profile_info, raw_profile, resource): plant_id_type = get_plant_with_resource(base_grid, resource) base_profile = ( raw_profile[plant_id_type] * base_grid.plant.loc[plant_id_type, "Pmax"] ) tg = TransformGrid(base_grid, ct) transformed_grid = tg.get_grid() tp = TransformProfile(profile_info, transformed_grid, ct) transformed_profile = tp.get_profile(resource) scaled_plant_id = [] scaling_factor_plant = [] if "zone_id" in ct[resource].keys(): for z, f in ct[resource]["zone_id"].items(): plant_id_zone = ( base_grid.plant.groupby(["zone_id", "type"]) .get_group((z, resource)) .index.tolist() ) scaled_plant_id += plant_id_zone scaling_factor_plant += [f] * len(plant_id_zone) if "plant_id" in ct[resource].keys(): for i, f in ct[resource]["plant_id"].items(): if i in scaled_plant_id: scaling_factor_plant[scaled_plant_id.index(i)] *= f else: scaled_plant_id.append(i) scaling_factor_plant.append(f) assert not base_profile.equals(transformed_profile) assert_almost_equal( transformed_profile[scaled_plant_id].values, base_profile[scaled_plant_id].multiply(scaling_factor_plant, axis=1).values, ) unscaled_plant_id = set(plant_id_type) - set(scaled_plant_id) if unscaled_plant_id: assert transformed_profile[unscaled_plant_id].equals( base_profile[unscaled_plant_id] ) return transformed_profile
def test_flexible_demand_profiles_are_trimmed(base_grid, raw_demand_flexibility_up, raw_demand_flexibility_dn, monkeypatch): monkeypatch.setattr(Context, "get_data_access", MockContext().get_data_access) data_access = Context.get_data_access() # Specify the fake demand flexibility profiles from MockInputData zone_keys = [f"zone.{z}" for z in base_grid.id2zone.keys()] base_demand_flexibility_up = raw_demand_flexibility_up[zone_keys] base_demand_flexibility_dn = raw_demand_flexibility_dn[zone_keys] # Create fake files in the expected directory path exp_path = f"raw/{base_grid.grid_model}" for csv_file in ( "demand_flexibility_up_Test.csv", "demand_flexibility_dn_Test.csv", ): with data_access.write(exp_path + "/" + csv_file) as f: pd.DataFrame().to_csv(f) # Specify the change table ct = ChangeTable(base_grid) ct.add_demand_flexibility({ "demand_flexibility_up": "Test", "demand_flexibility_dn": "Test", "demand_flexibility_duration": 6, }) # Transform the grid object accordingly tg = TransformGrid(base_grid, ct.ct) transformed_grid = tg.get_grid() # Test that the demand flexibility profiles are pruned empty_scenario_info = {"grid_model": base_grid.grid_model} tp = TransformProfile(empty_scenario_info, transformed_grid, ct.ct) transformed_demand_flexibility_up = tp.get_profile("demand_flexibility_up") transformed_demand_flexibility_dn = tp.get_profile("demand_flexibility_dn") assert base_demand_flexibility_up.equals(transformed_demand_flexibility_up) assert base_demand_flexibility_dn.equals(transformed_demand_flexibility_dn)
def get_profile(self, kind): """Returns demand, hydro, solar or wind profile. :param str kind: either *'demand'*, *'hydro'*, *'solar'*, *'wind'*. :return: (*pandas.DataFrame*) -- profile. :raises Exception: if :attr:`builder` has not been assigned yet through meth:`set_builder` or if :meth:`_Builder.set_base_profile` has not been called yet. """ if getattr(self.builder, kind): profile = TransformProfile( { "grid_model": getattr(self.builder, "grid_model"), "base_%s" % kind: getattr(self.builder, kind), }, self.get_grid(), self.get_ct(), ) return profile.get_profile(kind) else: raise Exception("%s profile version not set" % kind)
def prepare_profile(self, kind, profile_as=None, slice=False): """Prepares profile for simulation. :param kind: one of *demand*, *'hydro'*, *'solar'*, *'wind'*, *'demand_flexibility_up'*, *'demand_flexibility_dn'*, *'demand_flexibility_cost_up'*, or *'demand_flexibility_cost_dn'*. :param int/str profile_as: if given, copy profile from this scenario. :param bool slice: whether to slice the profiles by the Scenario's time range. """ file_name = f"{kind}.csv" dest_path = "/".join([self.REL_TMP_DIR, file_name]) if profile_as is None: tp = TransformProfile(self._scenario_info, self.grid, self.ct, slice) profile = tp.get_profile(kind) print(f"Writing scaled {kind} profile to {dest_path}") with self._data_access.write(dest_path, save_local=False) as f: profile.to_csv(f) else: from_dir = self._data_access.tmp_folder(profile_as) src = "/".join([from_dir, file_name]) self._data_access.fs.copy(src, dest_path)
def export_transformed_profile(kind, scenario_info, grid, ct, filepath, slice=True): """Apply transformation to the given kind of profile and save the result locally. :param str kind: which profile to export. This parameter is passed to :meth:`TransformProfile.get_profile`. :param dict scenario_info: a dict containing the profile version, with key in the form base_{kind} :param powersimdata.input.grid.Grid grid: a Grid object previously transformed. :param dict ct: change table. :param str filepath: path to save the result, including the filename :param bool slice: whether to slice the profiles by the Scenario's time range. """ tp = TransformProfile(scenario_info, grid, ct, slice) profile = tp.get_profile(kind) print(f"Writing scaled {kind} profile to {filepath} on local machine") profile.to_csv(filepath)
def test_demand_is_scaled(base_grid): input_data = InputData() demand_info = { "interconnect": "_".join(interconnect), "grid_model": "usa_tamu", "base_demand": param["demand"], } raw_demand = input_data.get_data(demand_info, "demand") base_demand = raw_demand[base_grid.id2zone.keys()] n_zone = param["n_zone_to_scale"] ct = ChangeTable(base_grid) ct.scale_demand( zone_id={ z: f for z, f in zip( np.random.choice( [i for i in base_grid.zone2id.values()], size=n_zone, replace=False ), 2 * np.random.random(size=n_zone), ) } ) tg = TransformGrid(base_grid, ct.ct) transformed_grid = tg.get_grid() tp = TransformProfile(demand_info, transformed_grid, ct.ct) transformed_profile = tp.get_profile("demand") assert not base_demand.equals(transformed_profile) scaled_zone = list(ct.ct["demand"]["zone_id"].keys()) unscaled_zone = set(base_grid.id2zone.keys()) - set(scaled_zone) factor = list(ct.ct["demand"]["zone_id"].values()) assert transformed_profile[scaled_zone].equals( base_demand[scaled_zone].multiply(factor, axis=1) ) if unscaled_zone: assert transformed_profile[unscaled_zone].equals(base_demand[unscaled_zone])
def _check_new_plants_are_added(ct, base_grid, raw_profile, resource): n_plant = param["n_plant_to_add"] plant_id_type = (base_grid.plant.isin( profile_type[resource]).query("type == True").index) base_profile = (raw_profile[plant_id_type] * base_grid.plant.loc[plant_id_type, "Pmax"]) tg = TransformGrid(base_grid, ct) transformed_grid = tg.get_grid() empty_scenario_info = { } # scenario_info not needed since profile_input is mocked tp = TransformProfile(empty_scenario_info, transformed_grid, ct) transformed_profile = tp.get_profile(resource) assert not transformed_profile.equals(base_profile) assert not len(base_profile.columns) == len(transformed_profile.columns) assert len(set(transformed_profile.columns) - set(base_profile.columns)) == n_plant assert set(transformed_profile.columns) - set(base_profile.columns) == set( transformed_grid.plant.index[-n_plant:]) return transformed_profile.drop(base_profile.columns, axis=1)
def construct_load_shed(scenario_info, grid, ct, infeasibilities=None): """Constructs load_shed dataframe from relevant scenario/grid data. :param dict scenario_info: info attribute of Scenario object. :param powersimdata.input.grid.Grid grid: grid to construct load_shed for. :param dict ct: ChangeTable dictionary. :param dict/None infeasibilities: dictionary of {interval (int): load shed percentage (int)}, or None. :return: (*pandas.DataFrame*) -- data frame of load_shed. """ hours = pd.date_range(start=scenario_info["start_date"], end=scenario_info["end_date"], freq="1H").tolist() buses = grid.bus.index if infeasibilities is None: print("No infeasibilities, constructing DataFrame") load_shed_data = coo_matrix((len(hours), len(buses))) load_shed = pd.DataFrame.sparse.from_spmatrix(load_shed_data) else: print("Infeasibilities, constructing DataFrame") zone_demand = TransformProfile(scenario_info, grid, ct) bus_demand = distribute_demand_from_zones_to_buses( zone_demand, grid.bus) load_shed = np.zeros((len(hours), len(buses))) # Convert '24H' to 24 interval = int(scenario_info["interval"][:-1]) for i, v in infeasibilities.items(): start = i * interval end = (i + 1) * interval base_demand = bus_demand.iloc[start:end, :].to_numpy() shed_demand = base_demand * (v / 100) load_shed[start:end, :] = shed_demand load_shed = pd.DataFrame(load_shed, columns=buses, index=hours) load_shed = load_shed.astype(pd.SparseDtype("float", 0)) load_shed.index = hours load_shed.index.name = "UTC" load_shed.columns = buses return load_shed