diff --git a/draf/core/case_study.py b/draf/core/case_study.py index 786b92c..0193499 100644 --- a/draf/core/case_study.py +++ b/draf/core/case_study.py @@ -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 @@ -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 @@ -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) @@ -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. @@ -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.""" @@ -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, @@ -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() @@ -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 @@ -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] @@ -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. @@ -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], @@ -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()} diff --git a/draf/core/datetime_handler.py b/draf/core/datetime_handler.py new file mode 100644 index 0000000..6310bcb --- /dev/null +++ b/draf/core/datetime_handler.py @@ -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 diff --git a/draf/core/entity_stores.py b/draf/core/entity_stores.py index 9f3cba2..a17ddff 100644 --- a/draf/core/entity_stores.py +++ b/draf/core/entity_stores.py @@ -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) diff --git a/draf/core/params_prepping.py b/draf/core/params_prepping.py index ba678ee..f3f07d3 100644 --- a/draf/core/params_prepping.py +++ b/draf/core/params_prepping.py @@ -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: diff --git a/draf/core/scenario.py b/draf/core/scenario.py index f4257da..a7e4a94 100644 --- a/draf/core/scenario.py +++ b/draf/core/scenario.py @@ -5,6 +5,8 @@ import logging import math import pickle +import time +from pathlib import Path from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union import gurobipy as gp @@ -13,22 +15,23 @@ import pyomo.environ as pyo 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, Results, Vars from draf.core.mappings import GRB_OPT_STATUS, VAR_PAR from draf.core.params_prepping import Prepper from draf.plotting import ScenPlotter -from draf.prep.data_base import DataBase, ParDat +from draf.prep.data_base import ParDat logger = logging.getLogger(__name__) logger.setLevel(level=logging.WARN) -class Scenario(DrafBaseClass): +class Scenario(DrafBaseClass, DateTimeHandler): """An energy system configuration scenario. Args: - cs: CaseStudy object. id: Scenario Id string which must not start with a number. name: Scenario name string. doc: Scenario documentation string. @@ -36,13 +39,19 @@ class Scenario(DrafBaseClass): def __init__( self, - cs: "CaseStudy", + freq: str, + year: str, + country: str, + dtindex, + dtindex_custom, + t1: int, + t2: int, id: str = "", name: str = "", doc: str = "", coords: Optional[Tuple[float, float]] = None, + cs_name: str = "no_case_study", ): - self._cs = cs self.id = id self.name = name self.doc = doc @@ -54,14 +63,14 @@ def __init__( self.plot = ScenPlotter(sc=self) self.prep = Prepper(sc=self) self.vars = Vars() - self.year = cs.year - self.country = cs.country - self.freq = cs.freq - self.dtindex = cs.dtindex - self.dtindex_custom = cs.dtindex_custom - self._freq_unit = cs._freq_unit - self._t1 = cs._t1 - self._t2 = cs._t2 + self.year = year + self.country = country + self.freq = freq + self.cs_name = cs_name + self.dtindex = dtindex + self.dtindex_custom = dtindex_custom + self._t1 = t1 + self._t2 = t2 def __repr__(self): """Get overview of attributes of the scenario object.""" @@ -78,9 +87,21 @@ def __getstate__(self) -> Dict: """Remove objects with dependencies for serialization with pickle.""" d = self.__dict__.copy() d.pop("mdl", None) - d.pop("_cs", None) return d + def _set_time_trace(self): + self._time = time.time() + + def _get_time_diff(self): + return time.time() - self._time + + @property + def _res_fp(self) -> Path: + """Returns the path to the case study's default result directory.""" + fp = paths.RESULTS / self.cs_name + fp.mkdir(exist_ok=True) + return fp + @property def _all_ents_dict(self) -> Dict: """Returns a name:data Dict of all entities without meta data.""" @@ -93,7 +114,7 @@ def _all_ents_dict(self) -> Dict: def _set_default_solver_params(self) -> None: defaults = { - "LogFile": str(self._cs._res_fp / "gurobi.log"), + "LogFile": str(self._res_fp / "gurobi.log"), "LogToConsole": 1, "OutputFlag": 1, "MIPGap": 0.1, @@ -191,7 +212,7 @@ def set_params(self, params_builder_func: Callable) -> Scenario: """Executes the params builder function to fill the params object and the variables meta-informations. """ - self._cs._set_time_trace() + self._set_time_trace() try: params_builder_func(self) @@ -204,7 +225,7 @@ def set_params(self, params_builder_func: Callable) -> Scenario: self.param( "timelog_params_", - self._cs._get_time_diff(), + self._get_time_diff(), doc="Time for building the params", unit="seconds", ) @@ -229,16 +250,16 @@ def set_model( if self.mdl_language == "gp": self._set_default_solver_params() - self._cs._set_time_trace() + self._set_time_trace() self._activate_vars() self.param( "timelog_vars_", - self._cs._get_time_diff(), + self._get_time_diff(), doc="Time for building the variables", unit="seconds", ) - self._cs._set_time_trace() + self._set_time_trace() if speed_up and self.mdl_language == "gp": params = self.get_tuple_dict_container() @@ -251,7 +272,7 @@ def set_model( self.param( "timelog_model_", - self._cs._get_time_diff(), + self._get_time_diff(), doc="Time for building the model", unit="seconds", ) @@ -377,13 +398,13 @@ def _optimize_gurobipy( self, logToConsole, outputFlag, show_results, keep_vars, postprocess_func ): logger.info(f"Optimize {self.id}.") - self._cs._set_time_trace() + self._set_time_trace() self.mdl.setParam("LogToConsole", int(logToConsole), verbose=False) self.mdl.setParam("OutputFlag", int(outputFlag), verbose=False) self.mdl.optimize() self.param( "timelog_solve_", - self._cs._get_time_diff(), + self._get_time_diff(), doc="Time for solving the model", unit="seconds", ) @@ -423,16 +444,16 @@ def _optimize_pyomo( self, logToConsole, outputFlag, show_results, keep_vars, postprocess_func, which_solver ): logger.info(f"Optimize {self.id}.") - self._cs._set_time_trace() + self._set_time_trace() solver = pyo.SolverFactory(which_solver) - logfile = str(self._cs._res_fp / "pyomo.log") if outputFlag else None + logfile = str(self._res_fp / "pyomo.log") if outputFlag else None results = solver.solve(self.mdl, tee=logToConsole, load_solutions=False, logfile=logfile) self.param( "timelog_solve_", - self._cs._get_time_diff(), + self._get_time_diff(), doc="Time for solving the model", unit="seconds", ) @@ -479,7 +500,6 @@ def calculate_IIS(self): def _special_copy(self) -> Scenario: """Returns a deepcopy of this scenario that preserves the pointer to the case study.""" scen_copy = copy.deepcopy(self) - scen_copy._cs = self._cs scen_copy.plot.sc = scen_copy scen_copy.prep.sc = scen_copy return scen_copy @@ -492,7 +512,7 @@ def export_model(self, filetype="lp", fp=None): given, the file is saved in the case study's default result directory.""" date_time = self._get_now_string() if fp is None: - fp = self._cs._res_fp / f"{date_time}_{self.id}.{filetype}" + fp = self._res_fp / f"{date_time}_{self.id}.{filetype}" self.mdl.write(str(fp)) logger.info(f"written to {fp}") @@ -501,7 +521,7 @@ def save(self) -> None: """Saves the scenario to a pickle-file.""" date_time = self._get_now_string() - fp = self._cs._res_fp / f"{date_time}_{self.id}.p" + fp = self._res_fp / f"{date_time}_{self.id}.p" try: with open(fp, "wb") as f: @@ -513,11 +533,6 @@ def save(self) -> None: f"PicklingError {e}: Try deactivate Ipython's autoreload to save the scenario." ) - def trim_to_datetimeindex( - self, data: Union[pd.DataFrame, pd.Series] - ) -> Union[pd.DataFrame, pd.Series]: - return data[self._t1 : self._t2 + 1] - def get_xarray_dataset(self, include_vars: bool = True, include_params: bool = True): """Get an xarray dataset with all parameters and results.""" import xarray as xr @@ -568,9 +583,9 @@ def var( def _infer_dimension_from_name(self, name: str) -> Tuple[str, str, Union[float, pd.Series]]: if name == "T": - doc = f"{self._cs.freq} time steps" - unit = self._cs._freq_unit - data = self._cs.get_T() + doc = f"{self.freq} time steps" + unit = self.freq_unit + data = self.get_T() else: raise AttributeError(f"No infer options available for {name}") return doc, unit, data @@ -624,22 +639,13 @@ def update_params(self, **kwargs) -> Scenario: if not hasattr(self.params, ent_name): raise RuntimeError(f"The parameter {ent_name} you want to update does not exist.") - if self.fits_convention(ent_name, data): + if hp.fits_convention(ent_name, data): self.param(ent_name, data=data, update=True) else: self.param(ent_name, fill=data, update=True) return self - def fits_convention(self, ent_name: str, data: Union[int, float, pd.Series]) -> bool: - """If the naming-conventions apply for the data dimensions and the entity name """ - - dims = hp.get_dims(ent_name) - if isinstance(data, (int, float)): - return dims == "" - elif isinstance(data, pd.Series): - return data.index.nlevels == len(dims) - def param( self, name: Optional[str] = None, @@ -772,8 +778,9 @@ def get_CAP(self, which="CAPn", agg: bool = True) -> Dict[str, Union[float, pd.S agg: If True, multi-dimensional CAP entities are aggregated. """ d = dict() - _map = {"CAPn": "res", "CAPx": "params"} - container = getattr(self, _map[which]) + + assert which in ("CAPn", "CAPx"), "`which` need to be either CAPn or CAPx." + container = self.res if which == "CAPn" else self.params for n, v in container.get_all().items(): if which in n: diff --git a/draf/helper.py b/draf/helper.py index dd85071..49fd333 100644 --- a/draf/helper.py +++ b/draf/helper.py @@ -21,6 +21,16 @@ logger.setLevel(level=logging.CRITICAL) +def fits_convention(ent_name: str, data: Union[int, float, pd.Series]) -> bool: + """If the naming-conventions apply for the data dimensions and the entity name """ + + dims = get_dims(ent_name) + if isinstance(data, (int, float)): + return dims == "" + elif isinstance(data, pd.Series): + return data.index.nlevels == len(dims) + + def get_type(ent_name: str) -> str: return ent_name.split("_")[0] @@ -39,6 +49,10 @@ def get_dims(ent_name: str) -> str: return ent_name.split("_")[-1] +def get_step_width(freq: str) -> float: + return int_from_freq(freq) / 60 + + def datetime_to_int(freq: str, year: int, month: int, day: int) -> int: """Returns the index location in a whole-year date-time-index for the given date.""" dtindex = make_datetimeindex(year=year, freq=freq) diff --git a/draf/plotting/cs_plotting.py b/draf/plotting/cs_plotting.py index 4ae4bb1..6794419 100644 --- a/draf/plotting/cs_plotting.py +++ b/draf/plotting/cs_plotting.py @@ -201,8 +201,6 @@ def get_colors(c_dict: Dict) -> List: def pareto_curves( self, - save_file: bool = False, - filetype: str = "png", groups: List[str] = None, c_unit: Optional[str] = None, ce_unit: Optional[str] = None, @@ -212,14 +210,13 @@ def pareto_curves( ) -> go.Figure: """EXPERIMENTAL: Plot based on pareto() considering multiple pareto curve-groups.""" - def get_hover_text(sc): - ref = sc._cs.REF_scen - sav_C = ref.res.C_ - sc.res.C_ + def get_hover_text(sc, ref_scen): + sav_C = ref_scen.res.C_ - sc.res.C_ sav_C_fmted, unit_C = hp.auto_fmt(sav_C, sc.get_unit("C_")) - sav_C_rel = sav_C / ref.res.C_ - sav_CE = ref.res.CE_ - sc.res.CE_ + sav_C_rel = sav_C / ref_scen.res.C_ + sav_CE = ref_scen.res.CE_ - sc.res.CE_ sav_CE_fmted, unit_CE = hp.auto_fmt(sav_CE, sc.get_unit("CE_")) - sav_CE_rel = sav_CE / ref.res.CE_ + sav_CE_rel = sav_CE / ref_scen.res.CE_ return "
".join( [ @@ -303,7 +300,7 @@ def get_text(sc, label_verbosity) -> str: text=[get_text(sc, label_verbosity) for sc in scens_] if bool(label_verbosity) else None, - hovertext=[get_hover_text(sc) for sc in scens_], + hovertext=[get_hover_text(sc, ref_scen=cs.REF_scen) for sc in scens_], textposition="bottom center", marker=dict(size=12, color=c, showscale=False), name=ix, diff --git a/draf/plotting/scen_plotting.py b/draf/plotting/scen_plotting.py index 7a10e84..d9298af 100644 --- a/draf/plotting/scen_plotting.py +++ b/draf/plotting/scen_plotting.py @@ -39,7 +39,6 @@ class ScenPlotter(BasePlotter): def __init__(self, sc): self.figsize = (16, 4) self.sc = sc - self.cs = sc._cs self.notebook_mode: bool = self.script_type() == "jupyter" self.optimize_layout_for_reveal_slides = True @@ -55,8 +54,7 @@ def display(self, what: str = "p"): Args: what: 'v' for Variables, 'p' for Parameters. """ - sc = self.sc - dims_dic = sc._get_entity_store(what=what)._to_dims_dic(unstack_to_first_dim=1) + dims_dic = self.sc._get_entity_store(what=what)._to_dims_dic(unstack_to_first_dim=1) for dim, data in dims_dic.items(): if dim == "": data = pd.Series(data).to_frame(name="Scalar value") @@ -69,7 +67,6 @@ def heatmap_py( timeseries: Union[np.ndarray, pd.Series] = None, ent_name: str = None, title: Optional[str] = None, - show_title: bool = True, cmap: str = "OrRd", colorbar_label: str = "", ) -> go.Figure: @@ -92,7 +89,7 @@ def heatmap_py( layout = hp.optimize_plotly_layout_for_reveal_slides(layout) data = timeseries.values.reshape((self.cs.steps_per_day, -1), order="F")[:, :] - idx = self.cs.dated(timeseries).index + idx = self.sc.dated(timeseries).index data = go.Heatmap( x=pd.date_range(start=idx[0], end=idx[-1], freq="D"), z=data, @@ -125,7 +122,7 @@ def heatmap_line_py( raise Exception("No timeseries specified!") data = ser.values.reshape((self.cs.steps_per_day, -1), order="F")[:, :] - idx = self.cs.dated(ser).index + idx = self.sc.dated(ser).index trace1 = go.Scatter(x=idx, y=ser.values, line_width=1) @@ -147,9 +144,9 @@ def heatmap_line_py( ) fig.update_yaxes(title_text=colorbar_label, row=1, col=1) - fig.update_xaxes(title_text=f"Time [{self.cs._freq_unit}]", row=1, col=1) - fig.update_yaxes(title_text=f"Time [{self.cs._freq_unit}]", row=2, col=1) - fig.update_xaxes(title_text=f"Days of {self.cs.year}", row=2, col=1) + fig.update_xaxes(title_text=f"Time [{self.sc.freq_unit}]", row=1, col=1) + fig.update_yaxes(title_text=f"Time [{self.sc.freq_unit}]", row=2, col=1) + fig.update_xaxes(title_text=f"Days of {self.sc.year}", row=2, col=1) fig.append_trace(trace1, 1, 1) fig.append_trace(trace2, 2, 1) @@ -271,7 +268,7 @@ def line( data = self.sc.get_entity(ent_name) if hp.get_dims(ent_name) == "T": - data = self.cs.dated(data) + data = self.sc.dated(data) assert isinstance(data, pd.Series) return self._get_line_fig(data) @@ -298,13 +295,10 @@ def line_T(self, what: str = "v", dated: bool = True) -> go.Figure: what: 'v' for Variables, 'p' for Parameters. dated: If index has datetimes. """ - sc = self.sc - cs = sc._cs - - df = sc.get_var_par_dic(what)["T"] + df = self.sc.get_var_par_dic(what)["T"] if dated: - df = cs.dated(df) + df = self.sc.dated(df) ser = df.stack() ser.index = ser.index.rename(["T", "ent"]) @@ -378,10 +372,10 @@ def plot( data = sc.get_entity(ent_name) if t_end is None: - data = self.cs.dated(dims_dic[dims][which_ents[dims]], y_dtindex) + data = self.sc.dated(dims_dic[dims][which_ents[dims]], y_dtindex) data.plot(title=title_dim, **pltargs) else: - data = self.cs.dated( + data = self.sc.dated( dims_dic[dims][which_ents[dims]][:][t_start:t_end], y_dtindex ) data.plot(title=title_dim, **pltargs) @@ -390,10 +384,10 @@ def plot( for ent in which_ents[dims]: title_ent = f"{self.sc.id}: {ent} ({dim_str})" if t_end is None: - data = self.cs.dated(dims_dic[dims][ent].unstack(), y_dtindex) + data = self.sc.dated(dims_dic[dims][ent].unstack(), y_dtindex) data.plot(title=title_ent, **pltargs) else: - data = self.cs.dated( + data = self.sc.dated( dims_dic[dims][ent][t_start:t_end].unstack(), y_dtindex ) data.plot(title=title_ent, **pltargs) @@ -402,10 +396,10 @@ def plot( for ent in which_ents[dims]: title_ent = f"{self.sc}: {ent} ({dim_str})" if t_end is None: - data = self.cs.dated(dims_dic[dims][ent].unstack().unstack(), y_dtindex) + data = self.sc.dated(dims_dic[dims][ent].unstack().unstack(), y_dtindex) data.plot(title=title_ent, **pltargs) else: - data = self.cs.dated( + data = self.sc.dated( dims_dic[dims][ent][t_start:t_end].unstack().unstack(), y_dtindex ) data.plot(title=title_ent, **pltargs) @@ -608,7 +602,7 @@ def _plot_plotly_fig(self, fig, **kwargs): def _get_core_heatmap_fig( self, series, cmap, yaxis_factor: float, divergingNorm: bool = True, **imshow_kws ) -> Tuple: - steps_per_day = self.cs.steps_per_day + steps_per_day = self.sc.steps_per_day assert len(series) % steps_per_day == 0, ( f"Timeseries doesn't fit the steps per day. There are " @@ -645,7 +639,7 @@ def _get_core_heatmap_fig( ) ax.set(yticks=hours) - ax.set_ylabel(f"Time [{self.cs._freq_unit}]") + ax.set_ylabel(f"Time [{self.cs.freq_unit}]") ax.set_xlabel("Time [Days]", labelpad=10) ax.xaxis_date() ax.set_xticklabels(ax.get_xticklabels(), rotation=0, ha="center", rotation_mode="anchor") diff --git a/tests/core/test_case_study.py b/tests/core/test_case_study.py index 01895f9..bd94def 100644 --- a/tests/core/test_case_study.py +++ b/tests/core/test_case_study.py @@ -35,32 +35,14 @@ def test_step_width(freq: str, expected: float, case): assert case.step_width == expected -def test_get_T(case): - t_list = [] - for t in range(8760): - t_list.append(t) - assert case.get_T() == t_list - - @pytest.mark.parametrize( - "freq, steps, unit", [["15min", 96, "1/4 h"], ["30min", 24, "h"], ["60min", 24, "h"]] + "freq, steps, unit", [["15min", 96, "1/4 h"], ["30min", 48, "1/2 h"], ["60min", 24, "h"]] ) def test__set_dtindex(freq: str, steps: int, unit: str, case): case.freq = freq case._set_dtindex() assert case.steps_per_day == steps - assert case._freq_unit == unit - - -def test__set_time_trace(case): - case._set_time_trace() - expected = time.time() - assert case._time == expected - - -def test__get_time_diff(case): - case._set_time_trace() - assert time.time() - case._time == case._get_time_diff() + assert case.freq_unit == unit @pytest.mark.parametrize(