epyt-flow 0.1.1__py3-none-any.whl → 0.3.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 (31) hide show
  1. epyt_flow/EPANET/compile_linux.sh +4 -0
  2. epyt_flow/EPANET/compile_macos.sh +4 -0
  3. epyt_flow/VERSION +1 -1
  4. epyt_flow/__init__.py +29 -18
  5. epyt_flow/data/benchmarks/leakdb.py +7 -12
  6. epyt_flow/data/networks.py +404 -40
  7. epyt_flow/rest_api/base_handler.py +14 -0
  8. epyt_flow/rest_api/scada_data/__init__.py +0 -0
  9. epyt_flow/rest_api/{scada_data_handler.py → scada_data/data_handlers.py} +3 -162
  10. epyt_flow/rest_api/scada_data/export_handlers.py +140 -0
  11. epyt_flow/rest_api/scada_data/handlers.py +209 -0
  12. epyt_flow/rest_api/scenario/__init__.py +0 -0
  13. epyt_flow/rest_api/scenario/event_handlers.py +118 -0
  14. epyt_flow/rest_api/{scenario_handler.py → scenario/handlers.py} +86 -67
  15. epyt_flow/rest_api/scenario/simulation_handlers.py +174 -0
  16. epyt_flow/rest_api/scenario/uncertainty_handlers.py +118 -0
  17. epyt_flow/rest_api/server.py +61 -24
  18. epyt_flow/simulation/events/leakages.py +27 -17
  19. epyt_flow/simulation/scada/scada_data.py +545 -14
  20. epyt_flow/simulation/scada/scada_data_export.py +39 -12
  21. epyt_flow/simulation/scenario_config.py +14 -20
  22. epyt_flow/simulation/scenario_simulator.py +358 -114
  23. epyt_flow/simulation/sensor_config.py +693 -37
  24. epyt_flow/topology.py +149 -8
  25. epyt_flow/utils.py +75 -18
  26. {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/METADATA +33 -5
  27. {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/RECORD +30 -22
  28. epyt_flow/EPANET/compile.sh +0 -4
  29. {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/LICENSE +0 -0
  30. {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/WHEEL +0 -0
  31. {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.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
- from ..sensor_config import SensorConfig, SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, \
10
- SENSOR_TYPE_NODE_DEMAND, SENSOR_TYPE_NODE_PRESSURE, SENSOR_TYPE_NODE_QUALITY, \
11
- SENSOR_TYPE_PUMP_STATE, SENSOR_TYPE_TANK_VOLUME, SENSOR_TYPE_VALVE_STATE, \
12
- SENSOR_TYPE_NODE_BULK_SPECIES, SENSOR_TYPE_LINK_BULK_SPECIES, SENSOR_TYPE_SURFACE_SPECIES
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
@@ -118,7 +122,6 @@ class ScadaData(Serializable):
118
122
 
119
123
  The default is False.
120
124
  """
121
-
122
125
  def __init__(self, sensor_config: SensorConfig, sensor_readings_time: np.ndarray,
123
126
  pressure_data_raw: np.ndarray = None, flow_data_raw: np.ndarray = None,
124
127
  demand_data_raw: np.ndarray = None, node_quality_data_raw: np.ndarray = None,
@@ -132,7 +135,8 @@ class ScadaData(Serializable):
132
135
  sensor_faults: list[SensorFault] = [],
133
136
  sensor_reading_attacks: list[SensorReadingAttack] = [],
134
137
  sensor_reading_events: list[SensorReadingEvent] = [],
135
- sensor_noise: SensorNoise = None, frozen_sensor_config: bool = False, **kwds):
138
+ sensor_noise: SensorNoise = None, frozen_sensor_config: bool = False,
139
+ **kwds):
136
140
  if not isinstance(sensor_config, SensorConfig):
137
141
  raise TypeError("'sensor_config' must be an instance of " +
138
142
  "'epyt_flow.simulation.SensorConfig' but not of " +
@@ -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.scada_data.scada_data.ScadaData` instances based
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.scada_data.scada_data.ScadaData`
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.scada_data.scada_data.ScadaData` instances
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.scada_data.scada_data.ScadaData` instance to this one.
1500
+ :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instance to this one.
970
1501
 
971
- Note that the two :class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData` instances
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.scada_data.scada_data.ScadaData`
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):