pydasa 0.4.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydasa/__init__.py +103 -0
- pydasa/_version.py +6 -0
- pydasa/analysis/__init__.py +0 -0
- pydasa/analysis/scenario.py +584 -0
- pydasa/analysis/simulation.py +1158 -0
- pydasa/context/__init__.py +0 -0
- pydasa/context/conversion.py +11 -0
- pydasa/context/system.py +17 -0
- pydasa/context/units.py +15 -0
- pydasa/core/__init__.py +15 -0
- pydasa/core/basic.py +287 -0
- pydasa/core/cfg/default.json +136 -0
- pydasa/core/constants.py +27 -0
- pydasa/core/io.py +102 -0
- pydasa/core/setup.py +269 -0
- pydasa/dimensional/__init__.py +0 -0
- pydasa/dimensional/buckingham.py +728 -0
- pydasa/dimensional/fundamental.py +146 -0
- pydasa/dimensional/model.py +1077 -0
- pydasa/dimensional/vaschy.py +633 -0
- pydasa/elements/__init__.py +19 -0
- pydasa/elements/parameter.py +218 -0
- pydasa/elements/specs/__init__.py +22 -0
- pydasa/elements/specs/conceptual.py +161 -0
- pydasa/elements/specs/numerical.py +469 -0
- pydasa/elements/specs/statistical.py +229 -0
- pydasa/elements/specs/symbolic.py +394 -0
- pydasa/serialization/__init__.py +27 -0
- pydasa/serialization/parser.py +133 -0
- pydasa/structs/__init__.py +0 -0
- pydasa/structs/lists/__init__.py +0 -0
- pydasa/structs/lists/arlt.py +578 -0
- pydasa/structs/lists/dllt.py +18 -0
- pydasa/structs/lists/ndlt.py +262 -0
- pydasa/structs/lists/sllt.py +746 -0
- pydasa/structs/tables/__init__.py +0 -0
- pydasa/structs/tables/htme.py +182 -0
- pydasa/structs/tables/scht.py +774 -0
- pydasa/structs/tools/__init__.py +0 -0
- pydasa/structs/tools/hashing.py +53 -0
- pydasa/structs/tools/math.py +149 -0
- pydasa/structs/tools/memory.py +54 -0
- pydasa/structs/types/__init__.py +0 -0
- pydasa/structs/types/functions.py +131 -0
- pydasa/structs/types/generics.py +54 -0
- pydasa/validations/__init__.py +0 -0
- pydasa/validations/decorators.py +510 -0
- pydasa/validations/error.py +100 -0
- pydasa/validations/patterns.py +32 -0
- pydasa/workflows/__init__.py +1 -0
- pydasa/workflows/influence.py +497 -0
- pydasa/workflows/phenomena.py +529 -0
- pydasa/workflows/practical.py +765 -0
- pydasa-0.4.7.dist-info/METADATA +320 -0
- pydasa-0.4.7.dist-info/RECORD +58 -0
- pydasa-0.4.7.dist-info/WHEEL +5 -0
- pydasa-0.4.7.dist-info/licenses/LICENSE +674 -0
- pydasa-0.4.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module numerical.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Numerical perspective for variable representation.
|
|
7
|
+
|
|
8
|
+
This module defines the NumericalSpecs class representing the computational
|
|
9
|
+
value ranges and discretization properties of a variable.
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
**NumericalSpecs**: Numerical variable specifications
|
|
13
|
+
|
|
14
|
+
*IMPORTANT:* Based on the theory from:
|
|
15
|
+
|
|
16
|
+
# H.Gorter, *Dimensionalanalyse: Eine Theoririe der physikalischen Dimensionen mit Anwendungen*
|
|
17
|
+
"""
|
|
18
|
+
# native python modules
|
|
19
|
+
# dataclass imports
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
# third-party numerical imports
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
# custom modules
|
|
28
|
+
from pydasa.validations.decorators import validate_type
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class NumericalSpecs:
|
|
33
|
+
"""Numerical perspective: computational value ranges. Answers the question: "What VALUES can this variable take?"
|
|
34
|
+
|
|
35
|
+
This perspective focuses on:
|
|
36
|
+
- Concrete bounds (minimum, maximum)
|
|
37
|
+
- Central tendency (mean value)
|
|
38
|
+
- Variation (standard deviation)
|
|
39
|
+
- Discretization for simulations (step size, range arrays)
|
|
40
|
+
- Unit conversions (original ↔ standardized)
|
|
41
|
+
- Variable dependencies (calculated variables)
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
# From NumericalSpecs:
|
|
45
|
+
# Value ranges (original units)
|
|
46
|
+
_min (Optional[float]): Minimum value in original units.
|
|
47
|
+
_max (Optional[float]): Maximum value in original units.
|
|
48
|
+
_mean (Optional[float]): Mean value in original units.
|
|
49
|
+
_dev (Optional[float]): Standard deviation in original units.
|
|
50
|
+
# Value ranges (standardized units)
|
|
51
|
+
_std_min (Optional[float]): Minimum value in standard units.
|
|
52
|
+
_std_max (Optional[float]): Maximum value in standard units.
|
|
53
|
+
_std_mean (Optional[float]): Mean value in standard units.
|
|
54
|
+
_std_dev (Optional[float]): Standard deviation in standard units.
|
|
55
|
+
_step (Optional[float]): Step size for simulations.
|
|
56
|
+
_std_range (np.ndarray): Range for numerical analysis in standardized units and discretization.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# Value ranges (original units)
|
|
60
|
+
# :attr: _min
|
|
61
|
+
_min: Optional[float] = None
|
|
62
|
+
"""Minimum value in original units."""
|
|
63
|
+
|
|
64
|
+
# :attr: _max
|
|
65
|
+
_max: Optional[float] = None
|
|
66
|
+
"""Maximum value in original units."""
|
|
67
|
+
|
|
68
|
+
# :attr: _mean
|
|
69
|
+
_mean: Optional[float] = None
|
|
70
|
+
"""Mean value in original units."""
|
|
71
|
+
|
|
72
|
+
# :attr: _dev
|
|
73
|
+
_dev: Optional[float] = None
|
|
74
|
+
"""Standard deviation in original units."""
|
|
75
|
+
|
|
76
|
+
# Value ranges (standardized units)
|
|
77
|
+
# :attr: _std_min
|
|
78
|
+
_std_min: Optional[float] = None
|
|
79
|
+
"""Minimum value in standard units."""
|
|
80
|
+
|
|
81
|
+
# :attr: _std_max
|
|
82
|
+
_std_max: Optional[float] = None
|
|
83
|
+
"""Maximum value in standard units."""
|
|
84
|
+
|
|
85
|
+
# :attr: _std_mean
|
|
86
|
+
_std_mean: Optional[float] = None
|
|
87
|
+
"""Mean value in standard units."""
|
|
88
|
+
|
|
89
|
+
# :attr: _std_dev
|
|
90
|
+
_std_dev: Optional[float] = None
|
|
91
|
+
"""Standard deviation in standard units."""
|
|
92
|
+
|
|
93
|
+
# :attr: _step
|
|
94
|
+
_step: Optional[float] = None
|
|
95
|
+
"""Step size for simulations."""
|
|
96
|
+
|
|
97
|
+
# :attr: _std_range
|
|
98
|
+
_std_range: np.ndarray = field(default_factory=lambda: np.array([]))
|
|
99
|
+
"""Range array for analysis."""
|
|
100
|
+
|
|
101
|
+
# Value Ranges (Original Units)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def min(self) -> Optional[float]:
|
|
105
|
+
"""*min* Get minimum range value.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Optional[float]: Minimum range value.
|
|
109
|
+
"""
|
|
110
|
+
return self._min
|
|
111
|
+
|
|
112
|
+
@min.setter
|
|
113
|
+
@validate_type(int, float)
|
|
114
|
+
def min(self, val: Optional[float]) -> None:
|
|
115
|
+
"""*min* Sets minimum range value.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
val (Optional[float]): Minimum range value.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If value not a valid number.
|
|
122
|
+
ValueError: If value is greater than max.
|
|
123
|
+
"""
|
|
124
|
+
if val is not None and self._max is not None and val > self._max:
|
|
125
|
+
_msg = f"Minimum {val} cannot be greater than maximum {self._max}."
|
|
126
|
+
raise ValueError(_msg)
|
|
127
|
+
|
|
128
|
+
self._min = val
|
|
129
|
+
|
|
130
|
+
# TODO reassert this code later, seems redundant with _prepare_dims()
|
|
131
|
+
# Update range if all values are available
|
|
132
|
+
if all([self._min is not None,
|
|
133
|
+
self._max is not None,
|
|
134
|
+
self._step is not None]):
|
|
135
|
+
self._range = np.arange(self._min,
|
|
136
|
+
self._max,
|
|
137
|
+
self._step)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def max(self) -> Optional[float]:
|
|
141
|
+
"""*max* Get the maximum range value.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Optional[float]: Maximum range value.
|
|
145
|
+
"""
|
|
146
|
+
return self._max
|
|
147
|
+
|
|
148
|
+
@max.setter
|
|
149
|
+
@validate_type(int, float)
|
|
150
|
+
def max(self, val: Optional[float]) -> None:
|
|
151
|
+
"""*max* Sets the maximum range value.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
val (Optional[float]): Maximum range value.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValueError: If value is not a valid number.
|
|
158
|
+
ValueError: If value is less than min.
|
|
159
|
+
"""
|
|
160
|
+
# Check if both values exist before comparing
|
|
161
|
+
if val is not None and self._min is not None and val < self._min:
|
|
162
|
+
_msg = f"Maximum {val} cannot be less than minimum {self._min}."
|
|
163
|
+
raise ValueError(_msg)
|
|
164
|
+
|
|
165
|
+
self._max = val
|
|
166
|
+
|
|
167
|
+
# TODO reassert this code later, seems redundant with _prepare_dims()
|
|
168
|
+
# Update range if all values are available
|
|
169
|
+
if all([self._min is not None,
|
|
170
|
+
self._max is not None,
|
|
171
|
+
self._step is not None]):
|
|
172
|
+
self._range = np.arange(self._min,
|
|
173
|
+
self._max,
|
|
174
|
+
self._step)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def mean(self) -> Optional[float]:
|
|
178
|
+
"""*mean* Get the Variable average value.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Optional[float]: Variable average value.
|
|
182
|
+
"""
|
|
183
|
+
return self._mean
|
|
184
|
+
|
|
185
|
+
@mean.setter
|
|
186
|
+
@validate_type(int, float)
|
|
187
|
+
def mean(self, val: Optional[float]) -> None:
|
|
188
|
+
"""*mean* Sets the Variable mean value.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
val (Optional[float]): Variable mean value.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If value not a valid number.
|
|
195
|
+
ValueError: If value is outside min-max range.
|
|
196
|
+
"""
|
|
197
|
+
# Only validate range if val is not None
|
|
198
|
+
if val is not None:
|
|
199
|
+
low = (self._min is not None and val < self._min)
|
|
200
|
+
high = (self._max is not None and val > self._max)
|
|
201
|
+
if low or high:
|
|
202
|
+
_msg = f"Mean {val} "
|
|
203
|
+
_msg += f"must be between {self._min} and {self._max}."
|
|
204
|
+
raise ValueError(_msg)
|
|
205
|
+
|
|
206
|
+
self._mean = val
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def dev(self) -> Optional[float]:
|
|
210
|
+
"""*dev* Get the Variable standard deviation.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Optional[float]: Variable standard deviation.
|
|
214
|
+
"""
|
|
215
|
+
return self._dev
|
|
216
|
+
|
|
217
|
+
@dev.setter
|
|
218
|
+
def dev(self, val: Optional[float]) -> None:
|
|
219
|
+
"""*dev* Sets the Variable standard deviation.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
val (Optional[float]): Variable standard deviation.
|
|
223
|
+
Raises:
|
|
224
|
+
ValueError: If value not a valid number.
|
|
225
|
+
"""
|
|
226
|
+
if val is not None and not isinstance(val, (int, float)):
|
|
227
|
+
raise ValueError("Standard deviation must be a number.")
|
|
228
|
+
|
|
229
|
+
self._dev = val
|
|
230
|
+
|
|
231
|
+
# Value Ranges (Standardized Units)
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def std_min(self) -> Optional[float]:
|
|
235
|
+
"""*std_min* Get the standardized minimum range value.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Optional[float]: standardized minimum range value.
|
|
239
|
+
"""
|
|
240
|
+
return self._std_min
|
|
241
|
+
|
|
242
|
+
@std_min.setter
|
|
243
|
+
def std_min(self, val: Optional[float]) -> None:
|
|
244
|
+
"""*std_min* Sets the standardized minimum range value.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
val (Optional[float]): standardized minimum range value.
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
ValueError: If value not a valid number.
|
|
251
|
+
ValueError: If value is greater than std_max.
|
|
252
|
+
"""
|
|
253
|
+
if val is not None and not isinstance(val, (int, float)):
|
|
254
|
+
raise ValueError("Standardized minimum must be a number")
|
|
255
|
+
|
|
256
|
+
# Check if both values exist before comparing
|
|
257
|
+
if val is not None and self._std_max is not None and val > self._std_max:
|
|
258
|
+
_msg = f"Standard minimum {val} cannot be greater"
|
|
259
|
+
_msg += f" than standard maximum {self._std_max}."
|
|
260
|
+
raise ValueError(_msg)
|
|
261
|
+
|
|
262
|
+
self._std_min = val
|
|
263
|
+
|
|
264
|
+
# TODO reassert this code later, seems redundant with _prepare_dims()
|
|
265
|
+
# Update range if all values are available
|
|
266
|
+
if all([self._std_min is not None,
|
|
267
|
+
self._std_max is not None,
|
|
268
|
+
self._step is not None]):
|
|
269
|
+
self._std_range = np.arange(self._std_min,
|
|
270
|
+
self._std_max,
|
|
271
|
+
self._step)
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def std_max(self) -> Optional[float]:
|
|
275
|
+
"""*std_max* Get the standardized maximum range value.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Optional[float]: standardized maximum range value.
|
|
279
|
+
"""
|
|
280
|
+
return self._std_max
|
|
281
|
+
|
|
282
|
+
@std_max.setter
|
|
283
|
+
def std_max(self, val: Optional[float]) -> None:
|
|
284
|
+
"""*std_max* Sets the standardized maximum range value.
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
ValueError: If value is not a valid number.
|
|
288
|
+
ValueError: If value is less than std_min.
|
|
289
|
+
"""
|
|
290
|
+
if val is not None and not isinstance(val, (int, float)):
|
|
291
|
+
raise ValueError("Standardized maximum must be a number")
|
|
292
|
+
|
|
293
|
+
# Check if both values exist before comparing
|
|
294
|
+
if val is not None and self._std_min is not None and val < self._std_min:
|
|
295
|
+
_msg = f"Standard maximum {val} cannot be less"
|
|
296
|
+
_msg += f" than standard minimum {self._std_min}."
|
|
297
|
+
raise ValueError(_msg)
|
|
298
|
+
|
|
299
|
+
self._std_max = val
|
|
300
|
+
|
|
301
|
+
# TODO reassert this code later, seems redundant with _prepare_dims()
|
|
302
|
+
# Update range if all values are available
|
|
303
|
+
if all([self._std_min is not None,
|
|
304
|
+
self._std_max is not None,
|
|
305
|
+
self._step is not None]):
|
|
306
|
+
self._std_range = np.arange(self._std_min,
|
|
307
|
+
self._std_max,
|
|
308
|
+
self._step)
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def std_mean(self) -> Optional[float]:
|
|
312
|
+
"""*std_mean* Get standardized mean value.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Optional[float]: standardized mean.
|
|
316
|
+
"""
|
|
317
|
+
return self._std_mean
|
|
318
|
+
|
|
319
|
+
@std_mean.setter
|
|
320
|
+
def std_mean(self, val: Optional[float]) -> None:
|
|
321
|
+
"""*std_mean* Sets the standardized mean value.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
val (Optional[float]): standardized mean value.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
ValueError: If value is not a valid number.
|
|
328
|
+
ValueError: If value is outside std_min-std_max range.
|
|
329
|
+
"""
|
|
330
|
+
if val is not None and not isinstance(val, (int, float)):
|
|
331
|
+
raise ValueError("Standardized mean must be a number")
|
|
332
|
+
|
|
333
|
+
# Only validate range if val is not None
|
|
334
|
+
if val is not None:
|
|
335
|
+
low = (self._std_min is not None and val < self._std_min)
|
|
336
|
+
high = (self._std_max is not None and val > self._std_max)
|
|
337
|
+
|
|
338
|
+
if low or high:
|
|
339
|
+
_msg = f"Standard mean {val} "
|
|
340
|
+
_msg += f"must be between {self._std_min} and {self._std_max}."
|
|
341
|
+
raise ValueError(_msg)
|
|
342
|
+
|
|
343
|
+
self._std_mean = val
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def std_dev(self) -> Optional[float]:
|
|
347
|
+
"""*std_dev* Get standardized standard deviation.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Optional[float]: Standardized standard deviation.
|
|
351
|
+
"""
|
|
352
|
+
return self._std_dev
|
|
353
|
+
|
|
354
|
+
@std_dev.setter
|
|
355
|
+
def std_dev(self, val: Optional[float]) -> None:
|
|
356
|
+
"""*std_dev* Sets the standardized standard deviation.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
val (Optional[float]): Standardized standard deviation.
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
ValueError: If value is not a valid number.
|
|
363
|
+
ValueError: If value is outside std_min-std_max range.
|
|
364
|
+
"""
|
|
365
|
+
if val is not None and not isinstance(val, (int, float)):
|
|
366
|
+
raise ValueError(
|
|
367
|
+
"Standardized standard deviation must be a number")
|
|
368
|
+
|
|
369
|
+
# Standard deviation should be non-negative
|
|
370
|
+
if val is not None and val < 0:
|
|
371
|
+
raise ValueError(f"Standard deviation {val} cannot be negative.")
|
|
372
|
+
|
|
373
|
+
self._std_dev = val
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def step(self) -> Optional[float]:
|
|
377
|
+
"""*step* Get standardized step size.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Optional[float]: Step size (always standardized).
|
|
381
|
+
"""
|
|
382
|
+
return self._step
|
|
383
|
+
|
|
384
|
+
@step.setter
|
|
385
|
+
def step(self, val: Optional[float]) -> None:
|
|
386
|
+
"""*step* Set standardized step size.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
val (Optional[float]): Step size (always standardized).
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
ValueError: If step is not a valid number.
|
|
393
|
+
ValueError: If step is zero.
|
|
394
|
+
ValueError: If step is greater than range.
|
|
395
|
+
"""
|
|
396
|
+
if val is not None and not isinstance(val, (int, float)):
|
|
397
|
+
raise ValueError("Step must be a number.")
|
|
398
|
+
|
|
399
|
+
if val == 0:
|
|
400
|
+
raise ValueError("Step cannot be zero.")
|
|
401
|
+
|
|
402
|
+
# Validate step against range (only if min/max are set)
|
|
403
|
+
if val is not None and self._std_min is not None and self._std_max is not None:
|
|
404
|
+
range_size = self._std_max - self._std_min
|
|
405
|
+
if val >= range_size:
|
|
406
|
+
_msg = f"Step {val} must be less than range: {range_size}."
|
|
407
|
+
raise ValueError(_msg)
|
|
408
|
+
|
|
409
|
+
self._step = val
|
|
410
|
+
|
|
411
|
+
# TODO reassert this code later, seems redundant with _prepare_dims()
|
|
412
|
+
# Update range if all values are available
|
|
413
|
+
if all([self._std_min is not None,
|
|
414
|
+
self._std_max is not None,
|
|
415
|
+
self._step is not None]):
|
|
416
|
+
self._std_range = np.arange(self._std_min,
|
|
417
|
+
self._std_max,
|
|
418
|
+
self._step)
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def std_range(self) -> np.ndarray:
|
|
422
|
+
"""*std_range* Get standardized range array.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
np.ndarray: Range array for range (always standardized).
|
|
426
|
+
"""
|
|
427
|
+
return self._std_range
|
|
428
|
+
|
|
429
|
+
@std_range.setter
|
|
430
|
+
def std_range(self, val: Optional[np.ndarray]) -> None:
|
|
431
|
+
"""*std_range* Set standardized range array.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
val (Optional[np.ndarray]): Data array for range (always standardized).
|
|
435
|
+
|
|
436
|
+
Raises:
|
|
437
|
+
ValueError: If value is not a numpy array.
|
|
438
|
+
"""
|
|
439
|
+
# TODO reassert this code later, seems redundant with _prepare_dims()
|
|
440
|
+
if val is None:
|
|
441
|
+
# Generate range from min, max, step
|
|
442
|
+
if all([self._std_min is not None,
|
|
443
|
+
self._std_max is not None,
|
|
444
|
+
self._step is not None]):
|
|
445
|
+
self._std_range = np.arange(self._std_min,
|
|
446
|
+
self._std_max,
|
|
447
|
+
self._step)
|
|
448
|
+
|
|
449
|
+
# TODO check this latter, might be a hindrance
|
|
450
|
+
elif not isinstance(val, np.ndarray):
|
|
451
|
+
_msg = f"Range must be a numpy array, got {type(val)}"
|
|
452
|
+
raise ValueError(_msg)
|
|
453
|
+
|
|
454
|
+
else:
|
|
455
|
+
self._std_range = val
|
|
456
|
+
|
|
457
|
+
def clear(self) -> None:
|
|
458
|
+
"""*clear()* Reset numerical attributes to default values.
|
|
459
|
+
|
|
460
|
+
Resets all value ranges and step size.
|
|
461
|
+
"""
|
|
462
|
+
self._min = None
|
|
463
|
+
self._max = None
|
|
464
|
+
self._mean = None
|
|
465
|
+
self._std_min = None
|
|
466
|
+
self._std_max = None
|
|
467
|
+
self._std_mean = None
|
|
468
|
+
self._step = None
|
|
469
|
+
self._std_range = np.array([])
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module statistical.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Statistical perspective for variable representation.
|
|
7
|
+
|
|
8
|
+
This module defines the StatisticalSpecs class representing the probabilistic
|
|
9
|
+
distribution and sampling properties of a variable.
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
**StatisticalSpecs**: Statistical variable specifications
|
|
13
|
+
|
|
14
|
+
*IMPORTANT:* Based on the theory from:
|
|
15
|
+
|
|
16
|
+
# H.Gorter, *Dimensionalanalyse: Eine Theoririe der physikalischen Dimensionen mit Anwendungen*
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Optional, Dict, Any, Callable, List, TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class StatisticalSpecs:
|
|
26
|
+
"""Statistical perspective: probabilistic distributions. Answers the question: "How do we MODEL uncertainty?"
|
|
27
|
+
|
|
28
|
+
This perspective focuses on:
|
|
29
|
+
- Probability distribution types (uniform, normal, beta, custom)
|
|
30
|
+
- Distribution parameters
|
|
31
|
+
- Random sampling mechanisms
|
|
32
|
+
- Monte Carlo simulation support
|
|
33
|
+
- Uncertainty quantification
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Type annotation for sym attribute (defined in Foundation, accessed via composition)
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
_sym: str
|
|
39
|
+
|
|
40
|
+
# distribution specifications
|
|
41
|
+
# :attr: _dist_type
|
|
42
|
+
_dist_type: str = "uniform"
|
|
43
|
+
"""Type of distribution (e.g., 'uniform', 'normal'). By default is 'uniform'."""
|
|
44
|
+
|
|
45
|
+
# :attr: _dist_params
|
|
46
|
+
_dist_params: Optional[Dict[str, Any]] = field(default_factory=dict)
|
|
47
|
+
"""Parameters for the distribution (e.g., {'min': 0, 'max': 1} for uniform)."""
|
|
48
|
+
|
|
49
|
+
# :attr: _depends
|
|
50
|
+
_depends: List[str] = field(default_factory=list)
|
|
51
|
+
"""List of variable names that this variable depends on. (e.g., for calculated variables like F = m*a)."""
|
|
52
|
+
|
|
53
|
+
# :attr: _dist_func
|
|
54
|
+
_dist_func: Optional[Callable[..., float]] = None
|
|
55
|
+
"""Callable representing the distribution function defined externally by the user."""
|
|
56
|
+
|
|
57
|
+
def sample(self, *args) -> float:
|
|
58
|
+
"""*sample()* Generate a random sample.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
*args: Additional arguments for the distribution function.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
float: Random sample from distribution.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
ValueError: If no distribution has been set.
|
|
68
|
+
"""
|
|
69
|
+
if self._dist_func is None:
|
|
70
|
+
_msg = f"No distribution set for variable '{self._sym}'. "
|
|
71
|
+
_msg += "Call set_function() first."
|
|
72
|
+
raise ValueError(_msg)
|
|
73
|
+
|
|
74
|
+
# if kwargs are provided, pass them to the function parameters
|
|
75
|
+
elif len(args) > 0:
|
|
76
|
+
return self._dist_func(*args)
|
|
77
|
+
|
|
78
|
+
# otherwise, execute without them
|
|
79
|
+
return self._dist_func()
|
|
80
|
+
|
|
81
|
+
def has_function(self) -> bool:
|
|
82
|
+
"""*has_function()* Check if distribution is set.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
bool: True if distribution is configured.
|
|
86
|
+
"""
|
|
87
|
+
return self._dist_func is not None
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def dist_type(self) -> str:
|
|
91
|
+
"""*dist_type* Get the distribution type.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
str: Distribution type (e.g., 'uniform', 'normal').
|
|
95
|
+
"""
|
|
96
|
+
return self._dist_type
|
|
97
|
+
|
|
98
|
+
@dist_type.setter
|
|
99
|
+
def dist_type(self, val: str) -> None:
|
|
100
|
+
"""*dist_type* Set the distribution type.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
val (str): Distribution type.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: If distribution type is not supported.
|
|
107
|
+
"""
|
|
108
|
+
# TODO improve this for later
|
|
109
|
+
supported_types = [
|
|
110
|
+
"uniform",
|
|
111
|
+
"normal",
|
|
112
|
+
"triangular",
|
|
113
|
+
"exponential",
|
|
114
|
+
"lognormal",
|
|
115
|
+
"custom",
|
|
116
|
+
]
|
|
117
|
+
if val not in supported_types:
|
|
118
|
+
_msg = f"Unsupported distribution type: {val}. "
|
|
119
|
+
_msg += f"Supported types: {', '.join(supported_types)}"
|
|
120
|
+
raise ValueError(_msg)
|
|
121
|
+
self._dist_type = val
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def dist_params(self) -> Optional[Dict[str, Any]]:
|
|
125
|
+
"""*dist_params* Get the distribution parameters.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Optional[Dict[str, Any]: Distribution parameters.
|
|
129
|
+
"""
|
|
130
|
+
return self._dist_params
|
|
131
|
+
|
|
132
|
+
@dist_params.setter
|
|
133
|
+
def dist_params(self, val: Optional[Dict[str, Any]]) -> None:
|
|
134
|
+
"""*dist_params* Set the distribution parameters.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
val (Optional[Dict[str, Any]): Distribution parameters.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ValueError: If parameters are invalid for the distribution type.
|
|
141
|
+
"""
|
|
142
|
+
if val is None:
|
|
143
|
+
self._dist_params = None
|
|
144
|
+
return None
|
|
145
|
+
# Validate parameters based on distribution type
|
|
146
|
+
if self._dist_type == "uniform":
|
|
147
|
+
if "min" not in val or "max" not in val:
|
|
148
|
+
_msg = f"Invalid keys for: {self._dist_type}: {val}"
|
|
149
|
+
_msg += f" {self._dist_type} needs 'min' and 'max' parameters."
|
|
150
|
+
_msg += f" Provided keys are: {list(val.keys())}."
|
|
151
|
+
raise ValueError(_msg)
|
|
152
|
+
if val["min"] >= val["max"]:
|
|
153
|
+
_msg = f"Invalid range for {self._dist_type}: {val}"
|
|
154
|
+
_msg += f" {self._dist_type} needs 'min' to be less than 'max'."
|
|
155
|
+
_msg += f" Provided: min={val['min']}, max={val['max']}."
|
|
156
|
+
raise ValueError(_msg)
|
|
157
|
+
elif self._dist_type == "normal":
|
|
158
|
+
if "mean" not in val or "std" not in val:
|
|
159
|
+
_msg = f"Invalid keys for: {self._dist_type}: {val}"
|
|
160
|
+
_msg += f" {self._dist_type} needs 'mean' and 'std' parameters."
|
|
161
|
+
_msg += f" Provided keys are: {list(val.keys())}."
|
|
162
|
+
raise ValueError(_msg)
|
|
163
|
+
if val["std"] < 0:
|
|
164
|
+
_msg = f"Invalid value for: {self._dist_type}: {val}"
|
|
165
|
+
_msg += f" {self._dist_type} requires 'std' to be positive."
|
|
166
|
+
_msg += f" Provided: std={val['std']}."
|
|
167
|
+
raise ValueError(_msg)
|
|
168
|
+
self._dist_params = val
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def dist_func(self) -> Optional[Callable[..., float]]:
|
|
172
|
+
"""*dist_func* Get the distribution function.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Optional[Callable]: Distribution function.
|
|
176
|
+
"""
|
|
177
|
+
return self._dist_func
|
|
178
|
+
|
|
179
|
+
@dist_func.setter
|
|
180
|
+
def dist_func(self, val: Optional[Callable[..., float]]) -> None:
|
|
181
|
+
"""*dist_func* Set the distribution function.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
val (Optional[Callable]): Distribution function.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
TypeError: If value is not callable when provided.
|
|
188
|
+
"""
|
|
189
|
+
if val is not None and not callable(val):
|
|
190
|
+
_msg = f"Distribution function must be callable, got {type(val)}"
|
|
191
|
+
raise TypeError(_msg)
|
|
192
|
+
self._dist_func = val
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def depends(self) -> List[str]:
|
|
196
|
+
"""*depends* Get the list of variable dependencies.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List[str]: List of variable names that this variable depends on.
|
|
200
|
+
"""
|
|
201
|
+
return self._depends
|
|
202
|
+
|
|
203
|
+
@depends.setter
|
|
204
|
+
def depends(self, val: List[str]) -> None:
|
|
205
|
+
"""*depends* Set the list of variable dependencies.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
val (List[str]): List of variable names that this variable depends on.
|
|
209
|
+
Raises:
|
|
210
|
+
ValueError: If value is not a list of strings.
|
|
211
|
+
"""
|
|
212
|
+
if not isinstance(val, list):
|
|
213
|
+
_msg = f"{val} must be a list of strings."
|
|
214
|
+
_msg += f" type {type(val)} found instead."
|
|
215
|
+
raise ValueError(_msg)
|
|
216
|
+
if not all(isinstance(v, str) for v in val):
|
|
217
|
+
_msg = f"{val} must be a list of strings."
|
|
218
|
+
_msg += f" Found types: {[type(v) for v in val]}."
|
|
219
|
+
raise ValueError(_msg)
|
|
220
|
+
self._depends = val
|
|
221
|
+
|
|
222
|
+
def clear(self) -> None:
|
|
223
|
+
"""*clear()* Reset statistical attributes to default values.
|
|
224
|
+
|
|
225
|
+
Resets distribution type, parameters, and function.
|
|
226
|
+
"""
|
|
227
|
+
self._dist_type = "uniform"
|
|
228
|
+
self._dist_params = {}
|
|
229
|
+
self._dist_func = None
|