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

develop -> master #670

Merged
merged 68 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
0310977
fix: pass grid instead of grid.dcline (#645)
jenhagg Jun 3, 2022
e868b29
docs: fix docstring in grid module
rouille Apr 15, 2022
e7d0892
feat: add HIFLD grid class
danielolsen Apr 14, 2021
1d3752c
feat: factor out check_and_format_interconnect and interconnect_to_name
rouille Apr 15, 2022
dd529cf
chore: add 'hifld' to list of acceptable models for ModelImmutables
danielolsen Apr 14, 2021
2c95005
feat: factor out storage constants
rouille Apr 18, 2022
fa44338
feat: update Grid class to enable HIFLD grids
danielolsen Apr 14, 2021
4c483b2
feat: access model immutables for Europe
rouille Apr 26, 2022
cd5db67
test: add tests for interconnect_to_name
rouille May 23, 2022
ad1d161
chore: add zenodo_get to list of dependencies
rouille May 5, 2022
f7eb58e
fix: swap ERCOT and Texas in interconnect mapping
rouille May 4, 2022
7d1fcd0
feat: factor out check_model
rouille Apr 18, 2022
af67229
feat: add pypsa-eur grid model
rouille May 4, 2022
eac9ebd
fix: cast Int64Index single element to int
rouille May 6, 2022
5b7b004
feat: factor out plant constants
rouille Apr 18, 2022
839046a
test: add tests for area_to_loadzone
rouille May 10, 2022
b8607a8
feat: add state_abbr mapping
rouille May 9, 2022
e766abd
refactor: change access to storage and plant constants
rouille Apr 19, 2022
199a3a0
refactor: enable european grid model in check functions
rouille May 10, 2022
e147c62
fix: correct list of loadzones for Texas as state or interconnect
rouille May 10, 2022
3e6307f
fix: correct interconnects in test_helpers
rouille Apr 23, 2022
b839c9e
test: add tests for check functions
rouille May 10, 2022
c19b962
refactor: remove dependency on ModelImmutables object in create state
rouille May 5, 2022
c2a825b
feat: factor out zone constants
rouille Apr 23, 2022
f86a53e
feat: add division attribute in model immutables
rouille May 23, 2022
e2beffb
feat: add attributes to AbstractGrid class
rouille May 5, 2022
2577aac
Merge pull request #622 from Breakthrough-Energy/ben/mi
rouille Apr 29, 2022
3a57eee
refactor: use powerset function
rouille May 26, 2022
335e89b
refactor: move grid model and model immutables setting in their respe…
rouille May 5, 2022
3a014f5
feat: create an abstract grid class for model enclosed in CSV files (…
rouille May 3, 2022
54d1947
refactor: use get to access values in dictionary
rouille May 26, 2022
02725ed
Merge pull request #628 from Breakthrough-Energy/ben/mi
rouille May 16, 2022
578e430
Merge pull request #631 from Breakthrough-Energy/ben/eu
rouille Jun 1, 2022
f83d107
feat: use factory pattern to instantiate the builder from grid model …
rouille Jun 2, 2022
b965495
chore: remove hifld from list of supported model (#644)
rouille Jun 3, 2022
a55e278
Merge pull request #646 from Breakthrough-Energy/hifld
rouille Jun 5, 2022
e930df8
chore(deps): bump pillow from 9.1.0 to 9.1.1 (#647)
dependabot[bot] Jun 6, 2022
b897d25
refactor: move function and read all CSV files in the CSVReader object
rouille Jun 2, 2022
6b33cb8
docs: fix docstring in scenario_grid module
rouille Jun 3, 2022
b3762d2
Merge pull request #648 from Breakthrough-Energy/ben/grid
rouille Jun 6, 2022
ad072df
fix: add missing rename in grid to pypsa exporter (#649)
rouille Jun 7, 2022
b8452df
fix: typo in column name (#651)
jenhagg Jun 9, 2022
f97a975
refactor: move/rename logic used to build grid from CSV files
rouille Jun 7, 2022
667370e
refactor: move/rename logic used to build grid from REISE output
rouille Jun 8, 2022
73a88f0
refactor: move tests
rouille Jun 8, 2022
8ee9905
refactor: move/rename logic used to export to engines
rouille Jun 8, 2022
e2c5ddc
refactor: remove function used to add columns to data frames
rouille Jun 9, 2022
d1a411e
Merge pull request #650 from Breakthrough-Energy/ben/grid
rouille Jun 9, 2022
9aed52f
fix: add conversion factor for co2 emissions when exporting to pypsa …
FabianHofmann Jun 15, 2022
cbc13de
chore: use extra components PyPS-Eur files from zenodo (#655)
rouille Jun 28, 2022
fc4da59
chore: update fs-azureblob to support new storage account (#660)
jenhagg Jul 25, 2022
1ef96ca
fix: unindent methods in Analyze class
rouille Jul 27, 2022
5208add
docs: update list of data that can be loaded
rouille Jul 27, 2022
78f6d65
Merge pull request #661 from Breakthrough-Energy/ben/load
rouille Jul 28, 2022
7d22a66
fix: replace "energy_price" by "energy_value" in storage template (#663)
BainanXia Aug 1, 2022
4102634
feat: augment list of exported methods (#664)
rouille Aug 5, 2022
2dbdbfe
feat: add get_profile to MockAnalyze
rouille Aug 5, 2022
7393bdb
test: ensure get_profile is correctly implemented
rouille Aug 5, 2022
db36fcf
Merge pull request #665 from Breakthrough-Energy/ben/mock
rouille Aug 5, 2022
c470d02
refactor: simplify constructor parameters
jenhagg Jul 28, 2022
c8ec216
perf: pass minimal multifs based on the context
jenhagg Jul 28, 2022
768d5c4
fix: keep delayed evaluation of ssh fs
jenhagg Jul 28, 2022
b127e0a
feat: use new storage account
jenhagg Aug 5, 2022
748c6df
chore: update remote fs for scenario object
jenhagg Aug 5, 2022
d183e1d
fix: typo while renaming
jenhagg Aug 5, 2022
c51d095
docs: fix docstrings
jenhagg Aug 17, 2022
02dc955
Merge pull request #666 from Breakthrough-Energy/jen/blob
jenhagg Aug 17, 2022
8972008
chore: bump version to 0.5.4 (#669)
jenhagg Aug 17, 2022
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# This is specific to this package
powersimdata/utility/.server_user
config.ini
powersimdata/network/europe_tub/data/*

# The remainder of this file taken from github/gitignore
# https://github.com/github/gitignore/blob/master/Python.gitignore
Expand Down
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pytest = "*"
coverage = "*"
pytest-cov = "*"
pypsa = "*"
zenodo_get = "*"

[packages]
networkx = "~=2.5"
Expand All @@ -20,4 +21,4 @@ tqdm = "==4.29.1"
requests = "~=2.25"
fs = "==2.4.14"
"fs.sshfs" = "*"
fs-azureblob = "*"
fs-azureblob = ">=0.2.1"
1,119 changes: 605 additions & 514 deletions Pipfile.lock

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions powersimdata/data_access/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ class Context:
"""Factory for data access instances"""

@staticmethod
def get_data_access():
def get_data_access(make_fs=None):
"""Return a data access instance appropriate for the current
environment.

:param callable make_fs: a function that returns a filesystem instance, or
None to use a default
:return: (:class:`powersimdata.data_access.data_access.DataAccess`) -- a data access
instance
"""
root = server_setup.DATA_ROOT_DIR

if server_setup.DEPLOYMENT_MODE == DeploymentMode.Server:
return SSHDataAccess(root)
return LocalDataAccess(root)
if make_fs is None:
make_fs = lambda: None # noqa: E731
return SSHDataAccess(make_fs())
return LocalDataAccess()

@staticmethod
def get_launcher(scenario):
Expand Down
24 changes: 12 additions & 12 deletions powersimdata/data_access/data_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
class DataAccess:
"""Interface to a local or remote data store."""

def __init__(self, root):
def __init__(self):
"""Constructor"""
self.root = root
self.join = fs.path.join
self.local_fs = None

Expand Down Expand Up @@ -163,10 +162,10 @@ def push(self, file_name, checksum):
class LocalDataAccess(DataAccess):
"""Interface to shared data volume"""

def __init__(self, root=server_setup.LOCAL_DIR):
super().__init__(root)
self.local_fs = fs.open_fs(root)
self.fs = self._get_fs()
def __init__(self, _fs=None):
super().__init__()
self.local_fs = fs.open_fs(server_setup.LOCAL_DIR)
self.fs = _fs if _fs is not None else self._get_fs()

def _get_fs(self):
mfs = MultiFS()
Expand All @@ -193,18 +192,19 @@ def push(self, file_name, checksum):
class SSHDataAccess(DataAccess):
"""Interface to a remote data store, accessed via SSH."""

def __init__(self, root=server_setup.DATA_ROOT_DIR):
def __init__(self, _fs=None):
"""Constructor"""
super().__init__(root)
self._fs = None
super().__init__()
self.root = server_setup.DATA_ROOT_DIR
self._fs = _fs
self.local_fs = fs.open_fs(server_setup.LOCAL_DIR)

@property
def fs(self):
"""Get or create the filesystem object
"""Get or create a filesystem object, defaulting to a MultiFS that combines the
server and blob containers.

:raises IOError: if connection failed or still within retry window
:return: (*fs.multifs.MultiFS*) -- filesystem instance
:return: (*fs.base.FS*) -- filesystem instance
"""
if self._fs is None:
self._fs = get_multi_fs(self.root)
Expand Down
24 changes: 22 additions & 2 deletions powersimdata/data_access/fs_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ def get_blob_fs(container):
:param str container: the container name
:return: (*fs.base.FS*) -- filesystem instance
"""
account = "besciences"
return fs.open_fs(f"azblob://{account}@{container}")
account = "esmi"
sas_token = server_setup.BLOB_TOKEN_RO
return fs.open_fs(f"azblobv2://{account}:{sas_token}@{container}")


def get_ssh_fs(root=""):
Expand Down Expand Up @@ -49,3 +50,22 @@ def get_multi_fs(root):
remotes = ",".join([f[0] for f in mfs.iterate_fs()])
print(f"Initialized remote filesystem with {remotes}")
return mfs


def get_scenario_fs():
"""Create filesystem combining the server (if connected) with blob storage,
prioritizing the server if connected.

:return: (*fs.base.FS*) -- filesystem instance
"""
scenario_data = get_blob_fs("scenariodata")
mfs = MultiFS()
try:
ssh_fs = get_ssh_fs(server_setup.DATA_ROOT_DIR)
mfs.add_fs("ssh_fs", ssh_fs, write=True, priority=2)
except: # noqa
print("Could not connect to ssh server")
mfs.add_fs("scenario_fs", scenario_data, priority=1)
remotes = ",".join([f[0] for f in mfs.iterate_fs()])
print(f"Initialized remote filesystem with {remotes}")
return mfs
4 changes: 1 addition & 3 deletions powersimdata/design/investment/investment_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,7 @@ def calculate_dc_inv_costs(scenario, sum_results=True, base_grid=None):
_check_grid_models_match(base_grid, grid_differences)

# find upgraded DC lines
capacity_difference = calculate_dcline_difference(
base_grid.dcline, grid_differences.dcline
)
capacity_difference = calculate_dcline_difference(base_grid, grid_differences)
grid_differences.dcline = grid_differences.dcline.assign(
Pmax=capacity_difference["diff"].to_numpy()
)
Expand Down
4 changes: 1 addition & 3 deletions powersimdata/design/transmission/statelines.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from powersimdata.network.usa_tamu.constants.zones import id2abv


def classify_interstate_intrastate(scenario):
"""Classifies branches in a change_table as interstate or intrastate.

Expand All @@ -27,6 +24,7 @@ def _classify_interstate_intrastate(ct, grid):
"""

branch = grid.branch
id2abv = grid.model_immutables.zones["id2abv"]
upgraded_branches = {"interstate": [], "intrastate": []}

if "branch" not in ct or "branch_id" not in ct["branch"]:
Expand Down
4 changes: 3 additions & 1 deletion powersimdata/input/abstract_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def __init__(self):
self.bus = pd.DataFrame()
self.branch = pd.DataFrame()
self.storage = storage_template()
self.grid_model = ""
self.model_immutables = None


def storage_template():
Expand All @@ -38,7 +40,7 @@ def storage_template():
"InEff": None,
"OutEff": None,
"LossFactor": None, # stored energy fraction / hour
"energy_price": None, # $/MWh
"energy_value": None, # $/MWh
"terminal_min": None,
"terminal_max": None,
}
Expand Down
39 changes: 16 additions & 23 deletions powersimdata/input/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def _check_connected_components(grid, error_messages):
num_connected_components = len([c for c in nx.connected_components(g)])
if len(grid.interconnect) == 1:
# Check for e.g. ['USA'] interconnect, which is really three interconnects
interconnect_aliases = grid.model_immutables.zones["interconnect_combinations"]
interconnect_aliases = grid.model_immutables.zones["name2interconnect"]
if grid.interconnect[0] in interconnect_aliases:
num_interconnects = len(interconnect_aliases[grid.interconnect[0]])
else:
Expand Down Expand Up @@ -299,19 +299,18 @@ def _check_grid_type(grid):
raise TypeError(f"grid must be a {_grid.Grid} object")


def _check_areas_and_format(areas, grid_model="usa_tamu"):
def _check_areas_and_format(areas, mi=ModelImmutables("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.
:param powersimdata.network.model.ModelImmutables mi: immutables of a 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/Country abbreviations are converted to
state/country names.
"""
mi = ModelImmutables(grid_model)
if isinstance(areas, str):
areas = {areas}
elif isinstance(areas, (list, set, tuple)):
Expand All @@ -322,34 +321,28 @@ def _check_areas_and_format(areas, grid_model="usa_tamu"):
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 = (
mi.zones["loadzone"]
| mi.zones["abv"]
| mi.zones["state"]
| mi.zones["interconnect"]
)
all_areas = set().union(*(mi.zones[z] for z in mi.zones["mappings"]))
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 mi.zones["abv"]]
for a in abv_in_areas:
areas.remove(a)
areas.add(mi.zones["abv2state"][a])
areas.add(mi.zones[f"abv2{mi.zones['division']}"][a])

return areas


def _check_resources_and_format(resources, grid_model="usa_tamu"):
def _check_resources_and_format(resources, mi=ModelImmutables("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.
:param powersimdata.network.model.ModelImmutables mi: immutables of a 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 @@ -366,17 +359,17 @@ def _check_resources_and_format(resources, grid_model="usa_tamu"):
return resources


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

:param powersimdata.network.model.ModelImmutables mi: immutables of a grid model.
: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
"""
mi = ModelImmutables(grid_model)
resources = _check_resources_and_format(resources, grid_model=grid_model)
resources = _check_resources_and_format(resources, mi=mi)
if not resources <= mi.plants["renewable_resources"]:
diff = resources - mi.plants["all_resources"]
raise ValueError("invalid renewable resource(s): %s" % " | ".join(diff))
Expand Down
71 changes: 71 additions & 0 deletions powersimdata/input/converter/csv_to_grid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os

from powersimdata.input.abstract_grid import AbstractGrid
from powersimdata.input.converter.helpers import (
add_coord_to_grid_data_frames,
add_zone_to_grid_data_frames,
)
from powersimdata.network.constants.model import model2region
from powersimdata.network.csv_reader import CSVReader


class FromCSV(AbstractGrid):
"""Grid Builder for grid model enclosed in CSV files."""

def _set_data_loc(self, top_dirname):
"""Sets data location.

:param str top_dirname: name of directory enclosing data.
:raises IOError: if directory does not exist.
"""
data_loc = os.path.join(top_dirname, "data")
if os.path.isdir(data_loc) is False:
raise IOError("%s directory not found" % data_loc)
else:
self.data_loc = data_loc

def _build(self, interconnect, grid_model):
"""Build network.

:param list interconnect: interconnect name(s).
:param str model: the grid model.
"""
reader = CSVReader(self.data_loc)
self.bus = reader.bus
self.plant = reader.plant
self.branch = reader.branch
self.dcline = reader.dcline
self.gencost["after"] = self.gencost["before"] = reader.gencost
self.sub = reader.sub
self.bus2sub = reader.bus2sub
self.id2zone = reader.zone["zone_name"].to_dict()
self.zone2id = {v: k for k, v in self.id2zone.items()}

self._add_information()

if model2region[grid_model] not in interconnect:
self._drop_interconnect(interconnect)

def _add_information(self):
add_zone_to_grid_data_frames(self)
add_coord_to_grid_data_frames(self)

def _drop_interconnect(self, interconnect):
"""Trim data frames to only keep information pertaining to the user
defined interconnect(s).

:param list interconnect: interconnect name(s).
"""
for key, value in self.__dict__.items():
if key in ["sub", "bus2sub", "bus", "plant", "branch"]:
value.query("interconnect == @interconnect", inplace=True)
elif key == "gencost":
value["before"].query("interconnect == @interconnect", inplace=True)
elif key == "dcline":
value.query(
"from_interconnect == @interconnect &"
"to_interconnect == @interconnect",
inplace=True,
)
self.id2zone = {k: self.id2zone[k] for k in self.bus.zone_id.unique()}
self.zone2id = {value: key for key, value in self.id2zone.items()}
Loading