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.
Files changed (58) hide show
  1. pydasa/__init__.py +103 -0
  2. pydasa/_version.py +6 -0
  3. pydasa/analysis/__init__.py +0 -0
  4. pydasa/analysis/scenario.py +584 -0
  5. pydasa/analysis/simulation.py +1158 -0
  6. pydasa/context/__init__.py +0 -0
  7. pydasa/context/conversion.py +11 -0
  8. pydasa/context/system.py +17 -0
  9. pydasa/context/units.py +15 -0
  10. pydasa/core/__init__.py +15 -0
  11. pydasa/core/basic.py +287 -0
  12. pydasa/core/cfg/default.json +136 -0
  13. pydasa/core/constants.py +27 -0
  14. pydasa/core/io.py +102 -0
  15. pydasa/core/setup.py +269 -0
  16. pydasa/dimensional/__init__.py +0 -0
  17. pydasa/dimensional/buckingham.py +728 -0
  18. pydasa/dimensional/fundamental.py +146 -0
  19. pydasa/dimensional/model.py +1077 -0
  20. pydasa/dimensional/vaschy.py +633 -0
  21. pydasa/elements/__init__.py +19 -0
  22. pydasa/elements/parameter.py +218 -0
  23. pydasa/elements/specs/__init__.py +22 -0
  24. pydasa/elements/specs/conceptual.py +161 -0
  25. pydasa/elements/specs/numerical.py +469 -0
  26. pydasa/elements/specs/statistical.py +229 -0
  27. pydasa/elements/specs/symbolic.py +394 -0
  28. pydasa/serialization/__init__.py +27 -0
  29. pydasa/serialization/parser.py +133 -0
  30. pydasa/structs/__init__.py +0 -0
  31. pydasa/structs/lists/__init__.py +0 -0
  32. pydasa/structs/lists/arlt.py +578 -0
  33. pydasa/structs/lists/dllt.py +18 -0
  34. pydasa/structs/lists/ndlt.py +262 -0
  35. pydasa/structs/lists/sllt.py +746 -0
  36. pydasa/structs/tables/__init__.py +0 -0
  37. pydasa/structs/tables/htme.py +182 -0
  38. pydasa/structs/tables/scht.py +774 -0
  39. pydasa/structs/tools/__init__.py +0 -0
  40. pydasa/structs/tools/hashing.py +53 -0
  41. pydasa/structs/tools/math.py +149 -0
  42. pydasa/structs/tools/memory.py +54 -0
  43. pydasa/structs/types/__init__.py +0 -0
  44. pydasa/structs/types/functions.py +131 -0
  45. pydasa/structs/types/generics.py +54 -0
  46. pydasa/validations/__init__.py +0 -0
  47. pydasa/validations/decorators.py +510 -0
  48. pydasa/validations/error.py +100 -0
  49. pydasa/validations/patterns.py +32 -0
  50. pydasa/workflows/__init__.py +1 -0
  51. pydasa/workflows/influence.py +497 -0
  52. pydasa/workflows/phenomena.py +529 -0
  53. pydasa/workflows/practical.py +765 -0
  54. pydasa-0.4.7.dist-info/METADATA +320 -0
  55. pydasa-0.4.7.dist-info/RECORD +58 -0
  56. pydasa-0.4.7.dist-info/WHEEL +5 -0
  57. pydasa-0.4.7.dist-info/licenses/LICENSE +674 -0
  58. 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