Coverage for pyrc \ core \ settings.py: 79%
105 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-13 16:59 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-13 16:59 +0200
1# -------------------------------------------------------------------------------
2# Copyright (C) 2026 Joel Kimmich, Tim Jourdan
3# ------------------------------------------------------------------------------
4# License
5# This file is part of PyRC, distributed under GPL-3.0-or-later.
6# ------------------------------------------------------------------------------
8import os
9from collections.abc import Callable
10from copy import copy
11from typing import Any, Optional
13import numpy as np
15from pyrc.dataHandler.weather import WeatherData
16from pyrc.core.wall import Wall
19class SolveSettings:
20 def __init__(
21 self,
22 max_saved_steps=5e4,
23 method="RK45",
24 max_step=0.4,
25 rtol=1e-7,
26 atol=1e-2,
27 save_interval=1,
28 minimize_ram_usage=True,
29 ):
30 """
32 Parameters
33 ----------
34 max_saved_steps : int, optional
35 The maximum number of seconds that are simulated in one solve_ivp call.
36 Defines the batch size.
37 method : str, optional
38 The method of the solver. See solve_ivp
39 max_step : int | float, optional
40 The maximum number of seconds that the solver can use as one step.
41 rtol, atol : float or array_like, optional
42 Relative and absolute tolerances. The solver keeps the local error
43 estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
44 relative accuracy (number of correct digits), while `atol` controls
45 absolute accuracy (number of correct decimal places). To achieve the
46 desired `rtol`, set `atol` to be smaller than the smallest value that
47 can be expected from ``rtol * abs(y)`` so that `rtol` dominates the
48 allowable error. If `atol` is larger than ``rtol * abs(y)`` the
49 number of correct digits is not guaranteed. Conversely, to achieve the
50 desired `atol` set `rtol` such that ``rtol * abs(y)`` is always smaller
51 than `atol`. If components of y have different scales, it might be
52 beneficial to set different `atol` values for different components by
53 passing array_like with shape (n,) for `atol`. Default values are
54 1e-3 for `rtol` and 1e-6 for `atol`. [Copied from scipy.solve_ivp doc]
55 save_interval : int, optional
56 In which interval the solution should be saved.
57 One interval is as long as max_saved_steps.
58 minimize_ram_usage : bool, optional
59 If True and a save_path is given to the RCNetwork it occasionally deletes the solution after saving to
60 disk to free RAM.
61 """
62 self.max_saved_steps: int = int(max_saved_steps)
63 self.method = method
64 self.max_step = max_step
65 self.rtol = rtol
66 self.atol = atol
67 self.save_interval = save_interval
68 self.minimize_ram_usage = minimize_ram_usage
70 @property
71 def keys(self):
72 return [
73 "max_saved_steps",
74 "method",
75 "max_step",
76 "rtol",
77 "atol",
78 "save_interval",
79 "minimize_ram_usage",
80 ]
82 @property
83 def values(self):
84 return [
85 self.max_saved_steps,
86 self.method,
87 self.max_step,
88 self.rtol,
89 self.atol,
90 self.save_interval,
91 self.minimize_ram_usage,
92 ]
94 @property
95 def dict(self):
96 return {k: v for k, v in zip(self.keys, self.values)}
98 def __copy__(self):
99 return SolveSettings(**{k: copy(v) for k, v in zip(self.keys, self.values)})
102class Settings:
103 """
104 Class for composition. Every object can get the same object of this class to determine global settings.
106 In the future a settings yaml could be used and loaded in.
107 """
109 def __init__(
110 self,
111 calculate_static,
112 use_weather_data,
113 wall: Wall = Wall(),
114 start_date="2022-01-01T00:00:00", # if this initial value is changed you have to change the
115 # Parameterization class values, too (it uses it hardcoded)
116 weather_data_path=None,
117 maximum_area_specific_power=400,
118 save_folder_path=None,
119 save_all_x_seconds=25, # TODO: Maybe use not this value if not given but save 1000 steps for each simulation
120 solve_settings: SolveSettings | Any = None,
121 ):
122 self.calculate_static = calculate_static
123 self.wall = wall
124 self.__start_date = start_date
125 self.__use_weather_data: bool = use_weather_data
126 self.__weather_data_path = os.path.normpath(weather_data_path) if weather_data_path is not None else None
127 self.weather_data: Optional["WeatherData"] | Any = None
128 self.__rebuild_weather_data()
130 self._area_specific_radiation_interpolator_short = None
131 self._area_specific_radiation_interpolator_long = None
132 # only used if weather data is not used
133 self.maximum_area_specific_power = maximum_area_specific_power
135 self.__save_folder_path = None
136 self.save_folder_path = save_folder_path
138 self.save_all_x_seconds = save_all_x_seconds
140 if solve_settings is None:
141 solve_settings = SolveSettings()
142 self.solve_settings: SolveSettings = solve_settings
144 def __rebuild_weather_data(self) -> None:
145 """(Re)create WeatherData if enabled and fully configured; otherwise set to None."""
146 if not self.__use_weather_data:
147 self.weather_data = None
148 return
150 if self.__weather_data_path is None:
151 raise ValueError("use_weather_data=True requires weather_data_path to be set.")
153 self.weather_data = WeatherData(
154 self.__weather_data_path,
155 start_time=self.__start_date,
156 )
158 def __copy__(self):
159 obj = Settings(
160 calculate_static=self.calculate_static,
161 use_weather_data=False,
162 wall=copy(self.wall),
163 start_date=self.start_date,
164 weather_data_path=self.weather_data_path,
165 maximum_area_specific_power=self.maximum_area_specific_power,
166 save_folder_path=self.save_folder_path,
167 save_all_x_seconds=self.save_all_x_seconds,
168 solve_settings=copy(self.solve_settings),
169 )
170 obj.use_weather_data = self.use_weather_data
171 return obj
173 def __deepcopy__(self):
174 return self.__copy__()
176 @property
177 def save_folder_path(self):
178 return self.__save_folder_path
180 @save_folder_path.setter
181 def save_folder_path(self, value):
182 if value is not None:
183 os.makedirs(os.path.normpath(value), exist_ok=True)
184 self.__save_folder_path = value
186 @property
187 def start_date(self) -> str:
188 return self.__start_date
190 @start_date.setter
191 def start_date(self, value: str) -> None:
192 self.__start_date = value
193 self.__rebuild_weather_data()
195 @property
196 def weather_data_path(self) -> Optional[str]:
197 return self.__weather_data_path
199 @weather_data_path.setter
200 def weather_data_path(self, value: Optional[str]) -> None:
201 self.__weather_data_path = os.path.normpath(value) if value is not None else None
202 self.__rebuild_weather_data()
204 @property
205 def use_weather_data(self) -> bool:
206 return self.__use_weather_data
208 @use_weather_data.setter
209 def use_weather_data(self, value: bool) -> None:
210 self.__use_weather_data = value
211 self.__rebuild_weather_data()
213 @property
214 def area_specific_radiation_interpolator_short(self) -> Callable | Any:
215 if self._area_specific_radiation_interpolator_short is None:
216 if self.weather_data is not None:
217 self._area_specific_radiation_interpolator_short = self.weather_data.radiation_interpolator(
218 self.wall, wavelength_type="short"
219 )
220 return self._area_specific_radiation_interpolator_short
222 @property
223 def area_specific_radiation_interpolator_long(self) -> Callable | Any:
224 if self._area_specific_radiation_interpolator_long is None:
225 if self.weather_data is not None:
226 self._area_specific_radiation_interpolator_long = self.weather_data.radiation_interpolator(
227 self.wall, wavelength_type="long"
228 )
229 return self._area_specific_radiation_interpolator_long
231 @property
232 def start_shift(self):
233 return (
234 np.datetime64(self.start_date) - np.datetime64(self.start_date).astype("datetime64[D]")
235 ) / np.timedelta64(1, "s")
238initial_wall = Wall()
239initial_settings = Settings(
240 calculate_static=False,
241 use_weather_data=False,
242 wall=initial_wall,
243 # weather_data_path=r"C:\path\to\dwd_data\TRY2015_523748130791_Jahr.dat",
244 # save_folder_path=r"C:\path\to\folder",
245 start_date="2023-09-15T00:00:00",
246)
247settings_first_try = Settings(
248 calculate_static=False,
249 use_weather_data=True,
250 wall=initial_wall,
251 weather_data_path=r"/path/to/dwd_data/TRY2015_523748130791_Jahr.dat",
252 # save_folder_path=r"/path/to/folder",
253 start_date="2023-09-15T00:00:00",
254)
255# settings = Settings(
256# calculate_static=True,
257# use_weather_data=True,
258# wall=initial_wall,
259# weather_data_path=r"/path/to/dwd_data/TRY2015_523748130791_Jahr.dat",
260# save_folder_path=r"/path/to/folder",
261# start_date="2023-09-15T00:00:00",
262# )