epyt-flow 0.15.0b1__py3-none-any.whl → 0.16.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.
- epyt_flow/VERSION +1 -1
- epyt_flow/data/benchmarks/batadal.py +1 -1
- epyt_flow/gym/scenario_control_env.py +6 -12
- epyt_flow/serialization.py +19 -3
- epyt_flow/simulation/events/actuator_events.py +24 -24
- epyt_flow/simulation/events/leakages.py +45 -45
- epyt_flow/simulation/events/quality_events.py +23 -23
- epyt_flow/simulation/events/sensor_reading_attack.py +27 -27
- epyt_flow/simulation/events/sensor_reading_event.py +33 -33
- epyt_flow/simulation/scada/complex_control.py +103 -103
- epyt_flow/simulation/scada/scada_data.py +58 -30
- epyt_flow/simulation/scada/simple_control.py +33 -33
- epyt_flow/simulation/scenario_config.py +31 -0
- epyt_flow/simulation/scenario_simulator.py +218 -82
- epyt_flow/simulation/sensor_config.py +220 -108
- epyt_flow/topology.py +197 -6
- epyt_flow/uncertainty/model_uncertainty.py +23 -20
- epyt_flow/uncertainty/sensor_noise.py +16 -16
- epyt_flow/uncertainty/uncertainties.py +6 -4
- epyt_flow/utils.py +240 -15
- epyt_flow/visualization/scenario_visualizer.py +14 -5
- epyt_flow/visualization/visualization_utils.py +22 -18
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/METADATA +8 -5
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/RECORD +27 -27
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/top_level.txt +0 -0
epyt_flow/utils.py
CHANGED
|
@@ -6,7 +6,12 @@ import math
|
|
|
6
6
|
import tempfile
|
|
7
7
|
import zipfile
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
import re
|
|
9
10
|
import requests
|
|
11
|
+
import time
|
|
12
|
+
import threading
|
|
13
|
+
import multiprocessing as mp
|
|
14
|
+
from deprecated import deprecated
|
|
10
15
|
from tqdm import tqdm
|
|
11
16
|
import numpy as np
|
|
12
17
|
import matplotlib
|
|
@@ -344,10 +349,153 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
344
349
|
return ax
|
|
345
350
|
|
|
346
351
|
|
|
347
|
-
def
|
|
348
|
-
|
|
352
|
+
def robust_download(download_path: str, urls: str or list,
|
|
353
|
+
verbose: bool = True, timeout: int = 30) -> None:
|
|
354
|
+
"""
|
|
355
|
+
Downloads a file from the given urls if it does not already exist in the
|
|
356
|
+
given path. The urls are tried in order. If a download stops or stalls,
|
|
357
|
+
the next url is tried until one succeeds or all urls have failed.
|
|
358
|
+
|
|
359
|
+
Parameters
|
|
360
|
+
----------
|
|
361
|
+
download_path : `str`
|
|
362
|
+
Local path to the file -- if this path does not exist, the file will be
|
|
363
|
+
downloaded from the provided 'urls' and stored there.
|
|
364
|
+
urls : `list` or `str`
|
|
365
|
+
One url or a list of urls (where additional urls function as backup) to
|
|
366
|
+
download the file from.
|
|
367
|
+
verbose : `bool`, optional
|
|
368
|
+
If True, a progress bar is shown while downloading the file.
|
|
369
|
+
|
|
370
|
+
The default is True.
|
|
371
|
+
timeout : `int`
|
|
372
|
+
If this time passed without progress while downloading, the download is
|
|
373
|
+
considered failed.
|
|
374
|
+
|
|
375
|
+
The default is 30 seconds.
|
|
376
|
+
"""
|
|
377
|
+
if isinstance(urls, str):
|
|
378
|
+
urls = [urls]
|
|
379
|
+
|
|
380
|
+
for url in urls:
|
|
381
|
+
try:
|
|
382
|
+
download_if_necessary(download_path, url, verbose, timeout)
|
|
383
|
+
return
|
|
384
|
+
except Exception as e:
|
|
385
|
+
print(f"Failed url: {url} with {e}")
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
raise SystemError("All download attempts failed")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _download_process(download_path: str, url: str, backup_urls: list[str],
|
|
392
|
+
last_update: mp.Value, stop_flag: mp.Value,
|
|
393
|
+
finish_flag: mp.Value, verbose: bool) -> None:
|
|
394
|
+
"""
|
|
395
|
+
Process that handles the actual download. It updates the last download
|
|
396
|
+
update variable and cleans up the corrupted file if the download fails from
|
|
397
|
+
within.
|
|
398
|
+
|
|
399
|
+
This function is only to be called from `download_if_necessary`.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
download_path : `str`
|
|
404
|
+
Local path to the file -- if this path does not exist, the file will be
|
|
405
|
+
downloaded from the provided 'urls' and stored there.
|
|
406
|
+
url : `str`
|
|
407
|
+
Web-URL pointing to the source the file should be downloaded from. Can
|
|
408
|
+
also point to a google drive file.
|
|
409
|
+
backup_urls : `list[str]`
|
|
410
|
+
List of alternative URLs that are being tried in the case that
|
|
411
|
+
downloading from 'url' fails. This is deprecated, but left in for
|
|
412
|
+
downward compatibility with `download_if_necessary` calls with
|
|
413
|
+
backup_urls. Not necessary when using `robust_download`.
|
|
414
|
+
last_update : `mp.Value`
|
|
415
|
+
Shared variable to keep track of the last successful download update.
|
|
416
|
+
stop_flag : `mp.Value`
|
|
417
|
+
Shared variable. Set to 1 when this process stopped by finishing or
|
|
418
|
+
error.
|
|
419
|
+
finish_flag : `mp.Value`
|
|
420
|
+
Shared variable. Set to 1 when download finished successfully.
|
|
421
|
+
verbose : `bool`
|
|
422
|
+
If True, a progress bar is shown while downloading the file.
|
|
423
|
+
"""
|
|
424
|
+
try:
|
|
425
|
+
progress_bar = None
|
|
426
|
+
response = None
|
|
427
|
+
|
|
428
|
+
if "drive.google.com" in url:
|
|
429
|
+
session = requests.Session()
|
|
430
|
+
response = session.get(url)
|
|
431
|
+
html = response.text
|
|
432
|
+
|
|
433
|
+
def extract(pattern):
|
|
434
|
+
match = re.search(pattern, html)
|
|
435
|
+
return match.group(1) if match else None
|
|
436
|
+
|
|
437
|
+
file_id = extract(r'name="id" value="([^"]+)"')
|
|
438
|
+
file_confirm = extract(r'name="confirm" value="([^"]+)"')
|
|
439
|
+
file_uuid = extract(r'name="uuid" value="([^"]+)"')
|
|
440
|
+
|
|
441
|
+
if not all([file_id, file_confirm, file_uuid]):
|
|
442
|
+
raise SystemError("Failed to extract download parameters")
|
|
443
|
+
|
|
444
|
+
download_url = (
|
|
445
|
+
f"https://drive.usercontent.google.com/download"
|
|
446
|
+
f"?id={file_id}&export=download&confirm={file_confirm}&uuid={file_uuid}"
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
response = session.get(download_url, stream=True)
|
|
450
|
+
|
|
451
|
+
else:
|
|
452
|
+
response = requests.get(url, stream=True, allow_redirects=True,
|
|
453
|
+
timeout=1000)
|
|
454
|
+
|
|
455
|
+
# Deprecated, left in for backward compatibility
|
|
456
|
+
if response.status_code != 200:
|
|
457
|
+
for backup_url in backup_urls:
|
|
458
|
+
response = requests.get(backup_url, stream=verbose,
|
|
459
|
+
allow_redirects=True, timeout=1000)
|
|
460
|
+
if response.status_code == 200:
|
|
461
|
+
break
|
|
462
|
+
if response.status_code != 200:
|
|
463
|
+
raise SystemError(f"Failed to download -- {response.status_code}")
|
|
464
|
+
|
|
465
|
+
content_length = int(response.headers.get("content-length", 0))
|
|
466
|
+
with open(download_path, "wb") as file:
|
|
467
|
+
progress_bar = False
|
|
468
|
+
if verbose:
|
|
469
|
+
progress_bar = tqdm(desc=download_path, total=content_length,
|
|
470
|
+
ascii=True, unit='B', unit_scale=True,
|
|
471
|
+
unit_divisor=1024)
|
|
472
|
+
for data in response.iter_content(chunk_size=1024):
|
|
473
|
+
size = file.write(data)
|
|
474
|
+
if progress_bar:
|
|
475
|
+
progress_bar.update(size)
|
|
476
|
+
with last_update.get_lock():
|
|
477
|
+
last_update.value = time.time()
|
|
478
|
+
with finish_flag.get_lock():
|
|
479
|
+
finish_flag.value = 1
|
|
480
|
+
with stop_flag.get_lock():
|
|
481
|
+
stop_flag.value = 1
|
|
482
|
+
finally:
|
|
483
|
+
if progress_bar:
|
|
484
|
+
progress_bar.close()
|
|
485
|
+
if response:
|
|
486
|
+
response.close()
|
|
487
|
+
with finish_flag.get_lock():
|
|
488
|
+
if os.path.exists(download_path) and finish_flag.value == 0:
|
|
489
|
+
os.remove(download_path)
|
|
490
|
+
with stop_flag.get_lock():
|
|
491
|
+
stop_flag.value = 1
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@deprecated(reason="Please use new function `robust_download` instead.")
|
|
495
|
+
def download_from_gdrive_if_necessary(download_path: str, url: str, verbose: bool = True) -> None:
|
|
349
496
|
"""
|
|
350
|
-
Downloads a file from a
|
|
497
|
+
Downloads a file from a google drive repository if it does not already exist
|
|
498
|
+
in a given path.
|
|
351
499
|
|
|
352
500
|
Note that if the path (folder) does not already exist, it will be created.
|
|
353
501
|
|
|
@@ -357,28 +505,39 @@ def download_if_necessary(download_path: str, url: str, verbose: bool = True,
|
|
|
357
505
|
Local path to the file -- if this path does not exist, the file will be downloaded from
|
|
358
506
|
the provided 'url' and stored in 'download_dir'.
|
|
359
507
|
url : `str`
|
|
360
|
-
Web-URL.
|
|
508
|
+
Web-URL of the google drive repository.
|
|
361
509
|
verbose : `bool`, optional
|
|
362
510
|
If True, a progress bar is shown while downloading the file.
|
|
363
511
|
|
|
364
512
|
The default is True.
|
|
365
|
-
backup_urls : `list[str]`, optional
|
|
366
|
-
List of alternative URLs that are being tried in the case that downloading from 'url' fails.
|
|
367
|
-
|
|
368
|
-
The default is an empty list.
|
|
369
513
|
"""
|
|
370
514
|
folder_path = str(Path(download_path).parent.absolute())
|
|
371
515
|
create_path_if_not_exist(folder_path)
|
|
372
516
|
|
|
373
517
|
if not os.path.isfile(download_path):
|
|
374
|
-
|
|
518
|
+
session = requests.Session()
|
|
519
|
+
|
|
520
|
+
response = session.get(url)
|
|
521
|
+
html = response.text
|
|
522
|
+
|
|
523
|
+
def extract(pattern):
|
|
524
|
+
match = re.search(pattern, html)
|
|
525
|
+
return match.group(1) if match else None
|
|
526
|
+
|
|
527
|
+
file_id = extract(r'name="id" value="([^"]+)"')
|
|
528
|
+
file_confirm = extract(r'name="confirm" value="([^"]+)"')
|
|
529
|
+
file_uuid = extract(r'name="uuid" value="([^"]+)"')
|
|
530
|
+
|
|
531
|
+
if not all([file_id, file_confirm, file_uuid]):
|
|
532
|
+
raise SystemError("Failed to extract download parameters")
|
|
533
|
+
|
|
534
|
+
download_url = (
|
|
535
|
+
f"https://drive.usercontent.google.com/download"
|
|
536
|
+
f"?id={file_id}&export=download&confirm={file_confirm}&uuid={file_uuid}"
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
response = session.get(download_url, stream=True)
|
|
375
540
|
|
|
376
|
-
if response.status_code != 200:
|
|
377
|
-
for backup_url in backup_urls:
|
|
378
|
-
response = requests.get(backup_url, stream=verbose, allow_redirects=True,
|
|
379
|
-
timeout=1000)
|
|
380
|
-
if response.status_code == 200:
|
|
381
|
-
break
|
|
382
541
|
if response.status_code != 200:
|
|
383
542
|
raise SystemError(f"Failed to download -- {response.status_code}")
|
|
384
543
|
|
|
@@ -398,6 +557,72 @@ def download_if_necessary(download_path: str, url: str, verbose: bool = True,
|
|
|
398
557
|
f_out.write(response.content)
|
|
399
558
|
|
|
400
559
|
|
|
560
|
+
# TODO: documentation
|
|
561
|
+
def download_if_necessary(download_path: str, url: str, verbose: bool = True,
|
|
562
|
+
backup_urls: list[str] = [], timeout: int = 30) -> None:
|
|
563
|
+
"""
|
|
564
|
+
Downloads a file from a given URL if it does not already exist in a given
|
|
565
|
+
path. This function is deprecated, please use `robust_download` instead.
|
|
566
|
+
|
|
567
|
+
Note that if the path (folder) does not already exist, it will be created.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
download_path : `str`
|
|
572
|
+
Local path to the file -- if this path does not exist, the file will be
|
|
573
|
+
downloaded from the provided 'url' and stored in 'download_dir'.
|
|
574
|
+
url : `str`
|
|
575
|
+
Web-URL.
|
|
576
|
+
verbose : `bool`, optional
|
|
577
|
+
If True, a progress bar is shown while downloading the file.
|
|
578
|
+
|
|
579
|
+
The default is True.
|
|
580
|
+
backup_urls : `list[str]`, optional
|
|
581
|
+
List of alternative URLs that are being tried in the case that downloading from 'url' fails.
|
|
582
|
+
|
|
583
|
+
The default is an empty list.
|
|
584
|
+
timeout : `int`, optional
|
|
585
|
+
Allowed download inactivity in seconds. After this time passed without
|
|
586
|
+
an update, the donwload is considered failed.
|
|
587
|
+
|
|
588
|
+
The default is 30 seconds.
|
|
589
|
+
"""
|
|
590
|
+
folder_path = str(Path(download_path).parent.absolute())
|
|
591
|
+
create_path_if_not_exist(folder_path)
|
|
592
|
+
|
|
593
|
+
if os.path.isfile(download_path):
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
last_update = mp.Value('d', time.time())
|
|
597
|
+
stop_flag = mp.Value('i', 0)
|
|
598
|
+
finish_flag = mp.Value('i', 0)
|
|
599
|
+
|
|
600
|
+
t = mp.Process(target=_download_process, args=(download_path, url, backup_urls, last_update, stop_flag, finish_flag, verbose))
|
|
601
|
+
t.start()
|
|
602
|
+
|
|
603
|
+
while True:
|
|
604
|
+
time.sleep(1)
|
|
605
|
+
with last_update.get_lock():
|
|
606
|
+
idle = time.time() - last_update.value
|
|
607
|
+
with stop_flag.get_lock():
|
|
608
|
+
if stop_flag.value == 1:
|
|
609
|
+
with finish_flag.get_lock():
|
|
610
|
+
if finish_flag.value == 1:
|
|
611
|
+
break
|
|
612
|
+
else:
|
|
613
|
+
if os.path.exists(download_path) and finish_flag.value == 0:
|
|
614
|
+
os.remove(download_path)
|
|
615
|
+
raise SystemError(f"failed downloading from {url}")
|
|
616
|
+
if idle > timeout:
|
|
617
|
+
with finish_flag.get_lock():
|
|
618
|
+
t.terminate()
|
|
619
|
+
t.join()
|
|
620
|
+
if os.path.exists(download_path) and finish_flag.value == 0:
|
|
621
|
+
os.remove(download_path)
|
|
622
|
+
raise SystemError(f"no progress in {timeout} seconds, aborting download")
|
|
623
|
+
t.join()
|
|
624
|
+
|
|
625
|
+
|
|
401
626
|
def create_path_if_not_exist(path_in: str) -> None:
|
|
402
627
|
"""
|
|
403
628
|
Creates a directory and all its parent directories if they do not already exist.
|
|
@@ -518,7 +518,7 @@ class ScenarioVisualizer:
|
|
|
518
518
|
return None
|
|
519
519
|
|
|
520
520
|
def show_plot(self, export_to_file: str = None,
|
|
521
|
-
suppress_plot: bool = False) -> None:
|
|
521
|
+
suppress_plot: bool = False, dpi: int = None) -> None:
|
|
522
522
|
"""
|
|
523
523
|
Displays a static plot of the water distribution network.
|
|
524
524
|
|
|
@@ -533,13 +533,21 @@ class ScenarioVisualizer:
|
|
|
533
533
|
Default is `None`.
|
|
534
534
|
suppress_plot : `bool`, default is False
|
|
535
535
|
If true, no plot is displayed after running this method.
|
|
536
|
+
dpi : `int`, optional
|
|
537
|
+
Dpi of the generated plot. If None, standard values (200 when
|
|
538
|
+
displaying and 900 when saving) are used.
|
|
536
539
|
"""
|
|
537
|
-
|
|
540
|
+
if dpi is None:
|
|
541
|
+
plot_dpi = 200
|
|
542
|
+
save_dpi = 900
|
|
543
|
+
else:
|
|
544
|
+
plot_dpi = save_dpi = dpi
|
|
545
|
+
self.fig = plt.figure(figsize=(6.4, 4.8), dpi=plot_dpi)
|
|
538
546
|
self._get_next_frame(0)
|
|
539
547
|
|
|
540
548
|
if export_to_file is not None:
|
|
541
549
|
plt.savefig(export_to_file, transparent=True, bbox_inches='tight',
|
|
542
|
-
dpi=
|
|
550
|
+
dpi=save_dpi)
|
|
543
551
|
if not suppress_plot:
|
|
544
552
|
plt.show()
|
|
545
553
|
else:
|
|
@@ -720,8 +728,9 @@ class ScenarioVisualizer:
|
|
|
720
728
|
shape links*timesteps. If `None`, a simulation is run to generate
|
|
721
729
|
SCADA data. Default is `None`.
|
|
722
730
|
parameter : `str`, optional
|
|
723
|
-
The link data to visualize. Options are 'flow_rate', '
|
|
724
|
-
'
|
|
731
|
+
The link data to visualize. Options are 'flow_rate', 'link_quality',
|
|
732
|
+
'custom_data', 'bulk_species_concentration' or 'diameter'.
|
|
733
|
+
Default is 'flow_rate'.
|
|
725
734
|
statistic : `str`, optional
|
|
726
735
|
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
727
736
|
'max', or 'time_step'. Default is 'mean'.
|
|
@@ -3,11 +3,9 @@ Module provides helper functions and data management classes for visualizing
|
|
|
3
3
|
scenarios.
|
|
4
4
|
"""
|
|
5
5
|
import inspect
|
|
6
|
-
from dataclasses import dataclass
|
|
7
6
|
from typing import Optional, Union, List, Tuple
|
|
8
7
|
|
|
9
8
|
import matplotlib as mpl
|
|
10
|
-
import matplotlib.pyplot as plt
|
|
11
9
|
import networkx.drawing.nx_pylab as nxp
|
|
12
10
|
import numpy as np
|
|
13
11
|
from scipy.interpolate import CubicSpline
|
|
@@ -23,7 +21,6 @@ stat_funcs = {
|
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
|
|
26
|
-
@dataclass
|
|
27
24
|
class JunctionObject:
|
|
28
25
|
"""
|
|
29
26
|
Represents a junction component (e.g. nodes, tanks, reservoirs, ...) in a
|
|
@@ -48,12 +45,15 @@ class JunctionObject:
|
|
|
48
45
|
interpolated : `bool`, default = False
|
|
49
46
|
Set to True, if node_colors are interpolated for smoother animation.
|
|
50
47
|
"""
|
|
51
|
-
nodelist: list
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
def __init__(self, nodelist: list, pos: dict, node_shape: mpl.path.Path = None,
|
|
49
|
+
node_size: int = 10, node_color: Union[str, list] = 'k',
|
|
50
|
+
interpolated: bool = False):
|
|
51
|
+
self.nodelist = nodelist
|
|
52
|
+
self.pos = pos
|
|
53
|
+
self.node_shape = node_shape
|
|
54
|
+
self.node_size = node_size
|
|
55
|
+
self.node_color = node_color
|
|
56
|
+
self.interpolated = interpolated
|
|
57
57
|
|
|
58
58
|
def add_frame(self, statistic: str, values: np.ndarray,
|
|
59
59
|
pit: int, intervals: Union[int, List[Union[int, float]]]):
|
|
@@ -188,7 +188,8 @@ class JunctionObject:
|
|
|
188
188
|
|
|
189
189
|
valid_params = {
|
|
190
190
|
key: value for key, value in attributes.items()
|
|
191
|
-
if key in sig.parameters and
|
|
191
|
+
if key in sig.parameters and key not in ['vmin', 'vmax', 'cmap']
|
|
192
|
+
and value is not None
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
return valid_params
|
|
@@ -232,7 +233,6 @@ class JunctionObject:
|
|
|
232
233
|
setattr(self, key, value)
|
|
233
234
|
|
|
234
235
|
|
|
235
|
-
@dataclass
|
|
236
236
|
class EdgeObject:
|
|
237
237
|
"""
|
|
238
238
|
Represents an edge component (pipes) in a water distribution network and
|
|
@@ -252,10 +252,12 @@ class EdgeObject:
|
|
|
252
252
|
interpolated : `dict`, default = {}
|
|
253
253
|
Filled with interpolated frames if interpolation method is called.
|
|
254
254
|
"""
|
|
255
|
-
edgelist: list
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
def __init__(self, edgelist: list, pos: dict, edge_color: Union[str, list] = 'k',
|
|
256
|
+
interpolated: dict = {}):
|
|
257
|
+
self.edgelist = edgelist
|
|
258
|
+
self.pos = pos
|
|
259
|
+
self.edge_color = edge_color
|
|
260
|
+
self.interpolated = interpolated
|
|
259
261
|
|
|
260
262
|
def rescale_widths(self, line_widths: Tuple[int, int] = (1, 2)):
|
|
261
263
|
"""
|
|
@@ -309,8 +311,9 @@ class EdgeObject:
|
|
|
309
311
|
SCADA data created by the :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
310
312
|
instance, is used to retrieve data for the next frame.
|
|
311
313
|
parameter : `str`, default = 'flow_rate'
|
|
312
|
-
The link data to visualize. Options are 'flow_rate', '
|
|
313
|
-
'
|
|
314
|
+
The link data to visualize. Options are 'flow_rate', 'link_quality',
|
|
315
|
+
'custom_data', 'bulk_species_concentration' or 'diameter'.
|
|
316
|
+
Default is 'flow_rate'.
|
|
314
317
|
statistic : `str`, default = 'mean'
|
|
315
318
|
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
316
319
|
'max' or 'time_step'.
|
|
@@ -518,7 +521,8 @@ class EdgeObject:
|
|
|
518
521
|
|
|
519
522
|
valid_params = {
|
|
520
523
|
key: value for key, value in attributes.items()
|
|
521
|
-
if key in sig.parameters and
|
|
524
|
+
if key in sig.parameters and key not in ['vmin', 'vmax', 'cmap']
|
|
525
|
+
and value is not None
|
|
522
526
|
}
|
|
523
527
|
|
|
524
528
|
return valid_params
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: epyt-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: EPyT-Flow -- EPANET Python Toolkit - Flow
|
|
5
5
|
Author-email: André Artelt <aartelt@techfak.uni-bielefeld.de>, "Marios S. Kyriakou" <kiriakou.marios@ucy.ac.cy>, "Stelios G. Vrachimis" <vrachimis.stelios@ucy.ac.cy>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,16 +11,19 @@ Project-URL: Issues, https://github.com/WaterFutures/EPyT-Flow/issues
|
|
|
11
11
|
Keywords: epanet,water,networks,hydraulics,quality,simulations
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
17
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Requires-Python: !=3.14.1,>=3.10
|
|
21
24
|
Description-Content-Type: text/markdown
|
|
22
25
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: epanet-plus>=0.0
|
|
26
|
+
Requires-Dist: epanet-plus>=0.2.0
|
|
24
27
|
Requires-Dist: requests>=2.31.0
|
|
25
28
|
Requires-Dist: scipy>=1.11.4
|
|
26
29
|
Requires-Dist: u-msgpack-python>=2.8.0
|
|
@@ -79,7 +82,7 @@ Unique features of EPyT-Flow that make it superior to other (Python) toolboxes a
|
|
|
79
82
|
|
|
80
83
|
## Installation
|
|
81
84
|
|
|
82
|
-
EPyT-Flow supports Python 3.
|
|
85
|
+
EPyT-Flow supports Python 3.10 - 3.14
|
|
83
86
|
|
|
84
87
|
Note that EPyT-Flow builds upon [EPANET-PLUS](https://github.com/WaterFutures/EPANET-PLUS) which
|
|
85
88
|
constitutes a C extension and Python package.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
epyt_flow/VERSION,sha256=
|
|
1
|
+
epyt_flow/VERSION,sha256=qgN0VLXoMgUhoysnij9K_wX8eQCatVA_XUobmjOzjEc,7
|
|
2
2
|
epyt_flow/__init__.py,sha256=mXQNlqjdqYKRiKCYnSCflFAJFEVlke7o-_ln6wZPAmY,154
|
|
3
|
-
epyt_flow/serialization.py,sha256=
|
|
4
|
-
epyt_flow/topology.py,sha256=
|
|
5
|
-
epyt_flow/utils.py,sha256=
|
|
3
|
+
epyt_flow/serialization.py,sha256=Tl-m9GQpDt1YcrVazaNGqvkWtSMxHcAFp-j5tvKyiGE,15109
|
|
4
|
+
epyt_flow/topology.py,sha256=GRFXpXHxhj99iNmaCOcKYarF4K7PJbuV6C7_5wyE0kU,36672
|
|
5
|
+
epyt_flow/utils.py,sha256=8jqjuBIrxWWcvu35e38_47saqQHsgxOBFdROftotfYs,25964
|
|
6
6
|
epyt_flow/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
epyt_flow/data/networks.py,sha256=fZLv3yv_iDS23H-7KNMxNhCvfvrr9WdO7VNR5K3e94U,39842
|
|
8
8
|
epyt_flow/data/benchmarks/__init__.py,sha256=nJ6hqPaPNp8YMizniev3fwOWpzvvNUBMoRF16wACUkE,754
|
|
9
|
-
epyt_flow/data/benchmarks/batadal.py,sha256=
|
|
9
|
+
epyt_flow/data/benchmarks/batadal.py,sha256=0c7Y2yX_Wii4j9xXait0Xg9p3PUhRC4g-5MJDjYYcqI,11256
|
|
10
10
|
epyt_flow/data/benchmarks/batadal_data.py,sha256=oIzcysGivMPAgrfzrk5l8i-j6Ii96DPcFa6sL4TSaw8,880
|
|
11
11
|
epyt_flow/data/benchmarks/battledim.py,sha256=sVV8w4ESjybD__jrmKFhQxuBjtmLoBQmSmU2Wp-v76w,20790
|
|
12
12
|
epyt_flow/data/benchmarks/battledim_data.py,sha256=0vHm-2eAiLv6U-n5dqUUWS1o_szFRy9mVJ3eqDRp4PE,3373
|
|
@@ -15,7 +15,7 @@ epyt_flow/data/benchmarks/leakdb.py,sha256=1EtGshPwnAGJFRrd9B8RqRCnABsCtAoG2kvg1
|
|
|
15
15
|
epyt_flow/data/benchmarks/leakdb_data.py,sha256=FNssgMkC1wqWVlaOrrihr4Od9trEZY7KeK5KuBeRMvM,507058
|
|
16
16
|
epyt_flow/data/benchmarks/water_usage.py,sha256=RiLGLof1HT0luwn7_dG11_nP5U5r9B3L8vDLuwsIKTM,4962
|
|
17
17
|
epyt_flow/gym/__init__.py,sha256=gDIP9VGjKraajsJo6MnPN-TaEUTLnnJNj2c_7jrb8Bg,36
|
|
18
|
-
epyt_flow/gym/scenario_control_env.py,sha256=
|
|
18
|
+
epyt_flow/gym/scenario_control_env.py,sha256=nYwMjvoFywbilCXy_rrC_sdxdi8ngeld-8SUuBJqNWQ,13559
|
|
19
19
|
epyt_flow/rest_api/__init__.py,sha256=4HilmXhdh6H56UHJBB2WUSULlEBUDnI1FPTP11ft3HE,126
|
|
20
20
|
epyt_flow/rest_api/base_handler.py,sha256=HDLXrMXqgWvxWAsB-3G4plyTyCv27_eBbq4eRl_FeBo,2609
|
|
21
21
|
epyt_flow/rest_api/res_manager.py,sha256=j6-3FUBZNLKM9bCsIDZzSytfDYJbDLRwjn1mIPstTqI,2342
|
|
@@ -32,34 +32,34 @@ epyt_flow/rest_api/scenario/simulation_handlers.py,sha256=A59Iv-6k7SlGF_RDQgoFlv
|
|
|
32
32
|
epyt_flow/rest_api/scenario/uncertainty_handlers.py,sha256=Pdo2YmiawOqKXWcLs2P-lv5qN-OKHS3sDgkAFQeRzK8,4568
|
|
33
33
|
epyt_flow/simulation/__init__.py,sha256=nihvZ8O2rJjYQkv7JhtVMqNacO5bA38VtS8Y_0BWrVQ,129
|
|
34
34
|
epyt_flow/simulation/parallel_simulation.py,sha256=ph4KXw9jCt-hiJFJbmC6fNvEsrbQoWV-tFKE5-qSfoQ,6523
|
|
35
|
-
epyt_flow/simulation/scenario_config.py,sha256=
|
|
36
|
-
epyt_flow/simulation/scenario_simulator.py,sha256=
|
|
37
|
-
epyt_flow/simulation/sensor_config.py,sha256=
|
|
35
|
+
epyt_flow/simulation/scenario_config.py,sha256=C-AwNcOd-pabxrF-SJ5Nx8hmdYhkBkSghkIJ3zLkoBY,31733
|
|
36
|
+
epyt_flow/simulation/scenario_simulator.py,sha256=498lmir4FloDh6tfHtYHZO5OMXwS7Of9a_ZHhf2mTQA,173211
|
|
37
|
+
epyt_flow/simulation/sensor_config.py,sha256=ZzZjcdiglkyNvU8DsbMvwDNPvxjEJYnEqG-KvheO8uE,99473
|
|
38
38
|
epyt_flow/simulation/events/__init__.py,sha256=gv8ZcvwjJN0Z5MwRXEOVFRNq4X5NPyyqXIQnhBxszQ0,215
|
|
39
|
-
epyt_flow/simulation/events/actuator_events.py,sha256
|
|
39
|
+
epyt_flow/simulation/events/actuator_events.py,sha256=CAd5A8NGACRKco5wLTMJ3RbL4DBv5FEcKj_JURkNsec,8381
|
|
40
40
|
epyt_flow/simulation/events/event.py,sha256=kARPV20XCAl6zxnJwI9U7ICtZUPACO_rgAmtHm1mGCs,2603
|
|
41
|
-
epyt_flow/simulation/events/leakages.py,sha256=
|
|
42
|
-
epyt_flow/simulation/events/quality_events.py,sha256=
|
|
41
|
+
epyt_flow/simulation/events/leakages.py,sha256=3uR_Zp9iKyar2T8BJ0qf4EfadnEdyIoh_bki7gmwY4I,18242
|
|
42
|
+
epyt_flow/simulation/events/quality_events.py,sha256=Ay67zEQ-zupz-LPdJmTAgQ5RmszSG3Gah5HWlSmjMVk,7827
|
|
43
43
|
epyt_flow/simulation/events/sensor_faults.py,sha256=RFR8LjePkDEkCgENHEgCGT7iKYYtr_qq87-IIwN6ezg,8423
|
|
44
|
-
epyt_flow/simulation/events/sensor_reading_attack.py,sha256=
|
|
45
|
-
epyt_flow/simulation/events/sensor_reading_event.py,sha256=
|
|
44
|
+
epyt_flow/simulation/events/sensor_reading_attack.py,sha256=ZcSSlRc4sN_w5-io5af3RtX_YMmTuJp6x2wgllNfBtQ,7932
|
|
45
|
+
epyt_flow/simulation/events/sensor_reading_event.py,sha256=tQZlmJ7RT0Yv-7BDkmIgEZm8FgQgwAO4HkJIvPhtBoQ,7553
|
|
46
46
|
epyt_flow/simulation/events/system_event.py,sha256=kyj6--D457vlbiVyg0M9yAXMs7GFHpcY8YZns3Aeo8c,2616
|
|
47
47
|
epyt_flow/simulation/scada/__init__.py,sha256=pfJhg-tM5DaiZTXs0_1qJsY2R6Py_LwSz6BUFJexfQM,150
|
|
48
|
-
epyt_flow/simulation/scada/complex_control.py,sha256=
|
|
48
|
+
epyt_flow/simulation/scada/complex_control.py,sha256=8NNl2cM1-AEn5AGg1YYUzfKIJdqWVL6C6YR12V4H_aI,22026
|
|
49
49
|
epyt_flow/simulation/scada/custom_control.py,sha256=HNO5WmRhN9BtMEzC82DKreAym1SC6xdLhNQ-cSOfU2c,4758
|
|
50
|
-
epyt_flow/simulation/scada/scada_data.py,sha256=
|
|
50
|
+
epyt_flow/simulation/scada/scada_data.py,sha256=8_ny9TPSEUm_f8DZ_zRlc4GFvoKfOIPie25pCQJKJtk,205066
|
|
51
51
|
epyt_flow/simulation/scada/scada_data_export.py,sha256=WNAFn_WNfzYAEFbl2Al-cOIx-A0ozY4AI60-i_qEHdc,11643
|
|
52
|
-
epyt_flow/simulation/scada/simple_control.py,sha256=
|
|
52
|
+
epyt_flow/simulation/scada/simple_control.py,sha256=H4uqP-1pifXDai2SnxrmS5SYxBc4UCYUn_1tc9ELb7M,12565
|
|
53
53
|
epyt_flow/uncertainty/__init__.py,sha256=ZRjuJL9rDpWVSdPwObPxFpEmMTcgAl3VmPOsS6cIyGg,89
|
|
54
|
-
epyt_flow/uncertainty/model_uncertainty.py,sha256=
|
|
55
|
-
epyt_flow/uncertainty/sensor_noise.py,sha256
|
|
56
|
-
epyt_flow/uncertainty/uncertainties.py,sha256=
|
|
54
|
+
epyt_flow/uncertainty/model_uncertainty.py,sha256=X9iEWVY58cE_kA_K8U0af5w0fN72m1fTHPnfXY6RuV8,51089
|
|
55
|
+
epyt_flow/uncertainty/sensor_noise.py,sha256=_tZvGF-4tjF-u-1sceIz7VwiVeUiqFeFej-KJenb6Ws,6459
|
|
56
|
+
epyt_flow/uncertainty/uncertainties.py,sha256=nTafn1AhtA2XvC6ESuyPxIhBAuju-BLpFRFdvYEW9jI,20602
|
|
57
57
|
epyt_flow/uncertainty/utils.py,sha256=K-ZhyO6Bg7UmNPgpfND0JLa_wRwyrtUUgGTWyWwy-fo,8029
|
|
58
58
|
epyt_flow/visualization/__init__.py,sha256=uQ7lO6AsgLc88X48Te3QhBQY-iDKGvdPtxKCl4b-Mfw,70
|
|
59
|
-
epyt_flow/visualization/scenario_visualizer.py,sha256=
|
|
60
|
-
epyt_flow/visualization/visualization_utils.py,sha256=
|
|
61
|
-
epyt_flow-0.
|
|
62
|
-
epyt_flow-0.
|
|
63
|
-
epyt_flow-0.
|
|
64
|
-
epyt_flow-0.
|
|
65
|
-
epyt_flow-0.
|
|
59
|
+
epyt_flow/visualization/scenario_visualizer.py,sha256=IGJBt_OKoZohmHy8LSA_THn2drsBxZSdxef-G09_Q4g,58237
|
|
60
|
+
epyt_flow/visualization/visualization_utils.py,sha256=CiLFX1ebi4lbiBOoI91707SfFAcIwjL31Xo2AgGDGG4,28018
|
|
61
|
+
epyt_flow-0.16.0.dist-info/licenses/LICENSE,sha256=YRJC2kcAhMlpEeHwUF0lzE-GRXLFyAjXNErI8L5UwYk,1071
|
|
62
|
+
epyt_flow-0.16.0.dist-info/METADATA,sha256=HSUoA3V4ElmaQofEwisSxuQjPIKFFqzDsqL4cNr9ZkM,9714
|
|
63
|
+
epyt_flow-0.16.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
64
|
+
epyt_flow-0.16.0.dist-info/top_level.txt,sha256=Wh_kd7TRL8ownCw3Y3dxx-9C0iTSk6wNauv_NX9JcrY,10
|
|
65
|
+
epyt_flow-0.16.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|