shepherd-data 2023.2.1__py3-none-any.whl → 2023.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
shepherd_data/__init__.py CHANGED
@@ -10,7 +10,8 @@ import logging
10
10
  from .reader import Reader
11
11
  from .writer import Writer
12
12
 
13
- __version__ = "2023.2.1"
13
+ __version__ = "2023.3.1"
14
+
14
15
  __all__ = [
15
16
  "Reader",
16
17
  "Writer",
@@ -2,21 +2,22 @@
2
2
 
3
3
  """
4
4
  from typing import Dict
5
- from typing import Union
5
+ from typing import TypeVar
6
6
 
7
7
  import numpy as np
8
+ from numpy.typing import NDArray
8
9
 
9
- # SI-value [SI-Unit] = raw-value * gain + offset
10
- cal_default: Dict[str, dict] = {
10
+ cal_default: Dict[str, Dict[str, float]] = {
11
11
  "voltage": {"gain": 3 * 1e-9, "offset": 0.0}, # allows 0 - 12 V in 3 nV-Steps
12
12
  "current": {"gain": 250 * 1e-12, "offset": 0.0}, # allows 0 - 1 A in 250 pA - Steps
13
13
  "time": {"gain": 1e-9, "offset": 0.0},
14
+ # SI-value [SI-Unit] = raw-value * gain + offset
14
15
  }
15
16
 
17
+ T_calc = TypeVar("T_calc", NDArray[np.float64], float)
16
18
 
17
- def raw_to_si(
18
- values_raw: Union[np.ndarray, float, int], cal: dict
19
- ) -> Union[np.ndarray, float]:
19
+
20
+ def raw_to_si(values_raw: T_calc, cal: Dict[str, float]) -> T_calc:
20
21
  """Helper to convert between physical units and raw unsigned integers
21
22
 
22
23
  :param values_raw: number or numpy array with raw values
@@ -24,11 +25,15 @@ def raw_to_si(
24
25
  :return: converted number or array
25
26
  """
26
27
  values_si = values_raw * cal["gain"] + cal["offset"]
27
- values_si[values_si < 0.0] = 0.0
28
+ if isinstance(values_si, np.ndarray):
29
+ values_si[values_si < 0.0] = 0.0
30
+ # if pyright still complains, cast with .astype(float)
31
+ else:
32
+ values_si = float(max(values_si, 0.0))
28
33
  return values_si
29
34
 
30
35
 
31
- def si_to_raw(values_si: Union[np.ndarray, float], cal: dict) -> Union[np.ndarray, int]:
36
+ def si_to_raw(values_si: T_calc, cal: Dict[str, float]) -> T_calc:
32
37
  """Helper to convert between physical units and raw unsigned integers
33
38
 
34
39
  :param values_si: number or numpy array with values in physical units
@@ -36,5 +41,8 @@ def si_to_raw(values_si: Union[np.ndarray, float], cal: dict) -> Union[np.ndarra
36
41
  :return: converted number or array
37
42
  """
38
43
  values_raw = (values_si - cal["offset"]) / cal["gain"]
39
- values_raw[values_raw < 0.0] = 0.0
44
+ if isinstance(values_raw, np.ndarray):
45
+ values_raw[values_raw < 0.0] = 0.0
46
+ else:
47
+ values_raw = max(values_raw, 0.0)
40
48
  return values_raw
shepherd_data/cli.py CHANGED
@@ -6,7 +6,7 @@ import os
6
6
  import sys
7
7
  from pathlib import Path
8
8
  from typing import List
9
- from typing import NoReturn
9
+ from typing import Optional
10
10
 
11
11
  import click
12
12
 
@@ -18,7 +18,7 @@ logger = logging.getLogger("SHPData.cli")
18
18
  verbose_level = 2
19
19
 
20
20
 
21
- def config_logger(verbose: int) -> NoReturn:
21
+ def config_logger(verbose: int) -> None:
22
22
  # TODO: put in __init__, provide logger, ditch global var
23
23
  if verbose == 0:
24
24
  logger.setLevel(logging.ERROR)
@@ -61,8 +61,8 @@ def path_to_flist(data_path: Path) -> List[Path]:
61
61
  default=0,
62
62
  help="4 Levels [0..3](Error, Warning, Info, Debug)",
63
63
  )
64
- @click.pass_context
65
- def cli(ctx, verbose: int):
64
+ @click.pass_context # TODO: is the ctx-type correct?
65
+ def cli(ctx: click.Context, verbose: int) -> None:
66
66
  """Shepherd: Synchronized Energy Harvesting Emulator and Recorder
67
67
 
68
68
  Args:
@@ -79,7 +79,7 @@ def cli(ctx, verbose: int):
79
79
 
80
80
  @cli.command(short_help="Validates a file or directory containing shepherd-recordings")
81
81
  @click.argument("in_data", type=click.Path(exists=True, resolve_path=True))
82
- def validate(in_data):
82
+ def validate(in_data: Path) -> None:
83
83
  """Validates a file or directory containing shepherd-recordings"""
84
84
  files = path_to_flist(in_data)
85
85
  valid_dir = True
@@ -111,7 +111,7 @@ def validate(in_data):
111
111
  type=click.STRING,
112
112
  help="Set an individual csv-separator",
113
113
  )
114
- def extract(in_data, ds_factor, separator):
114
+ def extract(in_data: Path, ds_factor: float, separator: str) -> None:
115
115
  """Extracts recorded IVSamples and stores it to csv"""
116
116
  files = path_to_flist(in_data)
117
117
  if not isinstance(ds_factor, (float, int)) or ds_factor < 1:
@@ -160,7 +160,7 @@ def extract(in_data, ds_factor, separator):
160
160
  type=click.STRING,
161
161
  help="Set an individual csv-separator",
162
162
  )
163
- def extract_meta(in_data, separator):
163
+ def extract_meta(in_data: Path, separator: str) -> None:
164
164
  """Extracts metadata and logs from file or directory containing shepherd-recordings"""
165
165
  files = path_to_flist(in_data)
166
166
  for file in files:
@@ -197,14 +197,16 @@ def extract_meta(in_data, separator):
197
197
  @click.option(
198
198
  "--sample-rate",
199
199
  "-r",
200
- type=int,
200
+ type=click.INT,
201
201
  help="Alternative Input to determine a downsample-factor (Choose One)",
202
202
  )
203
- def downsample(in_data, ds_factor, sample_rate):
203
+ def downsample(
204
+ in_data: Path, ds_factor: Optional[float], sample_rate: Optional[int]
205
+ ) -> None:
204
206
  """Creates an array of downsampling-files from file
205
207
  or directory containing shepherd-recordings"""
206
208
  if ds_factor is None and sample_rate is not None and sample_rate >= 1:
207
- ds_factor = int(Reader.samplerate_sps / sample_rate)
209
+ ds_factor = int(Reader.samplerate_sps_default / sample_rate)
208
210
  if isinstance(ds_factor, (float, int)) and ds_factor >= 1:
209
211
  ds_list = [ds_factor]
210
212
  else:
@@ -279,7 +281,14 @@ def downsample(in_data, ds_factor, sample_rate):
279
281
  is_flag=True,
280
282
  help="Plot all files (in directory) into one Multiplot",
281
283
  )
