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")