Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: abstract grid model #233

Merged
merged 1 commit into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions postreise/analyze/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
import numpy as np
import pandas as pd
from powersimdata.input.grid import Grid
from powersimdata.network.usa_tamu.constants import zones
from powersimdata.network.usa_tamu.constants.plants import (
all_resources,
renewable_resources,
)
from powersimdata.network.model import ModelImmutables
from powersimdata.scenario.analyze import Analyze
from powersimdata.scenario.scenario import Scenario

Expand Down Expand Up @@ -71,17 +67,19 @@ def _check_scenario_is_in_analyze_state(scenario):
raise ValueError("scenario must in analyze state")


def _check_areas_and_format(areas):
def _check_areas_and_format(areas, grid_model="usa_tamu"):
"""Ensure that areas are valid. Duplicates are removed and state abbreviations are
converted to their actual name.

:param str/list/tuple/set areas: areas(s) to check. Could be load zone name(s),
state name(s)/abbreviation(s) or interconnect(s).
:param str grid_model: grid model.
:raises TypeError: if areas is not a list/tuple/set of str.
:raises ValueError: if areas is empty or not valid.
:return: (*set*) -- areas as a set. State abbreviations are converted to state
names.
"""
mi = ModelImmutables(grid_model)
if isinstance(areas, str):
areas = {areas}
elif isinstance(areas, (list, set, tuple)):
Expand All @@ -92,27 +90,34 @@ def _check_areas_and_format(areas):
raise TypeError("areas must be a str or a list/tuple/set of str")
if len(areas) == 0:
raise ValueError("areas must be non-empty")
all_areas = zones.loadzone | zones.abv | zones.state | zones.interconnect
all_areas = (
mi.zones["loadzone"]
| mi.zones["abv"]
| mi.zones["state"]
| mi.zones["interconnect"]
)
if not areas <= all_areas:
diff = areas - all_areas
raise ValueError("invalid area(s): %s" % " | ".join(diff))

abv_in_areas = [z for z in areas if z in zones.abv]
abv_in_areas = [z for z in areas if z in mi.zones["abv"]]
for a in abv_in_areas:
areas.remove(a)
areas.add(zones.abv2state[a])
areas.add(mi.zones["abv2state"][a])

return areas


def _check_resources_and_format(resources):
def _check_resources_and_format(resources, grid_model="usa_tamu"):
"""Ensure that resources are valid and convert variable to a set.

:param str/list/tuple/set resources: resource(s) to check.
:param str grid_model: grid model.
:raises TypeError: if resources is not a list/tuple/set of str.
:raises ValueError: if resources is empty or not valid.
:return: (*set*) -- resources as a set.
"""
mi = ModelImmutables(grid_model)
if isinstance(resources, str):
resources = {resources}
elif isinstance(resources, (list, set, tuple)):
Expand All @@ -123,23 +128,25 @@ def _check_resources_and_format(resources):
raise TypeError("resources must be a str or a list/tuple/set of str")
if len(resources) == 0:
raise ValueError("resources must be non-empty")
if not resources <= all_resources:
diff = resources - all_resources
if not resources <= mi.plants["all_resources"]:
diff = resources - mi.plants["all_resources"]
raise ValueError("invalid resource(s): %s" % " | ".join(diff))
return resources


def _check_resources_are_renewable_and_format(resources):
def _check_resources_are_renewable_and_format(resources, grid_model="usa_tamu"):
"""Ensure that resources are valid renewable resources and convert variable to
a set.

:param str/list/tuple/set resources: resource(s) to analyze.
:param str grid_model: grid model.
:raises ValueError: if resources are not renewables.
return: (*set*) -- resources as a set
"""
resources = _check_resources_and_format(resources)
if not resources <= renewable_resources:
diff = resources - all_resources
mi = ModelImmutables(grid_model)
resources = _check_resources_and_format(resources, grid_model=grid_model)
if not resources <= mi.plants["renewable_resources"]:
diff = resources - mi.plants["all_resources"]
raise ValueError("invalid renewable resource(s): %s" % " | ".join(diff))
return resources

Expand All @@ -161,6 +168,7 @@ def _check_areas_are_in_grid_and_format(areas, grid):
if not isinstance(areas, dict):
raise TypeError("areas must be a dict")