282
- def plot(in_data, start: float, end: float, width: int, height: int, multiplot: bool):
284
+ def plot(
285
+ in_data: Path,
286
+ start: Optional[float],
287
+ end: Optional[float],
288
+ width: int,
289
+ height: int,
290
+ multiplot: bool,
291
+ ) -> None:
283
292
  """Plots IV-trace from file or directory containing shepherd-recordings"""
284
293
  files = path_to_flist(in_data)
285
294
  multiplot = multiplot and len(files) > 1
@@ -294,7 +303,10 @@ def plot(in_data, start: float, end: float, width: int, height: int, multiplot:
294
303
  if multiplot:
295
304
  logger.info("Got %d datasets to plot", len(data))
296
305
  mpl_path = Reader.multiplot_to_file(data, in_data, width, height)
297
- logger.info("Plot generated and saved to '%s'", mpl_path.name)
306
+ if mpl_path:
307
+ logger.info("Plot generated and saved to '%s'", mpl_path.name)
308
+ else:
309
+ logger.info("Plot not generated, path was already in use.")
298
310
 
299
311
 
300
312
  if __name__ == "__main__":
shepherd_data/ivonne.py CHANGED
@@ -9,7 +9,7 @@ import math
9
9
  import os
10
10
  import pickle # noqa: S403
11
11
  from pathlib import Path
12
- from typing import NoReturn
12
+ from typing import Optional
13
13
 
14
14
  import numpy as np
15
15
  import pandas as pd
@@ -34,27 +34,30 @@ def get_isc(coeffs: pd.DataFrame):
34
34
  class Reader:
35
35
  """container for converters to shepherd-data"""
36
36
 
37
- samplerate_sps: int = 50
38
- sample_interval_ns: int = int(10**9 // samplerate_sps)
39
- sample_interval_s: float = 1 / samplerate_sps
40
-
41
- runtime_s: float = None
42
- file_size: int = None
43
- data_rate: float = None
44
-
45
- _df: pd.DataFrame = None
46
-
47
37
  _logger: logging.Logger = logging.getLogger("SHPData.IVonne.Reader")
48
38
 
49
39
  def __init__(
50
- self, file_path: Path, samplerate_sps: int = None, verbose: bool = True
40
+ self,
41
+ file_path: Path,
42
+ samplerate_sps: Optional[int] = None,
43
+ verbose: bool = True,
51
44
  ):
52
45
  self._logger.setLevel(logging.INFO if verbose else logging.WARNING)
53
46
 
54
47
  self.file_path = Path(file_path)
48
+ self.samplerate_sps: int = 50
55
49
  if samplerate_sps is not None:
56
50
  self.samplerate_sps = samplerate_sps
57
51
 
52
+ self.sample_interval_ns: int = int(10**9 // self.samplerate_sps)
53
+ self.sample_interval_s: float = 1 / self.samplerate_sps
54
+
55
+ self.runtime_s: float = 0
56
+ self.file_size: int = 0
57
+ self.data_rate: float = 0
58
+
59
+ self._df: Optional[pd.DataFrame] = None
60
+
58
61
  def __enter__(self):
59
62
  if not self.file_path.exists():
60
63
  raise FileNotFoundError(
@@ -75,10 +78,12 @@ class Reader:
75
78
  )
76
79
  return self
77
80
 
78
- def __exit__(self, *exc):
81
+ def __exit__(self, *exc): # type: ignore
79
82
  pass
80
83
 
81
- def _refresh_file_stats(self) -> NoReturn:
84
+ def _refresh_file_stats(self) -> None:
85
+ if self._df is None:
86
+ raise RuntimeError("IVonne Context was not entered - file not open!")
82
87
  self.runtime_s = round(self._df.shape[0] / self.samplerate_sps, 3)
83
88
  self.file_size = self.file_path.stat().st_size
84
89
  self.data_rate = self.file_size / self.runtime_s if self.runtime_s > 0 else 0
@@ -88,8 +93,8 @@ class Reader:
88
93
  shp_output: Path,
89
94
  v_max: float = 5.0,
90
95
  pts_per_curve: int = 1000,
91
- duration_s: float = None,
92
- ) -> NoReturn:
96
+ duration_s: Optional[float] = None,
97
+ ) -> None:
93
98
  """Transforms previously recorded parameters to shepherd hdf database with IV curves.
94
99
  Shepherd should work with IV 'surfaces', where we have a stream of IV curves
95
100
 
@@ -98,6 +103,8 @@ class Reader:
98
103
  :param pts_per_curve: Number of sampling points of the prototype curve
99
104
  :param duration_s: time to stop in seconds, counted from beginning
100
105
  """
106
+ if self._df is None:
107
+ raise RuntimeError("IVonne Context was not entered - file not open!")
101
108
  if isinstance(duration_s, (float, int)) and self.runtime_s > duration_s:
102
109
  self._logger.info(" -> gets trimmed to %s s", duration_s)
103
110
  df_elements_n = min(
@@ -124,7 +131,7 @@ class Reader:
124
131
  for idx in job_iter:
125
132
  idx_top = min(idx + max_elements, df_elements_n)
126
133
  df_slice = self._df.iloc[idx : idx_top + 1].copy()
127
- df_slice.loc[:, "timestamp"] = pd.TimedeltaIndex(
134
+ df_slice["timestamp"] = pd.TimedeltaIndex(
128
135
  data=df_slice["time"], unit="s"
129
136
  )
130
137
  df_slice = df_slice.set_index("timestamp")
@@ -152,9 +159,9 @@ class Reader:
152
159
  self,
153
160
  shp_output: Path,
154
161
  v_max: float = 5.0,
155
- duration_s: float = None,
156
- tracker: MPPTracker = None,
157
- ) -> NoReturn:
162
+ duration_s: Optional[float] = None,
163
+ tracker: Optional[MPPTracker] = None,
164
+ ) -> None:
158
165
  """Transforms shepherd IV curves to shepherd IV traces.
159
166
 
160
167
  For the 'buck' and 'buck-boost' modes, shepherd takes voltage and current traces.
@@ -172,6 +179,8 @@ class Reader:
172
179
  :param duration_s: time to stop in seconds, counted from beginning
173
180
  :param tracker: VOC or OPT
174
181
  """
182
+ if self._df is None:
183
+ raise RuntimeError("IVonne Context was not entered - file not open!")
175
184
  if isinstance(duration_s, (float, int)) and self.runtime_s > duration_s:
176
185
  self._logger.info(" -> gets trimmed to %s s", duration_s)
177
186
  df_elements_n = min(
@@ -204,7 +213,7 @@ class Reader:
204
213
  df_slice.loc[:, "voc"] = get_voc(df_slice)
205
214
  df_slice.loc[df_slice["voc"] >= v_max, "voc"] = v_max
206
215
  df_slice = tracker.process(df_slice)
207
- df_slice.loc[:, "timestamp"] = pd.TimedeltaIndex(
216
+ df_slice["timestamp"] = pd.TimedeltaIndex(
208
217
  data=df_slice["time"], unit="s"
209
218
  )
210
219
  df_slice = df_slice[["time", "v", "i", "timestamp"]].set_index(
@@ -226,14 +235,16 @@ class Reader:
226
235
  self,
227
236
  shp_output: Path,
228
237
  v_max: float = 5.0,
229
- duration_s: float = None,
230
- ) -> NoReturn:
238
+ duration_s: Optional[float] = None,
239
+ ) -> None:
231
240
  """Transforms ivonne-parameters to upsampled version for shepherd
232
241
 
233
242
  :param shp_output: Path where the resulting hdf file shall be stored
234
243
  :param v_max: Maximum voltage supported by shepherd
235
244
  :param duration_s: time to stop in seconds, counted from beginning
236
245
  """
246
+ if self._df is None:
247
+ raise RuntimeError("IVonne Context was not entered - file not open!")
237
248
  if isinstance(duration_s, (float, int)) and self.runtime_s > duration_s:
238
249
  self._logger.info(" -> gets trimmed to %s s", duration_s)
239
250
  df_elements_n = min(
@@ -261,7 +272,7 @@ class Reader:
261
272
  df_slice.loc[:, "voc"] = get_voc(df_slice)
262
273
  df_slice.loc[df_slice["voc"] >= v_max, "voc"] = v_max
263
274
  df_slice.loc[:, "isc"] = get_isc(df_slice)
264
- df_slice.loc[:, "timestamp"] = pd.TimedeltaIndex(
275
+ df_slice["timestamp"] = pd.TimedeltaIndex(
265
276
  data=df_slice["time"], unit="s"
266
277
  )
267
278
  df_slice = df_slice[["time", "voc", "isc", "timestamp"]].set_index(
shepherd_data/mppt.py CHANGED
@@ -5,8 +5,10 @@ Might be exchanged by shepherds py-model of pru-harvesters
5
5
  import numpy as np
6
6
  import pandas as pd
7
7
 
8
+ from .calibration import T_calc
8
9
 
9
- def iv_model(voltages: np.ndarray, coeffs: pd.DataFrame) -> np.ndarray:
10
+
11
+ def iv_model(voltages: T_calc, coeffs: pd.Series) -> T_calc:
10
12
  """Simple diode based model of a solar panel IV curve.
11
13
 
12
14
  Args:
@@ -16,15 +18,18 @@ def iv_model(voltages: np.ndarray, coeffs: pd.DataFrame) -> np.ndarray:
16
18
  Returns:
17
19
  Solar current at given load voltage
18
20
  """
19
- currents = coeffs["a"] - coeffs["b"] * (np.exp(coeffs["c"] * voltages) - 1)
20
- if hasattr(currents, "__len__"):
21
- currents[currents < 0] = 0
21
+ currents = float(coeffs["a"]) - float(coeffs["b"]) * (
22
+ np.exp(float(coeffs["c"]) * voltages) - 1.0
23
+ )
24
+ if isinstance(currents, np.ndarray):
25
+ currents[currents < 0.0] = 0.0
22
26
  else:
23
- currents = max(0, currents)
27
+ currents = max(0.0, currents)
28
+
24
29
  return currents
25
30
 
26
31
 
27
- def find_oc(v_arr, i_arr, ratio: float = 0.05):
32
+ def find_oc(v_arr: np.ndarray, i_arr: np.ndarray, ratio: float = 0.05):
28
33
  """Approximates opencircuit voltage.
29
34
 
30
35
  Searches last current value that is above a certain ratio of the short-circuit
@@ -51,7 +56,7 @@ class MPPTracker:
51
56
  :param coeffs: ivonne coefficients
52
57
  :return:
53
58
  """
54
- pass
59
+ return pd.DataFrame()
55
60
 
56
61
 
57
62
  class OpenCircuitTracker(MPPTracker):