Skip to content

Commit b325c91

Browse files
init
1 parent 4a09b88 commit b325c91

File tree

7 files changed

+3378
-311
lines changed

7 files changed

+3378
-311
lines changed

poetry.lock

Lines changed: 3087 additions & 308 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
File renamed without changes.

pp2psdm/conversion/__init__.py

Whitespace-only changes.

pp2psdm/conversion/pandapower.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from datetime import datetime, timedelta
2+
3+
import pandas as pd
4+
5+
from pypsdm.models.enums import (
6+
EntitiesEnum,
7+
RawGridElementsEnum,
8+
SystemParticipantsEnum,
9+
)
10+
from pypsdm.models.result.grid.node import NodeResult, NodesResult
11+
from pypsdm.models.result.power import PQResult
12+
from pypsdm.models.ts.types import ComplexPowerDict
13+
14+
# These are just some early quite specific conversions. Additional ones will be added as needed.
15+
16+
17+
def pp_load_to_participants_result(
18+
pp_net_file: str, # pp net xlsx export
19+
participant_p_file: str, # pp time series p_mw export
20+
participant_q_file: str, # pp time series q_mvar export
21+
psdm_load_file: str, # psdm load csv input
22+
start: datetime, # start of the time series simulation
23+
resolution: timedelta, # resolution of the time series simulation
24+
) -> ComplexPowerDict:
25+
return _pp_to_psdm_result(
26+
entity_type=SystemParticipantsEnum.LOAD,
27+
pp_net_file=pp_net_file,
28+
pp_sheet_name="load",
29+
pp_attribute_a_file=participant_p_file,
30+
psdm_attribute_a_name="p",
31+
pp_attribute_b_file=participant_q_file,
32+
psdm_attribute_b_name="q",
33+
psdm_entity_input_file=psdm_load_file,
34+
start=start,
35+
resolution=resolution,
36+
res_entity_class=PQResult,
37+
res_dict_class=ComplexPowerDict,
38+
)
39+
40+
41+
def pp_node_to_nodes_result(
42+
pp_net_file: str, # pp net xlsx export
43+
node_vm_file: str, # pp time series vm_pu export
44+
node_va_file: str, # pp time series va_degree export
45+
psdm_node_file: str, # psdm node csv input
46+
start: datetime, # start of the time series simulation
47+
resolution: timedelta, # resolution of the time series simulation
48+
) -> ComplexPowerDict:
49+
return _pp_to_psdm_result(
50+
entity_type=RawGridElementsEnum.NODE,
51+
pp_net_file=pp_net_file,
52+
pp_sheet_name="bus",
53+
pp_attribute_a_file=node_vm_file,
54+
psdm_attribute_a_name="v_mag",
55+
pp_attribute_b_file=node_va_file,
56+
psdm_attribute_b_name="v_ang",
57+
psdm_entity_input_file=psdm_node_file,
58+
start=start,
59+
resolution=resolution,
60+
res_entity_class=NodeResult,
61+
res_dict_class=NodesResult,
62+
)
63+
64+
65+
def _pp_to_psdm_result(
66+
entity_type: EntitiesEnum,
67+
pp_net_file: str,
68+
pp_sheet_name: str,
69+
pp_attribute_a_file: str,
70+
psdm_attribute_a_name: str,
71+
pp_attribute_b_file: str,
72+
psdm_attribute_b_name: str,
73+
psdm_entity_input_file: str,
74+
start: datetime,
75+
resolution: timedelta,
76+
res_entity_class,
77+
res_dict_class,
78+
):
79+
pp_entity = pd.read_excel(pp_net_file, sheet_name=pp_sheet_name, index_col=0)
80+
idx_to_id = pp_entity["name"].to_dict()
81+
psdm_entity = pd.read_csv(psdm_entity_input_file, sep=";", index_col=0)
82+
id_to_uuid = {id: uuid for uuid, id in psdm_entity["id"].to_dict().items()}
83+
idx_to_uuid = {idx: id_to_uuid[id] for idx, id in idx_to_id.items()}
84+
pp_attribute_a = pd.read_json(pp_attribute_a_file)
85+
pp_attribute_b = pd.read_json(pp_attribute_b_file)
86+
pp_attribute_a["time"] = pd.date_range(
87+
start=start, periods=len(pp_attribute_a), freq=resolution
88+
)
89+
pp_attribute_a.set_index("time", inplace=True)
90+
pp_attribute_b["time"] = pd.date_range(
91+
start=start, periods=len(pp_attribute_a), freq=resolution
92+
)
93+
pp_attribute_b.set_index("time", inplace=True)
94+
participants = {}
95+
96+
for ts in pp_attribute_a.columns:
97+
data = pd.concat(
98+
[
99+
pp_attribute_a[ts].rename(psdm_attribute_a_name),
100+
pp_attribute_b[ts].rename(psdm_attribute_b_name),
101+
],
102+
axis=1,
103+
)
104+
uuid = idx_to_uuid[ts]
105+
node_res = res_entity_class(
106+
type=entity_type, name=idx_to_id[ts], input_model=uuid, data=data
107+
)
108+
participants[uuid] = node_res
109+
110+
return res_dict_class(entity_type=entity_type, entities=participants)