mi = grid.model_immutables
areas_formatted = {}
for a in areas.keys():
if a in ["loadzone", "state", "interconnect"]:
Expand All @@ -174,22 +182,22 @@ def _check_areas_are_in_grid_and_format(areas, grid):
interconnects = _check_areas_and_format(v)
for i in interconnects:
try:
all_loadzones.update(zones.interconnect2loadzone[i])
all_loadzones.update(mi.zones["interconnect2loadzone"][i])
except KeyError:
raise ValueError("invalid interconnect: %s" % i)
areas_formatted["interconnect"].update(interconnects)
elif k == "state":
states = _check_areas_and_format(v)
for s in states:
try:
all_loadzones.update(zones.state2loadzone[s])
all_loadzones.update(mi.zones["state2loadzone"][s])
except KeyError:
raise ValueError("invalid state: %s" % s)
areas_formatted["state"].update(states)
elif k == "loadzone":
loadzones = _check_areas_and_format(v)
for l in loadzones:
if l not in zones.loadzone:
if l not in mi.zones["loadzone"]:
raise ValueError("invalid load zone: %s" % l)
all_loadzones.update(loadzones)
areas_formatted["loadzone"].update(loadzones)
Expand Down
6 changes: 4 additions & 2 deletions postreise/analyze/demand.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from powersimdata.network.usa_tamu.usa_tamu_model import area_to_loadzone
from powersimdata.network.model import area_to_loadzone

