Source code for epyt_flow.uncertainty.model_uncertainty

  1"""
  2Module provides a class for implementing model uncertainty.
  3"""
  4from typing import Optional
  5from copy import deepcopy
  6import warnings
  7from epanet_plus import EPyT, EpanetConstants
  8import numpy as np
  9
 10from ..serialization import serializable, JsonSerializable, MODEL_UNCERTAINTY_ID
 11from .uncertainties import Uncertainty
 12
 13
[docs] 14@serializable(MODEL_UNCERTAINTY_ID, ".epytflow_uncertainty_model_uncertainty") 15class ModelUncertainty(JsonSerializable): 16 """ 17 Class implementing model uncertainty -- i.e. uncertainties in pipe length, pipe roughness, 18 base demand, etc. 19 20 Parameters 21 ---------- 22 global_pipe_length_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 23 Global uncertainty of pipe lengths. None, in the case of no uncertainty. 24 25 The default is None. 26 global_pipe_roughness_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 27 Global uncertainty of pipe roughness coefficients. None, in the case of no uncertainty. 28 29 The default is None. 30 global_pipe_diameter_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 31 Global uncertainty of pipe diameters. None, in the case of no uncertainty. 32 33 The default is None. 34 global_base_demand_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 35 Global uncertainty of base demands. None, in the case of no uncertainty. 36 37 The default is None. 38 global_demand_pattern_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 39 Global uncertainty of demand patterns. None, in the case of no uncertainty. 40 41 The default is None. 42 global_elevation_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 43 Global uncertainty of elevations. None, in the case of no uncertainty. 44 45 The default is None. 46 global_constants_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 47 Global uncertainty of MSX constants. None, in the case of no uncertainty. 48 49 The default is None. 50 global_parameters_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional 51 Global uncertainty of MSX parameters. None, in the case of no uncertaint. 52 53 The default is None. 54 local_pipe_length_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 55 Local uncertainty of pipe lengths -- i.e. a dictionary of pipe IDs and uncertainties. 56 57 None, in the case of no uncertainty. 58 59 The default is None. 60 local_pipe_roughness_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 61 Local uncertainty of pipe roughness coefficients -- i.e. a dictionary of pipe IDs and uncertainties. 62 63 None, in the case of no uncertainty. 64 65 The default is None. 66 local_pipe_diameter_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 67 Local uncertainty of pipe diameters -- i.e. a dictionary of pipe IDs and uncertainties. 68 69 None, in the case of no uncertainty. 70 71 The default is None. 72 local_base_demand_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 73 Local uncertainty of base demands -- i.e. a dictionary of node IDs and uncertainties. 74 75 None, in the case of no uncertainty. 76 77 The default is None. 78 local_demand_pattern_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 79 Local uncertainty of demand patterns -- 80 i.e. a dictionary of demand pattern IDs and uncertainties. 81 82 None, in the case of no uncertainty. 83 84 The default is None. 85 local_elevation_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 86 Local uncertainty of elevations -- i.e. a dictionary of node IDs and uncertainties. 87 88 None, in the case of no uncertainty. 89 90 The default is None. 91 local_constants_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 92 Local uncertainty of MSX constants -- i.e. a dictionary of constant IDs and uncertainties. 93 94 None, in the case of no uncertainty. 95 96 The default is None. 97 local_parameters_uncertainty : dict[tuple[str, int, str] :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 98 Local uncertainty of MSX parameters -- i.e. a dictionary of 99 (parameter ID, item type, item ID) and uncertainties. 100 101 None, in the case of no uncertainty. 102 103 The default is None. 104 local_patterns_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 105 Local uncertainty of EPANET patterns -- i.e. a dictionary of pattern IDs and uncertainties. 106 107 None, in the case of no uncertainty. 108 109 The default is None. 110 local_msx_patterns_uncertainty : dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`], optional 111 Local uncertainty of EPANET-MSX patterns -- i.e. a dictionary of MSX pattern IDs 112 and uncertainties. 113 114 None, in the case of no uncertainty. 115 116 The default is None. 117 seed : `int`, optional 118 Seed for the random number generator. 119 120 Thed default is None. 121 cache_original : `bool`, optional 122 If True, all original properties are cached before the uncertainties are applied. 123 This is necessary if you have multiple simulation runs and you want to reset/re-apply 124 the uncertainties before each run. 125 126 You can set it to False if you do not and want to re-apply the uncertainties and 127 save some working memory. 128 129 The default is True. 130 """ 131 def __init__(self, global_pipe_length_uncertainty: Optional[Uncertainty] = None, 132 global_pipe_roughness_uncertainty: Optional[Uncertainty] = None, 133 global_pipe_diameter_uncertainty: Optional[Uncertainty] = None, 134 global_base_demand_uncertainty: Optional[Uncertainty] = None, 135 global_demand_pattern_uncertainty: Optional[Uncertainty] = None, 136 global_elevation_uncertainty: Optional[Uncertainty] = None, 137 global_constants_uncertainty: Optional[Uncertainty] = None, 138 global_parameters_uncertainty: Optional[Uncertainty] = None, 139 local_pipe_length_uncertainty: Optional[dict[str, Uncertainty]] = None, 140 local_pipe_roughness_uncertainty: Optional[dict[str, Uncertainty]] = None, 141 local_pipe_diameter_uncertainty: Optional[dict[str, Uncertainty]] = None, 142 local_base_demand_uncertainty: Optional[dict[str, Uncertainty]] = None, 143 local_demand_pattern_uncertainty: Optional[dict[str, Uncertainty]] = None, 144 local_elevation_uncertainty: Optional[dict[str, Uncertainty]] = None, 145 local_constants_uncertainty: Optional[dict[str, Uncertainty]] = None, 146 local_parameters_uncertainty: Optional[dict[str, int, Uncertainty]] = None, 147 local_patterns_uncertainty: Optional[dict[str, Uncertainty]] = None, 148 local_msx_patterns_uncertainty: Optional[dict[str, Uncertainty]] = None, 149 seed: Optional[int] = None, cache_original: Optional[bool] = True, 150 **kwds): 151 if global_pipe_length_uncertainty is not None: 152 if not isinstance(global_pipe_length_uncertainty, Uncertainty): 153 raise TypeError("'global_pipe_length_uncertainty' must be an instance of " + 154 "'epyt_flow.uncertainty.Uncertainty' but not of " + 155 f"'{type(global_pipe_length_uncertainty)}'") 156 if global_pipe_roughness_uncertainty is not None: 157 if not isinstance(global_pipe_roughness_uncertainty, Uncertainty): 158 raise TypeError("'global_pipe_roughness_uncertainty' must be an instance of " + 159 "'epyt_flow.uncertainty.Uncertainty' but not of " + 160 f"'{type(global_pipe_roughness_uncertainty)}'") 161 if global_pipe_diameter_uncertainty is not None: 162 if not isinstance(global_pipe_diameter_uncertainty, Uncertainty): 163 raise TypeError("'global_pipe_diameter_uncertainty' must be an instance of " + 164 "'epyt_flow.uncertainty.Uncertainty' but not of " + 165 f"'{type(global_pipe_diameter_uncertainty)}'") 166 if global_base_demand_uncertainty is not None: 167 if not isinstance(global_base_demand_uncertainty, Uncertainty): 168 raise TypeError("'global_base_demand_uncertainty' must be an instance of " + 169 "'epyt_flow.uncertainty.Uncertainty' but not of " + 170 f"'{type(global_base_demand_uncertainty)}'") 171 if global_demand_pattern_uncertainty is not None: 172 if not isinstance(global_demand_pattern_uncertainty, Uncertainty): 173 raise TypeError("'global_demand_pattern_uncertainty' must be an instance of " + 174 "'epyt_flow.uncertainty.Uncertainty' but not of " + 175 f"'{type(global_demand_pattern_uncertainty)}'") 176 if global_elevation_uncertainty is not None: 177 if not isinstance(global_elevation_uncertainty, Uncertainty): 178 raise TypeError("'global_elevation_uncertainty' must be an instance of " + 179 "'epyt_flow.uncertainty.Uncertainty' but not of " + 180 f"'{type(global_elevation_uncertainty)}'") 181 if global_constants_uncertainty is not None: 182 if not isinstance(global_constants_uncertainty, Uncertainty): 183 raise TypeError("'global_constants_uncertainty' must be an instance of " + 184 "'epyt_flow.uncertainty.Uncertainty' but not of " + 185 f"'{type(global_constants_uncertainty)}'") 186 if global_parameters_uncertainty is not None: 187 if not isinstance(global_parameters_uncertainty, Uncertainty): 188 raise TypeError("'global_parameters_uncertainty' must be an instance of " + 189 "'epyt_flow.uncertainty.Uncertainty' but not of " + 190 f"'{type(global_parameters_uncertainty)}'") 191 192 if local_pipe_length_uncertainty is not None: 193 if not isinstance(local_pipe_length_uncertainty, dict): 194 raise TypeError("'local_pipe_length_uncertainty' must be an instance of " + 195 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 196 f"'{type(local_pipe_length_uncertainty)}'") 197 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 198 for key, val in local_pipe_length_uncertainty.items()): 199 raise TypeError("'local_pipe_length_uncertainty': " + 200 "All keys must be instances of 'str' and all values must be " + 201 "instances of 'epyt_flow.uncertainty.Uncertainty'") 202 if local_pipe_roughness_uncertainty is not None: 203 if not isinstance(local_pipe_roughness_uncertainty, dict): 204 raise TypeError("'local_pipe_roughness_uncertainty' must be an instance of " + 205 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 206 f"'{type(local_pipe_roughness_uncertainty)}'") 207 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 208 for key, val in local_pipe_roughness_uncertainty.items()): 209 raise TypeError("'local_pipe_roughness_uncertainty': " + 210 "All keys must be instances of 'str' and all values must be " + 211 "instances of 'epyt_flow.uncertainty.Uncertainty'") 212 if local_pipe_diameter_uncertainty is not None: 213 if not isinstance(local_pipe_diameter_uncertainty, dict): 214 raise TypeError("'local_pipe_diameter_uncertainty' must be an instance of " + 215 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 216 f"'{type(local_pipe_diameter_uncertainty)}'") 217 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 218 for key, val in local_pipe_diameter_uncertainty.items()): 219 raise TypeError("'local_pipe_diameter_uncertainty': " + 220 "All keys must be instances of 'str' and all values must be " + 221 "instances of 'epyt_flow.uncertainty.Uncertainty'") 222 if local_base_demand_uncertainty is not None: 223 if not isinstance(local_base_demand_uncertainty, dict): 224 raise TypeError("'local_base_demand_uncertainty' must be an instance of " + 225 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 226 f"'{type(local_base_demand_uncertainty)}'") 227 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 228 for key, val in local_base_demand_uncertainty.items()): 229 raise TypeError("'local_base_demand_uncertainty': " + 230 "All keys must be instances of 'str' and all values must be " + 231 "instances of 'epyt_flow.uncertainty.Uncertainty'") 232 if local_demand_pattern_uncertainty is not None: 233 if not isinstance(local_demand_pattern_uncertainty, dict): 234 raise TypeError("'local_demand_pattern_uncertainty' must be an instance of " + 235 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 236 f"'{type(local_demand_pattern_uncertainty)}'") 237 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 238 for key, val in local_demand_pattern_uncertainty.items()): 239 raise TypeError("'local_demand_pattern_uncertainty': " + 240 "All keys must be instances of 'str' and all values must be " + 241 "instances of 'epyt_flow.uncertainty.Uncertainty'") 242 if local_elevation_uncertainty is not None: 243 if not isinstance(local_elevation_uncertainty, dict): 244 raise TypeError("'local_elevation_uncertainty' must be an instance of " + 245 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 246 f"'{type(local_elevation_uncertainty)}'") 247 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 248 for key, val in local_elevation_uncertainty.items()): 249 raise TypeError("'local_elevation_uncertainty': " + 250 "All keys must be instances of 'str' and all values must be " + 251 "instances of 'epyt_flow.uncertainty.Uncertainty'") 252 if local_constants_uncertainty is not None: 253 if not isinstance(local_constants_uncertainty, dict): 254 raise TypeError("'local_constants_uncertainty' must be an instance of " + 255 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 256 f"'{type(local_constants_uncertainty)}'") 257 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 258 for key, val in local_constants_uncertainty.items()): 259 raise TypeError("'local_constants_uncertainty': " + 260 "All keys must be instances of 'str' and all values must be " + 261 "instances of 'epyt_flow.uncertainty.Uncertainty'") 262 if local_parameters_uncertainty is not None: 263 if not isinstance(local_parameters_uncertainty, dict): 264 raise TypeError("'local_parameters_uncertainty' must be an instance of " + 265 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 266 f"'{type(local_parameters_uncertainty)}'") 267 if any(not isinstance(key, tuple) or not isinstance(key[0], str) or 268 not isinstance(key[1], int) or not isinstance(key[2], str) or 269 not isinstance(local_parameters_uncertainty[key], Uncertainty) 270 for key in local_parameters_uncertainty.keys()): 271 raise TypeError("'local_parameters_uncertainty': " + 272 "All keys must be instances of 'tuple[str, int, str]' and all " + 273 "values must be instances of 'epyt_flow.uncertainty.Uncertainty'") 274 if local_patterns_uncertainty is not None: 275 if not isinstance(local_patterns_uncertainty, dict): 276 raise TypeError("'local_patterns_uncertainty' must be an instance of " + 277 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 278 f"'{type(local_patterns_uncertainty)}'") 279 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 280 for key, val in local_patterns_uncertainty.items()): 281 raise TypeError("'local_patterns_uncertainty': " + 282 "All keys must be instances of 'str' and all values must be " + 283 "instances of 'epyt_flow.uncertainty.Uncertainty'") 284 if local_msx_patterns_uncertainty is not None: 285 if not isinstance(local_msx_patterns_uncertainty, dict): 286 raise TypeError("'local_msx_patterns_uncertainty' must be an instance of " + 287 "'dict[str, epyt_flow.uncertainty.Uncertainty]' but not of " + 288 f"'{type(local_msx_patterns_uncertainty)}'") 289 if any(not isinstance(key, str) or not isinstance(val, Uncertainty) 290 for key, val in local_msx_patterns_uncertainty.items()): 291 raise TypeError("'local_msx_patterns_uncertainty': " + 292 "All keys must be instances of 'str' and all values must be " + 293 "instances of 'epyt_flow.uncertainty.Uncertainty'") 294 if not isinstance(cache_original, bool): 295 raise TypeError("'cache_original' must be an instance of 'bool' " + 296 f"but not of '{type(cache_original)}'") 297 298 self._global_pipe_length = global_pipe_length_uncertainty 299 self._global_pipe_roughness = global_pipe_roughness_uncertainty 300 self._global_pipe_diameter = global_pipe_diameter_uncertainty 301 self._global_base_demand = global_base_demand_uncertainty 302 self._global_demand_pattern = global_demand_pattern_uncertainty 303 self._global_elevation = global_elevation_uncertainty 304 self._global_constants = global_constants_uncertainty 305 self._global_parameters = global_parameters_uncertainty 306 self._local_pipe_length = local_pipe_length_uncertainty 307 self._local_pipe_roughness = local_pipe_roughness_uncertainty 308 self._local_pipe_diameter = local_pipe_diameter_uncertainty 309 self._local_base_demand = local_base_demand_uncertainty 310 self._local_demand_pattern = local_demand_pattern_uncertainty 311 self._local_elevation = local_elevation_uncertainty 312 self._local_constants = local_constants_uncertainty 313 self._local_parameters = local_parameters_uncertainty 314 self._local_patterns = local_patterns_uncertainty 315 self._local_msx_patterns = local_msx_patterns_uncertainty 316 self.__seed = seed 317 self.__cache_original = cache_original 318 319 self._cache_links_length = None 320 self._cache_links_diameter = None 321 self._cache_links_roughness_coeff = None 322 self._cache_nodes_base_demand = None 323 self._cache_nodes_demand_pattern = None 324 self._cache_nodes_elevation = None 325 self._cache_patterns = None 326 self._cache_msx_constants = None 327 self._cache_msx_links_parameters = None 328 self._cache_msx_tanks_parameters = None 329 self._cache_msx_patterns = None 330 331 self.__np_rand_gen = np.random.default_rng(seed=self.__seed) 332 333 super().__init__(**kwds) 334 335 @property 336 def seed(self) -> int: 337 """ 338 Returns the seed used for the random number generator. 339 340 Returns 341 ------- 342 `int` 343 Seed for the random number generator. 344 """ 345 return self.__seed 346 347 @property 348 def global_pipe_length(self) -> Uncertainty: 349 """ 350 Returns the global pipe length uncertainty. 351 352 Returns 353 ------- 354 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 355 Global pipe length uncertainty. 356 """ 357 return deepcopy(self._global_pipe_length) 358 359 @property 360 def global_pipe_roughness(self) -> Uncertainty: 361 """ 362 Returns the global pipe roughness uncertainty. 363 364 Returns 365 ------- 366 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 367 Global pipe roughness uncertainty. 368 """ 369 return deepcopy(self._global_pipe_roughness) 370 371 @property 372 def global_pipe_diameter(self) -> Uncertainty: 373 """ 374 Returns the global pipe diameter uncertainty. 375 376 Returns 377 ------- 378 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 379 Global pipe diameter uncertainty. 380 """ 381 return deepcopy(self._global_pipe_diameter) 382 383 @property 384 def global_base_demand(self) -> Uncertainty: 385 """ 386 Returns the global base demand uncertainty. 387 388 Returns 389 ------- 390 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 391 Global base demand uncertainty. 392 """ 393 return deepcopy(self._global_base_demand) 394 395 @property 396 def global_demand_pattern(self) -> Uncertainty: 397 """ 398 Returns the global demand pattern uncertainty. 399 400 Returns 401 ------- 402 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 403 Global demand pattern uncertainty. 404 """ 405 return deepcopy(self._global_demand_pattern) 406 407 @property 408 def global_elevation(self) -> Uncertainty: 409 """ 410 Returns the global node elevation uncertainty. 411 412 Returns 413 ------- 414 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 415 Global node elevation uncertainty. 416 """ 417 return deepcopy(self._global_elevation) 418 419 @property 420 def global_constants(self) -> Uncertainty: 421 """ 422 Returns the global MSX constant uncertainty. 423 424 Returns 425 ------- 426 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 427 Global MSX constant uncertainty. 428 """ 429 return deepcopy(self._global_constants) 430 431 @property 432 def global_parameters(self) -> Uncertainty: 433 """ 434 Returns the global MSX parameter uncertainty. 435 436 Returns 437 ------- 438 :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty` 439 Global MSX parameter uncertainty. 440 """ 441 return deepcopy(self._global_parameters) 442 443 @property 444 def local_pipe_length(self) -> dict[str, Uncertainty]: 445 """ 446 Returns the local pipe length uncertainty. 447 448 Returns 449 ------- 450 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 451 Local pipe length uncertainty. 452 """ 453 return deepcopy(self._local_pipe_length) 454 455 @property 456 def local_pipe_roughness(self) -> dict[str, Uncertainty]: 457 """ 458 Returns the local pipe roughness uncertainty. 459 460 Returns 461 ------- 462 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 463 Local pipe roughness uncertainty. 464 """ 465 return deepcopy(self._local_pipe_roughness) 466 467 @property 468 def local_pipe_diameter(self) -> dict[str, Uncertainty]: 469 """ 470 Returns the local pipe diameter uncertainty. 471 472 Returns 473 ------- 474 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 475 Local pipe diameter uncertainty. 476 """ 477 return deepcopy(self._local_pipe_diameter) 478 479 @property 480 def local_base_demand(self) -> dict[str, Uncertainty]: 481 """ 482 Returns the local base demand uncertainty. 483 484 Returns 485 ------- 486 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 487 Local base demand uncertainty. 488 """ 489 return deepcopy(self._local_base_demand) 490 491 @property 492 def local_demand_pattern(self) -> dict[str, Uncertainty]: 493 """ 494 Returns the local demand pattern uncertainty. 495 496 Returns 497 ------- 498 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 499 Local demand pattern uncertainty. 500 """ 501 return deepcopy(self._local_demand_pattern) 502 503 @property 504 def local_elevation(self) -> dict[str, Uncertainty]: 505 """ 506 Returns the local node elevation uncertainty. 507 508 Returns 509 ------- 510 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 511 Local node elevation uncertainty. 512 """ 513 return deepcopy(self._local_elevation) 514 515 @property 516 def local_constants(self) -> dict[str, Uncertainty]: 517 """ 518 Returns the local MSX constant uncertainty. 519 520 Returns 521 ------- 522 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 523 Local MSX constant uncertainty. 524 """ 525 return deepcopy(self._local_constants) 526 527 @property 528 def local_parameters(self) -> dict[tuple[str, int, str], Uncertainty]: 529 """ 530 Returns the local MSX parameter uncertainty. 531 532 Returns 533 ------- 534 dict[tuple[str, int, str], :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 535 Local MSX parameter uncertainty. 536 """ 537 return deepcopy(self._local_parameters) 538 539 @property 540 def local_patterns(self) -> dict[str, Uncertainty]: 541 """ 542 Returns the local EPANET patterns uncertainty. 543 544 Returns 545 ------- 546 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 547 Local EPANET patterns uncertainty. 548 """ 549 return deepcopy(self._local_patterns) 550 551 @property 552 def local_msx_patterns(self) -> dict[str, Uncertainty]: 553 """ 554 Returns the local EPANET-MSX patterns uncertainty. 555 556 Returns 557 ------- 558 dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`] 559 Local EPANET-MSX patterns uncertainty. 560 """ 561 return deepcopy(self._local_msx_patterns) 562
[docs] 563 def get_attributes(self) -> dict: 564 attribs = {"global_pipe_length_uncertainty": self._global_pipe_length, 565 "global_pipe_roughness_uncertainty": self._global_pipe_roughness, 566 "global_pipe_diameter_uncertainty": self._global_pipe_diameter, 567 "global_base_demand_uncertainty": self._global_base_demand, 568 "global_demand_pattern_uncertainty": self._global_demand_pattern, 569 "global_elevation_uncertainty": self._global_elevation, 570 "global_constants_uncertainty": self._global_constants, 571 "global_parameters_uncertainty": self._global_parameters, 572 "local_pipe_length_uncertainty": self._local_pipe_length, 573 "local_pipe_roughness_uncertainty": self._local_pipe_roughness, 574 "local_pipe_diameter_uncertainty": self._local_pipe_diameter, 575 "local_base_demand_uncertainty": self._local_base_demand, 576 "local_demand_pattern_uncertainty": self._local_demand_pattern, 577 "local_elevation_uncertainty": self._local_elevation, 578 "local_constants_uncertainty": self._local_constants, 579 "local_parameters_uncertainty": self._local_parameters, 580 "local_patterns_uncertainty": self._local_patterns, 581 "local_msx_patterns_uncertainty": self._local_msx_patterns, 582 "seed": self.__seed} 583 584 return super().get_attributes() | attribs
585 586 def __eq__(self, other) -> bool: 587 if not isinstance(other, ModelUncertainty): 588 raise TypeError("Can not compare 'ModelUncertainty' instance " + 589 f"with '{type(other)}' instance") 590 591 return self._global_pipe_length == other.global_pipe_length \ 592 and self._global_pipe_roughness == other.global_pipe_roughness \ 593 and self._global_pipe_diameter == other.global_pipe_diameter \ 594 and self._global_base_demand == other.global_base_demand \ 595 and self._global_demand_pattern == other.global_demand_pattern \ 596 and self._global_elevation == other.global_elevation \ 597 and self._global_parameters == other.global_parameters \ 598 and self._global_constants == other.global_constants \ 599 and self._local_pipe_length == other.local_pipe_length \ 600 and self._local_pipe_roughness == other.local_pipe_roughness \ 601 and self._local_pipe_diameter == other.local_pipe_diameter \ 602 and self._local_base_demand == other.local_base_demand \ 603 and self._local_demand_pattern == other.local_demand_pattern \ 604 and self._local_elevation == other.local_elevation \ 605 and self._local_parameters == other.local_parameters \ 606 and self._local_constants == other.local_constants \ 607 and self._local_patterns == other.local_patterns \ 608 and self._local_msx_patterns == other.local_msx_patterns \ 609 and self.__seed == other.seed 610 611 def __str__(self) -> str: 612 return f"global_pipe_length: {self._global_pipe_length} " +\ 613 f"global_pipe_roughness: {self._global_pipe_roughness} " + \ 614 f"global_pipe_diameter: {self._global_pipe_diameter} " + \ 615 f"global_demand_base: {self._global_base_demand} " + \ 616 f"global_demand_pattern: {self._global_demand_pattern} " + \ 617 f"global_elevation: {self._global_elevation} " + \ 618 f"global_constants: {self._global_constants} " + \ 619 f"global_parameters: {self._global_parameters}" + \ 620 f"local_pipe_length: {self._local_pipe_length} " +\ 621 f"local_pipe_roughness: {self._local_pipe_roughness} " + \ 622 f"local_pipe_diameter: {self._local_pipe_diameter} " + \ 623 f"local_demand_base: {self._local_base_demand} " + \ 624 f"local_demand_pattern: {self._local_demand_pattern} " + \ 625 f"local_elevation: {self._local_elevation} " + \ 626 f"local_constants: {self._local_constants} " + \ 627 f"local_parameters: {self._local_parameters} " + \ 628 f"local_patterns: {self._local_patterns} " + \ 629 f"local_msx_patterns: {self._local_msx_patterns} + seed: {self.__seed}" 630
[docs] 631 def undo(self, epanet_api: EPyT) -> None: 632 """ 633 Undo all applied uncertainties -- i.e, resets the properties to their original value. 634 635 Note that this function can only be used if `cache_original` (of the constructor) 636 was set to True (default). 637 638 Parameters 639 ---------- 640 epanet_api : `epanet_plus.EPyT <https://epanet-plus.readthedocs.io/en/stable/api.html#epanet_plus.epanet_toolkit.EPyT>`_ 641 Interface to EPANET and EPANET-MSX 642 """ 643 if self.__cache_original is False: 644 raise ValueError("Caching was disabled by the user") 645 646 if self._cache_links_length is not None: 647 for link_idx, link_len in zip(epanet_api.get_all_links_idx(), 648 self._cache_links_length): 649 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_len) 650 651 if self._cache_links_diameter is not None: 652 for link_idx, link_diam in zip(epanet_api.get_all_links_idx(), 653 self._cache_links_diameter): 654 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_diam) 655 656 if self._cache_links_roughness_coeff is not None: 657 for link_idx, link_roughness in zip(epanet_api.get_all_links_idx(), 658 self._cache_links_roughness_coeff): 659 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS, link_roughness) 660 661 if self._cache_nodes_base_demand is not None: 662 for node_idx in self._cache_nodes_base_demand.keys(): 663 for demand_category, base_demand in self._cache_nodes_base_demand[node_idx].items(): 664 epanet_api.setbasedemand(node_idx, demand_category + 1, base_demand) 665 666 if self._cache_nodes_demand_pattern is not None: 667 for pattern_idx, demand_pattern in self._cache_nodes_demand_pattern.items(): 668 epanet_api.set_pattern(pattern_idx, demand_pattern.tolist()) 669 670 if self._cache_nodes_elevation is not None: 671 for node_idx, node_elev in zip(epanet_api.get_all_nodes_idx(), 672 self._cache_nodes_elevation): 673 epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION, node_elev) 674 675 if self._cache_patterns is not None: 676 for pattern_idx, pattern in self._cache_patterns.items(): 677 epanet_api.set_pattern(pattern_idx, pattern.tolist()) 678 679 if self._cache_msx_constants is not None: 680 for constant_idx, constant_value in enumerate(self._cache_msx_constants): 681 epanet_api.MSXsetconstant(constant_idx + 1, constant_value) 682 683 if self._cache_msx_links_parameters is not None: 684 for pipe_idx, parameters_pipes_val in self._cache_msx_links_parameters.items(): 685 for param_idx, param_value in enumerate(parameters_pipes_val): 686 epanet_api.MSXsetparameter(EpanetConstants.MSX_LINK, pipe_idx, 687 param_idx + 1, param_value) 688 689 if self._cache_msx_tanks_parameters is not None: 690 for tank_idx, parameters_tanks_val in self._cache_msx_tanks_parameters.items(): 691 for param_idx, param_value in enumerate(parameters_tanks_val): 692 epanet_api.MSXsetparameter(EpanetConstants.MSX_NODE, tank_idx, 693 param_idx + 1, param_value) 694 695 if self._cache_msx_patterns is not None: 696 for pattern_idx, pattern in self._cache_msx_patterns: 697 epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern))
698
[docs] 699 def apply(self, epanet_api: EPyT) -> None: 700 """ 701 Applies the specified model uncertainties to the scenario. 702 703 Parameters 704 ---------- 705 epanet_api : `epanet_plus.EPyT <https://epanet-plus.readthedocs.io/en/stable/api.html#epanet_plus.epanet_toolkit.EPyT>`_ 706 Interface to EPANET and EPANET-MSX. 707 """ 708 709 all_links_idx = epanet_api.get_all_links_idx() 710 all_nodes_idx = epanet_api.get_all_nodes_idx() 711 712 if self._global_pipe_length is not None: 713 self._global_pipe_length.set_random_generator(self.__np_rand_gen) 714 715 link_length = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH) 716 for link_idx in all_links_idx]) 717 self._cache_links_length = np.copy(link_length) 718 719 link_length = self._global_pipe_length.apply_batch(link_length) 720 for link_idx, link_value in zip(all_links_idx, link_length): 721 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_value) 722 723 if self._local_pipe_length is not None: 724 self._local_pipe_length.set_random_generator(self.__np_rand_gen) 725 self._cache_links_length = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH) 726 for link_idx in all_links_idx]) 727 728 for pipe_id, uncertainty in self._local_pipe_length.items(): 729 link_idx = epanet_api.get_link_idx(pipe_id) 730 link_length = epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH) 731 732 link_length = uncertainty.apply(link_length) 733 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_length) 734 735 if self._global_pipe_diameter is not None: 736 self._global_pipe_diameter.set_random_generator(self.__np_rand_gen) 737 738 link_diameters = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_DIAMETER) 739 for link_idx in all_links_idx]) 740 self._cache_links_diameter = np.copy(link_diameters) 741 742 link_diameters = self._global_pipe_diameter.apply_batch(link_diameters) 743 for link_idx, link_value in zip(all_links_idx, link_diameters): 744 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_value) 745 746 if self._local_pipe_diameter is not None: 747 self._local_pipe_diameter.set_random_generator(self.__np_rand_gen) 748 self._cache_links_diameter = np.array([epanet_api.getlinkvalue(link_idx, 749 EpanetConstants.EN_DIAMETER) 750 for link_idx in all_links_idx]) 751 752 for pipe_id, uncertainty in self._local_pipe_diameter.items(): 753 link_idx = epanet_api.get_link_idx(pipe_id) 754 link_diameter = epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_DIAMETER) 755 756 link_diameter = uncertainty.apply(link_diameter) 757 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_diameter) 758 759 if self._global_pipe_roughness is not None: 760 self._global_pipe_roughness.set_random_generator(self.__np_rand_gen) 761 762 coeffs = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS) 763 for link_idx in all_links_idx]) 764 self._cache_links_roughness_coeff = np.copy(coeffs) 765 766 coeffs = self._global_pipe_roughness.apply_batch(coeffs) 767 for link_idx, link_value in zip(all_links_idx, coeffs): 768 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS, link_value) 769 770 if self._local_pipe_roughness is not None: 771 self._local_pipe_roughness.set_random_generator(self.__np_rand_gen) 772 self._cache_links_roughness_coeff = \ 773 np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS) 774 for link_idx in all_links_idx]) 775 776 for pipe_id, uncertainty in self._local_pipe_roughness.items(): 777 link_idx = epanet_api.get_link_idx(pipe_id) 778 link_roughness_coeff = epanet_api.getlinkvalue(link_idx, 779 EpanetConstants.EN_ROUGHNESS) 780 781 link_roughness_coeff = uncertainty.apply(link_roughness_coeff) 782 epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS, 783 link_roughness_coeff) 784 785 if self._global_base_demand is not None: 786 self._global_base_demand.set_random_generator(self.__np_rand_gen) 787 788 self._cache_nodes_base_demand = {} 789 for node_idx in all_nodes_idx: 790 self._cache_nodes_base_demand[node_idx] = {} 791 n_demand_categories = epanet_api.getnumdemands(node_idx) 792 for demand_idx in range(n_demand_categories): 793 base_demand = epanet_api.getbasedemand(node_idx, demand_idx + 1) 794 self._cache_nodes_base_demand[node_idx][demand_idx] = base_demand 795 796 base_demand = self._global_base_demand.apply(base_demand) 797 epanet_api.setbasedemand(node_idx, demand_idx + 1, base_demand) 798 799 if self._local_base_demand is not None: 800 self._local_base_demand.set_random_generator(self.__np_rand_gen) 801 802 self._cache_nodes_base_demand = {} 803 for node_id, uncertainty in self._local_base_demand.items(): 804 node_idx = epanet_api.get_node_idx(node_id) 805 self._cache_nodes_base_demand[node_idx] = {} 806 n_demand_categories = epanet_api.getnumdemands(node_idx) 807 for demand_idx in range(n_demand_categories): 808 base_demand = epanet_api.getbasedemand(node_idx, demand_idx + 1) 809 self._cache_nodes_base_demand[node_idx][demand_idx] = base_demand 810 811 base_demand = uncertainty.apply(base_demand) 812 epanet_api.setbasedemand(node_idx, demand_idx + 1, base_demand) 813 814 if self._global_demand_pattern is not None: 815 self._global_demand_pattern.set_random_generator(self.__np_rand_gen) 816 817 self._cache_nodes_demand_pattern = {} 818 demand_patterns_idx = np.array([epanet_api.getdemandpattern(node_idx, demand_idx + 1) 819 for node_idx in epanet_api.get_all_nodes_idx() 820 for demand_idx in range(epanet_api.getnumdemands(node_idx))]) 821 822 demand_patterns_idx = set(demand_patterns_idx) 823 demand_patterns_idx.discard(0) 824 for pattern_idx in list(demand_patterns_idx): 825 demand_pattern = np.array(epanet_api.get_pattern(pattern_idx)) 826 self._cache_nodes_demand_pattern[pattern_idx] = np.copy(demand_pattern) 827 828 demand_pattern = self._global_demand_pattern.apply_batch(demand_pattern) 829 epanet_api.set_pattern(pattern_idx, demand_pattern.tolist()) 830 831 if self._local_demand_pattern is not None: 832 self._local_demand_pattern.set_random_generator(self.__np_rand_gen) 833 834 self._cache_nodes_demand_pattern = {} 835 836 for pattern_id, uncertainty in self._local_demand_pattern.items(): 837 pattern_idx = epanet_api.getpatternindex(pattern_id) 838 demand_pattern = np.array(epanet_api.get_pattern(pattern_idx)) 839 self._cache_nodes_demand_pattern[pattern_id] = np.copy(demand_pattern) 840 841 demand_pattern = uncertainty.apply_batch(demand_pattern) 842 epanet_api.set_pattern(pattern_idx, demand_pattern.tolist()) 843 844 if self._global_elevation is not None: 845 self._global_elevation.set_random_generator(self.__np_rand_gen) 846 847 elevations = np.array([epanet_api.get_node_elevation(node_idx) 848 for node_idx in epanet_api.get_all_nodes_idx()]) 849 self._cache_nodes_elevation = np.copy(elevations) 850 851 elevations = self._global_elevation.apply_batch(elevations) 852 for node_idx, node_elev in enumerate(elevations): 853 epanet_api.setnodevalue(node_idx + 1, EpanetConstants.EN_ELEVATION, node_elev) 854 855 if self._local_elevation is not None: 856 self._local_elevation.set_random_generator(self.__np_rand_gen) 857 self._cache_nodes_elevation = np.array([epanet_api.get_node_elevation(node_idx) 858 for node_idx in epanet_api.get_all_nodes_idx()]) 859 860 for node_id, uncertainty in self._local_elevation.items(): 861 node_idx = epanet_api.get_node_idx(node_id) 862 elevation = epanet_api.get_node_elevation(node_idx) 863 864 elevation = uncertainty.apply(elevation) 865 epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION, elevation) 866 867 if self._local_patterns is not None: 868 self._local_patterns.set_random_generator(self.__np_rand_gen) 869 self._cache_patterns = {} 870 871 for pattern_id, uncertainty in self._local_patterns.items(): 872 pattern_idx = epanet_api.getpatternindex(pattern_id) 873 pattern = np.array(epanet_api.get_pattern(pattern_idx)) 874 self._cache_patterns[pattern_idx] = np.copy(pattern) 875 876 pattern = uncertainty.apply_batch(pattern) 877 epanet_api.set_pattern(pattern_idx, pattern.tolist()) 878 879 if epanet_api.msx_file is not None: 880 if self._global_constants is not None: 881 self._global_constants.set_random_generator(self.__np_rand_gen) 882 883 constants = np.array([epanet_api.MSXgetconstant(const_idx + 1) 884 for const_idx in range(epanet_api.MSXgetcount(EpanetConstants.MSX_CONSTANT))]) 885 self._cache_msx_patterns = np.copy(constants) 886 887 constants = self._global_constants.apply_batch(constants) 888 for const_idx, const_value in enumerate(constants): 889 epanet_api.MSXsetconstant(const_idx + 1, const_value) 890 891 if self._local_constants: 892 self._local_constants.set_random_generator(self.__np_rand_gen) 893 894 self._cache_msx_patterns = np.array([epanet_api.MSXgetconstant(const_idx + 1) 895 for const_idx in range(epanet_api.MSXgetcount(EpanetConstants.MSX_CONSTANT))]) 896 897 for constant_id, uncertainty in self._local_constants.items(): 898 idx = epanet_api.MSXgetindex(EpanetConstants.MSX_CONSTANT, constant_id) 899 constant = epanet_api.MSXgetconstant(idx) 900 901 constant = uncertainty.apply(constant) 902 epanet_api.MSXsetconstant(idx, constant) 903 904 if self._global_parameters is not None: 905 self._global_parameters.set_random_generator(self.__np_rand_gen) 906 907 self._cache_msx_links_parameters = {} 908 num_params = epanet_api.MSXgetcount(EpanetConstants.MSX_PARAMETER) 909 parameters_pipes = [np.array([epanet_api.MSXgetparameter(EpanetConstants.MSX_LINK, 910 pipe_idx, 911 param_idx + 1) 912 for param_idx in range(num_params)]) 913 for pipe_idx in epanet_api.get_all_pipes_idx()] 914 for i, pipe_idx in enumerate(epanet_api.get_all_pipes_idx()): 915 if len(parameters_pipes[i]) == 0: 916 continue 917 918 self._cache_msx_links_parameters[pipe_idx] = parameters_pipes[i] 919 parameters_pipes_val = self._global_parameters.apply_batch(parameters_pipes[i]) 920 for param_idx, param_value in enumerate(parameters_pipes_val): 921 epanet_api.MSXsetparameter(EpanetConstants.MSX_LINK, pipe_idx, 922 param_idx + 1, param_value) 923 924 self._cache_msx_tanks_parameters = {} 925 num_params = epanet_api.MSXgetcount(EpanetConstants.MSX_PARAMETER) 926 parameters_tanks = [np.array([epanet_api.MSXgetparameter(EpanetConstants.MSX_NODE, 927 tank_idx + 1, param_idx + 1) 928 for param_idx in range(num_params)]) 929 for tank_idx in range(epanet_api.get_num_tanks())] 930 for i, tank_idx in enumerate(epanet_api.get_all_tanks_idx()): 931 if parameters_tanks[i] is None or len(parameters_tanks[i]) == 0: 932 continue 933 934 self._cache_msx_tanks_parameters[tank_idx] = parameters_tanks[i] 935 parameters_tanks_val = self._global_parameters.apply_batch(parameters_tanks[i]) 936 for idx, val in enumerate(parameters_tanks_val): 937 epanet_api.MSXsetparameter(EpanetConstants.MSX_NODE, tank_idx, idx + 1, val) 938 939 if self._local_parameters is not None: 940 self._local_parameters.set_random_generator(self.__np_rand_gen) 941 self._cache_msx_links_parameters = {} 942 self._cache_msx_tanks_parameters = {} 943 944 for (param_id, item_type, item_id), uncertainty in self._local_parameters.items(): 945 idx, = epanet_api.MSXgetindex(EpanetConstants.MSX_PARAMETER, param_id) 946 947 if item_type == EpanetConstants.MSX_NODE: 948 item_idx = epanet_api.get_node_idx(item_id) 949 elif item_type == EpanetConstants.MSX_LINK: 950 item_idx = epanet_api.get_link_idx(item_id) 951 else: 952 raise ValueError(f"Unknown item type '{item_type}' must be either " + 953 "EpanetConstants.MSX_NODE or EpanetConstants.MSX_LINK") 954 955 parameter = epanet_api.MSXgetparameter(item_type, item_idx, idx) 956 if item_type == EpanetConstants.MSX_NODE: 957 self._cache_msx_tanks_parameters[item_idx] = parameter 958 elif item_type == EpanetConstants.MSX_LINK: 959 self._cache_msx_links_parameters[item_idx] = parameter 960 961 parameter = uncertainty.apply(parameter) 962 epanet_api.MSXsetparameter(item_type, item_idx, idx, parameter) 963 964 if self._local_msx_patterns is not None: 965 self._local_msx_patterns.set_random_generator(self.__np_rand_gen) 966 self._cache_msx_patterns = {} 967 968 for pattern_id, uncertainty in self._local_msx_patterns.items(): 969 pattern_idx = epanet_api.MSXgetindex(EpanetConstants.MSX_PATTERN, pattern_id) 970 pattern = np.array(epanet_api.get_msx_pattern(pattern_idx)) 971 self._cache_msx_patterns[pattern_idx] = np.copy(pattern) 972 973 pattern = uncertainty.apply_batch(pattern) 974 epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern)) 975 else: 976 if self._local_msx_patterns is not None or self._local_parameters is not None or \ 977 self._local_constants is not None or self._global_constants is not None or \ 978 self._global_parameters is not None: 979 warnings.warn("Ignoring EPANET-MSX uncertainties because not .msx file was loaded")