Skip to content

Commit

Permalink
wip: remove hard coded resources
Browse files Browse the repository at this point in the history
  • Loading branch information
rouille committed Aug 5, 2022
1 parent 3a48cf9 commit 7149e43
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 89 deletions.
30 changes: 20 additions & 10 deletions powersimdata/design/generation/clean_capacity_scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ def add_resource_data_to_targets(input_targets, scenario, calculate_curtailment=
:param pandas.DataFrame input_targets: table includeing target names, used to
summarize resource data.
:param powersimdata.scenario.scenario.Scenario scenario: A Scenario instance.
:return: (*pandas.DataFrame*) -- DataFrame of targets including resource data.
:return: (*pandas.DataFrame*) -- data frame of targets including resource data.
"""
targets = input_targets.copy()
grid = scenario.state.get_grid()
grid = scenario.get_grid()
plant = grid.plant
curtailment_types = ["hydro", "solar", "wind"]
scenario_length = _get_scenario_length(scenario)
Expand All @@ -174,23 +174,33 @@ def add_resource_data_to_targets(input_targets, scenario, calculate_curtailment=
capacity_groupby = plant.Pmax.groupby(groupby_cols)
capacity_by_target_type = capacity_groupby.sum().unstack(fill_value=0)
# Generated energy
pg_groupby = scenario.state.get_pg().sum().groupby(groupby_cols)
pg_groupby = scenario.get_pg().sum().groupby(groupby_cols)
summed_generation = pg_groupby.sum().unstack(fill_value=0)
# Calculate capacity factors
possible_energy = scenario_length * capacity_by_target_type[curtailment_types]
capacity_factor = summed_generation[curtailment_types] / possible_energy

# To be generalized
if calculate_curtailment:
# Calculate: curtailment, no_curtailment_cap_factor
# Hydro and solar are straightforward
hydro_plant_sum = scenario.state.get_hydro().sum()
hydro_plant_targets = plant[plant.type == "hydro"].target_area
hydro_plant_sum = scenario.get_profile("hydro").sum()
hydro_plant_targets = plant[
plant["type"].isin(
grid.model_immutables.plants["group_profile_resources"]["hydro"]
)
]["target_area"]
hydro_potential_by_target = hydro_plant_sum.groupby(hydro_plant_targets).sum()
solar_plant_sum = scenario.state.get_solar().sum()
solar_plant_targets = plant[plant.type == "solar"].target_area
solar_plant_sum = scenario.get_profile("solar").sum()
solar_plant_targets = plant[
plant["type"].isin(
grid.model_immutables.plants["group_profile_resources"]["solar"]
)
]["target_area"]
solar_potential_by_target = solar_plant_sum.groupby(solar_plant_targets).sum()
# Wind is a little tricker because get_wind() returns 'wind' and 'wind_offshore'
onshore_wind_plants = plant[plant.type == "wind"].index
onshore_wind_plant_sum = scenario.state.get_wind().sum()[onshore_wind_plants]
onshore_wind_plant_sum = scenario.get_wind().sum()[onshore_wind_plants]
wind_plant_targets = plant[plant.type == "wind"].target_area
wind_potential_by_target = onshore_wind_plant_sum.groupby(
wind_plant_targets
Expand Down Expand Up @@ -234,7 +244,7 @@ def add_demand_to_targets(input_targets, scenario):

zonename2target = _make_zonename2target(grid, targets)
zoneid2target = {grid.zone2id[z]: target for z, target in zonename2target.items()}
summed_demand = scenario.state.get_demand().sum().to_frame()
summed_demand = scenario.get_demand().sum().to_frame()
summed_demand["target"] = [zoneid2target[id] for id in summed_demand.index]
targets["demand"] = summed_demand.groupby("target").sum()
return targets
Expand Down Expand Up @@ -512,7 +522,7 @@ def calculate_clean_capacity_scaling(
# Input validation
if not isinstance(ref_scenario, Scenario):
raise TypeError("ref_scenario must be a Scenario object")
if ref_scenario.state.name != "analyze":
if ref_scenario.name != "analyze":
raise ValueError("ref_scenario must be in Analyze state")
if method not in allowed_methods:
raise ValueError(f"method must be one of: {allowed_methods}")
Expand Down
31 changes: 18 additions & 13 deletions powersimdata/design/generation/curtailment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from powersimdata.scenario.scenario import Scenario

# To be generalized
default_pmin_dict = {
"coal": None,
"dfo": 0,
Expand All @@ -14,19 +15,13 @@
"wind": 0,
"wind_offshore": 0,
}
profile_methods = {
"hydro": "get_hydro",
"solar": "get_solar",
"wind": "get_wind",
"wind_offshore": "get_wind",
}


def temporal_curtailment(
scenario, pmin_by_type=None, pmin_by_id=None, curtailable={"solar", "wind"}
):
"""Calculate the minimum share of potential renewable energy that will be curtailed
due to supply/demand mismatch, assuming no storage is present.
due to supply/demand mismatch, assuming no storage is present.
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
:param dict/pandas.Series pmin_by_type: Mapping of types to Pmin assumptions. Values
Expand Down Expand Up @@ -60,7 +55,8 @@ def temporal_curtailment(
if not all([v is None or 0 <= v <= 1 for v in values]):
err_msg = f"all entries in {name} must be None or in the range [0, 1]"
raise ValueError(err_msg)
plant = scenario.state.get_grid().plant
grid = scenario.get_grid()
plant = grid.plant
valid_types = plant["type"].unique()
if not set(pmin_by_type.keys()) <= set(valid_types):
raise ValueError("Got invalid plant type as a key to pmin_by_type")
Expand All @@ -74,11 +70,17 @@ def temporal_curtailment(

# Get profiles, filter out plant-level overrides, then sum
all_profiles = pd.concat(
[getattr(scenario.state, m)() for m in set(profile_methods.values())], axis=1
[
scenario.get_profile(k)
for k in grid.model_immutables.plants["group_profile_resources"]
],
axis=1,
)
plant_id_mask = ~plant.index.isin(pmin_by_id.keys())
base_plant_ids_by_type = plant.loc[plant_id_mask].groupby("type").groups
valid_profile_types = set(base_plant_ids_by_type) & set(profile_methods)
valid_profile_types = (
set(base_plant_ids_by_type) & grid.model_immutables.plants["profile_resources"]
)
plant_ids_for_summed_profiles = set().union(
*[set(base_plant_ids_by_type[g]) for g in valid_profile_types]
)
Expand All @@ -89,7 +91,7 @@ def temporal_curtailment(
)

# Build up a series of firm generation
summed_demand = scenario.state.get_demand().sum(axis=1)
summed_demand = scenario.get_demand().sum(axis=1)
firm_generation = pd.Series(0, index=summed_demand.index)
# Add plants without plant-level overrides ('base' plants)
pmin_dict = {**default_pmin_dict, **pmin_by_type}
Expand All @@ -99,7 +101,7 @@ def temporal_curtailment(
if (resource in curtailable) or (pmin == 0):
continue
if pmin is None:
if resource in profile_methods:
if resource in grid.model_immutables.plants["profile_resources"]:
firm_generation += summed_profiles[resource]
else:
summed_pmin = plant.Pmin.loc[base_plant_ids_by_type[resource]].sum()
Expand All @@ -112,7 +114,10 @@ def temporal_curtailment(
if pmin == 0:
continue
if pmin is None:
if plant.loc[plant_id, "type"] in profile_methods:
if (
plant.loc[plant_id, "type"]
in grid.model_immutables.plants["profile_resources"]
):
firm_generation += all_profiles[plant_id]
else:
plant_pmin = plant.loc[plant_id, "Pmin"]
Expand Down
1 change: 1 addition & 0 deletions powersimdata/design/transmission/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def scale_renewable_stubs(change_table, fuzz=1, inplace=True, verbose=False):
ct["branch"]["branch_id"] = {}
branch_id_ct = ct["branch"]["branch_id"]

# To be replaced
ren_types = ("hydro", "solar", "wind", "wind_offshore")
for r in ren_types:
ren_plants = ref_plant[ref_plant["type"] == r]
Expand Down
30 changes: 7 additions & 23 deletions powersimdata/input/change_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,16 @@
)
from powersimdata.input.transform_grid import TransformGrid

_resources = (
"coal",
"dfo",
"geothermal",
"ng",
"nuclear",
"hydro",
"solar",
"wind",
"wind_offshore",
"biomass",
"other",
)


class ChangeTable:
"""Create change table for changes that need to be applied to the original
grid as well as to the original demand, hydro, solar and wind profiles.
A pickle file enclosing the change table in form of a dictionary can be
created and transferred on the server. Keys are *'demand'*, *'branch'*, *'dcline'*,
'*new_branch*', *'new_dcline'*, *'new_plant'*, *'storage'*,
*'[resource]'*, *'[resource]_cost'*, and *'[resource]_pmin'*,; where 'resource'
is one of: {*'biomass'*, *'coal'*, *'dfo'*, *'geothermal'*, *'ng'*, *'nuclear'*,
*'hydro'*, *'solar'*, *'wind'*, *'wind_offshore'*, *'other'*}.
If a key is missing in the dictionary, then no changes will be applied.
'*new_branch*', *'new_dcline'*, *'new_plant'*, *'storage'*, *'[resource]'*,
*'[resource]_cost'*, and *'[resource]_pmin'*,; where 'resource' are defined in
:class:`powersimdata.network.constants.plants.Resource` and depends on the grid
model. If a key is missing in the dictionary, then no changes will be applied.
The data structure is given below:
* *'demand'*:
Expand Down Expand Up @@ -149,14 +134,13 @@ def __init__(self, grid):
}
}

@staticmethod
def _check_resource(resource):
def _check_resource(self, resource):
"""Checks resource.
:param str resource: type of generator.
:raises ValueError: if resource cannot be changed.
"""
possible = _resources
possible = self.grid.model_immutables.plants["all_resources"]
if resource not in possible:
print("-----------------------")
print("Possible Generator type")
Expand Down Expand Up @@ -248,7 +232,7 @@ def clear(self, which=None):
for key in {"new_plant", "remove_plant"}:
if key in self.ct:
del self.ct[key]
for r in _resources:
for r in self.grid.model_immutables.plants["all_resources"]:
for suffix in {"", "_cost", "_pmin"}:
key = r + suffix
if key in self.ct:
Expand Down
4 changes: 1 addition & 3 deletions powersimdata/input/changes/plant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from powersimdata.input.transform_grid import TransformGrid
from powersimdata.utility.distance import find_closest_neighbor

_profile_resource = {"hydro", "solar", "wind", "wind_offshore"}


def add_plant(obj, info):
"""Sets parameters of new generator(s) in change table.
Expand Down Expand Up @@ -42,7 +40,7 @@ def add_plant(obj, info):
if plant["Pmin"] < 0 or plant["Pmin"] > plant["Pmax"]:
err_msg = f"0 <= Pmin <= Pmax must be satisfied for plant #{i + 1}"
raise ValueError(err_msg)
if plant["type"] in _profile_resource:
if plant["type"] in obj.grid.model_immutables.plants["profile_resources"]:
lon = anticipated_bus.loc[plant["bus_id"]].lon
lat = anticipated_bus.loc[plant["bus_id"]].lat
plant_same_type = obj.grid.plant.groupby("type").get_group(plant["type"])
Expand Down
24 changes: 5 additions & 19 deletions powersimdata/input/transform_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,6 @@ def __init__(self, grid, ct):
"""
self.grid = copy.deepcopy(grid)
self.ct = copy.deepcopy(ct)
self.gen_types = [
"biomass",
"coal",
"dfo",
"geothermal",
"ng",
"nuclear",
"hydro",
"solar",
"wind",
"wind_offshore",
"other",
]
self.thermal_gen_types = ["coal", "dfo", "geothermal", "ng", "nuclear"]

def get_grid(self):
"""Returns the transformed grid.
Expand All @@ -44,7 +30,7 @@ def get_grid(self):
def _apply_change_table(self):
"""Apply changes listed in change table to the grid."""
# First scale by zones, so that zone factors are not applied to additions.
for g in self.gen_types:
for g in self.grid.model_immutables.plants["all_resources"]:
if g in self.ct.keys():
self._scale_gen_by_zone(g)
if f"{g}_cost" in self.ct.keys():
Expand Down Expand Up @@ -72,7 +58,7 @@ def _apply_change_table(self):
self._add_storage()

# Scale by IDs, so that additions can be scaled.
for g in self.gen_types:
for g in self.grid.model_immutables.plants["all_resources"]:
if g in self.ct.keys():
self._scale_gen_by_id(g)
if f"{g}_cost" in self.ct.keys():
Expand Down Expand Up @@ -106,7 +92,7 @@ def _scale_gen_by_zone(self, gen_type):
.index.tolist()
)
self._scale_gen_capacity(plant_id, factor)
if gen_type in self.thermal_gen_types:
if gen_type in self.grid.model_immutables.plants["thermal_resources"]:
self._scale_gencost_by_capacity(plant_id, factor)

def _scale_gen_by_id(self, gen_type):
Expand All @@ -118,7 +104,7 @@ def _scale_gen_by_id(self, gen_type):
if "plant_id" in self.ct[gen_type].keys():
for plant_id, factor in self.ct[gen_type]["plant_id"].items():
self._scale_gen_capacity(plant_id, factor)
if gen_type in self.thermal_gen_types:
if gen_type in self.grid.model_immutables.plants["thermal_resources"]:
self._scale_gencost_by_capacity(plant_id, factor)

def _scale_gencost_by_zone(self, gen_type):
Expand Down Expand Up @@ -389,7 +375,7 @@ def _add_gencost(self):
new_gencost["type"] = 2
new_gencost["n"] = 3
new_gencost["interconnect"] = self.grid.bus.loc[bus_id].interconnect
if entry["type"] in self.thermal_gen_types:
if entry["type"] in self.grid.model_immutables.plants["thermal_resources"]:
new_gencost["c0"] = entry["c0"]
new_gencost["c1"] = entry["c1"]
new_gencost["c2"] = entry["c2"]
Expand Down
36 changes: 15 additions & 21 deletions powersimdata/input/transform_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,17 @@ def __init__(self, scenario_info, grid, ct, slice=True):
self.ct = copy.deepcopy(ct)
self.grid = copy.deepcopy(grid)

self.scale_keys = {
"wind": {"wind", "wind_offshore"},
"solar": {"solar"},
"hydro": {"hydro"},
"demand": {"demand"},
}
self.scale_keys = {"demand": {"demand"}} | self.grid.model_immutables.plants[
"group_profile_resources"
]

self.n_new_plant, self.n_new_clean_plant = self._get_number_of_new_plant()

def _get_number_of_new_plant(self):
"""Return the total number of new plant and new plant with profiles.
:return: (*tuple*) -- first element is the total number of new plant and second
element is the total number of new clean plant (*hydro*, *solar*,
*onshore wind* and *offshore wind*).
element is the total number of new plant with profile.
"""
n_plant = [0, 0]
if "new_plant" in self.ct.keys():
Expand All @@ -51,7 +48,7 @@ def _get_number_of_new_plant(self):
def _get_renewable_profile(self, resource):
"""Return the transformed profile.
:param str resource: *'hydro'*, *'solar'* or *'wind'*.
:param str resource: a genertaor type with profile.
:return: (*pandas.DataFrame*) -- power output for generators of specified type
with plant identification number as columns and UTC timestamp as indices.
"""
Expand Down Expand Up @@ -188,24 +185,21 @@ def _slice_df(self, df):
def get_profile(self, name):
"""Return profile.
:param str name: either *demand*, *'hydro'*, *'solar'*, *'wind'*,
*'demand_flexibility_up'*, *'demand_flexibility_dn'*,
*'demand_flexibility_cost_up'*, or *'demand_flexibility_cost_dn'*.
:param str name: either *demand*, *'demand_flexibility_up'*,
*'demand_flexibility_dn'*, *'demand_flexibility_cost_up'*,
*'demand_flexibility_cost_dn'* or a generator type with profile.
:return: (*pandas.DataFrame*) -- profile.
:raises ValueError: if argument not one of *'demand'*, *'hydro'*, *'solar'*,
*'wind'*, *'demand_flexibility_up'*, *'demand_flexibility_dn'*,
*'demand_flexibility_cost_up'*, or *'demand_flexibility_cost_dn'*.
:raises ValueError: if argument not one of *'demand'*,
*'demand_flexibility_up'*, *'demand_flexibility_dn'*,
*'demand_flexibility_cost_up'*, *'demand_flexibility_cost_dn'* or a generator type wit profile.
"""
possible = [
"demand",
"hydro",
"solar",
"wind",
possible = {
"demand_flexibility_up",
"demand_flexibility_dn",
"demand_flexibility_cost_up",
"demand_flexibility_cost_dn",
]
}.union(*self.scale_keys.values())

if name not in possible:
raise ValueError("Choose from %s" % " | ".join(possible))
elif name == "demand":
Expand Down
Loading

0 comments on commit 7149e43

Please sign in to comment.