Skip to content

Commit

Permalink
Make Scenario independent from CaseStudy
Browse files Browse the repository at this point in the history
  • Loading branch information
mfleschutz committed Aug 11, 2021
1 parent d8599d1 commit 9882a9e
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 194 deletions.
107 changes: 17 additions & 90 deletions draf/core/case_study.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import logging
import pickle
import textwrap
import time
from collections import OrderedDict
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
Expand All @@ -15,6 +14,7 @@

from draf import helper as hp
from draf import paths
from draf.core.datetime_handler import DateTimeHandler
from draf.core.draf_base_class import DrafBaseClass
from draf.core.entity_stores import Dimensions, Params, Scenarios
from draf.core.params_prepping import Prepper
Expand All @@ -40,7 +40,6 @@ def open_casestudy(fp) -> CaseStudy:
cs.__dict__ = _load_pickle_object(fp).__dict__
cs.plot = CsPlotter(cs=cs)
for sc in cs.scens.get_all().values():
sc._cs = cs
sc.plot = ScenPlotter(sc=sc)
sc.prep = Prepper(sc=sc)

Expand All @@ -58,7 +57,7 @@ def open_latest_casestudy(name: str, verbose: bool = True) -> CaseStudy:
return open_casestudy(fp)


class CaseStudy(DrafBaseClass):
class CaseStudy(DrafBaseClass, DateTimeHandler):
"""Contains all relevant information for a case study: timeranges, optimization models,
scenarios and functions.
Expand Down Expand Up @@ -108,19 +107,6 @@ def __repr__(self):
main = "\n".join(l)
return f"{preface}\n{main}"

def _set_year(self, year: int):
assert year in range(1980, 2100)
self.year = year
self._set_dtindex()
self._t1 = 0
self._t2 = self.dtindex.size - 1 # =8759 for a normal year

@property
def step_width(self) -> float:
"""Returns the step width of the current datetimeindex.
e.g. 0.25 for a frequency of 15min."""
return float(int(self.freq[:2]) / 60)

@property
def scens_ids(self) -> List[str]:
"""Returns a list of all scenario IDs."""
Expand Down Expand Up @@ -183,17 +169,6 @@ def REF_scen(self) -> Scenario:
pass
return None

@property
def dt_info(self) -> str:
"""Get an info string of the chosen time horizon of the case study."""
t1_str = f"{self.dtindex_custom[0].day_name()}, {self.dtindex_custom[0]}"
t2_str = f"{self.dtindex_custom[-1].day_name()}, {self.dtindex_custom[-1]}"
return (
f"t1 = {self._t1:<5} ({t1_str}),\n"
f"t2 = {self._t2:<5} ({t2_str})\n"
f"Length = {self.dtindex_custom.size}"
)

def set_solver_params(self, **kwargs) -> CaseStudy:
"""Set some gurobi solver parameters e.g.:
LogFile,
Expand All @@ -211,10 +186,6 @@ def set_solver_params(self, **kwargs) -> CaseStudy:

return self

def get_T(self) -> List:
"""Returns the set T from case-study configuration."""
return list(range(self._t1, self._t2 + 1))

