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,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']
|