epyt-flow 0.9.0__py3-none-any.whl → 0.11.0__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.
Files changed (35) hide show
  1. epyt_flow/VERSION +1 -1
  2. epyt_flow/data/networks.py +27 -14
  3. epyt_flow/gym/control_gyms.py +8 -0
  4. epyt_flow/gym/scenario_control_env.py +17 -4
  5. epyt_flow/metrics.py +5 -0
  6. epyt_flow/models/event_detector.py +5 -0
  7. epyt_flow/models/sensor_interpolation_detector.py +5 -0
  8. epyt_flow/serialization.py +5 -0
  9. epyt_flow/simulation/__init__.py +0 -1
  10. epyt_flow/simulation/events/actuator_events.py +7 -1
  11. epyt_flow/simulation/events/sensor_reading_attack.py +16 -3
  12. epyt_flow/simulation/events/sensor_reading_event.py +18 -3
  13. epyt_flow/simulation/scada/__init__.py +3 -1
  14. epyt_flow/simulation/scada/advanced_control.py +6 -2
  15. epyt_flow/simulation/scada/complex_control.py +625 -0
  16. epyt_flow/simulation/scada/custom_control.py +134 -0
  17. epyt_flow/simulation/scada/scada_data.py +547 -8
  18. epyt_flow/simulation/scada/simple_control.py +317 -0
  19. epyt_flow/simulation/scenario_config.py +87 -26
  20. epyt_flow/simulation/scenario_simulator.py +865 -51
  21. epyt_flow/simulation/sensor_config.py +34 -2
  22. epyt_flow/topology.py +16 -0
  23. epyt_flow/uncertainty/model_uncertainty.py +80 -62
  24. epyt_flow/uncertainty/sensor_noise.py +15 -4
  25. epyt_flow/uncertainty/uncertainties.py +71 -18
  26. epyt_flow/uncertainty/utils.py +40 -13
  27. epyt_flow/utils.py +15 -1
  28. epyt_flow/visualization/__init__.py +2 -0
  29. epyt_flow/{simulation → visualization}/scenario_visualizer.py +429 -586
  30. epyt_flow/visualization/visualization_utils.py +611 -0
  31. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/LICENSE +1 -1
  32. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/METADATA +18 -6
  33. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/RECORD +35 -30
  34. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/WHEEL +1 -1
  35. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
  Module provides some helper functions regarding the implementation of uncertainty.
3
3
  """
4
4
  import numpy as np
5
+ from numpy.random import Generator
5
6
  from scipy.ndimage import gaussian_filter1d
6
7
 
7
8
 
@@ -54,7 +55,8 @@ def scale_to_range(pattern: np.ndarray, min_value: float, max_value: float) -> n
54
55
  + min_value for x in pattern]
55
56
 
56
57
 
57
- def generate_random_gaussian_noise(n_samples: int):
58
+ def generate_random_gaussian_noise(n_samples: int, np_rand_gen: Generator = np.random.default_rng()
59
+ ) -> np.ndarray:
58
60
  """
59
61
  Generates Gaussian noise using a random mean ([0,1]) and random standard deviation ([0,1]).
60
62
 
@@ -62,16 +64,23 @@ def generate_random_gaussian_noise(n_samples: int):
62
64
  ----------
63
65
  n_samples : `int`
64
66
  Number of random samples.
67
+ np_rand_generator : `numpy.random.Generator <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.Generator>`_, optional
68
+ The random number generator that is going to be used.
69
+
70
+ The default is the default BitGenerator (PCG64) as constructed by
71
+ `numpy.random.default_rng() <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng>`_.
65
72
 
66
73
  Returns
67
74
  -------