def activate_vars(self) -> None:
for sc in self.scens_list:
sc._activate_vars()
Expand Down Expand Up @@ -251,7 +222,20 @@ def add_scen(
id = f"sc{len(self.scens_list)}"

if based_on is None:
sc = Scenario(cs=self, id=id, name=name, doc=doc, coords=self.coords)
sc = Scenario(
id=id,
name=name,
doc=doc,
coords=self.coords,
year=self.year,
country=self.country,
freq=self.freq,
cs_name=self.name,
dtindex=self.dtindex,
dtindex_custom=self.dtindex_custom,
t1=self._t1,
t2=self._t2,
)
else:
sc = getattr(self.scens, based_on)._special_copy()
sc.id = id
Expand Down Expand Up @@ -307,8 +291,7 @@ def add_scens(
self.scen_vars = scen_vars

names_long, names_short, value_lists = zip(*scen_vars)
idx = pd.MultiIndex.from_product(value_lists, names=names_long)
df = pd.DataFrame(index=idx)
df = pd.DataFrame(index=pd.MultiIndex.from_product(value_lists, names=names_long))
df = df.reset_index()
dfn = df.T.astype(str).apply(lambda x: names_short + x)
df.index = ["_".join(dfn[x].values) for x in dfn]
Expand Down Expand Up @@ -345,23 +328,6 @@ def _load_cs_from_file(self, fp: str) -> Dict:
cs = pickle.load(f)
return cs

def _set_dtindex(self) -> None:
self.dtindex = hp.make_datetimeindex(year=self.year, freq=self.freq)
self.dtindex_custom = self.dtindex

if self.freq == "15min":
self.steps_per_day = 96
self._freq_unit = "1/4 h"
elif self.freq == "60min":
self.steps_per_day = 24
self._freq_unit = "h"

def _set_time_trace(self):
self._time = time.time()

def _get_time_diff(self):
return time.time() - self._time

def save(self, name: str = "", fp: Any = None):
"""Saves the CaseStudy object to a pickle-file. The current timestamp is used for a
unique file-name.
Expand Down Expand Up @@ -391,22 +357,6 @@ def save(self, name: str = "", fp: Any = None):
size = hp.sizeof_fmt(fp.stat().st_size)
print(f"CaseStudy saved to {fp.as_posix()} ({size})")

def _get_datetime_int_loc_from_string(self, s: str) -> int:
return self.dtindex.get_loc(f"{self.year}-{s}")

def _get_integer_locations(self, start, steps, end) -> Tuple[int, int]:
t1 = self._get_datetime_int_loc_from_string(start) if isinstance(start, str) else start
if steps is not None and end is None:
assert t1 + steps < self.dtindex.size, "Too many steps are given."
t2 = t1 + steps - 1
elif steps is None and end is not None:
t2 = self._get_datetime_int_loc_from_string(end) if isinstance(end, str) else end
elif steps is None and end is None:
t2 = self.dtindex.size - 1
else:
raise ValueError("One of steps or end must be given.")
return t1, t2

def set_time_horizon(
self,
start: Union[int, str],
Expand Down Expand Up @@ -533,29 +483,6 @@ def optimize(

return self

def dated(
self, df: Union[pd.Series, pd.DataFrame], activated=True
) -> Union[pd.Series, pd.DataFrame]:
"""Add datetime index to a data entity. The frequency and year are taken from the casestudy.
Args:
df: A pandas data entity.
activated: If False, the df is returned without modification.
"""
try:
dtindex_to_use = self.dtindex[df.index.min() : df.index.max() + 1]
if activated:
if isinstance(df, pd.DataFrame):
return df.set_index(dtindex_to_use, inplace=False)
elif isinstance(df, pd.Series):
return df.set_axis(dtindex_to_use, inplace=False)
else:
return df
except TypeError as e:
logger.warning(f"Dated function could not add date-time index to data: {e}")
return df

def get_ent(self, ent_name: str) -> Dict:
"""Returns the data of an entity for all scenarios."""
return {name: sc.get_entity(ent_name) for name, sc in self.scens_dic.items()}
Expand Down
104 changes: 104 additions & 0 deletions draf/core/datetime_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import logging
from abc import ABC
from typing import List, Tuple, Union

import pandas as pd

from draf import helper as hp

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.WARN)


class DateTimeHandler(ABC):
@property
def step_width(self) -> float:
"""Returns the step width of the current datetimeindex.
e.g. 0.25 for a frequency of 15min."""
return hp.get_step_width(self.freq)

@property
def dt_info(self) -> str:
"""Get an info string of the chosen time horizon of the case study."""
t1_str = f"{self.dtindex_custom[0].day_name()}, {self.dtindex_custom[0]}"
t2_str = f"{self.dtindex_custom[-1].day_name()}, {self.dtindex_custom[-1]}"
return (
f"t1 = {self._t1:<5} ({t1_str}),\n"
f"t2 = {self._t2:<5} ({t2_str})\n"
f"Length = {self.dtindex_custom.size}"
)

def _set_dtindex(self) -> None:
self.dtindex = hp.make_datetimeindex(year=self.year, freq=self.freq)
self.dtindex_custom = self.dtindex

@property
def steps_per_day(self):
steps_per_hour = 60 / hp.int_from_freq(self.freq)
return steps_per_hour * 24

@property
def freq_unit(self):
if self.freq == "15min":
return "1/4 h"
elif self.freq == "30min":
return "1/2 h"
elif self.freq == "60min":
return "h"

def get_T(self) -> List:
"""Returns the set T from dtindex configuration."""
return list(range(self._t1, self._t2 + 1))

def trim_to_datetimeindex(
self, data: Union[pd.DataFrame, pd.Series]
) -> Union[pd.DataFrame, pd.Series]:
return data[self._t1 : self._t2 + 1]

def _set_year(self, year: int) -> None:
assert year in range(1980, 2100)
self.year = year
self._set_dtindex()
self._t1 = 0
self._t2 = self.dtindex.size - 1 # =8759 for a normal year

def _get_datetime_int_loc_from_string(self, s: str) -> int:
return self.dtindex.get_loc(f"{self.year}-{s}")

def _get_integer_locations(self, start, steps, end) -> Tuple[int, int]:
t1 = self._get_datetime_int_loc_from_string(start) if isinstance(start, str) else start
if steps is not None and end is None:
assert t1 + steps < self.dtindex.size, "Too many steps are given."
t2 = t1 + steps - 1
elif steps is None and end is not None:
t2 = self._get_datetime_int_loc_from_string(end) if isinstance(end, str) else end
elif steps is None and end is None:
t2 = self.dtindex.size - 1
else:
raise ValueError("One of steps or end must be given.")
return t1, t2

def dated(
self, df: Union[pd.Series, pd.DataFrame], activated=True
) -> Union[pd.Series, pd.DataFrame]:
"""Add datetime index to a data entity.
The frequency and year are taken from the CaseStudy or the Scenario object.
Args:
df: A pandas data entity.
activated: If False, the df is returned without modification.
"""
try:
dtindex_to_use = self.dtindex[df.index.min() : df.index.max() + 1]
if activated:
if isinstance(df, pd.DataFrame):
return df.set_index(dtindex_to_use)
elif isinstance(df, pd.Series):
return df.set_axis(dtindex_to_use)
else:
return df
except TypeError as e:
Logger.warning(f"Dated function could not add date-time index to data: {e}")
return df
3 changes: 3 additions & 0 deletions draf/core/entity_stores.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ def __init__(self, sc: "Scenario"):
super().__init__()
self._get_results_from_variables(sc=sc)

# TODO: Move to _get_results_from_variables, _from_gurobipy, _from_pyomo to Scenario for
# better type hinting

def _get_results_from_variables(self, sc: "Scenario") -> None:
if sc.mdl_language == "gp":
self._from_gurobipy(sc)
Expand Down
6 changes: 3 additions & 3 deletions draf/core/params_prepping.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ def c_GRID_PP_T(self, name: str = "c_GRID_PP_T", method: str = "PP") -> pd.Serie
name=name,
unit="€/kWh_el",
doc=f"Marginal Costs {sc.year}, {sc.freq}, {sc.country}",
data=get_prices(year=sc.year, freq=sc.freq, country=sc.country, method=method)[
sc._t1 : sc._t2 + 1
],
data=sc.trim_to_datetimeindex(
get_prices(year=sc.year, freq=sc.freq, country=sc.country, method=method)
),
)

def c_GRID_PWL_T(self, name: str = "c_GRID_PWL_T", method: str = "PWL", **kwargs) -> pd.Series:
Expand Down
Loading

0 comments on commit 9882a9e

Please sign in to comment.