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,728 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module vashchy.py
4
+ ===========================================
5
+
6
+ Module for representing Dimensionless Coefficients in Dimensional Analysis for *PyDASA*.
7
+
8
+ This module provides the Coefficient class which models dimensionless numbers used in Vaschy-Buckingham's Pi-theorem for dimensional analysis.
9
+
10
+ Classes:
11
+
12
+ **Coefficient**: Represents a dimensionless coefficient with properties, validation, and symbolic expression.
13
+
14
+ *IMPORTANT:* Based on the theory from:
15
+
16
+ # H.Gorter, *Dimensionalanalyse: Eine Theoririe der physikalischen Dimensionen mit Anwendungen*
17
+ """
18
+
19
+ # native python modules
20
+ # dataclass imports
21
+ from __future__ import annotations
22
+ from dataclasses import dataclass, field, fields
23
+ from typing import Optional, List, Dict, Any, Tuple, Union, Sequence
24
+ # numerical imports
25
+ import numpy as np
26
+
27
+ # custom modules
28
+ # basic-core class imports
29
+ from pydasa.core.basic import Foundation
30
+ from pydasa.elements.parameter import Variable
31
+ # import global variables
32
+ from pydasa.core.setup import CoefCardinality
33
+ from pydasa.core.setup import PYDASA_CFG
34
+ # generic error handling and type checking
35
+ from pydasa.validations.error import inspect_var
36
+ # pattern interpreter imports
37
+ from pydasa.serialization.parser import latex_to_python
38
+ # import validation decorators
39
+ from pydasa.validations.decorators import validate_type
40
+ from pydasa.validations.decorators import validate_custom
41
+ from pydasa.validations.decorators import validate_range
42
+ from pydasa.validations.decorators import validate_choices
43
+
44
+
45
+ @dataclass
46
+ class Coefficient(Foundation):
47
+ """**Coefficient** class for Dimensional Analysis in *PyDASA*.
48
+
49
+ A comprehensive implementation that represents dimensionless coefficients
50
+ (Pi numbers) used in the Vaschy-Buckingham Pi-theorem method.
51
+
52
+ Args:
53
+ Foundation: Foundation class for validation of symbols and frameworks.
54
+
55
+ Attributes:
56
+ # Identification and Classification
57
+ name (str): User-friendly name of the coefficient.
58
+ description (str): Brief summary of the coefficient.
59
+ _idx (int): Index/precedence in the dimensional matrix.
60
+ _sym (str): Symbol representation (LaTeX or alphanumeric).
61
+ _alias (str): Python-compatible alias for use in code.
62
+ _fwk (str): Frameworks context (PHYSICAL, COMPUTATION, SOFTWARE, CUSTOM).
63
+ _cat (str): Category (COMPUTED, DERIVED).
64
+ relevance (bool): Flag indicating if coefficient is relevant for analysis.
65
+
66
+ # Coefficient Construction
67
+ _variables (Dict[str, Variable]): Variables symbols used in coefficient.
68
+ _dim_col (List[int]): Dimensional column for matrix operations.
69
+ _pivot_lt (List[int]): Pivot indices in dimensional matrix.
70
+ _pi_expr (str): Symbolic expression of coefficient.
71
+ var_dims (Dict[str, int]): Dimensional variable exponents.
72
+
73
+ # Value Ranges
74
+ _min (float): Minimum value of the coefficient.
75
+ _max (float): Maximum value of the coefficient.
76
+ _mean (float): Average value of the coefficient.
77
+ _step (float): Step size for simulations.
78
+ _data (np.ndarray): Array of coefficient values for analysis.
79
+ """
80
+
81
+ # Category attribute (COMPUTED, DERIVED)
82
+ # :attr: _cat
83
+ _cat: str = CoefCardinality.COMPUTED.value
84
+ """Category of the coefficient (COMPUTED, DERIVED)."""
85
+
86
+ # Coefficient construction properties
87
+ # :attr: _variables
88
+ _variables: Dict[str, Variable] = field(default_factory=dict)
89
+ """Variables symbols used in the coefficient."""
90
+
91
+ # Coefficient calculation related variables
92
+ # :attr: _dim_col
93
+ _dim_col: List[int] = field(default_factory=list)
94
+ """Dimensional column for matrix operations."""
95
+
96
+ # :attr: var_dims
97
+ var_dims: Dict[str, int] = field(default_factory=dict)
98
+ """Dimensional variable exponents in coefficient."""
99
+
100
+ # :attr: _pivot_lt
101
+ _pivot_lt: Optional[List[int]] = field(default_factory=list)
102
+ """Pivot indices in dimensional matrix."""
103
+
104
+ # :attr: _pi_expr
105
+ _pi_expr: Optional[str] = None
106
+ """Symbolic expression of coefficient."""
107
+
108
+ # Value ranges Variable
109
+ # :attr: _min
110
+ _min: Optional[float] = None
111
+ """Minimum value of the coefficient, always in standardized units."""
112
+
113
+ # :attr: _max
114
+ _max: Optional[float] = None
115
+ """Maximum value of the coefficient, always in standardized units."""
116
+
117
+ # :attr: _mean
118
+ _mean: Optional[float] = None
119
+ """Average value of the coefficient, always in standardized units."""
120
+
121
+ # :attr: _dev
122
+ _dev: Optional[float] = None
123
+ """Standard deviation of the coefficient, always in standardized units."""
124
+
125
+ # :attr: _step
126
+ _step: Optional[float] = 1e-3
127
+ """Step size for simulations, always in standardized units."""
128
+
129
+ # :attr: _data
130
+ _data: np.ndarray = field(default_factory=lambda: np.array([]))
131
+ """Array of coefficient values for analysis."""
132
+
133
+ # Flags
134
+ # :attr: relevance
135
+ relevance: bool = True
136
+ """Flag indicating if coefficient is relevant for analysis."""
137
+
138
+ def __post_init__(self) -> None:
139
+ """*__post_init__()* Initializes the coefficient and validates its properties.
140
+
141
+ Performs validation of core properties and builds the coefficient expression
142
+ based on variable symbols and their respective dimensional exponents.
143
+
144
+ Raises:
145
+ ValueError: If variable list and dimensional column have different lengths.
146
+ """
147
+ # Initialize from base class
148
+ super().__post_init__()
149
+
150
+ # Set the Pi symbol if not specified
151
+ if not self._sym:
152
+ if self._idx >= 0:
153
+ self._sym = f"\\Pi_{{{self._idx}}}"
154
+ else:
155
+ self._sym = "\\Pi_{}"
156
+ # Set the Python alias if not specified
157
+ if not self._alias:
158
+ self._alias = latex_to_python(self._sym)
159
+
160
+ self.cat = self._cat
161
+ self.variables = self._variables
162
+
163
+ # Defaults to empty list
164
+ self.dim_col = self._dim_col or []
165
+ self.pivot_lt = self._pivot_lt or []
166
+
167
+ # Only build expression if parameters and dimensions are provided
168
+ if len(self._variables) > 0 and len(self._dim_col) > 0:
169
+ var_keys = list(self._variables.keys())
170
+ self.pi_expr, self.var_dims = self._build_expression(var_keys,
171
+ self._dim_col)
172
+
173
+ else:
174
+ self.pi_expr = ""
175
+ self.var_dims = {}
176
+
177
+ # Set data
178
+ self.data = self._data
179
+
180
+ # Set up data array if all required values are provided
181
+ if all([self._min, self._max, self._step]):
182
+ self._data = np.arange(self._min, self._max, self._step)
183
+
184
+ def _validate_sequence(self, seq: Sequence,
185
+ exp_type: Union[type, Tuple[type, ...]]) -> bool:
186
+ """*_validate_sequence()* Validates a list with expected element types.
187
+
188
+ Args:
189
+ seq (Sequence): Sequence to validate.
190
+ exp_type (Union[type, Tuple[type, ...]]): Expected type(s) for sequence elements. Can be a single type or a tuple of types.
191
+
192
+ Raises:
193
+ ValueError: If the object is not a sequence.
194
+ ValueError: If the sequence is empty.
195
+ ValueError: If the sequence contains elements of unexpected types.
196
+
197
+ Returns:
198
+ bool: True if the list is valid.
199
+ """
200
+ # Explicitly reject strings (they're sequences but not what we want)
201
+ if isinstance(seq, str):
202
+ _msg = f"{inspect_var(seq)} must be a list or tuple, not a string. "
203
+ _msg += f"Provided: {type(seq).__name__}"
204
+ raise ValueError(_msg)
205
+
206
+ if not isinstance(seq, Sequence):
207
+ _msg = f"{inspect_var(seq)} must be from type: '{exp_type}', "
208
+ _msg += f"Provided: {type(seq).__name__}"
209
+ raise ValueError(_msg)
210
+
211
+ if len(seq) == 0:
212
+ _msg = f"{inspect_var(seq)} cannot be empty. Actual sequence: {seq}"
213
+ raise ValueError(_msg)
214
+
215
+ # Convert list to tuple if needed for isinstance(), just in case
216
+ type_check = exp_type if isinstance(exp_type, tuple) else (exp_type,)
217
+
218
+ if not all(isinstance(x, type_check) for x in seq):
219
+
220
+ # Format expected types for error message
221
+ if isinstance(exp_type, tuple):
222
+ type_names = " or ".join(t.__name__ for t in exp_type)
223
+ else:
224
+ type_names = exp_type.__name__
225
+
226
+ _msg = f"{inspect_var(seq)} must contain {type_names} elements."
227
+ _msg += f" Provided: {[type(x).__name__ for x in seq]}"
228
+ raise ValueError(_msg)
229
+
230
+ return True
231
+
232
+ def _build_expression(self,
233
+ var_lt: List[str],
234
+ dim_col: List[int]) -> tuple[str, dict]:
235
+ """*_build_expression()* Builds LaTeX expression for coefficient.
236
+
237
+ Args:
238
+ var_lt (List[str]): List of variable symbols.
239
+ dim_col (List[int]): List of dimensional exponents.
240
+
241
+ Raises:
242
+ ValueError: If variable list and dimensional column have different lengths.
243
+
244
+ Returns:
245
+ tuple[str, Dict[str, int]]: LaTeX expression and variable exponents.
246
+ """
247
+ # Validate variable list and dimensional column
248
+ if len(var_lt) != len(dim_col):
249
+ _msg = f"Variables list len ({len(var_lt)}) and "
250
+ _msg += f"dimensional column len ({len(dim_col)}) must be equal."
251
+ raise ValueError(_msg)
252
+
253
+ # Initialize working variables
254
+ numerator = []
255
+ denominator = []
256
+ parameters = {}
257
+
258
+ # Process parameters and their exponents
259
+ for sym, exp in zip(var_lt, dim_col):
260
+ # Add to numerator if exponent is positive
261
+ if exp > 0:
262
+ part = sym if exp == 1 else f"{sym}^{{{exp}}}"
263
+ numerator.append(part)
264
+ # Add to denominator if exponent is negative
265
+ elif exp < 0:
266
+ part = sym if exp == -1 else f"{sym}^{{{-exp}}}"
267
+ denominator.append(part)
268
+ # Skip zero exponents
269
+ else:
270
+ continue
271
+ # Store variable exponent
272
+ parameters[sym] = exp
273
+
274
+ # Build expression
275
+ num_str = "1" if not numerator else "*".join(numerator)
276
+
277
+ # Return expression based on whether denominator exists
278
+ if not denominator:
279
+ return num_str, parameters
280
+ else:
281
+ denom_str = "*".join(denominator)
282
+ return f"\\frac{{{num_str}}}{{{denom_str}}}", parameters
283
+
284
+ # Custom validators for decorators
285
+
286
+ def _validate_variables_dict(self, value: Dict[str, Variable]) -> None:
287
+ """*_validate_variables_dict()* Custom validator for variables property.
288
+
289
+ Args:
290
+ value (Dict[str, Variable]): Variables dictionary to validate.
291
+
292
+ Raises:
293
+ ValueError: If dict has invalid keys or values.
294
+ """
295
+ if len(value) > 0:
296
+ self._validate_sequence(list(value.keys()), (str,))
297
+ self._validate_sequence(list(value.values()), (Variable,))
298
+
299
+ def _validate_dim_col_list(self, value: List) -> None:
300
+ """*_validate_dim_col_list()* Custom validator for dim_col property.
301
+
302
+ Args:
303
+ value (List): Dimensional column list to validate.
304
+
305
+ Raises:
306
+ ValueError: If list has invalid elements.
307
+ """
308
+ if len(value) > 0:
309
+ self._validate_sequence(value, (int, float))
310
+
311
+ def _validate_pivot_list(self, value: Optional[List[int]]) -> None:
312
+ """*_validate_pivot_list()* Custom validator for pivot_lt property.
313
+
314
+ Args:
315
+ value (Optional[List[int]]): Pivot list to validate.
316
+
317
+ Raises:
318
+ ValueError: If non-empty list has invalid elements.
319
+ """
320
+ if value is not None and len(value) > 0:
321
+ self._validate_sequence(value, (int,))
322
+
323
+ def _validate_step_value(self, value: Optional[float]) -> None:
324
+ """*_validate_step_value()* Custom validator for step property.
325
+
326
+ Args:
327
+ value (Optional[float]): Step value to validate.
328
+
329
+ Raises:
330
+ ValueError: If step is zero or exceeds range.
331
+ """
332
+ if value == 0:
333
+ raise ValueError("Step cannot be zero.")
334
+
335
+ if value is not None and self._min is not None and self._max is not None:
336
+ range_size = self._max - self._min
337
+ if value >= range_size:
338
+ _msg = f"Step {value} must be less than range: {range_size}."
339
+ raise ValueError(_msg)
340
+
341
+ # Property getters and setters
342
+
343
+ @property
344
+ def cat(self) -> str:
345
+ """*cat* Get the coefficient category.
346
+
347
+ Returns:
348
+ str: Category (COMPUTED, DERIVED).
349
+ """
350
+ return self._cat
351
+
352
+ @cat.setter
353
+ @validate_choices(PYDASA_CFG.coefficient_cardinality)
354
+ def cat(self, val: str) -> None:
355
+ """*cat* Set the coefficient category.
356
+
357
+ Args:
358
+ val (str): Category value.
359
+
360
+ Raises:
361
+ ValueError: If category is not supported.
362
+ """
363
+ self._cat = val.upper()
364
+
365
+ @property
366
+ def variables(self) -> Dict[str, Variable]:
367
+ """*variables* Get the variable symbols list.
368
+
369
+ Returns:
370
+ Dict[str, Variable]: Variables symbols list.
371
+ """
372
+ return self._variables
373
+
374
+ @variables.setter
375
+ @validate_type(dict, allow_none=False)
376
+ @validate_custom(lambda self, val: self._validate_variables_dict(val))
377
+ def variables(self, val: Dict[str, Variable]) -> None:
378
+ """*variables* Set the variable symbols list.
379
+
380
+ Args:
381
+ val (Dict[str, Variable]): Variables symbols list.
382
+
383
+ Raises:
384
+ ValueError: If variables list is invalid.
385
+ """
386
+ self._variables = val
387
+
388
+ @property
389
+ def dim_col(self) -> List[int]:
390
+ """*dim_col* Get the dimensional column.
391
+
392
+ Returns:
393
+ List[int]: Dimensional column.
394
+ """
395
+ return self._dim_col
396
+
397
+ @dim_col.setter
398
+ @validate_type(list, allow_none=False)
399
+ @validate_custom(lambda self, val: self._validate_dim_col_list(val))
400
+ def dim_col(self, val: Union[List[int], List[float]]) -> None:
401
+ """*dim_col* Set the dimensional column.
402
+
403
+ Args:
404
+ val (Union[List[int], List[float]]): Dimensional column.
405
+ Raises:
406
+ ValueError: If dimensional column is invalid.
407
+ """
408
+ # Convert to integers
409
+ self._dim_col = [int(x) for x in val]
410
+
411
+ @property
412
+ def pivot_lt(self) -> Optional[List[int]]:
413
+ """*pivot_lt* Get the pivot indices list.
414
+
415
+ Returns:
416
+ Optional[List[int]]: Pivot indices list.
417
+ """
418
+ return self._pivot_lt
419
+
420
+ @pivot_lt.setter
421
+ @validate_custom(lambda self, val: self._validate_pivot_list(val))
422
+ def pivot_lt(self, val: List[int]) -> None:
423
+ """*pivot_lt* Set the pivot indices list.
424
+
425
+ Args:
426
+ val (List[int]): Pivot indices list.
427
+
428
+ Raises:
429
+ ValueError: If pivot list is invalid.
430
+ """
431
+ self._pivot_lt = val
432
+
433
+ @property
434
+ def pi_expr(self) -> Optional[str]:
435
+ """*pi_expr* Get the coefficient expression.
436
+
437
+ Returns:
438
+ Optional[str]: Coefficient expression.
439
+ """
440
+ return self._pi_expr
441
+
442
+ @pi_expr.setter
443
+ @validate_type(str, allow_none=False)
444
+ def pi_expr(self, val: str) -> None:
445
+ """*pi_expr* Set the coefficient expression.
446
+
447
+ Args:
448
+ val (str): Coefficient expression.
449
+
450
+ Raises:
451
+ ValueError: If the coefficient expression is not a string.
452
+ """
453
+ self._pi_expr = val
454
+
455
+ # Value range properties
456
+
457
+ @property
458
+ def min(self) -> Optional[float]:
459
+ """*min* Get minimum range value.
460
+
461
+ Returns:
462
+ Optional[float]: Minimum range value.
463
+ """
464
+ return self._min
465
+
466
+ @min.setter
467
+ @validate_type(int, float)
468
+ @validate_range(max_attr='_max')
469
+ def min(self, val: Optional[float]) -> None:
470
+ """*min* Sets minimum range value.
471
+
472
+ Args:
473
+ val (Optional[float]): Minimum range value.
474
+
475
+ Raises:
476
+ ValueError: If value is not a number.
477
+ ValueError: If value is greater than max.
478
+ """
479
+ self._min = val
480
+
481
+ # Update range if all values are available
482
+ if all([self._min is not None,
483
+ self._max is not None,
484
+ self._step is not None]):
485
+ self._range = np.arange(self._min,
486
+ self._max,
487
+ self._step)
488
+
489
+ @property
490
+ def max(self) -> Optional[float]:
491
+ """*max* Get the maximum range value.
492
+
493
+ Returns:
494
+ Optional[float]: Maximum range value.
495
+ """
496
+ return self._max
497
+
498
+ @max.setter
499
+ @validate_type(int, float)
500
+ @validate_range(min_attr='_min')
501
+ def max(self, val: Optional[float]) -> None:
502
+ """*max* Sets the maximum range value.
503
+
504
+ Args:
505
+ val (Optional[float]): Maximum range value.
506
+
507
+ Raises:
508
+ ValueError: If value is not a number.
509
+ ValueError: If value is less than min.
510
+ """
511
+ self._max = val
512
+
513
+ # Update range if all values are available
514
+ if all([self._min is not None,
515
+ self._max is not None,
516
+ self._step is not None]):
517
+ self._range = np.arange(self._min,
518
+ self._max,
519
+ self._step)
520
+
521
+ @property
522
+ def mean(self) -> Optional[float]:
523
+ """*mean* Get the average value.
524
+
525
+ Returns:
526
+ Optional[float]: average value.
527
+ """
528
+ return self._mean
529
+
530
+ @mean.setter
531
+ @validate_type(int, float)
532
+ @validate_range(min_attr='_min', max_attr='_max')
533
+ def mean(self, val: Optional[float]) -> None:
534
+ """*mean* Sets the average value.
535
+
536
+ Args:
537
+ val (Optional[float]): average value.
538
+
539
+ Raises:
540
+ ValueError: If value is not a number.
541
+ ValueError: If value is outside min-max range.
542
+ """
543
+ self._mean = val
544
+
545
+ @property
546
+ def dev(self) -> Optional[float]:
547
+ """*dev* Get the Variable standard deviation.
548
+
549
+ Returns:
550
+ Optional[float]: Variable standard deviation.
551
+ """
552
+ return self._dev
553
+
554
+ @dev.setter
555
+ @validate_type(int, float)
556
+ def dev(self, val: Optional[float]) -> None:
557
+ """*dev* Sets the Variable standard deviation.
558
+
559
+ Args:
560
+ val (Optional[float]): Variable standard deviation.
561
+ Raises:
562
+ ValueError: If value not a valid number.
563
+ """
564
+ self._dev = val
565
+
566
+ @property
567
+ def step(self) -> Optional[float]:
568
+ """*step* Get standardized step size.
569
+
570
+ Returns:
571
+ Optional[float]: Step size (always standardized).
572
+ """
573
+ return self._step
574
+
575
+ @step.setter
576
+ @validate_type(int, float)
577
+ @validate_custom(lambda self, val: self._validate_step_value(val))
578
+ def step(self, val: Optional[float]) -> None:
579
+ """*step* Set standardized step size.
580
+
581
+ Args:
582
+ val (Optional[float]): Step size (always standardized).
583
+
584
+ Raises:
585
+ ValueError: If step is not a valid number
586
+ ValueError: If step is zero.
587
+ ValueError: If step is greater than range.
588
+ """
589
+ self._step = val
590
+
591
+ # Update range if all values are available
592
+ if all([self._min is not None,
593
+ self._max is not None,
594
+ self._step is not None]):
595
+ self._range = np.arange(self._min,
596
+ self._max,
597
+ self._step)
598
+
599
+ @property
600
+ def data(self) -> np.ndarray:
601
+ """*data* Get the data array.
602
+
603
+ Returns:
604
+ np.ndarray: Data array. If not explicitly set, generates from min, max, step.
605
+ """
606
+ # If data was explicitly set, return it
607
+ if len(self._data) > 0:
608
+ return self._data
609
+
610
+ # Otherwise, generate from range parameters
611
+ if self._min is not None and self._max is not None and self._step != 0:
612
+ return np.arange(self._min, self._max, self._step)
613
+
614
+ # Default to empty array
615
+ return np.array([], dtype=float)
616
+
617
+ @data.setter
618
+ @validate_type(np.ndarray, list, allow_none=False)
619
+ def data(self, val: Union[np.ndarray, list]) -> None:
620
+ """*data* Set the data array.
621
+
622
+ Args:
623
+ val (Union[np.ndarray, list]): Data array or list.
624
+
625
+ Raises:
626
+ ValueError: If data cannot be converted to numpy array.
627
+ """
628
+ # Convert list to numpy array if needed
629
+ if isinstance(val, list):
630
+ self._data = np.array(val, dtype=float)
631
+ else:
632
+ self._data = val
633
+
634
+ def clear(self) -> None:
635
+ """*clear()* Reset all attributes to default values.
636
+
637
+ Resets all coefficient properties to their initial state.
638
+ """
639
+ # Reset base class attributes
640
+ self._idx = -1
641
+ self._sym = ""
642
+ self._alias = ""
643
+ self._fwk = "PHYSICAL"
644
+ self.name = ""
645
+ self.description = ""
646
+
647
+ # Reset coefficient-specific attributes
648
+ self._cat = "COMPUTED"
649
+ self._variables = {}
650
+ self._dim_col = []
651
+ self._pivot_lt = None
652
+ self._pi_expr = None
653
+ self.var_dims = {}
654
+ self._min = None
655
+ self._max = None
656
+ self._mean = None
657
+ self._step = 1e-3
658
+ self._data = np.array([])
659
+ self.relevance = True
660
+
661
+ def to_dict(self) -> Dict[str, Any]:
662
+ """*to_dict()* Convert variable to dictionary representation.
663
+
664
+ Returns:
665
+ Dict[str, Any]: Dictionary representation of variable.
666
+ """
667
+ result = {}
668
+
669
+ # Get all dataclass fields
670
+ for f in fields(self):
671
+ attr_name = f.name
672
+ attr_value = getattr(self, attr_name)
673
+
674
+ # Skip numpy arrays (not JSON serializable without special handling)
675
+ if isinstance(attr_value, np.ndarray):
676
+ # Convert to list for JSON compatibility
677
+ attr_value = attr_value.tolist()
678
+
679
+ # Skip callables (can't be serialized)
680
+ if callable(attr_value) and attr_name == "_dist_func":
681
+ continue
682
+
683
+ # Remove leading underscore from private attributes
684
+ if attr_name.startswith("_"):
685
+ clean_name = attr_name[1:] # Remove first character
686
+ else:
687
+ clean_name = attr_name
688
+
689
+ result[clean_name] = attr_value
690
+
691
+ return result
692
+
693
+ @classmethod
694
+ def from_dict(cls, data: Dict[str, Any]) -> Coefficient:
695
+ """*from_dict()* Create variable from dictionary representation.
696
+
697
+ Args:
698
+ data (Dict[str, Any]): Dictionary representation of variable.
699
+
700
+ Returns:
701
+ Variable: New variable instance.
702
+ """
703
+ # Get all valid field names from the dataclass
704
+ field_names = {f.name for f in fields(cls)}
705
+
706
+ # Map keys without underscores to keys with underscores
707
+ mapped_data = {}
708
+
709
+ for key, value in data.items():
710
+ # Try the key as-is first (handles both _idx and name)
711
+ if key in field_names:
712
+ mapped_data[key] = value
713
+ # Try adding underscore prefix (handles idx -> _idx)
714
+ elif f"_{key}" in field_names:
715
+ mapped_data[f"_{key}"] = value
716
+ # Try removing underscore prefix (handles _name -> name if needed)
717
+ elif key.startswith("_") and key[1:] in field_names:
718
+ mapped_data[key[1:]] = value
719
+ else:
720
+ # Use as-is for unknown keys (will be validated by dataclass)
721
+ mapped_data[key] = value
722
+
723
+ # Convert lists back to numpy arrays for range attributes
724
+ for range_key in ["std_range", "_std_range"]:
725
+ if range_key in mapped_data and isinstance(mapped_data[range_key], list):
726
+ mapped_data[range_key] = np.array(mapped_data[range_key])
727
+
728
+ return cls(**mapped_data)