from postreise.analyze.generation.summarize import (
get_generation_time_series_by_resources,
Expand All @@ -17,7 +17,9 @@ def get_demand_time_series(scenario, area, area_type=None):
column: demand values
"""
grid = scenario.state.get_grid()
loadzone_set = area_to_loadzone(grid, area, area_type=area_type)
loadzone_set = area_to_loadzone(
scenario.info["grid_model"], area, area_type=area_type
)
loadzone_id_set = {grid.zone2id[lz] for lz in loadzone_set if lz in grid.zone2id}

return scenario.state.get_demand()[loadzone_id_set].sum(axis=1)
Expand Down
30 changes: 22 additions & 8 deletions postreise/analyze/generation/curtailment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pandas as pd
from powersimdata.network.usa_tamu.constants.plants import renewable_resources

from postreise.analyze.check import (
_check_areas_are_in_grid_and_format,
Expand Down Expand Up @@ -32,7 +31,10 @@ def calculate_curtailment_time_series(scenario):
pg = scenario.state.get_pg()

plant_id = get_plant_id_for_resources(
renewable_resources.intersection(set(grid.plant.type)), grid
grid.model_immutables.plants["renewable_resources"].intersection(
set(grid.plant.type)
),
grid,
)
profiles = pd.concat(
[scenario.state.get_solar(), scenario.state.get_wind()], axis=1
Expand All @@ -55,9 +57,13 @@ def calculate_curtailment_time_series_by_resources(scenario, resources=None):
grid = scenario.state.get_grid()

if resources is None:
resources = renewable_resources.intersection(set(grid.plant.type))
resources = grid.model_immutables.plants["renewable_resources"].intersection(
set(grid.plant.type)
)
else:
resources = _check_resources_are_renewable_and_format(resources)
resources = _check_resources_are_renewable_and_format(
resources, grid_model=scenario.info["grid_model"]
)

curtailment_by_resources = decompose_plant_data_frame_into_resources(
curtailment, resources, grid
Expand Down Expand Up @@ -144,9 +150,13 @@ def calculate_curtailment_time_series_by_areas_and_resources(
)

if resources is None:
resources = renewable_resources.intersection(set(grid.plant.type))
resources = grid.model_immutables.plants["renewable_resources"].intersection(
set(grid.plant.type)
)
else:
resources = _check_resources_are_renewable_and_format(resources)
resources = _check_resources_are_renewable_and_format(
resources, grid_model=scenario.info["grid_model"]
)

curtailment_by_areas_and_resources = (
decompose_plant_data_frame_into_areas_and_resources(
Expand Down Expand Up @@ -181,9 +191,13 @@ def calculate_curtailment_time_series_by_resources_and_areas(
)

if resources is None:
resources = renewable_resources.intersection(set(grid.plant.type))
resources = grid.model_immutables.plants["renewable_resources"].intersection(
set(grid.plant.type)
)
else:
resources = _check_resources_are_renewable_and_format(resources)
resources = _check_resources_are_renewable_and_format(
resources, grid_model=scenario.info["grid_model"]
)

curtailment_by_resources_and_areas = (
decompose_plant_data_frame_into_resources_and_areas(
Expand Down
39 changes: 20 additions & 19 deletions postreise/analyze/generation/emissions.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import numpy as np
import pandas as pd
from numpy.polynomial.polynomial import polyval
from powersimdata.network.usa_tamu.constants.plants import (
carbon_per_mmbtu,
carbon_per_mwh,
carbon_resources,
nox_per_mwh,
so2_per_mwh,
)
from powersimdata.network.model import ModelImmutables

from postreise.analyze.check import (
_check_gencost,
_check_grid,
_check_scenario_is_in_analyze_state,
_check_time_series,
)
Expand All @@ -29,16 +24,16 @@ def generate_emissions_stats(scenario, pollutant="carbon", method="simple"):
:return: (*pandas.DataFrame*) -- emissions data frame.
"""
_check_scenario_is_in_analyze_state(scenario)

mi = ModelImmutables(scenario.info["grid_model"])
allowed_methods = {
"carbon": {"simple", "always-on", "decommit"},
"nox": {"simple"},
"so2": {"simple"},
}
emissions_per_mwh = {
"carbon": carbon_per_mwh,
"nox": nox_per_mwh,
"so2": so2_per_mwh,
"carbon": mi.plants["carbon_per_mwh"],
"nox": mi.plants["nox_per_mwh"],
"so2": mi.plants["so2_per_mwh"],
}

if pollutant not in allowed_methods.keys():
Expand All @@ -63,7 +58,7 @@ def generate_emissions_stats(scenario, pollutant="carbon", method="simple"):
costs = calc_costs(pg, grid.gencost["before"], decommit=decommit)
heat = np.zeros_like(costs)

for fuel, val in carbon_per_mmbtu.items():
for fuel, val in mi.plants["carbon_per_mmbtu"].items():
indices = (grid.plant["type"] == fuel).to_numpy()
heat[:, indices] = (
costs[:, indices] / grid.plant["GenFuelCost"].values[indices]
Expand All @@ -75,34 +70,40 @@ def generate_emissions_stats(scenario, pollutant="carbon", method="simple"):
return emissions


def summarize_emissions_by_bus(emissions, plant):
def summarize_emissions_by_bus(emissions, grid):
"""Summarize time series emissions dataframe by type and bus.

:param pandas.DataFrame emissions: Hourly emissions by generator.
:param pandas.DataFrame plant: Generator specification table.
:return: (*dict*) -- Annual emissions by fuel and bus.
:param pandas.DataFrame emissions: hourly emissions by generator.
:param powersimdata.input.grid.Grid grid: grid object.
:return: (*dict*) -- annual emissions by fuel and bus.
"""

_check_time_series(emissions, "emissions")
if (emissions < -1e-3).any(axis=None):
raise ValueError("emissions must be non-negative")

_check_grid(grid)
plant = grid.plant

# sum by generator
plant_totals = emissions.sum()
# set up output data structure
plant_buses = plant["bus_id"].unique()
bus_totals_by_type = {f: {b: 0 for b in plant_buses} for f in carbon_resources}
bus_totals_by_type = {
f: {b: 0 for b in plant_buses}
for f in grid.model_immutables.plants["carbon_resources"]
}
# sum by fuel by bus
for p in plant_totals.index:
plant_type = plant.loc[p, "type"]
if plant_type not in carbon_resources:
if plant_type not in grid.model_immutables.plants["carbon_resources"]:
continue
plant_bus = plant.loc[p, "bus_id"]
bus_totals_by_type[plant_type][plant_bus] += plant_totals.loc[p]
# filter out buses whose emissions are zero
bus_totals_by_type = {
r: {b: v for b, v in bus_totals_by_type[r].items() if v > 0}
for r in carbon_resources
for r in grid.model_immutables.plants["carbon_resources"]
}

return bus_totals_by_type
Expand Down
Loading