epyt-flow 0.2.0__py3-none-any.whl → 0.4.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/EPANET/compile_macos.sh +4 -0
- epyt_flow/VERSION +1 -1
- epyt_flow/__init__.py +6 -2
- epyt_flow/data/benchmarks/leakdb.py +7 -12
- epyt_flow/data/networks.py +404 -40
- epyt_flow/rest_api/base_handler.py +14 -0
- epyt_flow/rest_api/scada_data/handlers.py +42 -0
- epyt_flow/rest_api/server.py +3 -1
- epyt_flow/simulation/events/leakages.py +28 -18
- epyt_flow/simulation/parallel_simulation.py +7 -7
- epyt_flow/simulation/scada/scada_data.py +543 -12
- epyt_flow/simulation/scada/scada_data_export.py +38 -5
- epyt_flow/simulation/scenario_config.py +7 -5
- epyt_flow/simulation/scenario_simulator.py +81 -48
- epyt_flow/simulation/sensor_config.py +342 -47
- epyt_flow/topology.py +313 -11
- epyt_flow/uncertainty/model_uncertainty.py +26 -19
- epyt_flow/utils.py +1 -1
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/METADATA +18 -6
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/RECORD +23 -22
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/WHEEL +0 -0
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
Module provides a class for storing and processing SCADA data.
|
|
3
3
|
"""
|
|
4
4
|
import warnings
|
|
5
|
-
from typing import Callable
|
|
5
|
+
from typing import Callable, Any
|
|
6
6
|
from copy import deepcopy
|
|
7
7
|
import numpy as np
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
from epyt.epanet import ToolkitConstants
|
|
9
|
+
|
|
10
|
+
from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str, \
|
|
11
|
+
MASS_UNIT_MG, MASS_UNIT_UG, TIME_UNIT_HRS, MASS_UNIT_MOL, MASS_UNIT_MMOL, \
|
|
12
|
+
AREA_UNIT_CM2, AREA_UNIT_FT2, AREA_UNIT_M2, \
|
|
13
|
+
SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, SENSOR_TYPE_NODE_DEMAND, \
|
|
14
|
+
SENSOR_TYPE_NODE_PRESSURE, SENSOR_TYPE_NODE_QUALITY, SENSOR_TYPE_PUMP_STATE, \
|
|
15
|
+
SENSOR_TYPE_TANK_VOLUME, SENSOR_TYPE_VALVE_STATE, SENSOR_TYPE_NODE_BULK_SPECIES, \
|
|
16
|
+
SENSOR_TYPE_LINK_BULK_SPECIES, SENSOR_TYPE_SURFACE_SPECIES
|
|
13
17
|
from ..events import SensorFault, SensorReadingAttack, SensorReadingEvent
|
|
14
18
|
from ...uncertainty import SensorNoise
|
|
15
19
|
from ...serialization import serializable, Serializable, SCADA_DATA_ID
|
|
@@ -381,6 +385,533 @@ class ScadaData(Serializable):
|
|
|
381
385
|
|
|
382
386
|
super().__init__(**kwds)
|
|
383
387
|
|
|
388
|
+
def convert_units(self, flow_unit: int = None, quality_unit: int = None,
|
|
389
|
+
bulk_species_mass_unit: list[int] = None,
|
|
390
|
+
surface_species_mass_unit: list[int] = None,
|
|
391
|
+
surface_species_area_unit: int = None) -> Any:
|
|
392
|
+
"""
|
|
393
|
+
Changes the units of some measurement units.
|
|
394
|
+
|
|
395
|
+
.. note::
|
|
396
|
+
|
|
397
|
+
Beaware of potential rounding errors.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
flow_unit : `int`, optional
|
|
402
|
+
New units of hydraulic measurements -- note that the flow unit specifies all other
|
|
403
|
+
hydraulic measurement units.
|
|
404
|
+
|
|
405
|
+
Must be one of the following EPANET toolkit constants:
|
|
406
|
+
|
|
407
|
+
- EN_CFS = 0 (cubic foot/sec)
|
|
408
|
+
- EN_GPM = 1 (gal/min)
|
|
409
|
+
- EN_MGD = 2 (Million gal/day)
|
|
410
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
411
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
412
|
+
- EN_LPS = 5 (liter/sec)
|
|
413
|
+
- EN_LPM = 6 (liter/min)
|
|
414
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
415
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
416
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
417
|
+
|
|
418
|
+
If None, units of hydraulic measurement are not changed.
|
|
419
|
+
|
|
420
|
+
The default is None.
|
|
421
|
+
quality_unit : `int`, optional
|
|
422
|
+
New unit of quality measurements -- i.e. chemical concentration.
|
|
423
|
+
Only relevant if basic quality analysis was performed.
|
|
424
|
+
|
|
425
|
+
Must be one of the following constants:
|
|
426
|
+
|
|
427
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
428
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
429
|
+
|
|
430
|
+
If None, units of quality measurements are not changed.
|
|
431
|
+
|
|
432
|
+
The default is None.
|
|
433
|
+
bulk_species_mass_unit : `list[int]`, optional
|
|
434
|
+
New units of all bulk species measurements -- i.e. for each
|
|
435
|
+
bulk species the measurement unit is specified.
|
|
436
|
+
Note that the assumed ordering is the same as given in 'bulk_species'
|
|
437
|
+
in the sensor configuration -- only relevant if EPANET-MSX is used.
|
|
438
|
+
|
|
439
|
+
Must be one of the following constants:
|
|
440
|
+
|
|
441
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
442
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
443
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
444
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
445
|
+
|
|
446
|
+
If None, measurement units of bulk species are not changed.
|
|
447
|
+
|
|
448
|
+
The default is None.
|
|
449
|
+
surface_species_mass_unit : `list[int]`, optional
|
|
450
|
+
New units of all surface species measurements -- i.e. for each
|
|
451
|
+
surface species the measurement unit is specified.
|
|
452
|
+
Note that the assumed ordering is the same as given in 'surface_species'
|
|
453
|
+
in the sensor configuration -- only relevant if EPANET-MSX is used.
|
|
454
|
+
|
|
455
|
+
Must be one of the following constants:
|
|
456
|
+
|
|
457
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
458
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
459
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
460
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
461
|
+
|
|
462
|
+
If None, measurement units of surface species are not changed.
|
|
463
|
+
|
|
464
|
+
The default is None.
|
|
465
|
+
surface_species_area_unit : `int`, optional
|
|
466
|
+
New area unit of all surface species -- only relevant if EPANET-MSX is used.
|
|
467
|
+
|
|
468
|
+
Must be one of the following constants:
|
|
469
|
+
|
|
470
|
+
- AREA_UNIT_FT2 = 1 (square feet)
|
|
471
|
+
- AREA_UNIT_M2 = 2 (square meters)
|
|
472
|
+
- AREA_UNIT_CM2 = 3 (square centimeters)
|
|
473
|
+
|
|
474
|
+
If None, are units of surface species are not changed.
|
|
475
|
+
|
|
476
|
+
The default is None.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
481
|
+
SCADA data instance with the new units.
|
|
482
|
+
"""
|
|
483
|
+
if flow_unit is not None:
|
|
484
|
+
if not isinstance(flow_unit, int):
|
|
485
|
+
raise TypeError("'flow_unit' must be a an instance of 'int' " +
|
|
486
|
+
f"but not of '{type(flow_unit)}'")
|
|
487
|
+
if flow_unit not in range(10):
|
|
488
|
+
raise ValueError("Invalid value of 'flow_unit'")
|
|
489
|
+
|
|
490
|
+
if quality_unit is not None:
|
|
491
|
+
if not isinstance(quality_unit, int):
|
|
492
|
+
raise TypeError("'quality_mass_unit' must be an instance of 'int' " +
|
|
493
|
+
f"but not of '{type(quality_unit)}'")
|
|
494
|
+
if quality_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, TIME_UNIT_HRS]:
|
|
495
|
+
raise ValueError("Invalid value of 'quality_unit'")
|
|
496
|
+
|
|
497
|
+
if bulk_species_mass_unit is not None:
|
|
498
|
+
if not isinstance(bulk_species_mass_unit, list):
|
|
499
|
+
raise TypeError("'bulk_species_mass_unit' must be an instance of 'list[int]' " +
|
|
500
|
+
f"but not of '{type(bulk_species_mass_unit)}'")
|
|
501
|
+
if len(bulk_species_mass_unit) != len(self.__sensor_config.bulk_species):
|
|
502
|
+
raise ValueError("Inconsistency between 'bulk_species_mass_unit' and " +
|
|
503
|
+
"'bulk_species'")
|
|
504
|
+
if any(not isinstance(mass_unit, int) for mass_unit in bulk_species_mass_unit):
|
|
505
|
+
raise TypeError("All items in 'bulk_species_mass_unit' must be an instance " +
|
|
506
|
+
"of 'int'")
|
|
507
|
+
if any(mass_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, MASS_UNIT_MOL, MASS_UNIT_MMOL]
|
|
508
|
+
for mass_unit in bulk_species_mass_unit):
|
|
509
|
+
raise ValueError("Invalid mass unit in 'bulk_species_mass_unit'")
|
|
510
|
+
|
|
511
|
+
if surface_species_mass_unit is not None:
|
|
512
|
+
if not isinstance(surface_species_mass_unit, list):
|
|
513
|
+
raise TypeError("'surface_species_mass_unit' must be an instance of 'list[int]' " +
|
|
514
|
+
f"but not of '{type(surface_species_mass_unit)}'")
|
|
515
|
+
if len(surface_species_mass_unit) != len(self.__sensor_config.surface_species):
|
|
516
|
+
raise ValueError("Inconsistency between 'surface_species_mass_unit' and " +
|
|
517
|
+
"'surface_species'")
|
|
518
|
+
if any(not isinstance(mass_unit, int) for mass_unit in surface_species_mass_unit):
|
|
519
|
+
raise TypeError("All items in 'surface_species_mass_unit' must be an instance " +
|
|
520
|
+
"of 'int'")
|
|
521
|
+
if any(mass_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, MASS_UNIT_MOL, MASS_UNIT_MMOL]
|
|
522
|
+
for mass_unit in surface_species_mass_unit):
|
|
523
|
+
raise ValueError("Invalid mass unit in 'surface_species_mass_unit'")
|
|
524
|
+
|
|
525
|
+
if surface_species_area_unit is not None:
|
|
526
|
+
if surface_species_area_unit is not None:
|
|
527
|
+
if not isinstance(surface_species_area_unit, int):
|
|
528
|
+
raise TypeError("'surface_species_area_unit' must be a an instance of 'int' " +
|
|
529
|
+
f"but not of '{type(surface_species_area_unit)}'")
|
|
530
|
+
if surface_species_area_unit not in [AREA_UNIT_FT2, AREA_UNIT_M2, AREA_UNIT_CM2]:
|
|
531
|
+
raise ValueError("Invalid area unit 'surface_species_area_unit'")
|
|
532
|
+
|
|
533
|
+
def __get_mass_convert_factor(new_unit_id: int, old_unit_id: int) -> float:
|
|
534
|
+
if new_unit_id == MASS_UNIT_MG and old_unit_id == MASS_UNIT_UG:
|
|
535
|
+
return .001
|
|
536
|
+
elif new_unit_id == MASS_UNIT_UG and old_unit_id == MASS_UNIT_MG:
|
|
537
|
+
return 1000.
|
|
538
|
+
elif new_unit_id == MASS_UNIT_MOL and old_unit_id == MASS_UNIT_MMOL:
|
|
539
|
+
return .001
|
|
540
|
+
elif new_unit_id == MASS_UNIT_MMOL and old_unit_id == MASS_UNIT_MOL:
|
|
541
|
+
return 1000.
|
|
542
|
+
else:
|
|
543
|
+
raise NotImplementedError(f"Can not convert '{massunit_to_str(old_unit_id)}' to " +
|
|
544
|
+
f"'{massunit_to_str(new_unit_id)}'")
|
|
545
|
+
|
|
546
|
+
def __get_flow_convert_factor(new_unit_id: int, old_unit: int) -> float:
|
|
547
|
+
if new_unit_id == ToolkitConstants.EN_CFS:
|
|
548
|
+
if old_unit == ToolkitConstants.EN_GPM:
|
|
549
|
+
return .0022280093
|
|
550
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
551
|
+
return 1.5472286523
|
|
552
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
553
|
+
return 1.8581441347
|
|
554
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
555
|
+
return .5041666667
|
|
556
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
557
|
+
return .0353146667
|
|
558
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
559
|
+
return .0005885778
|
|
560
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
561
|
+
return .40873456853575
|
|
562
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
563
|
+
return .0098096296
|
|
564
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
565
|
+
return .0004087346
|
|
566
|
+
elif new_unit_id == ToolkitConstants.EN_GPM:
|
|
567
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
568
|
+
return 448.8325660485
|
|
569
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
570
|
+
return 694.44444444
|
|
571
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
572
|
+
return 833.99300382
|
|
573
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
574
|
+
return 226.28571429
|
|
575
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
576
|
+
return 15.850323141
|
|
577
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
578
|
+
return .2641720524
|
|
579
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
580
|
+
return 183.4528141376
|
|
581
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
582
|
+
return 4.4028675393
|
|
583
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
584
|
+
return .1834528141
|
|
585
|
+
elif new_unit_id == ToolkitConstants.EN_MGD:
|
|
586
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
587
|
+
return .6463168831
|
|
588
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
589
|
+
return .00144
|
|
590
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
591
|
+
return 1.2009499255
|
|
592
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
593
|
+
return 0.3258514286
|
|
594
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
595
|
+
return .0228244653
|
|
596
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
597
|
+
return .0003804078
|
|
598
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
599
|
+
return .26417205124156
|
|
600
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
601
|
+
return .0063401293
|
|
602
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
603
|
+
return .0002641721
|
|
604
|
+
elif new_unit_id == ToolkitConstants.EN_IMGD:
|
|
605
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
606
|
+
return .5381713837
|
|
607
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
608
|
+
return .8326741846
|
|
609
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
610
|
+
return .0011990508
|
|
611
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
612
|
+
return .2713280726
|
|
613
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
614
|
+
return .0190053431
|
|
615
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
616
|
+
return .0003167557
|
|
617
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
618
|
+
return .21996924829908776
|
|
619
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
620
|
+
return .005279262
|
|
621
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
622
|
+
return .0002199692
|
|
623
|
+
elif new_unit_id == ToolkitConstants.EN_AFD:
|
|
624
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
625
|
+
return 1.9834710744
|
|
626
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
627
|
+
return 3.0688832772
|
|
628
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
629
|
+
return .0044191919
|
|
630
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
631
|
+
return 3.6855751432
|
|
632
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
633
|
+
return .0700456199
|
|
634
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
635
|
+
return .001167427
|
|
636
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
637
|
+
return .81070995093708
|
|
638
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
639
|
+
return .0194571167
|
|
640
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
641
|
+
return .0008107132
|
|
642
|
+
elif new_unit_id == ToolkitConstants.EN_LPS:
|
|
643
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
644
|
+
return 28.316846592
|
|
645
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
646
|
+
return 43.812636389
|
|
647
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
648
|
+
return 52.616782407
|
|
649
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
650
|
+
return .0630901964
|
|
651
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
652
|
+
return 14.276410157
|
|
653
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
654
|
+
return .0166666667
|
|
655
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
656
|
+
return 11.574074074074
|
|
657
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
658
|
+
return .2777777778
|
|
659
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
660
|
+
return .0115740741
|
|
661
|
+
elif new_unit_id == ToolkitConstants.EN_LPM:
|
|
662
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
663
|
+
return 1699.0107955
|
|
664
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
665
|
+
return 2628.7581833
|
|
666
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
667
|
+
return 3157.0069444
|
|
668
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
669
|
+
return 856.58460941
|
|
670
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
671
|
+
return 60
|
|
672
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
673
|
+
return 3.785411784
|
|
674
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
675
|
+
return 694.44444444443
|
|
676
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
677
|
+
return 16.666666667
|
|
678
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
679
|
+
return 0.6944444444
|
|
680
|
+
elif new_unit_id == ToolkitConstants.EN_MLD:
|
|
681
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
682
|
+
return 2.4465755456688
|
|
683
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
684
|
+
return 3.7854117999999777
|
|
685
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
686
|
+
return 4.54609
|
|
687
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
688
|
+
return 1.2334867714947
|
|
689
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
690
|
+
return .0864
|
|
691
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
692
|
+
return .00144
|
|
693
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
694
|
+
return .00545099296896
|
|
695
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
696
|
+
return .024
|
|
697
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
698
|
+
return .00099999999999999
|
|
699
|
+
elif new_unit_id == ToolkitConstants.EN_CMH:
|
|
700
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
701
|
+
return 101.94064773
|
|
702
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
703
|
+
return 157.725491
|
|
704
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
705
|
+
return 189.42041667
|
|
706
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
707
|
+
return 51.395076564
|
|
708
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
709
|
+
return 3.6
|
|
710
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
711
|
+
return .06
|
|
712
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
713
|
+
return 41.666666666666
|
|
714
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
715
|
+
return .227124707
|
|
716
|
+
elif old_unit == ToolkitConstants.EN_CMD:
|
|
717
|
+
return 0.0416666667
|
|
718
|
+
elif new_unit_id == ToolkitConstants.EN_CMD:
|
|
719
|
+
if old_unit == ToolkitConstants.EN_CFS:
|
|
720
|
+
return 2446.5755455
|
|
721
|
+
elif old_unit == ToolkitConstants.EN_MGD:
|
|
722
|
+
return 3785.411784
|
|
723
|
+
elif old_unit == ToolkitConstants.EN_IMGD:
|
|
724
|
+
return 4546.09
|
|
725
|
+
elif old_unit == ToolkitConstants.EN_AFD:
|
|
726
|
+
return 1233.4818375
|
|
727
|
+
elif old_unit == ToolkitConstants.EN_LPS:
|
|
728
|
+
return 86.4
|
|
729
|
+
elif old_unit == ToolkitConstants.EN_LPM:
|
|
730
|
+
return 1.44
|
|
731
|
+
elif old_unit == ToolkitConstants.EN_MLD:
|
|
732
|
+
return 1000.
|
|
733
|
+
elif old_unit == ToolkitConstants.EN_CMH:
|
|
734
|
+
return 24
|
|
735
|
+
elif old_unit == ToolkitConstants.EN_GPM:
|
|
736
|
+
return 5.450992969
|
|
737
|
+
|
|
738
|
+
# Convert units
|
|
739
|
+
pressure_data = self.pressure_data_raw
|
|
740
|
+
flow_data = self.flow_data_raw
|
|
741
|
+
demand_data = self.demand_data_raw
|
|
742
|
+
quality_node_data = self.node_quality_data_raw
|
|
743
|
+
quality_link_data = self.link_quality_data_raw
|
|
744
|
+
tanks_volume_data = self.tanks_volume_data_raw
|
|
745
|
+
surface_species_concentrations = self.surface_species_concentration_raw
|
|
746
|
+
bulk_species_node_concentrations = self.bulk_species_node_concentration_raw
|
|
747
|
+
bulk_species_link_concentrations = self.bulk_species_link_concentration_raw
|
|
748
|
+
|
|
749
|
+
if flow_unit is not None:
|
|
750
|
+
old_flow_unit = self.__sensor_config.flow_unit
|
|
751
|
+
if flow_unit == old_flow_unit:
|
|
752
|
+
warnings.warn("'flow_unit' is identical to the current flow units " +
|
|
753
|
+
"-- nothing to do!", UserWarning)
|
|
754
|
+
else:
|
|
755
|
+
# Convert flows and demands
|
|
756
|
+
convert_factor = __get_flow_convert_factor(flow_unit, old_flow_unit)
|
|
757
|
+
|
|
758
|
+
flow_data *= convert_factor
|
|
759
|
+
demand_data *= convert_factor
|
|
760
|
+
|
|
761
|
+
if is_flowunit_simetric(flow_unit) != is_flowunit_simetric(old_flow_unit):
|
|
762
|
+
# Convert tank volume and pressure
|
|
763
|
+
convert_factor = None
|
|
764
|
+
convert_factor_pressure = None
|
|
765
|
+
if is_flowunit_simetric(flow_unit) is True and \
|
|
766
|
+
is_flowunit_simetric(old_flow_unit) is False:
|
|
767
|
+
convert_factor_volume = .0283168
|
|
768
|
+
convert_factor_pressure = .70325
|
|
769
|
+
else:
|
|
770
|
+
convert_factor_volume = 35.3147
|
|
771
|
+
convert_factor_pressure = 1.4219702084872
|
|
772
|
+
|
|
773
|
+
pressure_data *= convert_factor_pressure
|
|
774
|
+
tanks_volume_data *= convert_factor_volume
|
|
775
|
+
|
|
776
|
+
if quality_unit is not None:
|
|
777
|
+
old_quality_unit = self.__sensor_config.quality_unit()
|
|
778
|
+
if quality_unit == old_quality_unit:
|
|
779
|
+
warnings.warn("'quality_unit' are identical to the current quality units " +
|
|
780
|
+
"-- nothing to do!", UserWarning)
|
|
781
|
+
else:
|
|
782
|
+
# Convert chemical concentration and time (basic quality analysis)
|
|
783
|
+
if quality_unit != TIME_UNIT_HRS:
|
|
784
|
+
convert_factor = __get_mass_convert_factor(quality_unit, old_quality_unit)
|
|
785
|
+
|
|
786
|
+
quality_node_data *= convert_factor
|
|
787
|
+
quality_link_data *= convert_factor
|
|
788
|
+
|
|
789
|
+
if bulk_species_mass_unit is not None:
|
|
790
|
+
# Convert bulk species concentrations
|
|
791
|
+
if self.__frozen_sensor_config is True:
|
|
792
|
+
for i, species_id, _ in enumerate(self.__sensor_config.bulk_species_node_sensors):
|
|
793
|
+
species_idx = self.__sensor_config.bulk_species.index(species_id)
|
|
794
|
+
new_mass_unit = bulk_species_mass_unit[species_idx]
|
|
795
|
+
old_mass_unit = self.__sensor_config.bulk_species_mass_unit[species_idx]
|
|
796
|
+
|
|
797
|
+
if new_mass_unit != old_mass_unit:
|
|
798
|
+
convert_factor = __get_mass_convert_factor(new_mass_unit, old_mass_unit)
|
|
799
|
+
bulk_species_node_concentrations[:, i, :] *= convert_factor
|
|
800
|
+
|
|
801
|
+
for i, species_id, _ in enumerate(self.__sensor_config.bulk_species_link_sensors):
|
|
802
|
+
species_idx = self.__sensor_config.bulk_species.index(species_id)
|
|
803
|
+
new_mass_unit = bulk_species_mass_unit[species_idx]
|
|
804
|
+
old_mass_unit = self.__sensor_config.bulk_species_mass_unit[species_idx]
|
|
805
|
+
|
|
806
|
+
if new_mass_unit != old_mass_unit:
|
|
807
|
+
convert_factor = __get_mass_convert_factor(new_mass_unit, old_mass_unit)
|
|
808
|
+
bulk_species_link_concentrations[:, i, :] *= convert_factor
|
|
809
|
+
else:
|
|
810
|
+
for i in range(bulk_species_node_concentrations.shape[1]):
|
|
811
|
+
if bulk_species_mass_unit[i] != self.__sensor_config.bulk_species_mass_unit[i]:
|
|
812
|
+
old_mass_unit = self.__sensor_config.bulk_species_mass_unit[i]
|
|
813
|
+
convert_factor = __get_mass_convert_factor(bulk_species_mass_unit[i],
|
|
814
|
+
old_mass_unit)
|
|
815
|
+
|
|
816
|
+
bulk_species_node_concentrations[:, i, :] *= convert_factor
|
|
817
|
+
bulk_species_link_concentrations[:, i, :] *= convert_factor
|
|
818
|
+
|
|
819
|
+
if surface_species_mass_unit is not None:
|
|
820
|
+
# Convert surface species concentrations
|
|
821
|
+
if self.__frozen_sensor_config is True:
|
|
822
|
+
for i, species_id, _ in enumerate(self.__sensor_config.surface_species_sensors):
|
|
823
|
+
species_idx = self.__sensor_config.surface_species.index(species_id)
|
|
824
|
+
new_mass_unit = surface_species_mass_unit[species_idx]
|
|
825
|
+
old_mass_unit = self.__sensor_config.surface_species_mass_unit[species_idx]
|
|
826
|
+
|
|
827
|
+
if new_mass_unit != old_mass_unit:
|
|
828
|
+
convert_factor = __get_mass_convert_factor(new_mass_unit, old_mass_unit)
|
|
829
|
+
surface_species_concentrations[:, i, :] *= convert_factor
|
|
830
|
+
else:
|
|
831
|
+
for i in range(surface_species_concentrations.shape[1]):
|
|
832
|
+
old_mass_unit = self.__sensor_config.surface_species_mass_unit[i]
|
|
833
|
+
if surface_species_mass_unit[i] != old_mass_unit:
|
|
834
|
+
convert_factor = __get_mass_convert_factor(surface_species_mass_unit[i],
|
|
835
|
+
old_mass_unit)
|
|
836
|
+
|
|
837
|
+
surface_species_concentrations[:, i, :] *= convert_factor
|
|
838
|
+
|
|
839
|
+
# Create new SCADA data instance
|
|
840
|
+
new_flow_unit = self.__sensor_config.flow_unit
|
|
841
|
+
if flow_unit is not None:
|
|
842
|
+
new_flow_unit = flow_unit
|
|
843
|
+
|
|
844
|
+
new_quality_unit = self.__sensor_config.quality_unit
|
|
845
|
+
if quality_unit is not None:
|
|
846
|
+
new_quality_unit = quality_unit
|
|
847
|
+
|
|
848
|
+
new_bulk_species_mass_unit = self.__sensor_config.bulk_species_mass_unit
|
|
849
|
+
if bulk_species_mass_unit is not None:
|
|
850
|
+
new_bulk_species_mass_unit = bulk_species_mass_unit
|
|
851
|
+
|
|
852
|
+
new_surface_species_mass_unit = self.__sensor_config.surface_species_mass_unit
|
|
853
|
+
if surface_species_mass_unit is not None:
|
|
854
|
+
new_surface_species_mass_unit = surface_species_mass_unit
|
|
855
|
+
|
|
856
|
+
new_surface_species_area_unit = self.__sensor_config.surface_species_area_unit
|
|
857
|
+
if surface_species_area_unit is not None:
|
|
858
|
+
new_surface_species_mass_unit = surface_species_area_unit
|
|
859
|
+
|
|
860
|
+
sensor_config = SensorConfig(nodes=self.__sensor_config.nodes,
|
|
861
|
+
links=self.__sensor_config.links,
|
|
862
|
+
valves=self.__sensor_config.valves,
|
|
863
|
+
pumps=self.__sensor_config.pumps,
|
|
864
|
+
tanks=self.__sensor_config.tanks,
|
|
865
|
+
bulk_species=self.__sensor_config.bulk_species,
|
|
866
|
+
surface_species=self.__sensor_config.surface_species,
|
|
867
|
+
node_id_to_idx=self.__sensor_config.node_id_to_idx,
|
|
868
|
+
link_id_to_idx=self.__sensor_config.link_id_to_idx,
|
|
869
|
+
valve_id_to_idx=self.__sensor_config.valve_id_to_idx,
|
|
870
|
+
pump_id_to_idx=self.__sensor_config.pump_id_to_idx,
|
|
871
|
+
tank_id_to_idx=self.__sensor_config.tank_id_to_idx,
|
|
872
|
+
bulkspecies_id_to_idx=self.__sensor_config.
|
|
873
|
+
bulkspecies_id_to_idx,
|
|
874
|
+
surfacespecies_id_to_idx=self.__sensor_config.
|
|
875
|
+
surfacespecies_id_to_idx,
|
|
876
|
+
flow_unit=new_flow_unit,
|
|
877
|
+
pressure_sensors=self.__sensor_config.pressure_sensors,
|
|
878
|
+
flow_sensors=self.__sensor_config.flow_sensors,
|
|
879
|
+
demand_sensors=self.__sensor_config.demand_sensors,
|
|
880
|
+
quality_node_sensors=self.__sensor_config.quality_node_sensors,
|
|
881
|
+
quality_link_sensors=self.__sensor_config.quality_link_sensors,
|
|
882
|
+
valve_state_sensors=self.__sensor_config.valve_state_sensors,
|
|
883
|
+
pump_state_sensors=self.__sensor_config.pump_state_sensors,
|
|
884
|
+
tank_volume_sensors=self.__sensor_config.tank_volume_sensors,
|
|
885
|
+
bulk_species_node_sensors=
|
|
886
|
+
self.__sensor_config.bulk_species_node_sensors,
|
|
887
|
+
bulk_species_link_sensors=
|
|
888
|
+
self.__sensor_config.bulk_species_link_sensors,
|
|
889
|
+
surface_species_sensors=
|
|
890
|
+
self.__sensor_config.surface_species_sensors,
|
|
891
|
+
quality_unit=new_quality_unit,
|
|
892
|
+
bulk_species_mass_unit=new_bulk_species_mass_unit,
|
|
893
|
+
surface_species_mass_unit=new_surface_species_mass_unit,
|
|
894
|
+
surface_species_area_unit=new_surface_species_area_unit)
|
|
895
|
+
|
|
896
|
+
return ScadaData(sensor_config=sensor_config,
|
|
897
|
+
sensor_readings_time=self.sensor_readings_time,
|
|
898
|
+
sensor_reading_events=self.sensor_reading_events,
|
|
899
|
+
sensor_noise=self.sensor_noise,
|
|
900
|
+
frozen_sensor_config=self.frozen_sensor_config,
|
|
901
|
+
pressure_data_raw=pressure_data,
|
|
902
|
+
flow_data_raw=flow_data,
|
|
903
|
+
demand_data_raw=demand_data,
|
|
904
|
+
node_quality_data_raw=quality_node_data,
|
|
905
|
+
link_quality_data_raw=quality_link_data,
|
|
906
|
+
pumps_state_data_raw=self.pumps_state_data_raw,
|
|
907
|
+
valves_state_data_raw=self.valves_state_data_raw,
|
|
908
|
+
tanks_volume_data_raw=tanks_volume_data,
|
|
909
|
+
pump_energy_usage_data=self.pump_energy_usage_data,
|
|
910
|
+
pump_efficiency_data=self.pump_efficiency_data,
|
|
911
|
+
bulk_species_node_concentration_raw=bulk_species_node_concentrations,
|
|
912
|
+
bulk_species_link_concentration_raw=bulk_species_link_concentrations,
|
|
913
|
+
surface_species_concentration_raw=surface_species_concentrations)
|
|
914
|
+
|
|
384
915
|
@property
|
|
385
916
|
def frozen_sensor_config(self) -> bool:
|
|
386
917
|
"""
|
|
@@ -863,7 +1394,7 @@ class ScadaData(Serializable):
|
|
|
863
1394
|
|
|
864
1395
|
def join(self, other) -> None:
|
|
865
1396
|
"""
|
|
866
|
-
Joins two :class:`~epyt_flow.simulation.
|
|
1397
|
+
Joins two :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instances based
|
|
867
1398
|
on the sensor reading times. Consequently, **both instances must be equal in their
|
|
868
1399
|
sensor reading times**.
|
|
869
1400
|
Attributes (i.e. types of sensor readings) that are NOT present in THIS instance
|
|
@@ -872,7 +1403,7 @@ class ScadaData(Serializable):
|
|
|
872
1403
|
|
|
873
1404
|
Parameters
|
|
874
1405
|
----------
|
|
875
|
-
other : :class:`~epyt_flow.simulation.
|
|
1406
|
+
other : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
876
1407
|
Other scada data to be concatenated to this data.
|
|
877
1408
|
"""
|
|
878
1409
|
if not isinstance(other, ScadaData):
|
|
@@ -964,16 +1495,16 @@ class ScadaData(Serializable):
|
|
|
964
1495
|
|
|
965
1496
|
def concatenate(self, other) -> None:
|
|
966
1497
|
"""
|
|
967
|
-
Concatenates two :class:`~epyt_flow.simulation.
|
|
1498
|
+
Concatenates two :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instances
|
|
968
1499
|
-- i.e. add SCADA data from another given
|
|
969
|
-
:class:`~epyt_flow.simulation.
|
|
1500
|
+
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instance to this one.
|
|
970
1501
|
|
|
971
|
-
Note that the two :class:`~epyt_flow.simulation.
|
|
1502
|
+
Note that the two :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instances
|
|
972
1503
|
must be the same in all other attributs (e.g. sensor configuration, etc.).
|
|
973
1504
|
|
|
974
1505
|
Parameters
|
|
975
1506
|
----------
|
|
976
|
-
other : :class:`~epyt_flow.simulation.
|
|
1507
|
+
other : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
977
1508
|
Other scada data to be concatenated to this data.
|
|
978
1509
|
"""
|
|
979
1510
|
if not isinstance(other, ScadaData):
|
|
@@ -8,7 +8,8 @@ from scipy.io import savemat
|
|
|
8
8
|
import pandas as pd
|
|
9
9
|
|
|
10
10
|
from .scada_data import ScadaData
|
|
11
|
-
from ..sensor_config import SensorConfig
|
|
11
|
+
from ..sensor_config import SensorConfig, massunit_to_str, flowunit_to_str, qualityunit_to_str, \
|
|
12
|
+
is_flowunit_simetric
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class ScadaDataExport():
|
|
@@ -106,13 +107,42 @@ class ScadaDataExport():
|
|
|
106
107
|
"""
|
|
107
108
|
sensor_readings = scada_data.get_data()
|
|
108
109
|
|
|
110
|
+
def __get_sensor_unit(sensor_type):
|
|
111
|
+
if sensor_type == "pressure":
|
|
112
|
+
if not is_flowunit_simetric(scada_data.sensor_config.flow_unit):
|
|
113
|
+
return "psi"
|
|
114
|
+
else:
|
|
115
|
+
return "meter"
|
|
116
|
+
elif sensor_type == "flow" or sensor_type == "demand":
|
|
117
|
+
return flowunit_to_str(scada_data.sensor_config.flow_unit)
|
|
118
|
+
elif sensor_type == "quality_node" or sensor_type == "quality_link":
|
|
119
|
+
return qualityunit_to_str(scada_data.sensor_config.quality_unit)
|
|
120
|
+
elif sensor_type == "tank_volume":
|
|
121
|
+
if is_flowunit_simetric(scada_data.sensor_config.flow_unit):
|
|
122
|
+
return "cubic meter"
|
|
123
|
+
else:
|
|
124
|
+
return "cubic foot"
|
|
125
|
+
else:
|
|
126
|
+
return ""
|
|
127
|
+
|
|
109
128
|
col_desc = [None for _ in range(sensor_readings.shape[1])]
|
|
110
129
|
sensor_config = scada_data.sensor_config
|
|
111
130
|
sensors_id_to_idx = sensor_config.sensors_id_to_idx
|
|
112
131
|
for sensor_type in sensors_id_to_idx:
|
|
132
|
+
unit_desc = __get_sensor_unit(sensor_type)
|
|
113
133
|
for item_id in sensors_id_to_idx[sensor_type]:
|
|
114
134
|
col_id = sensors_id_to_idx[sensor_type][item_id]
|
|
115
|
-
|
|
135
|
+
|
|
136
|
+
if sensor_type == "bulk_species_node" or sensor_type == "bulk_species_link":
|
|
137
|
+
bulk_species_idx = sensor_config.bulk_species.index(item_id)
|
|
138
|
+
unit_desc = massunit_to_str(sensor_config.
|
|
139
|
+
bulk_species_mass_unit[bulk_species_idx])
|
|
140
|
+
elif sensor_type == "surface_species":
|
|
141
|
+
surface_species_idx = sensor_config.surface_species.index(item_id)
|
|
142
|
+
unit_desc = massunit_to_str(sensor_config.
|
|
143
|
+
bulk_species_mass_unit[surface_species_idx])
|
|
144
|
+
|
|
145
|
+
col_desc[col_id] = [sensor_type, item_id, unit_desc]
|
|
116
146
|
|
|
117
147
|
return np.array(col_desc, dtype=object)
|
|
118
148
|
|
|
@@ -163,7 +193,8 @@ class ScadaDataNumpyExport(ScadaDataExport):
|
|
|
163
193
|
scada_data.change_sensor_config(old_sensor_config)
|
|
164
194
|
|
|
165
195
|
np.savez(self.f_out, sensor_readings=sensor_readings, col_desc=col_desc,
|
|
166
|
-
sensor_readings_time=sensor_readings_time
|
|
196
|
+
sensor_readings_time=sensor_readings_time,
|
|
197
|
+
flow_unit=scada_data.sensor_config.flow_unit)
|
|
167
198
|
|
|
168
199
|
|
|
169
200
|
class ScadaDataXlsxExport(ScadaDataExport):
|
|
@@ -208,7 +239,7 @@ class ScadaDataXlsxExport(ScadaDataExport):
|
|
|
208
239
|
to_excel(writer, sheet_name="Sensor readings", index=False)
|
|
209
240
|
pd.DataFrame(sensor_readings_time, columns=["Time (s)"]). \
|
|
210
241
|
to_excel(writer, sheet_name="Sensor readings time", index=False)
|
|
211
|
-
pd.DataFrame(col_desc, columns=["Name", "Type", "Location"]). \
|
|
242
|
+
pd.DataFrame(col_desc, columns=["Name", "Type", "Location", "Unit"]). \
|
|
212
243
|
to_excel(writer, sheet_name="Sensors description", index=False)
|
|
213
244
|
|
|
214
245
|
|
|
@@ -246,4 +277,6 @@ class ScadaDataMatlabExport(ScadaDataExport):
|
|
|
246
277
|
scada_data.change_sensor_config(old_sensor_config)
|
|
247
278
|
|
|
248
279
|
savemat(self.f_out, {"sensor_readings": sensor_readings,
|
|
249
|
-
"sensor_readings_time": sensor_readings_time,
|
|
280
|
+
"sensor_readings_time": sensor_readings_time,
|
|
281
|
+
"col_desc": col_desc,
|
|
282
|
+
"flow_unit": scada_data.sensor_config.flow_unit})
|