Source code for epyt_flow.simulation.scenario_config

  1"""
  2Module provides a class for specifying scenario configurations.
  3"""
  4from typing import Any
  5from copy import deepcopy
  6import os
  7import json
  8from pathlib import Path
  9import numpy as np
 10
 11from ..uncertainty import AbsoluteGaussianUncertainty, RelativeGaussianUncertainty, \
 12    AbsoluteUniformUncertainty, RelativeUniformUncertainty, ModelUncertainty, \
 13    SensorNoise, Uncertainty
 14from .sensor_config import SensorConfig
 15from .scada import CustomControlModule, SimpleControlModule, ComplexControlModule
 16from .events import SystemEvent, SensorReadingEvent
 17from .events.sensor_faults import SensorFaultConstant, SensorFaultDrift, SensorFaultGaussian, \
 18    SensorFaultPercentage, SensorFaultStuckZero
 19from .events.leakages import AbruptLeakage, IncipientLeakage
 20from ..serialization import serializable, Serializable, SCENARIO_CONFIG_ID
 21from ..topology import NetworkTopology
 22
 23
[docs] 24@serializable(SCENARIO_CONFIG_ID, ".epytflow_scenario_config") 25class ScenarioConfig(Serializable): 26 """ 27 Configuration of a scenario. 28 29 Parameters 30 ---------- 31 scenario_config : :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig`, optional 32 Uses the given scenario configuration to create this instance -- 33 other attributes passed to this constructor override the attributes in 'scenario_config'. 34 35 Note that if 'scenario_config' is None then 'f_inp_in' can not be None -- 36 i.e. either 'scenario_config' or 'f_inp_in' must be given. 37 38 The default is None. 39 f_inp_in : `str`, optional 40 Path to the .inp file. 41 42 Note that if 'f_inp_in' is None then 'scenario_config' can not be None -- 43 i.e. either 'scenario_config' or 'f_inp_in' must be given. 44 45 The default is None. 46 f_msx_in : `str`, optional 47 Path to the .msx file -- optional, only necessary if EPANET-MSX is used. 48 49 The default is None 50 network_topology : :class:`~epyt_flow.topology.NetworkTopology`, optional 51 Specification of the network topology -- necessary if .inp file does not exist 52 or is not shared. 53 54 The default is None. 55 general_params : `dict`, optional 56 General parameters such as the demand model, hydraulic time steps, etc. 57 58 The default is None 59 sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig`, optional 60 Specification of all sensors. 61 62 The default is None 63 memory_consumption_estimate : float, optional 64 Estimated memory consumption of this scenario in MB -- i.e. the amount of memory that is 65 needed on the hard disk as well as in RAM. 66 67 The default is None. 68 sensor_noise : :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise`, optional 69 Speciation of sensor noise -- i.e. noise/uncertainty affecting the sensor readings. 70 71 The default is None 72 csutom_controls : list[:class:`~epyt_flow.simulation.scada.custom_control.CustomControlModule`], optional 73 List of custom control modules that are active during the simulation. 74 75 The default is an empty list. 76 simple_controls : list[:class:`~epyt_flow.simulation.scada.simple_control.SimpleControlModule`], optional 77 List of EPANET control rules that are active during the simulation. 78 79 The default is an empty list. 80 complex_controls : list[:class:`~epyt_flow.simulation.scada.complex_control.ComplexControlModule`], optional 81 List of complex (i.e. IF-THEN-ELSE) EPANET control rules that are active during the simulation. 82 83 The default is an empty list. 84 model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`, optional 85 Specification of model uncertainty. 86 system_events : list[:class:`~epyt_flow.simulation.events.system_event.SystemEvent`], optional 87 List of system events -- i.e. events that directly affect the simulation (e.g. leakages). 88 89 The default is an empty list. 90 sensor_reading_events : list[:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`], optional 91 List of sensor reading events -- i.e. events that affect the readings of sensors. 92 93 The default is an empty list. 94 """ 95 96 def __init__(self, scenario_config: Any = None, f_inp_in: str = None, f_msx_in: str = None, 97 network_topology: NetworkTopology = None, 98 general_params: dict = None, sensor_config: SensorConfig = None, 99 memory_consumption_estimate: float = None, 100 custom_controls: list[CustomControlModule] = [], 101 simple_controls: list[SimpleControlModule] = [], 102 complex_controls: list[ComplexControlModule] = [], 103 sensor_noise: SensorNoise = None, 104 model_uncertainty: ModelUncertainty = None, 105 system_events: list[SystemEvent] = [], 106 sensor_reading_events: list[SensorReadingEvent] = [], **kwds): 107 if f_inp_in is None and scenario_config is None: 108 raise ValueError("Either 'f_inp_in' or 'scenario_config' must be given") 109 if scenario_config is not None: 110 if not isinstance(scenario_config, ScenarioConfig): 111 raise TypeError("'scenario_config' must be an instance of " + 112 "'epyt_flow.simulation.ScenarioConfig' but not of " + 113 f"'{type(scenario_config)}'") 114 if f_inp_in is not None: 115 if not isinstance(f_inp_in, str): 116 raise TypeError("'f_inp_in' must be an instance of 'str' " + 117 f"but no of '{type(f_inp_in)}'") 118 if f_msx_in is not None: 119 if not isinstance(f_msx_in, str): 120 raise TypeError("'f_msx_in' must be an instance of 'str' " + 121 f"but no of '{type(f_msx_in)}'") 122 if network_topology is not None: 123 if not isinstance(network_topology, NetworkTopology): 124 raise TypeError("'network_topology' msut be an instance of 'NetworkTopology' " + 125 f"but not of '{type(network_topology)}'") 126 if general_params is not None: 127 if not isinstance(general_params, dict): 128 raise TypeError("'general_params' must be an instance of 'dict' " + 129 f"but not of '{type(general_params)}'") 130 if sensor_config is not None: 131 if not isinstance(sensor_config, SensorConfig): 132 raise TypeError("'sensor_config' must be an instance of " + 133 "'epyt_flow.simulation.SensorConfig' but not of " + 134 f"'{type(sensor_config)}'") 135 if memory_consumption_estimate is not None: 136 if not isinstance(memory_consumption_estimate, float) or \ 137 memory_consumption_estimate <= 0: 138 raise ValueError("'memory_consumption_estimate' must be a positive integer") 139 if len(custom_controls) != 0: 140 if any(not isinstance(c, CustomControlModule) for c in custom_controls): 141 raise TypeError("Each item in 'custom_controls' must be an instance of " + 142 "'epyt_flow.simulation.scada.CustomControlModule'") 143 if not isinstance(simple_controls, list): 144 raise TypeError("'simple_controls' must be an instance of " + 145 "'list[epyt_flow.simulation.scada.SimpleControlModule]' but no of " + 146 f"'{type(simple_controls)}'") 147 if len(simple_controls) != 0: 148 if any(not isinstance(c, SimpleControlModule) for c in simple_controls): 149 raise TypeError("Each item in 'simple_controls' must be an instance of " + 150 "'epyt_flow.simulation.scada.SimppleControlModule'") 151 if len(complex_controls) != 0: 152 if any(not isinstance(c, ComplexControlModule) for c in complex_controls): 153 raise TypeError("Each item in 'complex_controls' must be an instance of " + 154 "'epyt_flow.simulation.scada.ComplexControlModule'") 155 if sensor_noise is not None: 156 if not isinstance(sensor_noise, SensorNoise): 157 raise TypeError("'sensor_noise' must be an instance of " + 158 "'epyt_flow.uncertainty.SensorNoise' but not of " + 159 f"'{type(sensor_noise)}'") 160 if model_uncertainty is not None: 161 if not isinstance(model_uncertainty, ModelUncertainty): 162 raise TypeError("'model_uncertainty' must be an instance of " + 163 "'epyt_flow.uncertainty.ModelUncertainty' but not of " + 164 f"'{type(model_uncertainty)}'") 165 if not isinstance(system_events, list): 166 raise TypeError("'system_events' must be an instance of " + 167 "'list[epyt_flow.simulation.events.SystemEvent]' but no of " + 168 f"'{type(system_events)}'") 169 if len(system_events) != 0: 170 if any(not isinstance(c, SystemEvent) for c in system_events): 171 raise TypeError("Each item in 'system_events' must be an instance of " + 172 "'epyt_flow.simulation.events.SystemEvent'") 173 if not isinstance(sensor_reading_events, list): 174 raise TypeError("'sensor_reading_events' must be an instance of " + 175 "'list[epyt_flow.simulation.events.SensorReadingEvent]' but not of " + 176 f"'{type(sensor_reading_events)}'") 177 if len(sensor_reading_events) != 0: 178 if any(not isinstance(c, SensorReadingEvent) for c in sensor_reading_events): 179 raise TypeError("Each item in 'sensor_reading_events' must be an instance of " + 180 "'epyt_flow.simulation.events.SensorReadingEvent'") 181 182 if scenario_config is not None: 183 self.__f_inp_in = scenario_config.f_inp_in 184 self.__f_msx_in = scenario_config.f_msx_in if f_msx_in is None else f_msx_in 185 186 if network_topology is None: 187 self.__network_topology = scenario_config.network_topology 188 else: 189 self.__network_topology = network_topology 190 191 if general_params is None: 192 self.__general_params = scenario_config.general_params 193 else: 194 self.__general_params = general_params 195 196 if sensor_config is None: 197 self.__sensor_config = scenario_config.sensor_config 198 else: 199 self.__sensor_config = sensor_config 200 201 if memory_consumption_estimate is None: 202 self.__memory_consumption_estimate = scenario_config.memory_consumption_estimate 203 else: 204 self.__memory_consumption_estimate = memory_consumption_estimate 205 206 if len(custom_controls) == 0: 207 self.__custom_controls = scenario_config.custom_controls 208 else: 209 self.__custom_controls = custom_controls 210 211 if len(simple_controls) == 0: 212 self.__simple_controls = scenario_config.simple_controls 213 else: 214 self.__simple_controls = simple_controls 215 216 if len(complex_controls) == 0: 217 self.__complex_controls = scenario_config.complex_controls 218 else: 219 self.__complex_controls = complex_controls 220 221 if sensor_noise is None: 222 self.__sensor_noise = scenario_config.sensor_noise 223 else: 224 self.__sensor_noise = sensor_noise 225 226 if model_uncertainty is None: 227 self.__model_uncertainty = scenario_config.model_uncertainty 228 else: 229 self.__model_uncertainty = model_uncertainty 230 231 if len(system_events) == 0: 232 self.__system_events = scenario_config.system_events 233 else: 234 self.__system_events = system_events 235 236 if len(sensor_reading_events) == 0: 237 self.__sensor_reading_events = scenario_config.sensor_reading_events 238 else: 239 self.__sensor_reading_events = sensor_reading_events 240 else: 241 self.__f_inp_in = f_inp_in 242 self.__f_msx_in = f_msx_in 243 self.__network_topology = network_topology 244 self.__general_params = general_params 245 self.__sensor_config = sensor_config 246 self.__memory_consumption_estimate = memory_consumption_estimate 247 self.__custom_controls = custom_controls 248 self.__simple_controls = simple_controls 249 self.__complex_controls = complex_controls 250 self.__sensor_noise = sensor_noise 251 self.__system_events = system_events 252 self.__sensor_reading_events = sensor_reading_events 253 254 if model_uncertainty is not None: 255 self.__model_uncertainty = model_uncertainty 256 else: 257 self.__model_uncertainty = ModelUncertainty() 258 259 super().__init__(**kwds) 260 261 @property 262 def f_inp_in(self) -> str: 263 """ 264 Gets the path to the .inp file. 265 266 Returns 267 ------- 268 `str` 269 Path to the .inp file. 270 """ 271 if Path(self.__f_inp_in).is_absolute(): 272 return self.__f_inp_in 273 elif Path(self.__f_inp_in).name == self.__f_inp_in: 274 return os.path.join(self._parent_path, self.__f_inp_in) 275 else: 276 return self.__f_inp_in 277 278 @property 279 def f_msx_in(self) -> str: 280 """ 281 Gets the path to the .msx file. 282 283 Returns 284 ------- 285 `str` 286 Path to the .msx file. 287 """ 288 if self.__f_msx_in is None: 289 return None 290 else: 291 if Path(self.__f_msx_in).is_absolute(): 292 return self.__f_msx_in 293 elif Path(self.__f_msx_in).name == self.__f_msx_in: 294 return os.path.join(self._parent_path, self.__f_msx_in) 295 else: 296 return self.__f_msx_in 297 298 @property 299 def network_topology(self) -> NetworkTopology: 300 """ 301 Returns the specification of the network topology. 302 303 Returns 304 ------- 305 :class:`~epyt_flow.topology.NetworkTopology` 306 Network topology. 307 """ 308 return deepcopy(self.__network_topology) 309 310 @property 311 def general_params(self) -> dict: 312 """ 313 Gets general parameters such as hydraulic time step, etc. 314 315 Returns 316 ------- 317 `dict` 318 All general parameters as dictionary -- the parameter name serves as a key. 319 """ 320 return deepcopy(self.__general_params) 321 322 @property 323 def sensor_config(self) -> SensorConfig: 324 """ 325 Gets the sensor configuration. 326 327 Returns 328 ------- 329 :class:`~epyt_flow.simulation.sensor_config.SensorConfig` 330 Sensor configuration. 331 """ 332 return deepcopy(self.__sensor_config) 333 334 @property 335 def memory_consumption_estimate(self) -> float: 336 """ 337 Gets the estimated memory consumption of this scenario -- i.e. the amount of memory that is 338 needed on the hard disk as well as in RAM. 339 340 Returns 341 ------- 342 `float` 343 Estimated memory consumption in MB. 344 """ 345 return self.__memory_consumption_estimate 346 347 @property 348 def custom_controls(self) -> list[CustomControlModule]: 349 """ 350 Returns the list of all custom control modules that are active during the simulation. 351 352 Returns 353 ------- 354 list[:class:`~epyt_flow.simulation.scada.custom_control.CustomControlModule`] 355 List of all custom control modules that are active during the simulation. 356 """ 357 return deepcopy(self.__custom_controls) 358 359 @property 360 def simple_controls(self) -> list[SimpleControlModule]: 361 """ 362 Gets the list of all EPANET control rules that are active during the simulation. 363 364 Returns 365 ------- 366 list[:class:`~epyt_flow.simulation.scada.simple_control.SimpleControlModule`] 367 List of all EPANET control rules that are active during the simulation. 368 """ 369 return deepcopy(self.__simple_controls) 370 371 @property 372 def complex_controls(self) -> list[ComplexControlModule]: 373 """ 374 Gets the list of all complex (i.e. IF-THEN-ELSE) EPANET control rules 375 that are active during the simulation. 376 377 Returns 378 ------- 379 list[:class:`~epyt_flow.simulation.scada.complex_control.ComplexControlModule`] 380 List of all complex EPANET control rules that are active during the simulation. 381 """ 382 return deepcopy(self.__complex_controls) 383 384 @property 385 def sensor_noise(self) -> SensorNoise: 386 """ 387 Gets the sensor noise/uncertainty specification. 388 389 Returns 390 ------- 391 :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise` 392 Sensor noise/uncertainty. 393 """ 394 return deepcopy(self.__sensor_noise) 395 396 @property 397 def model_uncertainty(self) -> ModelUncertainty: 398 """ 399 Gets the model uncertainty specification. 400 401 Returns 402 ------- 403 :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty` 404 Model uncertainty specification. 405 """ 406 return deepcopy(self.__model_uncertainty) 407 408 @property 409 def system_events(self) -> list[SystemEvent]: 410 """ 411 Gets all system events. 412 413 Returns 414 ------- 415 list[:class:`~epyt_flow.simulation.events.system_event.SystemEvent`] 416 All system events. 417 """ 418 return deepcopy(self.__system_events) 419 420 @property 421 def sensor_reading_events(self) -> list[SensorReadingEvent]: 422 """ 423 Gets all sensor reading events. 424 425 Returns 426 ------- 427 list[:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`] 428 All sensor reading events. 429 """ 430 return deepcopy(self.__sensor_reading_events) 431
[docs] 432 def get_attributes(self) -> dict: 433 my_attributes = {"f_inp_in": self.__f_inp_in, "f_msx_in": self.__f_msx_in, 434 "network_topology": self.__network_topology, 435 "general_params": self.__general_params, 436 "sensor_config": self.__sensor_config, 437 "memory_consumption_estimate": self.__memory_consumption_estimate, 438 "custom_controls": self.__custom_controls, 439 "simple_controls": self.__simple_controls, 440 "complex_controls": self.__complex_controls, 441 "sensor_noise": self.__sensor_noise, 442 "model_uncertainty": self.__model_uncertainty, 443 "system_events": self.__system_events, 444 "sensor_reading_events": self.__sensor_reading_events} 445 446 return super().get_attributes() | my_attributes
447 448 def __eq__(self, other) -> bool: 449 if not isinstance(other, ScenarioConfig): 450 raise TypeError("Can not compare 'ScenarioConfig' instance " + 451 f"with '{type(other)}' instance") 452 453 return self.__f_inp_in == other.f_inp_in and self.__f_msx_in == other.f_msx_in \ 454 and self.__network_topology == other.network_topology \ 455 and self.__general_params == other.general_params \ 456 and self.__memory_consumption_estimate == other.memory_consumption_estimate \ 457 and self.__sensor_config == other.sensor_config \ 458 and np.all(self.__custom_controls == other.custom_controls) \ 459 and np.all(self.__simple_controls == other.simple_controls) \ 460 and np.all(self.__complex_controls == other.complex_controls) \ 461 and self.__model_uncertainty == other.model_uncertainty \ 462 and np.all(self.__system_events == other.system_events) \ 463 and np.all(self.__sensor_reading_events == other.sensor_reading_events) 464 465 def __str__(self) -> str: 466 return f"f_inp_in: {self.f_inp_in} f_msx_in: {self.f_msx_in} " + \ 467 f"general_params: {self.general_params} sensor_config: {self.sensor_config} " + \ 468 f"memory_consumption_estimate: {self.memory_consumption_estimate} " + \ 469 f"simple_controls: {self.simple_controls} " + \ 470 f"complex_controls: {self.__complex_controls} " + \ 471 f"custom_controls: {self.__custom_controls}" + \ 472 f"sensor_noise: {self.sensor_noise} model_uncertainty: {self.model_uncertainty} " + \ 473 f"system_events: {','.join(map(str, self.system_events))} " + \ 474 f"sensor_reading_events: {','.join(map(str, self.sensor_reading_events))}" 475
[docs] 476 @staticmethod 477 def load_from_json_file(f_json_in: str) -> Any: 478 """ 479 Loads a scenario configuration from a given JSON file. 480 481 Parameters 482 ---------- 483 f_json_in : `str` 484 Path to JSON configuration file. 485 486 Returns 487 ------- 488 :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig` 489 Loaded scenario configuration. 490 """ 491 with open(f_json_in, "r", encoding="utf-8") as f: 492 return ScenarioConfig.load_from_json(f.read())
493
[docs] 494 @staticmethod 495 def load_from_json(config_data: str) -> Any: 496 """ 497 Loads a scenario configuration from a given JSON string. 498 499 Parameters 500 ---------- 501 config_data : `str` 502 JSON data. 503 504 Returns 505 ------- 506 :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig` 507 Loaded scenario configuration. 508 """ 509 data = json.loads(config_data) 510 511 # General parameters and sensor configuration 512 general_settings = data["general"] 513 f_inp_in = general_settings["file_inp"] 514 f_msx_in = general_settings["file_msx"] if "file_msx" in general_settings.keys() else None 515 516 general_params = {"simulation_duration": general_settings["simulation_duration"], 517 "hydraulic_time_step": general_settings["hydraulic_time_step"], 518 "quality_time_step": general_settings["quality_time_step"]} 519 if "reporting_time_step" in general_settings.keys(): 520 general_params["reporting_time_step"] = general_settings["reporting_time_step"] 521 if "reporting_time_start" in general_settings.keys(): 522 general_params["reporting_time_start"] = general_settings["reporting_time_start"] 523 if "demand_model" in general_settings.keys(): 524 general_params["demand_model"] = general_settings["demand_model"] 525 if "quality_model" in general_settings.keys(): 526 general_params["quality_model"] = general_settings["quality_model"] 527 if "flow_units_id" in general_settings.keys(): 528 general_params["flow_units_id"] = general_settings["flow_units_id"] 529 if "pressure_units_id" in general_settings.keys(): 530 general_params["pressure_units_id"] = general_settings["pressure_units_id"] 531 532 sensor_config = data["sensors"] 533 534 if "pressure_sensors" in sensor_config.keys(): 535 pressure_sensors = sensor_config["pressure_sensors"] 536 else: 537 pressure_sensors = [] 538 539 if "flow_sensors" in sensor_config.keys(): 540 flow_sensors = sensor_config["flow_sensors"] 541 else: 542 flow_sensors = [] 543 544 if "demand_sensors" in sensor_config.keys(): 545 demand_sensors = sensor_config["demand_sensors"] 546 else: 547 demand_sensors = [] 548 549 if "node_quality_sensors" in sensor_config.keys(): 550 node_quality_sensors = sensor_config["node_quality_sensors"] 551 else: 552 node_quality_sensors = [] 553 554 if "link_quality_sensors" in sensor_config.keys(): 555 link_quality_sensors = sensor_config["link_quality_sensors"] 556 else: 557 link_quality_sensors = [] 558 559 if "tank_volume_sensors" in sensor_config.keys(): 560 tank_volume_sensors = sensor_config["tank_volume_sensors"] 561 else: 562 tank_volume_sensors = [] 563 564 if "valve_state_sensors" in sensor_config.keys(): 565 valve_state_sensors = sensor_config["valve_state_sensors"] 566 else: 567 valve_state_sensors = [] 568 569 if "pump_state_sensors" in sensor_config.keys(): 570 pump_state_sensors = sensor_config["pump_state_sensors"] 571 else: 572 pump_state_sensors = [] 573 574 if "bulk_species_node_sensors" in sensor_config.keys(): 575 bulk_species_node_sensors = sensor_config["bulk_species_node_sensors"] 576 else: 577 bulk_species_node_sensors = {} 578 579 if "bulk_species_link_sensors" in sensor_config.keys(): 580 bulk_species_link_sensors = sensor_config["bulk_species_link_sensors"] 581 else: 582 bulk_species_link_sensors = {} 583 584 if "surface_species_sensors" in sensor_config.keys(): 585 surface_species_sensors = sensor_config["surface_species_sensors"] 586 else: 587 surface_species_sensors = {} 588 589 # Uncertainties 590 if "uncertainties" in data.keys(): 591 def parse_uncertantiy(uncertainty_desc: dict) -> Uncertainty: 592 uncertainty_type = uncertainty_desc["type"] 593 del uncertainty_desc["type"] 594 595 if uncertainty_type == "absolute_gaussian": 596 return AbsoluteGaussianUncertainty(**uncertainty_desc) 597 elif uncertainty_type == "relative_gaussian": 598 return RelativeGaussianUncertainty(**uncertainty_desc) 599 elif uncertainty_type == "absolute_uniform": 600 return AbsoluteUniformUncertainty(**uncertainty_desc) 601 elif uncertainty_type == "relative_uniform": 602 return RelativeUniformUncertainty(**uncertainty_desc) 603 else: 604 raise ValueError(f"Unknown uncertainty '{uncertainty_type}'") 605 606 uncertanties = data["uncertainties"] 607 if "pipe_length_uncertainty" in uncertanties.keys(): 608 pipe_length_uncertainty = parse_uncertantiy(uncertanties["pipe_length_uncertainty"]) 609 else: 610 pipe_length_uncertainty = None 611 if "pipe_roughness_uncertainty" in uncertanties.keys(): 612 pipe_roughness_uncertainty = parse_uncertantiy( 613 uncertanties["pipe_roughness_uncertainty"]) 614 else: 615 pipe_roughness_uncertainty = None 616 if "pipe_diameter_uncertainty" in uncertanties.keys(): 617 pipe_diameter_uncertainty = parse_uncertantiy( 618 uncertanties["pipe_diameter_uncertainty"]) 619 else: 620 pipe_diameter_uncertainty = None 621 if "demand_base_uncertainty" in uncertanties.keys(): 622 demand_base_uncertainty = parse_uncertantiy(uncertanties["demand_base_uncertainty"]) 623 else: 624 demand_base_uncertainty = None 625 if "demand_pattern_uncertainty" in uncertanties.keys(): 626 demand_pattern_uncertainty = parse_uncertantiy( 627 uncertanties["demand_pattern_uncertainty"]) 628 else: 629 demand_pattern_uncertainty = None 630 if "elevation_uncertainty" in uncertanties.keys(): 631 elevation_uncertainty = parse_uncertantiy(uncertanties["elevation_uncertainty"]) 632 else: 633 elevation_uncertainty = None 634 if "constants_uncertainty" in uncertanties.keys(): 635 constants_uncertainty = parse_uncertantiy(uncertanties["constants_uncertainty"]) 636 else: 637 constants_uncertainty = None 638 if "parameters_uncertainty" in uncertanties.keys(): 639 parameters_uncertainty = parse_uncertantiy(uncertanties["parameters_uncertainty"]) 640 else: 641 parameters_uncertainty = None 642 643 model_uncertainty = ModelUncertainty(pipe_length_uncertainty, 644 pipe_roughness_uncertainty, 645 pipe_diameter_uncertainty, demand_base_uncertainty, 646 demand_pattern_uncertainty, elevation_uncertainty, 647 constants_uncertainty, parameters_uncertainty) 648 649 if "sensor_noise" in uncertanties.keys(): 650 sensor_noise = SensorNoise(parse_uncertantiy(uncertanties["sensor_noise"])) 651 else: 652 sensor_noise = None 653 654 # Events 655 leakages = [] 656 if "leakages" in data.keys(): 657 def parse_leak(leak_desc): 658 leak_type = leak_desc["type"] 659 del leak_desc["type"] 660 661 if leak_type == "abrupt": 662 return AbruptLeakage(**leak_desc) 663 elif leak_type == "incipient": 664 return IncipientLeakage(**leak_desc) 665 else: 666 raise ValueError(f"Unknown leakage type '{leak_type}'") 667 668 leakages = [parse_leak(leak) for leak in data["leakages"]] 669 670 sensor_faults = [] 671 if "sensor_faults" in data.keys(): 672 def parse_sensor_fault(sensor_fault_desc): 673 fault_type = sensor_fault_desc["type"] 674 del sensor_fault_desc["type"] 675 676 if fault_type == "constant": 677 return SensorFaultConstant(**sensor_fault_desc) 678 elif fault_type == "drift": 679 return SensorFaultDrift(**sensor_fault_desc) 680 elif fault_type == "gaussian": 681 return SensorFaultGaussian(**sensor_fault_desc) 682 elif fault_type == "percentage": 683 return SensorFaultPercentage(**sensor_fault_desc) 684 elif fault_type == "stuckatzero": 685 return SensorFaultStuckZero(**sensor_fault_desc) 686 else: 687 raise ValueError(f"Unknown sensor fault '{fault_type}'") 688 689 sensor_faults = [parse_sensor_fault(sensor_fault) 690 for sensor_fault in data["sensor_faults"]] 691 692 # Load .inp file to get a list of all nodes and links/pipes 693 sensor_config = None 694 from .scenario_simulator import ScenarioSimulator 695 with ScenarioSimulator(f_inp_in) as scenario: 696 sensor_config = SensorConfig.create_empty_sensor_config(scenario.sensor_config) 697 sensor_config.pressure_sensors = pressure_sensors 698 sensor_config.flow_sensors = flow_sensors 699 sensor_config.demand_sensors = demand_sensors 700 sensor_config.quality_node_sensors = node_quality_sensors 701 sensor_config.quality_link_sensors = link_quality_sensors 702 sensor_config.valve_state_sensors = valve_state_sensors 703 sensor_config.pump_state_sensors = pump_state_sensors 704 sensor_config.tank_volume_sensors = tank_volume_sensors 705 sensor_config.bulk_species_node_sensors = bulk_species_node_sensors 706 sensor_config.bulk_species_link_sensors = bulk_species_link_sensors 707 sensor_config.surface_species_sensors = surface_species_sensors 708 709 # Create final scenario configuration 710 return ScenarioConfig(f_inp_in=f_inp_in, f_msx_in=f_msx_in, general_params=general_params, 711 sensor_config=sensor_config, sensor_noise=sensor_noise, 712 model_uncertainty=model_uncertainty, system_events=leakages, 713 sensor_reading_events=sensor_faults)