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,633 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module framework.py
4
+ ===========================================
5
+
6
+ Module for **Schema** to manage Fundamental Dimensional Units (FDUs) for Dimensional Analysis in *PyDASA*.
7
+
8
+ This module provides the Schema class which manages dimensional frameworks, FDU precedence, and regex patterns for dimensional expression validation.
9
+
10
+ Classes:
11
+ **Schema**: Manages dimensional frameworks and FDUs, providing methods for validation,
12
+
13
+ *IMPORTANT:* Based on the theory from:
14
+
15
+ # H.Gorter, *Dimensionalanalyse: Eine Theoririe der physikalischen Dimensionen mit Anwendungen*
16
+ """
17
+
18
+ # native python modules
19
+ # dataclass imports
20
+ from __future__ import annotations
21
+ from dataclasses import dataclass, field, fields
22
+ # data type imports
23
+ from typing import List, Dict, Optional, Any, Sequence, Mapping, cast
24
+
25
+ # custom modules
26
+ from pydasa.validations.decorators import validate_type
27
+ from pydasa.validations.decorators import validate_emptiness
28
+ from pydasa.validations.decorators import validate_custom
29
+ from pydasa.core.basic import Foundation
30
+ from pydasa.core.setup import Frameworks
31
+ from pydasa.dimensional.fundamental import Dimension
32
+ from pydasa.validations.patterns import DFLT_POW_RE
33
+ from pydasa.core.setup import PYDASA_CFG
34
+
35
+
36
+ @dataclass
37
+ class Schema(Foundation):
38
+ """**Schema** Manages dimensional frameworks and FDUs for *PyDASA*.
39
+
40
+ Maintains a collection of Dimensions with their precedence, provides regex patterns for dimensional expressions, and manages the dimensional framework context.
41
+
42
+ Args:
43
+ Foundation: Foundation class for validation of symbols and frameworks.
44
+
45
+ Attributes:
46
+ _fdu_lt (List[Dimension]): List of Fundamental Dimensional Units in precedence order.
47
+ _fdu_map (Dict[str, Dimension]): Dictionary mapping FDU symbols to Dimension objects.
48
+ _fdu_regex (str): Regex pattern for matching dimensional expressions (e.g., 'M/L*T^-2' to 'M^1*L^-1*T^-2').
49
+ _fdu_pow_regex (str): Regex pattern for matching dimensions with exponents. (e.g., 'M*L^-1*T^-2' to 'M^(1)*L^(-1)*T^(-2)').
50
+ _fdu_no_pow_regex (str): Regex pattern for matching dimensions without exponents. (e.g., 'M*L*T' to 'M^(1)*L^(1)*T^(1)').
51
+ _fdu_sym_regex (str): Regex pattern for matching FDUs in symbolic expressions. (e.g., 'M^(1)*L^(-1)*T^(-2)' to 'L**(-1)*M**(1)*T**(-2)').
52
+ """
53
+
54
+ # FDUs storage
55
+ # Default Fundamental Dimensional Units (FDU) precedence list
56
+ # FDU precedence list, linked to WKNG_FDU_PREC_LT, full object list.
57
+ # :attr: _fdu_lt
58
+ _fdu_lt: List[Dimension] = field(default_factory=list[Dimension])
59
+ """List Fundamental Dimensional Units (FDUs) objects in precedence order."""
60
+
61
+ # Default Fundamental Dimensional Units (FDU) framework
62
+ # FDU map, linked to WKNG_FDU_PREC_LT, symbol to Dimension object.
63
+ # :attr: _fwk
64
+ _fdu_map: Dict[str, Dimension] = field(default_factory=dict)
65
+ """Dictionary mapping FDU symbols to Dimension objects in *PyDASA*. procesess (e.g., Mass [M], Length [L], Time [T]).
66
+
67
+ Purpose:
68
+ - Defines the default dimensional framework used in *PyDASA*.
69
+ - Used to initialize entities without a specified framework.
70
+ - Basis for dimensional analysis precedence list in *PyDASA*.
71
+ - Validates parameter and variable dimensions in *PyDASA*.
72
+ - Default is the Physical FDUs framework.
73
+ - Can be customized for specific applications or domains.
74
+ """
75
+
76
+ # FDU symbol list, linked to WKNG_FDU_PREC_LT, string symbol list.
77
+ # :attr: _fdu_symbols
78
+ _fdu_symbols: List[str] = field(default_factory=list)
79
+ """List of FDU symbols in the framework for the dimensional matrix (e.g., 'M*L^-1*T^-2').
80
+
81
+ Purpose:
82
+ - Defines the row order in the dimensional matrix.
83
+ - Validates parameter and variable dimensions in *PyDASA*."""
84
+
85
+ # Regex patterns
86
+ # Default/Working FDU Pattern, WKNG_FDU_RE
87
+ # :attr: _fdu_regex
88
+ _fdu_regex: str = ""
89
+ """Regex pattern for matching dimensional expressions.
90
+
91
+ Default/Working regex pattern to match FDUs in *PyDASA* (e.g., 'M*L^-1*T^-2' to 'M^(1)*L^(-1)*T^(-2)')."""
92
+
93
+ # FDU power regex pattern, linked to WKNG_POW_RE.
94
+ # :attr: _fdu_pow_regex
95
+ _fdu_pow_regex: str = DFLT_POW_RE
96
+ """Regex pattern for matching FDUs with exponents (e.g., 'M*L^-1*T^-2' to 'M^(1)*L^(-1)*T^(-2)')"""
97
+
98
+ # FDU no power regex pattern, linked to WKNG_NO_POW_RE.
99
+ # :attr: _fdu_no_pow_regex
100
+ _fdu_no_pow_regex: str = ""
101
+ """Regex pattern for matching dimensions without exponents.
102
+
103
+ Default/Working regex to match FDUs without exponents (e.g., 'T*D' instead of 'T^2*D^-1')."""
104
+
105
+ # FDU symbolic regex pattern, linked to WKNG_FDU_SYM_RE.
106
+ # :attr: _fdu_sym_regex
107
+ _fdu_sym_regex: str = ""
108
+ """Regex pattern for matching FDUs in symbolic expressions.
109
+
110
+ Default/Working regex to match FDU symbols in *PyDASA* (e.g., 'T^(1)*D^(-1)' to 'D**(-1)*T**(2)')."""
111
+
112
+ def __post_init__(self) -> None:
113
+ """*__post_init__()* Initializes the framework and sets up regex patterns.
114
+ """
115
+ # Initialize base class
116
+ super().__post_init__()
117
+
118
+ # Initialize FDUs based on framework
119
+ self._setup_fdus()
120
+
121
+ # Initialize indices, map, and symbol precedence
122
+ self._validate_fdu_precedence()
123
+ self._update_fdu_map()
124
+ self._update_fdu_symbols()
125
+
126
+ # Generate regex patterns
127
+ self._setup_regex()
128
+
129
+ def _setup_fdus(self) -> None:
130
+ """*_setup_fdus()* Initializes FDUs based on the selected framework.
131
+
132
+ Creates and adds standard FDUs for the selected framework (PHYSICAL,
133
+ COMPUTATION, SOFTWARE) or validates custom FDUs.
134
+
135
+ Raises:
136
+ ValueError: If the FDU framework is not properly defined.
137
+ """
138
+ # if the framework is supported, configure the default
139
+ if self.fwk in PYDASA_CFG.get_instance().frameworks and self.fwk != Frameworks.CUSTOM.value:
140
+ self.fdu_lt = self._setup_default_framework()
141
+
142
+ # if the framework is user-defined, use the provided list[dict]
143
+ elif self.fwk == Frameworks.CUSTOM.value:
144
+ if not self._fdu_lt:
145
+ _msg = "Custom framework requires '_fdu_lt' to define FDUs"
146
+ raise ValueError(_msg)
147
+
148
+ # Check if _fdu_lt contains Dimension objects (already created)
149
+ if all(isinstance(val, Dimension) for val in self._fdu_lt):
150
+ # Already Dimension objects, just assign them
151
+ self.fdu_lt = self._fdu_lt
152
+ # Check if _fdu_lt contains dicts (need to be converted)
153
+ elif all(isinstance(val, dict) for val in self._fdu_lt):
154
+ # Convert dicts to Dimension objects
155
+ raw = cast(Sequence[Mapping[str, Any]], self._fdu_lt)
156
+ self.fdu_lt = self._setup_custom_framework(raw)
157
+ else:
158
+ _msg = "'fdu_lt' elements must be type Dimension() or dict()"
159
+ raise ValueError(_msg)
160
+
161
+ # otherwise, raise an error
162
+ else:
163
+ _msg = f"Invalid Frameworks: {self.fwk}. "
164
+ _fwk = PYDASA_CFG.get_instance().frameworks
165
+ _msg += f"Valid options: {', '.join(_fwk)}."
166
+ raise ValueError(_msg)
167
+
168
+ def _setup_custom_framework(self,
169
+ fdus: Sequence[Mapping[str, Any]]) -> List[Dimension]:
170
+ """*_setup_custom_framework()* Initializes a custom framework with the provided FDUs.
171
+
172
+ Args:
173
+ fdus (List[Dict]): List of dictionaries representing custom FDUs.
174
+
175
+ Returns:
176
+ List[Dimension]: List of Dimension objects created from the provided FDUs.
177
+ """
178
+ # detecting custom framework
179
+ ans = []
180
+ if self.fwk == Frameworks.CUSTOM.value:
181
+ # Create custom FDU set
182
+ for idx, data in enumerate(fdus):
183
+ data = dict(data)
184
+ fdu = Dimension(
185
+ _idx=idx,
186
+ _sym=data.get("_sym", ""),
187
+ _fwk=self._fwk,
188
+ _unit=data.get("_unit", ""),
189
+ _name=data.get("_name", ""),
190
+ description=data.get("description", ""))
191
+
192
+ ans.append(fdu)
193
+ return ans
194
+
195
+ def _setup_default_framework(self) -> List[Dimension]:
196
+ """*_setup_default_framework()* Returns the default FDU precedence list for the specified framework.
197
+
198
+ Returns:
199
+ List[str]: Default FDUs precedence list based on the framework map.
200
+ """
201
+ # fp =
202
+ # map for easy access to the FDUs
203
+ _frk_map = {
204
+ # "PHYSICAL": PHY_FDU_PREC_DT,
205
+ # "COMPUTATION": COMPU_FDU_PREC_DT,
206
+ # "SOFTWARE": SOFT_FDU_PREC_DT,
207
+ }
208
+ _frk_map = PYDASA_CFG.get_instance().SPT_FDU_FWKS
209
+ ans = []
210
+ # select FDU framework
211
+ if self.fwk in _frk_map:
212
+ # Get the fdus dictionary from the framework config
213
+ fdus_dict = _frk_map[self.fwk].get("fdus", {})
214
+ # Create standard FDU set
215
+ for idx, (sym, data) in enumerate(fdus_dict.items()):
216
+ fdu = Dimension(
217
+ _idx=idx,
218
+ _sym=sym,
219
+ _fwk=self._fwk,
220
+ _unit=data.get("unit", ""),
221
+ _name=data.get("name", ""),
222
+ description=data.get("description", ""))
223
+
224
+ ans.append(fdu)
225
+ # _prec_lt = list(_frk_map[self.fwk].keys())
226
+ return ans
227
+
228
+ def _validate_fdu_precedence(self) -> None:
229
+ """*_validate_fdu_precedence()* Ensures FDUs have valid and unique precedence values.
230
+
231
+ Raises:
232
+ ValueError: If FDU precedence values are duplicated.
233
+ """
234
+ # trick to do nothing if FDU set is null
235
+ if not self._fdu_lt:
236
+ return
237
+
238
+ # Check for duplicate precedence values
239
+ indices = [fdu.idx for fdu in self._fdu_lt]
240
+ if len(indices) != len(set(indices)):
241
+ raise ValueError("Duplicate precedence values in FDUs.")
242
+
243
+ # Sort FDUs by idx precedence
244
+ self._fdu_lt.sort(key=lambda fdu: fdu.idx)
245
+
246
+ def _update_fdu_map(self) -> None:
247
+ """*_update_fdu_map()* Updates the FDU symbol to object mapping.
248
+ """
249
+ self._fdu_map.clear()
250
+ for fdu in self._fdu_lt:
251
+ self._fdu_map[fdu.sym] = fdu
252
+
253
+ def _update_fdu_symbols(self) -> None:
254
+ """*_update_fdu_symbols()* Updates the list of FDU symbols in precedence order."""
255
+ self._fdu_symbols = [fdu.sym for fdu in self._fdu_lt]
256
+
257
+ def _setup_regex(self) -> None:
258
+ """*_setup_regex()* Sets up regex patterns for dimensional validation. Generates regex patterns for:
259
+ - validating dimensional expressions.
260
+ - parsing exponents.
261
+ - completing expressions with exponent.
262
+ - handling symbolic expressions.
263
+ """
264
+ # trick to do nothing if FDU set is null
265
+ if not self._fdu_lt:
266
+ return None
267
+
268
+ # Get FDU symbols in precedence order
269
+ # fdu_symbols = [fdu.sym for fdu in self._fdu_lt]
270
+ _fdu_str = ''.join(self.fdu_symbols)
271
+
272
+ # NOTE: OG REGEX!
273
+ # DFLT_FDU_RE: str = rf"^[{''.join(DFLT_FDU_PREC_LT)}](\^-?\d+)?(\*[{''.join(DFLT_FDU_PREC_LT)}](?:\^-?\d+)?)*$"
274
+ # Generate main regex for dimensional expressions
275
+ self._fdu_regex = rf"^[{_fdu_str}](\^-?\d+)?(\*[{_fdu_str}](?:\^-?\d+)?)*$"
276
+
277
+ # Use default regex for exponents
278
+ self._fdu_pow_regex = DFLT_POW_RE
279
+
280
+ # NOTE: OG REGEX!
281
+ # DFLT_NO_POW_RE: str = rf"[{''.join(DFLT_FDU_PREC_LT)}](?!\^)"
282
+ # Generate regex for dimensions without exponents
283
+ self._fdu_no_pow_regex = rf"[{_fdu_str}](?!\^)"
284
+
285
+ # NOTE: OG REGEX!
286
+ # DFLT_FDU_SYM_RE: str = rf"[{''.join(DFLT_FDU_PREC_LT)}]"
287
+ # Generate regex for dimensions in symbolic expressions
288
+ self._fdu_sym_regex = rf"[{_fdu_str}]"
289
+
290
+ def _validate_fdu_list(self, value: List[Dimension]) -> None:
291
+ """*_validate_fdu_list()* Custom validator for fdu_lt property.
292
+
293
+ Args:
294
+ value (List[Dimension]): List of FDUs to validate.
295
+
296
+ Raises:
297
+ ValueError: If list is empty or contains non-Dimension objects.
298
+ """
299
+ if not value:
300
+ _msg = "FDUs list must be non-empty. "
301
+ _msg += f"Provided: {value}"
302
+ raise ValueError(_msg)
303
+ if not all(isinstance(i, Dimension) for i in value):
304
+ _msg = "FDUs list must contain only Dimension objects. "
305
+ _msg += f"Provided: {value}"
306
+ raise ValueError(_msg)
307
+
308
+ # propierties getters and setters
309
+
310
+ @property
311
+ def fdu_lt(self) -> List[Dimension]:
312
+ """*fdu_lt* Get the list of FDUs in precedence order.
313
+
314
+ Returns:
315
+ List[Dimension]: List of FDUs.
316
+ """
317
+ return self._fdu_lt.copy()
318
+
319
+ @fdu_lt.setter
320
+ @validate_type(list, allow_none=False)
321
+ @validate_custom(lambda self, val: self._validate_fdu_list(val))
322
+ def fdu_lt(self, val: List[Dimension]) -> None:
323
+ """*fdu_lt* Set the FDUs in precedence order.
324
+
325
+ Args:
326
+ val (List[Dimension]): List of FDUs.
327
+
328
+ Raises:
329
+ ValueError: If the FDUs list is empty or invalid.
330
+ """
331
+ self._fdu_lt = val
332
+
333
+ @property
334
+ def fdu_symbols(self) -> List[str]:
335
+ """*fdu_symbols* Get the list of FDU symbols in precedence order.
336
+
337
+ Returns:
338
+ List[str]: List of FDU symbols.
339
+ """
340
+ return self._fdu_symbols.copy()
341
+
342
+ @property
343
+ def size(self) -> int:
344
+ """*size* Get the number of FDUs in the framework.
345
+
346
+ Returns:
347
+ int: Number of FDUs.
348
+ """
349
+ return len(self._fdu_lt)
350
+
351
+ @property
352
+ def fdu_regex(self) -> str:
353
+ """*fdu_regex* Get the FDU regex pattern.
354
+
355
+ Returns:
356
+ str: Regex pattern for validating dimensional expressions.
357
+ """
358
+ return self._fdu_regex
359
+
360
+ @fdu_regex.setter
361
+ @validate_type(str, allow_none=False)
362
+ @validate_emptiness()
363
+ def fdu_regex(self, val: str) -> None:
364
+ """*fdu_regex* Set the FDUs regex pattern.
365
+
366
+ Args:
367
+ val (str): FDUs regex pattern.
368
+
369
+ Raises:
370
+ ValueError: If the FDUs regex pattern is empty or not a string.
371
+ """
372
+ self._fdu_regex = val
373
+
374
+ @property
375
+ def fdu_pow_regex(self) -> str:
376
+ """*fdu_pow_regex* Get the FDU powered regex pattern.
377
+
378
+ Returns:
379
+ str: Regex pattern for matching dimensions with exponents.
380
+ """
381
+ return self._fdu_pow_regex
382
+
383
+ @fdu_pow_regex.setter
384
+ @validate_type(str, allow_none=False)
385
+ @validate_emptiness()
386
+ def fdu_pow_regex(self, val: str) -> None:
387
+ """*fdu_pow_regex* Set the FDUs pow-regex pattern.
388
+
389
+ Args:
390
+ val (str): FDUs pow-regex pattern for matching dimensions with exponent.
391
+
392
+ Raises:
393
+ ValueError: If the FDUs pow-regex pattern is empty or not a string.
394
+ """
395
+ self._fdu_pow_regex = val
396
+
397
+ @property
398
+ def fdu_no_pow_regex(self) -> str:
399
+ """*fdu_no_pow_regex* Get the FDU no-power regex pattern.
400
+
401
+ Returns:
402
+ str: Regex pattern for matching dimensions without exponents.
403
+ """
404
+ return self._fdu_no_pow_regex
405
+
406
+ @fdu_no_pow_regex.setter
407
+ @validate_type(str, allow_none=False)
408
+ @validate_emptiness()
409
+ def fdu_no_pow_regex(self, val: str) -> None:
410
+ """*fdu_no_pow_regex* Set the FDUs no-pow-regex pattern.
411
+
412
+ Args:
413
+ val (str): FDUs no-pow-regex pattern for matching dimensions without exponent.
414
+
415
+ Raises:
416
+ ValueError: If the FDUs no-pow-regex pattern is empty or not a string.
417
+ """
418
+ self._fdu_no_pow_regex = val
419
+
420
+ @property
421
+ def fdu_sym_regex(self) -> str:
422
+ """*fdu_sym_regex* Get the FDU symbol regex pattern.
423
+
424
+ Returns:
425
+ str: Regex pattern for matching FDUs in symbolic expressions.
426
+ """
427
+ return self._fdu_sym_regex
428
+
429
+ @fdu_sym_regex.setter
430
+ @validate_type(str, allow_none=False)
431
+ @validate_emptiness()
432
+ def fdu_sym_regex(self, val: str) -> None:
433
+ """*fdu_sym_regex* Set the FDUs sym-regex pattern.
434
+
435
+ Args:
436
+ val (str): FDUs sym-regex pattern for matching dimensions in symbolic expressions.
437
+
438
+ Raises:
439
+ ValueError: If the FDUs sym-regex pattern is empty or not a string.
440
+ """
441
+ self._fdu_sym_regex = val
442
+
443
+ def get_fdu(self, symbol: str) -> Optional[Dimension]:
444
+ """*get_fdu()* Get an FDU by its symbol.
445
+
446
+ Args:
447
+ symbol (str): FDU symbol.
448
+
449
+ Returns:
450
+ Optional[Dimension]: FDU object if found, None otherwise.
451
+ """
452
+ return self._fdu_map.get(symbol)
453
+
454
+ def has_fdu(self, symbol: str) -> bool:
455
+ """*has_fdu()* Check if an FDU with the given symbol exists.
456
+
457
+ Args:
458
+ symbol (str): FDU symbol.
459
+
460
+ Returns:
461
+ bool: True if the FDU exists, False otherwise.
462
+ """
463
+ return symbol in self._fdu_map
464
+
465
+ def add_fdu(self, fdu: Dimension) -> None:
466
+ """*add_fdu()* Add an FDU to the framework.
467
+
468
+ Args:
469
+ fdu (Dimension): FDU to add.
470
+
471
+ Raises:
472
+ ValueError: If an FDU with the same symbol already exists.
473
+ ValueError: If the FDU framework does not match the current framework.
474
+ """
475
+ if self.has_fdu(fdu.sym):
476
+ raise ValueError(f"FDU with symbol '{fdu.sym}' already exists.")
477
+
478
+ # Set framework
479
+ if fdu.fwk != self._fwk:
480
+ _msg = "FDU framework mismatch: "
481
+ _msg += f"Expected '{self._fwk}', got '{fdu.fwk}'"
482
+ raise ValueError(_msg)
483
+
484
+ # Add FDU
485
+ self._fdu_lt.append(fdu)
486
+
487
+ # Update indices, map, and symbol precedence
488
+ self._validate_fdu_precedence()
489
+ self._update_fdu_map()
490
+ self._update_fdu_symbols()
491
+
492
+ # Update regex patterns
493
+ self._setup_regex()
494
+
495
+ def remove_fdu(self, sym: str) -> Dimension:
496
+ """*remove_fdu()* Remove an FDU from the framework.
497
+
498
+ Args:
499
+ sym (str): Symbol of the FDU to remove.
500
+
501
+ Returns:
502
+ Dimension: removed FDU object.
503
+ """
504
+ if not self.has_fdu(sym):
505
+ raise ValueError(f"FDU with symbol '{sym}' does not exist.")
506
+
507
+ # Remove FDU
508
+ # find index with the symbol
509
+ if sym in self._fdu_map:
510
+ # direct retrieve the FDU to avoid Optional return of dict.get
511
+ fdu_obj = self._fdu_map[sym]
512
+ # Remove by precedence index and capture the removed Dimension
513
+ idx = fdu_obj.idx
514
+ ans = self._fdu_lt.pop(idx)
515
+ else:
516
+ # Should not happen because of the earlier has_fdu check, but keep safe
517
+ raise ValueError(f"FDU with symbol '{sym}' does not exist.")
518
+
519
+ # Update indices, map, and symbol precedence
520
+ self._validate_fdu_precedence()
521
+ self._update_fdu_map()
522
+ self._update_fdu_symbols()
523
+
524
+ # Update regex patterns
525
+ self._setup_regex()
526
+
527
+ return ans
528
+
529
+ def reset(self) -> None:
530
+
531
+ self._fdu_lt.clear()
532
+ self._fdu_map.clear()
533
+ self._fdu_symbols.clear()
534
+ self._fdu_regex = ""
535
+ self._fdu_pow_regex = DFLT_POW_RE
536
+ self._fdu_no_pow_regex = ""
537
+ self._fdu_sym_regex = ""
538
+
539
+ def to_dict(self) -> Dict[str, Any]:
540
+ """*to_dict()* Convert framework to dictionary representation.
541
+
542
+ Returns:
543
+ Dict[str, Any]: Dictionary representation of the framework.
544
+ """
545
+ result = {}
546
+
547
+ # Get all dataclass fields
548
+ for f in fields(self):
549
+ attr_name = f.name
550
+ attr_value = getattr(self, attr_name)
551
+
552
+ # Handle Dimension list (convert each Dimension)
553
+ if isinstance(attr_value, list) and all(isinstance(d, Dimension) for d in attr_value):
554
+ attr_value = [d.to_dict() for d in attr_value]
555
+
556
+ # Handle Dimension dictionary (convert each Dimension)
557
+ if isinstance(attr_value, dict) and all(isinstance(d, Dimension) for d in attr_value.values()):
558
+ attr_value = {k: d.to_dict() for k, d in attr_value.items()}
559
+
560
+ # Skip None values for optional fields
561
+ if attr_value is None:
562
+ continue
563
+
564
+ # Remove leading underscore from private attributes
565
+ if attr_name.startswith("_"):
566
+ clean_name = attr_name[1:] # Remove first character
567
+ else:
568
+ clean_name = attr_name
569
+
570
+ result[clean_name] = attr_value
571
+
572
+ return result
573
+
574
+ @classmethod
575
+ def from_dict(cls, data: Dict[str, Any]) -> Schema:
576
+ """*from_dict()* Create framework from dictionary representation.
577
+
578
+ Args:
579
+ data (Dict[str, Any]): Dictionary representation of the framework.
580
+
581
+ Returns:
582
+ DimScheme: New DimScheme instance.
583
+ """
584
+ # Get all valid field names from the dataclass
585
+ field_names = {f.name for f in fields(cls)}
586
+
587
+ # Map keys without underscores to keys with underscores
588
+ mapped_data = {}
589
+
590
+ for key, value in data.items():
591
+ # Try the key as-is first (handles both _fwk and name)
592
+ if key in field_names:
593
+ mapped_data[key] = value
594
+ # Try adding underscore prefix (handles fwk -> _fwk)
595
+ elif f"_{key}" in field_names:
596
+ mapped_data[f"_{key}"] = value
597
+ # Try removing underscore prefix (handles _name -> name if needed)
598
+ elif key.startswith("_") and key[1:] in field_names:
599
+ mapped_data[key[1:]] = value
600
+
601
+ # Convert Dimension list back
602
+ if "fdu_lt" in mapped_data or "_fdu_lt" in mapped_data:
603
+ fdu_data = mapped_data.get("fdu_lt") or mapped_data.get("_fdu_lt")
604
+ if isinstance(fdu_data, list):
605
+ mapped_data["_fdu_lt"] = [
606
+ Dimension.from_dict(d) if isinstance(d, dict) else d
607
+ for d in fdu_data
608
+ ]
609
+
610
+ # Convert Dimension map back
611
+ if "fdu_map" in mapped_data or "_fdu_map" in mapped_data:
612
+ map_data = mapped_data.get(
613
+ "fdu_map") or mapped_data.get("_fdu_map")
614
+ if isinstance(map_data, dict):
615
+ mapped_data["_fdu_map"] = {
616
+ k: Dimension.from_dict(d) if isinstance(d, dict) else d
617
+ for k, d in map_data.items()
618
+ }
619
+
620
+ # Remove computed/derived fields that shouldn't be passed to constructor
621
+ computed_fields = [
622
+ "fdu_map", "_fdu_map", # Reconstructed from fdu_lt
623
+ "fdu_symbols", "_fdu_symbols", # Reconstructed from fdu_lt
624
+ "size" # Computed property
625
+ ]
626
+
627
+ for field_name in computed_fields:
628
+ mapped_data.pop(field_name, None)
629
+
630
+ # Create framework instance
631
+ framework = cls(**mapped_data)
632
+
633
+ return framework
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module elements
4
+ ===========================================
5
+
6
+ Module for representing **Variable** entities and their compositional specifications
7
+ in Dimensional Analysis for *PyDASA*.
8
+
9
+ Classes:
10
+ **Variable**: Main class combining all perspectives (Conceptual, Symbolic, Numerical, Statistical).
11
+
12
+ Submodules:
13
+ **parameter**: Variable class implementation.
14
+ **specs**: Compositional perspective specifications (ConceptualSpecs, SymbolicSpecs, NumericalSpecs, StatisticalSpecs).
15
+ """
16
+
17
+ from pydasa.elements.parameter import Variable
18
+
19
+ __all__ = ['Variable']