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

chore: merge develop into master for v0.4.3 release #514

Merged
merged 31 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8062dd0
feat: add substation capacity calculation function
danielolsen Jun 14, 2021
b8d58f2
test: add test for substation capacity calculation
danielolsen Jun 14, 2021
40f302c
Merge pull request #500 from Breakthrough-Energy/daniel/substation_ca…
danielolsen Jun 15, 2021
defd713
chore: add pf input to MockAnalyze (#501)
danielolsen Jun 15, 2021
37e6394
feat: enable retrieval of pf and dcline_pf from MockAnalyze (#502)
danielolsen Jun 15, 2021
17e630f
chore: add storage_e input/output to MockAnalyze
danielolsen Jun 15, 2021
9af34b4
chore: add exported_methods to MockAnalyze
danielolsen Jun 15, 2021
0d79412
feat: add logic to access state attributes without .state
danielolsen Jun 15, 2021
26caf6b
Merge pull request #503 from Breakthrough-Energy/daniel/mock_scenario…
danielolsen Jun 17, 2021
4c677c8
feat: calculate inter-zone-level transmission capacity
ywang303 Jun 15, 2021
06b3d44
Merge pull request #504 from Breakthrough-Energy/josephine/calculate_…
ywang303 Jun 17, 2021
64f0d66
feat: add check of Grid model correspondence
danielolsen Jun 24, 2021
1ac171f
feat: add base_grid input to investment cost calculation functions
danielolsen Jun 22, 2021
88e9b7b
Merge pull request #506 from Breakthrough-Energy/daniel/investment_co…
danielolsen Jun 24, 2021
805764e
ci: code coverage reporting (#508)
jenhagg Jun 24, 2021
68ccd7c
ci: fix yaml and add codecov badge (#509)
jenhagg Jun 25, 2021
0ec5e43
chore: move check functions and tests from postreise into powersimdata
danielolsen Jun 22, 2021
7271139
refactor: use new grid checks in existing higher-level check functions
danielolsen Jun 22, 2021
6785b16
chore: add __init__ file for new compare module
danielolsen Jun 22, 2021
553d2c7
chore: move transmission upgrade calculation from postreise to powers…
danielolsen Jun 23, 2021
10d5de2
feat: add new generation module to compare
danielolsen Jun 22, 2021
b46cae3
refactor: use compare module in investment cost calculations
danielolsen Jun 22, 2021
2fe6f7f
Merge pull request #507 from Breakthrough-Energy/daniel/compare_module
danielolsen Jun 25, 2021
85d2132
feat: deduplicate interconnect passed to grid constructor
Jun 28, 2021
3035c3d
refactor: support arbitrary iterable
Jun 28, 2021
8b15e52
Merge pull request #510 from Breakthrough-Energy/jon/grid
jenhagg Jun 28, 2021
6fab3e6
fix: correct Exception message during grid check
danielolsen Jun 29, 2021
a1cfd10
refactor: move helper functions and tests from postreise to powersimdata
danielolsen Jun 29, 2021
e6b9dc1
Merge pull request #512 from Breakthrough-Energy/daniel/postreise_hel…
danielolsen Jun 29, 2021
f0bdcf2
doc: add docstring for kind param in export_transformed_profile (#515)
danielolsen Jun 30, 2021
e480a62
chore: bump version number to v0.4.3 (#516)
danielolsen Jun 30, 2021
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
4 changes: 0 additions & 4 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
[run]
branch = True
omit =
**/test_*.py
**/__init__.py

34 changes: 34 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Code coverage

on:
push:
branches:
- 'develop'

jobs:
coverage:
runs-on: ubuntu-latest

name: Generate coverage report
steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9

- name: Start postgres container
run: |
docker-compose -f stack.yml up -d
while ! nc -z localhost 5432; do sleep 1; done;
working-directory: powersimdata/data_access

- run: python -m pip install --upgrade pip tox
- run: tox -e pytest-ci -- --cov-report=xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
name: codecov-powersimdata
fail_ci_if_error: true
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

![logo](https://raw.githubusercontent.com/Breakthrough-Energy/docs/master/source/_static/img/BE_Sciences_RGB_Horizontal_Color.svg)

[![codecov](https://codecov.io/gh/Breakthrough-Energy/PowerSimData/branch/develop/graph/badge.svg?token=5A20TCV5XL)](https://codecov.io/gh/Breakthrough-Energy/PowerSimData)
[![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
![Tests](https://github.com/Breakthrough-Energy/PowerSimData/workflows/Pytest/badge.svg)
Expand Down
Empty file.
25 changes: 25 additions & 0 deletions powersimdata/design/compare/generation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from powersimdata.design.compare.helpers import _reindex_as_necessary
from powersimdata.input.check import _check_data_frame


def calculate_plant_difference(plant1, plant2):
"""Calculate the capacity differences between two plant data frames. If capacity in
``plant2`` is larger than capacity in ``plant1``, the return will be positive.

:param pandas.DataFrame plant1: first plant data frame.
:param pandas.DataFrame plant2: second plant data frame.
:return: (*pandas.DataFrame*) -- merged data frames with a new 'diff' column.
"""
_check_data_frame(plant1, "plant1")
_check_data_frame(plant2, "plant2")
# Reindex so that we don't get NaN when calculating upgrades for new generators
plant1, plant2 = _reindex_as_necessary(plant1, plant2, ["bus_id", "type"])
plant_merge = plant1.merge(
plant2, how="outer", right_index=True, left_index=True, suffixes=(None, "_2")
)
plant_merge["diff"] = plant_merge.Pmax_2.fillna(0) - plant_merge.Pmax.fillna(0)
# Ensure that lats & lons get filled in as necessary from plant2 entries
for l in ["lat", "lon"]:
plant_merge[l].fillna(plant_merge[f"{l}_2"], inplace=True)

return plant_merge
19 changes: 19 additions & 0 deletions powersimdata/design/compare/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def _reindex_as_necessary(df1, df2, check_columns):
"""Check for indices with mismatched entries in specified columns. If any entries
don't match, reindex based on these columns such that there are no shared indices
with mismatched entries in these columns.

:param pandas.DataFrame df1: data frame containing ``check_columns``.
:param pandas.DataFrame df2: data frame containing ``check_columns``.
:param iterable check_columns: column
:return: (*tuple*) -- data frames, reindexed as necessary.
"""
# Coerce to list for safety, since pandas interprets lists and tuples differently
check_columns_list = list(check_columns)
shared_indices = set(df1.index) & set(df2.index)
check1 = df1.loc[shared_indices, check_columns_list]
check2 = df2.loc[shared_indices, check_columns_list]
if not check1.equals(check2):
df1 = df1.set_index(keys=check_columns_list, drop=False, append=True)
df2 = df2.set_index(keys=check_columns_list, drop=False, append=True)
return df1, df2
61 changes: 61 additions & 0 deletions powersimdata/design/compare/transmission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from powersimdata.design.compare.helpers import _reindex_as_necessary
from powersimdata.input.check import _check_data_frame, _check_grid_type


def calculate_branch_difference(branch1, branch2):
"""Calculate the capacity differences between two branch data frames. If capacity in
``branch2`` is larger than capacity in ``branch1``, the return will be positive.

:param pandas.DataFrame branch1: first branch data frame.
:param pandas.DataFrame branch2: second branch data frame.
:param float/int difference_threshold: drop any changes less than this value from
the returned Series.
:return: (*pandas.Series*) -- capacity difference between the two branch data
frames.
"""
_check_data_frame(branch1, "branch1")
_check_data_frame(branch2, "branch2")
if not ("rateA" in branch1.columns) and ("rateA" in branch2.columns):
raise ValueError("branch1 and branch2 both must have 'rateA' columns")
branch1, branch2 = _reindex_as_necessary(
branch1, branch2, ["from_bus_id", "to_bus_id"]
)
branch_merge = branch1.merge(
branch2, how="outer", right_index=True, left_index=True, suffixes=(None, "_2")
)
branch_merge["diff"] = branch_merge.rateA_2.fillna(0) - branch_merge.rateA.fillna(0)
# Ensure that lats & lons get filled in as necessary from branch2 entries
for l in ["from_lat", "from_lon", "to_lat", "to_lon"]:
branch_merge[l].fillna(branch_merge[f"{l}_2"], inplace=True)

return branch_merge


def calculate_dcline_differences(grid1, grid2):
"""Calculate capacity differences between dcline tables, and add to/from lat/lon.

:param powersimdata.input.grid.Grid grid1: first grid instance.
:param powersimdata.input.grid.Grid grid2: second grid instance.
:return: (*pandas.DataFrame*) -- data frame with all indices, plus new columns:
diff, from_lat, from_lon, to_lat, to_lon.
"""
_check_grid_type(grid1)
_check_grid_type(grid2)
dcline1, dcline2 = _reindex_as_necessary(
grid1.dcline, grid2.dcline, ["from_bus_id", "to_bus_id"]
)
# Get latitudes and longitudes for to & from buses
for dcline, grid in [(dcline1, grid1), (dcline2, grid2)]:
dcline["from_lat"] = grid.bus.loc[dcline.from_bus_id, "lat"].to_numpy()
dcline["from_lon"] = grid.bus.loc[dcline.from_bus_id, "lon"].to_numpy()
dcline["to_lat"] = grid.bus.loc[dcline.to_bus_id, "lat"].to_numpy()
dcline["to_lon"] = grid.bus.loc[dcline.to_bus_id, "lon"].to_numpy()
dc_merge = dcline1.merge(
dcline2, how="outer", right_index=True, left_index=True, suffixes=(None, "_2")
)
dc_merge["diff"] = dc_merge.Pmax_2.fillna(0) - dc_merge.Pmax.fillna(0)
# Ensure that lats & lons get filled in as necessary from grid2.dcline entries
for l in ["from_lat", "from_lon", "to_lat", "to_lon"]:
dc_merge[l].fillna(dc_merge[f"{l}_2"], inplace=True)

return dc_merge
88 changes: 58 additions & 30 deletions powersimdata/design/investment/investment_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
import numpy as np
import pandas as pd

from powersimdata.design.compare.generation import calculate_plant_difference
from powersimdata.design.compare.transmission import (
calculate_branch_difference,
calculate_dcline_differences,
)
from powersimdata.design.investment import const
from powersimdata.design.investment.create_mapping_files import (
bus_to_neem_reg,
bus_to_reeds_reg,
)
from powersimdata.design.investment.inflation import calculate_inflation
from powersimdata.input.check import _check_grid_models_match
from powersimdata.input.grid import Grid
from powersimdata.utility.distance import haversine

Expand Down Expand Up @@ -40,29 +46,38 @@ def append_keep_index_name(df1, other, *args, **kwargs):
return new_df


def calculate_ac_inv_costs(scenario, sum_results=True, exclude_branches=None):
def calculate_ac_inv_costs(
scenario,
sum_results=True,
exclude_branches=None,
base_grid=None,
):
"""Calculate cost of upgrading AC lines and/or transformers in a scenario.
NEEM regions are used to find regional multipliers.

:param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
:param bool sum_results: whether to sum data frame for each branch type. Defaults to
True.
:param powersimdata.input.grid.Grid base_grid: a Grid to compare against. If None,
the grid model and interconnect from the ``scenario`` are used to instantiate a
corresponding unmodified Grid.
:return: (*dict*) -- keys are {'line_cost', 'transformer_cost'}, values are either
float if ``sum_results``, or pandas Series indexed by branch ID.
Whether summed or not, values are $USD, inflation-adjusted to today.
"""

base_grid = Grid(
scenario.info["interconnect"].split("_"), source=scenario.info["grid_model"]
)
grid = scenario.state.get_grid()
if base_grid is None:
base_grid = Grid(
scenario.info["interconnect"].split("_"), source=scenario.info["grid_model"]
)
else:
_check_grid_models_match(base_grid, grid)

# find upgraded AC lines
grid_new = cp.deepcopy(grid)
# Reindex so that we don't get NaN when calculating upgrades for new branches
base_grid.branch = base_grid.branch.reindex(grid_new.branch.index).fillna(0)
grid_new.branch.rateA = grid.branch.rateA - base_grid.branch.rateA
grid_new.branch = grid_new.branch[grid_new.branch.rateA != 0.0]
# find upgraded AC lines
capacity_difference = calculate_branch_difference(base_grid.branch, grid.branch)
grid_new.branch = grid.branch.assign(rateA=capacity_difference.diff)
grid_new.branch = grid_new.branch.query("rateA != 0.0")
if exclude_branches is not None:
present_exclude_branches = set(exclude_branches) & set(grid_new.branch.index)
grid_new.branch.drop(index=present_exclude_branches, inplace=True)
Expand Down Expand Up @@ -257,27 +272,32 @@ def get_branch_mult(x, bus_reg, ac_reg_mult, branch_lookup_alerted=set()):
return {"line_cost": lines.cost, "transformer_cost": transformers.cost}


def calculate_dc_inv_costs(scenario, sum_results=True):
def calculate_dc_inv_costs(scenario, sum_results=True, base_grid=None):
"""Calculate cost of upgrading HVDC lines in a scenario.

:param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
:param bool sum_results: whether to sum series to return total cost. Defaults to
True.
:param powersimdata.input.grid.Grid base_grid: a Grid to compare against. If None,
the grid model and interconnect from the ``scenario`` are used to instantiate a
corresponding unmodified Grid.
:return: (*pandas.Series/float*) -- cost of upgrading HVDC lines, in $USD,
inflation-adjusted to today. If ``sum_results``, a float is returned, otherwise
a Series.
"""
base_grid = Grid(
scenario.info["interconnect"].split("_"), source=scenario.info["grid_model"]
)
grid = scenario.state.get_grid()
if base_grid is None:
base_grid = Grid(
scenario.info["interconnect"].split("_"), source=scenario.info["grid_model"]
)
else:
_check_grid_models_match(base_grid, grid)

grid_new = cp.deepcopy(grid)
# Reindex so that we don't get NaN when calculating upgrades for new DC lines
base_grid.dcline = base_grid.dcline.reindex(grid_new.dcline.index).fillna(0)
# find upgraded DC lines
grid_new.dcline.Pmax = grid.dcline.Pmax - base_grid.dcline.Pmax
grid_new.dcline = grid_new.dcline[grid_new.dcline.Pmax != 0.0]
capacity_difference = calculate_dcline_differences(base_grid.dcline, grid.dcline)
grid_new.dcline = grid.dcline.assign(Pmax=capacity_difference.diff)
grid_new.dcline = grid_new.dcline.query("Pmax != 0.0")

costs = _calculate_dc_inv_costs(grid_new, sum_results)
return costs
Expand Down Expand Up @@ -329,7 +349,13 @@ def _calculate_single_line_cost(line, bus):
return 0.0


def calculate_gen_inv_costs(scenario, year, cost_case, sum_results=True):
def calculate_gen_inv_costs(
scenario,
year,
cost_case,
sum_results=True,
base_grid=None,
):
"""Calculate cost of upgrading generators in a scenario. ReEDS regions are used to
find regional multipliers.

Expand All @@ -339,6 +365,9 @@ def calculate_gen_inv_costs(scenario, year, cost_case, sum_results=True):
*'Conservative'*: generally higher costs, *'Advanced'*: generally lower costs
:param bool sum_results: whether to sum data frame for plant costs. Defaults to
True.
:param powersimdata.input.grid.Grid base_grid: a Grid to compare against. If None,
the grid model and interconnect from the ``scenario`` are used to instantiate a
corresponding unmodified Grid.
:return: (*pandas.Series*) -- Overnight generation investment cost.
If ``sum_results``, indices are technologies and values are total cost.
Otherwise, indices are IDs of plants (including storage, which is given
Expand All @@ -349,17 +378,19 @@ def calculate_gen_inv_costs(scenario, year, cost_case, sum_results=True):
are dropped. Wind and solar will need to be fixed based on the resource supply
curves.
"""

base_grid = Grid(
scenario.info["interconnect"].split("_"), source=scenario.info["grid_model"]
)
grid = scenario.state.get_grid()
if base_grid is None:
base_grid = Grid(
scenario.info["interconnect"].split("_"), source=scenario.info["grid_model"]
)
else:
_check_grid_models_match(base_grid, grid)

# Find change in generation capacity
grid_new = cp.deepcopy(grid)
# Reindex so that we don't get NaN when calculating upgrades for new generators
base_grid.plant = base_grid.plant.reindex(grid_new.plant.index).fillna(0)
grid_new.plant.Pmax = grid.plant.Pmax - base_grid.plant.Pmax
# Find change in generation capacity
capacity_difference = calculate_plant_difference(base_grid.plant, grid.plant)
grid_new.plant = grid.plant.assign(Pmax=capacity_difference.diff)
grid_new.plant = grid_new.plant.query("Pmax >= 0.01")
# Find change in storage capacity
# Reindex so that we don't get NaN when calculating upgrades for new storage
base_grid.storage["gen"] = base_grid.storage["gen"].reindex(
Expand All @@ -370,9 +401,6 @@ def calculate_gen_inv_costs(scenario, year, cost_case, sum_results=True):
)
grid_new.storage["gen"]["type"] = "storage"

# Drop small changes
grid_new.plant = grid_new.plant[grid_new.plant.Pmax > 0.01]

costs = _calculate_gen_inv_costs(grid_new, year, cost_case, sum_results)
return costs

Expand Down
19 changes: 19 additions & 0 deletions powersimdata/design/transmission/substations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def calculate_substation_capacity(grid):
"""For each substation in a grid, calculate the total substation transmission
capacity (in a transport model, ignoring power flow).

:param powersimdata.input.grid.Grid grid: a grid instance.
:return: (*pandas.Series*) -- index is substation IDs, value are total transmission
capacity (MW).
"""
# Get new branch data frame with 'from_sub' and 'to_sub' columns
branch = grid.branch.assign(
from_sub_id=grid.branch.from_bus_id.map(grid.bus2sub.sub_id),
to_sub_id=grid.branch.to_bus_id.map(grid.bus2sub.sub_id),
)
# Calculate total substation capacity for matching 'from_sub' branches
filtered_branch = branch.query("from_sub_id != to_sub_id")
from_cap = filtered_branch.groupby("from_sub_id").sum()["rateA"]
to_cap = filtered_branch.groupby("to_sub_id").sum()["rateA"]
total_capacities = from_cap.combine(to_cap, lambda x, y: x + y, fill_value=0)
return total_capacities
32 changes: 32 additions & 0 deletions powersimdata/design/transmission/tests/test_substations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pandas as pd

from powersimdata.design.transmission.substations import calculate_substation_capacity
from powersimdata.tests.mock_grid import MockGrid


def test_calculate_substation_capacity():
mock_sub = {"sub_id": [1, 2, 3, 4]}
mock_bus2sub = {"bus_id": [10, 20, 21, 30, 40], "sub_id": [1, 2, 2, 3, 4]}
mock_branch = {
"branch_id": [200, 400, 420, 600, 601, 1200],
"from_bus_id": [10, 40, 20, 20, 30, 30],
"to_bus_id": [20, 10, 21, 30, 20, 40],
"rateA": [1, 2, 4, 8, 16, 32],
}

mock_grid_data = {
"sub": mock_sub,
"bus2sub": mock_bus2sub,
"branch": mock_branch,
}
mock_grid = MockGrid(grid_attrs=mock_grid_data)
substation_capacity = calculate_substation_capacity(mock_grid)
expected_return = pd.Series(
{
1: 3,
2: 25,
3: 56,
4: 34,
}
)
assert substation_capacity.equals(expected_return)
Loading