pp2psdm/io/__init__.py

Whitespace-only changes.

pp2psdm/io/utils.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import os
2+
from datetime import datetime
3+
from enum import Enum
4+
from pathlib import Path
5+
from typing import Optional, Union
6+
7+
import pandas as pd
8+
from pandas import DataFrame
9+
from pandas.core.groupby.generic import DataFrameGroupBy
10+
11+
ROOT_DIR = os.path.abspath(__file__ + "/../../../")
12+
13+
14+
class DateTimePattern(Enum):
15+
UTC_TIME_PATTERN_EXTENDED = "%Y-%m-%dT%H:%M:%SZ"
16+
UTC_TIME_PATTERN = "%Y-%m-%dT%H:%MZ"
17+
PLAIN = "%Y-%m-%d %H:%M:%S"
18+
19+
20+
def get_absolute_path_from_project_root(path: str):
21+
if not isinstance(path, str):
22+
path = str(path)
23+
if path.startswith(ROOT_DIR):
24+
return path
25+
else:
26+
return Path(ROOT_DIR).joinpath(path)
27+
28+
29+
def get_absolute_path_from_working_dir(path: str | Path) -> Path:
30+
"""
31+
Given a path (as string or pathlib.Path), returns its absolute path based on
32+
the current working directory. If the path is already absolute, it's returned unchanged.
33+
34+
Args:
35+
- path (Union[str, Path]): The input path.
36+
37+
Returns:
38+
- Path: The absolute path as a pathlib.Path object.
39+
"""
40+
path_obj = Path(path)
41+
if path_obj.is_absolute():
42+
return path_obj
43+
return path_obj.resolve()
44+
45+
46+
def get_file_path(path: str | Path, file_name: str):
47+
return Path(path).resolve().joinpath(file_name)
48+
49+
50+
def read_csv(
51+
path: str | Path,
52+
file_name: str,
53+
delimiter: str | None = None,
54+
index_col: Optional[str] = None,
55+
) -> DataFrame:
56+
full_path = get_file_path(path, file_name)
57+
if not full_path.exists():
58+
raise IOError("File with path: " + str(full_path) + " does not exist")
59+
if index_col:
60+
return pd.read_csv(
61+
full_path, delimiter=delimiter, quotechar='"', index_col=index_col, compression="zip"
62+
)
63+
else:
64+
return pd.read_csv(full_path, delimiter=delimiter, quotechar='"')
65+
66+
67+
def to_date_time(zoned_date_time: str) -> datetime:
68+
"""
69+
Converts zoned date time string with format: "yyyy-MM-dd'T'HH:mm:ss[.S[S][S]]'Z'"
70+
e.g. '2022-02-01T00:15Z[UTC]' to python datetime
71+
72+
Args:
73+
zoned_date_time: The zoned date time string to convert.
74+
75+
Returns:
76+
The converted datetime object.
77+
"""
78+
if not zoned_date_time or not isinstance(zoned_date_time, str):
79+
raise ValueError(f"Unexpected date time string: {zoned_date_time}")
80+
try:
81+
year = int(zoned_date_time[0:4])
82+
month = int(zoned_date_time[5:7])
83+
day = int(zoned_date_time[8:10])
84+
hour = int(zoned_date_time[11:13])
85+
minute = int(zoned_date_time[14:16])
86+
except Exception:
87+
return pd.to_datetime(zoned_date_time)
88+
return datetime(year=year, month=month, day=day, hour=hour, minute=minute)
89+
90+
91+
def csv_to_grpd_df(
92+
file_name: str, simulation_data_path: str, delimiter: str | None = None
93+
) -> DataFrameGroupBy:
94+
"""
95+
Reads in a PSDM csv results file cleans it up and groups it by input_archive model.
96+
97+
Args:
98+
file_name: name of the file to read
99+
simulation_data_path: base directory of the result data
100+
delimiter: the csv delimiter
101+
102+
Returns:
103+
DataFrameGroupBy object of the file
104+
"""
105+
data = read_csv(simulation_data_path, file_name, delimiter)
106+
107+
if "uuid" in data.columns:
108+
data = data.drop(columns=["uuid"])
109+
return data.groupby(by="input_model")
110+
111+
112+
def check_filter(filter_start: Optional[datetime], filter_end: Optional[datetime]):
113+
if (filter_start or filter_end) and not (filter_start and filter_end):
114+
raise ValueError(
115+
"Both start and end of the filter must be provided if one is provided."
116+
)
117+
if (filter_start and filter_end) and (filter_start > filter_end):
118+
raise ValueError("Filter start must be before end.")
119+
120+
121+
def df_to_csv(
122+
df: DataFrame,
123+
path: Union[str, Path],
124+
file_name: str,
125+
mkdirs=False,
126+
delimiter: str = ",",
127+
index_label="uuid",
128+
datetime_pattern=DateTimePattern.UTC_TIME_PATTERN,
129+
):
130+
df = df.copy(deep=True)
131+
if isinstance(path, Path):
132+
path = str(path)
133+
file_path = get_file_path(path, file_name)
134+
if mkdirs:
135+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
136+
137+
bool_cols = []
138+
for col in df.columns:
139+
is_bool_col = df[col].dropna().apply(lambda x: isinstance(x, bool)).all()
140+
if is_bool_col:
141+
bool_cols.append(col)
142+
143+
# replace True with 'true' only in boolean columns
144+
df[bool_cols] = df[bool_cols].replace({True: "true", False: "false"})
145+
146+
if isinstance(df.index, pd.DatetimeIndex):
147+
df.index = df.index.strftime(datetime_pattern.value)
148+
149+
datetime_cols = df.select_dtypes(
150+
include=["datetime64[ns, UTC]", "datetime64"]
151+
).columns
152+
for col in datetime_cols:
153+
df[col] = df[col].apply(
154+
lambda x: x.strftime(datetime_pattern.value) if not pd.isnull(x) else x
155+
)
156+
157+
df.to_csv(file_path, index=True, index_label=index_label, sep=delimiter)
158+
159+
160+
def bool_converter(maybe_bool):
161+
if isinstance(maybe_bool, bool):
162+
return maybe_bool
163+
elif isinstance(maybe_bool, str) and maybe_bool.lower() in ["true", "false"]:
164+
return maybe_bool.lower() == "true"
165+
else:
166+
raise ValueError("Cannot convert to bool: " + str(maybe_bool))

pyproject.toml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
[tool.poetry]
2-
name = "python-project"
2+
name = "pp2psdm"
33
version = "0.1.0"
44
description = ""
5-
authors = ["Thomas <[email protected]>"]
5+
authors = ["Daniel <[email protected]>"]
66
readme = "README.md"
77

88
[tool.poetry.dependencies]
9-
python = "^3.10"
9+
python = "^3.11"
10+
jupyter = "^1.0.0"
11+
numba = "^0.59.0"
12+
networkx = "^3.2.1"
13+
scipy = "^1.12.0"
14+
numpy = "^1.26.4"
15+
packaging = "^23.2"
16+
tqdm = "^4.66.2"
17+
deepdiff = "^6.7.1"
18+
plotly = "^5.19.0"
19+
matplotlib = "^3.8.3"
20+
pandas = "~2.0.0"
21+
1022

1123
[tool.poetry.group.dev.dependencies]
1224
pytest = "^7.2.1"

0 commit comments

Comments
 (0)