68
75
  `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
69
76
  Gaussian noise.
70
77
  """
71
- return np.random.normal(np.random.rand(), np.random.rand(), size=n_samples)
78
+ return np_rand_gen.normal(np.random.rand(), np.random.rand(), size=n_samples)
72
79
 
73
80
 
74
- def generate_deep_random_gaussian_noise(n_samples: int, mean: float = None):
81
+ def generate_deep_random_gaussian_noise(n_samples: int, mean: float = None,
82
+ np_rand_gen: Generator = np.random.default_rng()
83
+ ) -> np.ndarray:
75
84
  """
76
85
  Generates random Gaussian noise where the standard deviations (and mean) are changing over time.
77
86
 
@@ -84,6 +93,11 @@ def generate_deep_random_gaussian_noise(n_samples: int, mean: float = None):
84
93
  If None, random means are generated.
85
94
 
86
95
  The default is None.
96
+ np_rand_generator : `numpy.random.Generator <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.Generator>`_, optional
97
+ The random number generator that is going to be used.
98
+
99
+ The default is the default BitGenerator (PCG64) as constructed by
100
+ `numpy.random.default_rng() <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng>`_.
87
101
 
88
102
  Returns
89
103
  -------
@@ -93,17 +107,19 @@ def generate_deep_random_gaussian_noise(n_samples: int, mean: float = None):
93
107
  noise = []
94
108
 
95
109
  if mean is None:
96
- mean = create_deep_random_pattern(n_samples, min_value=-1., max_value=1.)
110
+ mean = create_deep_random_pattern(n_samples, min_value=-1., max_value=1.,
111
+ np_rand_gen=np_rand_gen)
97
112
  else:
98
113
  mean = [mean] * n_samples
99
- rand_std = create_deep_random_pattern(n_samples)
100
- noise = np.array([np.random.normal(m, s) for m, s in zip(mean, rand_std)])
114
+ rand_std = create_deep_random_pattern(n_samples, np_rand_gen=np_rand_gen)
115
+ noise = np.array([np_rand_gen.normal(m, s) for m, s in zip(mean, rand_std)])
101
116
 
102
117
  return noise
103
118
 
104
119
 
105
120
  def create_deep_random_pattern(n_samples: int, min_value: float = 0., max_value: float = 1.,
106
- init_value: float = None) -> np.ndarray:
121
+ init_value: float = None,
122
+ np_rand_gen: Generator = np.random.default_rng()) -> np.ndarray:
107
123
  """
108
124
  Generates a random pattern.
109
125
 
@@ -124,6 +140,11 @@ def create_deep_random_pattern(n_samples: int, min_value: float = 0., max_value:
124
140
  If None, a random value is used.
125
141
 
126
142
  The default is None.
143
+ np_rand_generator : `numpy.random.Generator <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.Generator>`_, optional
144
+ The random number generator that is going to be used.
145
+
146
+ The default is the default BitGenerator (PCG64) as constructed by
147
+ `numpy.random.default_rng() <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng>`_.
127
148
 
128
149
  Returns
129
150
  -------
@@ -138,7 +159,7 @@ def create_deep_random_pattern(n_samples: int, min_value: float = 0., max_value:
138
159
  start_value = pattern[-1]
139
160
 
140
161
  pattern += _create_deep_random_pattern(start_value, min_value=min_value,
141
- max_value=max_value)
162
+ max_value=max_value, np_rand_gen=np_rand_gen)
142
163
 
143
164
  pattern = pattern[:n_samples]
144
165
 
@@ -149,7 +170,8 @@ def create_deep_random_pattern(n_samples: int, min_value: float = 0., max_value:
149
170
 
150
171
 
151
172
  def _create_deep_random_pattern(start_value: float = None, min_length: int = 2, max_length: int = 5,
152
- min_value: float = None, max_value: float = None) -> np.ndarray:
173
+ min_value: float = None, max_value: float = None,
174
+ np_rand_gen: Generator = np.random.default_rng()) -> np.ndarray:
153
175
  """
154
176
  Generates a random pattern of random length.
155
177
 
@@ -176,6 +198,11 @@ def _create_deep_random_pattern(start_value: float = None, min_length: int = 2,
176
198
  Upper bound of the pattern.
177
199
 
178
200
  The default is one.
201
+ np_rand_generator : `numpy.random.Generator <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.Generator>`_, optional
202
+ The random number generator that is going to be used.
203
+
204
+ The default is the default BitGenerator (PCG64) as constructed by
205
+ `numpy.random.default_rng() <https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng>`_.
179
206
 
180
207
  Returns
181
208
  -------
@@ -186,16 +213,16 @@ def _create_deep_random_pattern(start_value: float = None, min_length: int = 2,
186
213
 
187
214
  # Random parameters of pattern
188
215
  if start_value is None:
189
- start_value = np.random.rand()
190
- length = np.random.randint(low=min_length, high=max_length)
191
- vec = np.random.choice([-.1, .1])
216
+ start_value = np_rand_gen.random()
217
+ length = np_rand_gen.integers(low=min_length, high=max_length)
218
+ vec = np_rand_gen.choice([-.1, .1])
192
219
 
193
220
  # Generate pattern
194
221
  cur_value = start_value
195
222
  pattern.append(start_value)
196
223
 
197
224
  for _ in range(length):
198
- cur_value = cur_value + np.random.rand() * vec
225
+ cur_value = cur_value + np_rand_gen.random() * vec
199
226
  pattern.append(cur_value)
200
227
  if min_value is not None and max_value is not None:
201
228
  if cur_value < min_value:
epyt_flow/utils.py CHANGED
@@ -314,7 +314,8 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
314
314
  return ax
315
315
 
316
316
 
317
- def download_if_necessary(download_path: str, url: str, verbose: bool = True) -> None:
317
+ def download_if_necessary(download_path: str, url: str, verbose: bool = True,
318
+ backup_urls: list[str] = []) -> None:
318
319
  """
319
320
  Downloads a file from a given URL if it does not already exist in a given path.
320
321
 
@@ -331,6 +332,10 @@ def download_if_necessary(download_path: str, url: str, verbose: bool = True) ->
331
332
  If True, a progress bar is shown while downloading the file.
332
333
 
333
334
  The default is True.
335
+ backup_urls : `list[str]`, optional
336
+ List of alternative URLs that are being tried in the case that downloading from 'url' fails.
337
+
338
+ The default is an empty list.
334
339
  """
335
340
  folder_path = str(Path(download_path).parent.absolute())
336
341
  create_path_if_not_exist(folder_path)
@@ -338,6 +343,15 @@ def download_if_necessary(download_path: str, url: str, verbose: bool = True) ->
338
343
  if not os.path.isfile(download_path):
339
344
  response = requests.get(url, stream=verbose, allow_redirects=True, timeout=1000)
340
345
 
346
+ if response.status_code != 200:
347
+ for backup_url in backup_urls:
348
+ response = requests.get(backup_url, stream=verbose, allow_redirects=True,
349
+ timeout=1000)
350
+ if response.status_code == 200:
351
+ break
352
+ if response.status_code != 200:
353
+ raise SystemError(f"Failed to download -- {response.status_code}")
354
+
341
355
  if verbose is True:
342
356
  content_length = int(response.headers.get('content-length', 0))
343
357
  with open(download_path, "wb") as file, tqdm(desc=download_path,
@@ -0,0 +1,2 @@
1
+ from .visualization_utils import *
2
+ from .scenario_visualizer import *