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,394 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module symbolic.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Symbolic perspective for variable representation.
|
|
7
|
+
|
|
8
|
+
This module defines the SymbolicSpecs class representing the mathematical
|
|
9
|
+
and notational properties of a variable.
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
**SymbolicSpecs**: Symbolic 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
|
+
# native python modules
|
|
20
|
+
# dataclass imports
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Optional, List, TYPE_CHECKING
|
|
24
|
+
import re
|
|
25
|
+
|
|
26
|
+
# custom modules
|
|
27
|
+
from pydasa.validations.decorators import validate_type
|
|
28
|
+
from pydasa.validations.decorators import validate_emptiness
|
|
29
|
+
|
|
30
|
+
# Type checking imports (not at runtime to avoid circular imports)
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pydasa.dimensional.vaschy import Schema
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class SymbolicSpecs:
|
|
37
|
+
"""Symbolic perspective: mathematical representation. Answers the question: "How do we WRITE this variable?"
|
|
38
|
+
|
|
39
|
+
This perspective focuses on:
|
|
40
|
+
- Symbol notation (LaTeX, Python alias)
|
|
41
|
+
- Dimensional formulas (L, M, T, etc.)
|
|
42
|
+
- Unit systems (original and standardized)
|
|
43
|
+
- Matrix representation for linear algebra
|
|
44
|
+
- Integration with symbolic math libraries
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
# From SymbolicSpecs
|
|
48
|
+
_dims (str): Dimensional expression (e.g., "L*T^-1").
|
|
49
|
+
_units (str): Units of measure (e.g., "m/s").
|
|
50
|
+
_std_dims (Optional[str]): Standardized dimensional expression. e.g.: from [T^2*L^-1] to [L^(-1)*T^(2)].
|
|
51
|
+
_sym_exp (Optional[str]): Sympy-compatible dimensional expression. e.g.: from [T^2*L^-1] to [T**2*L**(-1)].
|
|
52
|
+
_dim_col (List[int]): Dimensional column for matrix operations. e.g.: from [T^2*L^-1] to [2, -1].
|
|
53
|
+
_std_units (str): Standardized units of measure. e.g `km/h` -> `m/s`, `kByte/s` -> `bit/s`.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Type annotation for _schema attribute (defined in ConceptualSpecs, accessed via composition)
|
|
57
|
+
if TYPE_CHECKING:
|
|
58
|
+
_schema: Optional[Schema]
|
|
59
|
+
|
|
60
|
+
# Dimensional properties
|
|
61
|
+
# :attr: _dims
|
|
62
|
+
_dims: str = ""
|
|
63
|
+
"""Dimensional expression (e.g., "L*T^-1")."""
|
|
64
|
+
|
|
65
|
+
# :attr: _units
|
|
66
|
+
_units: str = ""
|
|
67
|
+
"""Units of measure (e.g., "m/s")."""
|
|
68
|
+
|
|
69
|
+
# Processed dimensional attributes
|
|
70
|
+
# :attr: _std_dims
|
|
71
|
+
_std_dims: Optional[str] = None
|
|
72
|
+
"""Standardized dimensional expression. e.g.: from [T^2*L^-1] to [L^(-1)*T^(2)]."""
|
|
73
|
+
|
|
74
|
+
# :attr: _sym_exp
|
|
75
|
+
_sym_exp: Optional[str] = None
|
|
76
|
+
"""Sympy-compatible dimensional expression. e.g.: from [T^2*L^-1] to [T**2*L**(-1)]."""
|
|
77
|
+
|
|
78
|
+
# :attr: _std_col
|
|
79
|
+
_dim_col: List[int] = field(default_factory=list)
|
|
80
|
+
"""Dimensional column for matrix operations. e.g.: from [T^2*L^-1] to [2, -1]."""
|
|
81
|
+
|
|
82
|
+
# Value ranges (standardized units)
|
|
83
|
+
# :attr: _std_units
|
|
84
|
+
_std_units: str = ""
|
|
85
|
+
"""Standardized units of measure. e.g `km/h` -> `m/s`, `kByte/s` -> `bit/s`."""
|
|
86
|
+
|
|
87
|
+
def _validate_exp(self, exp: str, regex: str) -> bool:
|
|
88
|
+
"""*_validate_exp()* Validates an expression using a regex pattern (inclde dimensions and units,).
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
dims (str): Dimensions of the parameter. It is a string with the FDU formula of the parameter. e.g.: [T^2*L^-1]
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
bool: True if the dimensions are valid, False otherwise, ignoring null or empty strings.
|
|
95
|
+
"""
|
|
96
|
+
# TODO improve this ignoring null or empty strings for constants
|
|
97
|
+
if exp in [None, ""]:
|
|
98
|
+
return True
|
|
99
|
+
return bool(re.match(regex, exp))
|
|
100
|
+
|
|
101
|
+
def _prepare_dims(self) -> None:
|
|
102
|
+
"""*_prepare_dims()* Processes dimensional expressions for analysis.
|
|
103
|
+
|
|
104
|
+
Standardizes and sorts dimensions, creates sympy expression and dimensional column.
|
|
105
|
+
"""
|
|
106
|
+
self._std_dims = self._standardize_dims(self._dims)
|
|
107
|
+
self._std_dims = self._sort_dims(self._std_dims)
|
|
108
|
+
self._sym_exp = self._setup_sympy(self._std_dims)
|
|
109
|
+
self._dim_col = self._setup_column(self._sym_exp)
|
|
110
|
+
|
|
111
|
+
def _standardize_dims(self, dims: str) -> str:
|
|
112
|
+
"""*_standardize_dims()* Standardizes dimensional expression format.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
dims (str): Dimensional expression (e.g., "L*T^-1").
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
str: Standardized expression with parentheses (e.g., "L^(1)*T^(-1)").
|
|
119
|
+
"""
|
|
120
|
+
if self._schema is None:
|
|
121
|
+
_msg = "Schema must be initialized before standardizing dimensions"
|
|
122
|
+
raise ValueError(_msg)
|
|
123
|
+
|
|
124
|
+
# Add parentheses to powers
|
|
125
|
+
# _pattern = re.compile(cfg.WKNG_POW_RE)
|
|
126
|
+
_pattern = re.compile(self._schema.fdu_pow_regex)
|
|
127
|
+
dims = _pattern.sub(lambda m: f"({m.group(0)})", dims)
|
|
128
|
+
|
|
129
|
+
# Add ^1 to dimensions without explicit powers
|
|
130
|
+
# _pattern = re.compile(cfg.WKNG_NO_POW_RE)
|
|
131
|
+
_pattern = re.compile(self._schema.fdu_no_pow_regex)
|
|
132
|
+
dims = _pattern.sub(lambda m: f"{m.group(0)}^(1)", dims)
|
|
133
|
+
return dims
|
|
134
|
+
|
|
135
|
+
def _sort_dims(self, dims: str) -> str:
|
|
136
|
+
"""*_sort_dims()* Sorts dimensions based on FDU precedence.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
dims (str): Standardized dimensional expression. e.g.: [T^2*L^-1].
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
str: Sorted dimensional expression. e.g.: [L^(-1)*T^(2)].
|
|
143
|
+
"""
|
|
144
|
+
if self._schema is None:
|
|
145
|
+
_msg = "Schema must be initialized before standardizing dimensions"
|
|
146
|
+
raise ValueError(_msg)
|
|
147
|
+
# Local variable for type narrowing
|
|
148
|
+
_schema = self._schema
|
|
149
|
+
|
|
150
|
+
# TODO move '*' as global operator to cfg module?
|
|
151
|
+
# Split by multiplication operator
|
|
152
|
+
_dims_lt = dims.split("*")
|
|
153
|
+
|
|
154
|
+
# Sort based on FDU precedence
|
|
155
|
+
# _dims_lt.sort(key=lambda x: cfg.WKNG_FDU_PREC_LT.index(x[0]))
|
|
156
|
+
_dims_lt.sort(key=lambda x: _schema.fdu_symbols.index(x[0]))
|
|
157
|
+
|
|
158
|
+
# Rejoin with multiplication operator
|
|
159
|
+
_dims = "*".join(_dims_lt)
|
|
160
|
+
return _dims
|
|
161
|
+
|
|
162
|
+
def _setup_sympy(self, dims: str) -> str:
|
|
163
|
+
"""*_setup_sympy()* Creates sympy-compatible expression.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
dims (str): Standardized dimensional expression. e.g.: [T^2*L^-1].
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
str: Sympy-compatible expression. e.g.: [T**2* L**(-1)].
|
|
170
|
+
"""
|
|
171
|
+
# TODO move '*' and '* ' as global operator to cfg module?
|
|
172
|
+
# TODO do I use also regex for this?
|
|
173
|
+
# replace '*' with '* ' for sympy processing
|
|
174
|
+
# # replace '^' with '**' for sympy processing
|
|
175
|
+
return dims.replace("*", "* ").replace("^", "**")
|
|
176
|
+
|
|
177
|
+
def _setup_column(self, dims: str) -> List[int]:
|
|
178
|
+
"""*_setup_column()* Generates dimensional column (list of exponents) in the Dimensional Matrix.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
dims (str): Standardized dimensional expression. e.g.: [T^(2)*L^(-1)]
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
List[int]: Exponents with the dimensional expression. e.g.: [2, -1]
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ValueError: If dimensional expression cannot be parsed.
|
|
188
|
+
"""
|
|
189
|
+
if self._schema is None:
|
|
190
|
+
_msg = "Schema must be initialized before standardizing dimensions"
|
|
191
|
+
raise ValueError(_msg)
|
|
192
|
+
# Local variable for type narrowing
|
|
193
|
+
_schema = self._schema
|
|
194
|
+
|
|
195
|
+
# split the sympy expression into a list of dimensions
|
|
196
|
+
dims_list = dims.split("* ")
|
|
197
|
+
# set the default list of zeros with the FDU length
|
|
198
|
+
# col = [0] * len(cfg.WKNG_FDU_PREC_LT)
|
|
199
|
+
col = [0] * len(_schema.fdu_symbols)
|
|
200
|
+
|
|
201
|
+
for dim in dims_list:
|
|
202
|
+
# match the exponent of the dimension
|
|
203
|
+
exp_match = re.search(_schema.fdu_pow_regex, dim)
|
|
204
|
+
if exp_match is None:
|
|
205
|
+
_msg = f"Could not extract exponent from dimension: {dim}"
|
|
206
|
+
raise ValueError(_msg)
|
|
207
|
+
_exp = int(exp_match.group(0))
|
|
208
|
+
|
|
209
|
+
# match the symbol of the dimension
|
|
210
|
+
sym_match = re.search(_schema.fdu_sym_regex, dim)
|
|
211
|
+
if sym_match is None:
|
|
212
|
+
_msg = f"Could not extract symbol from dimension: {dim}"
|
|
213
|
+
raise ValueError(_msg)
|
|
214
|
+
_sym = sym_match.group(0)
|
|
215
|
+
|
|
216
|
+
# Check if symbol exists in the precedence list
|
|
217
|
+
if _sym not in _schema.fdu_symbols:
|
|
218
|
+
_msg = f"Unknown dimensional symbol: {_sym}"
|
|
219
|
+
raise ValueError(_msg)
|
|
220
|
+
|
|
221
|
+
# update the column with the exponent of the dimension
|
|
222
|
+
col[_schema.fdu_symbols.index(_sym)] = _exp
|
|
223
|
+
|
|
224
|
+
return col
|
|
225
|
+
|
|
226
|
+
# Dimensional Properties
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def dims(self) -> str:
|
|
230
|
+
"""*dims* Get the dimensional expression.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: Dimensions. e.g.: [T^2*L^-1]
|
|
234
|
+
"""
|
|
235
|
+
return self._dims
|
|
236
|
+
|
|
237
|
+
@dims.setter
|
|
238
|
+
@validate_type(str, allow_none=False)
|
|
239
|
+
@validate_emptiness()
|
|
240
|
+
def dims(self, val: str) -> None:
|
|
241
|
+
"""*dims* Sets the dimensional expression.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
val (str): Dimensions. e.g.: [T^2*L^-1]
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ValueError: If expression is empty
|
|
248
|
+
ValueError: If dimensions are invalid according to the precedence.
|
|
249
|
+
"""
|
|
250
|
+
if self._schema is None:
|
|
251
|
+
_msg = "Schema must be initialized before standardizing dimensions"
|
|
252
|
+
raise ValueError(_msg)
|
|
253
|
+
# Local variable for type narrowing
|
|
254
|
+
_schema = self._schema
|
|
255
|
+
|
|
256
|
+
# Process dimensions
|
|
257
|
+
if val and not self._validate_exp(val, _schema.fdu_regex):
|
|
258
|
+
_msg = f"Invalid dimensional expression: {val}. "
|
|
259
|
+
_msg += f"FDUS precedence is: {_schema.fdu_regex}"
|
|
260
|
+
raise ValueError(_msg)
|
|
261
|
+
|
|
262
|
+
self._dims = val
|
|
263
|
+
|
|
264
|
+
# automatically prepare the dimensions for analysis
|
|
265
|
+
self._prepare_dims()
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def units(self) -> str:
|
|
269
|
+
"""*units* Get the units of measure.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
str: Units of measure. e.g.: `m/s`, `kg/m3`, etc.
|
|
273
|
+
"""
|
|
274
|
+
return self._units
|
|
275
|
+
|
|
276
|
+
@units.setter
|
|
277
|
+
@validate_type(str, allow_none=False)
|
|
278
|
+
@validate_emptiness()
|
|
279
|
+
def units(self, val: str) -> None:
|
|
280
|
+
"""*units* Sets the units of measure.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
val (str): Units of measure. i.e `m/s`, `kg/m3`, etc.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
ValueError: If units are empty.
|
|
287
|
+
"""
|
|
288
|
+
self._units = val
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def sym_exp(self) -> Optional[str]:
|
|
292
|
+
"""*sym_exp* Get Sympy-compatible expression.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Optional[str]: Sympy expression. e.g.: [T**2*L**(-1)]
|
|
296
|
+
"""
|
|
297
|
+
return self._sym_exp
|
|
298
|
+
|
|
299
|
+
@sym_exp.setter
|
|
300
|
+
@validate_type(str, allow_none=False)
|
|
301
|
+
@validate_emptiness()
|
|
302
|
+
def sym_exp(self, val: str) -> None:
|
|
303
|
+
"""*sym_exp* Sets Sympy-compatible expression.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
val (str): Sympy expression. e.g.: [T**2*L**(-1)]
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ValueError: If the string is empty.
|
|
310
|
+
"""
|
|
311
|
+
self._sym_exp = val
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def dim_col(self) -> Optional[List[int]]:
|
|
315
|
+
"""*dim_col* Get dimensional column.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Optional[List[int]]: Dimensional exponents. e.g.: [2, -1]
|
|
319
|
+
"""
|
|
320
|
+
return self._dim_col
|
|
321
|
+
|
|
322
|
+
@dim_col.setter
|
|
323
|
+
def dim_col(self, val: List[int]) -> None:
|
|
324
|
+
"""*dim_col* Sets the dimensional column
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
val (List[int]): Dimensional exponents. i.e..: [2, -1]
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
ValueError: if the dimensional column is not a list of integers.
|
|
331
|
+
"""
|
|
332
|
+
if val is not None and not isinstance(val, list):
|
|
333
|
+
raise ValueError("Dimensional column must be a list of integers.")
|
|
334
|
+
self._dim_col = val
|
|
335
|
+
|
|
336
|
+
# Standardized Dimensional Properties
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def std_dims(self) -> Optional[str]:
|
|
340
|
+
"""*std_dims* Get the standardized dimensional expression.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Optional[str]: Standardized dimensional expression. e.g.: [L^(-1)*T^(2)]
|
|
344
|
+
"""
|
|
345
|
+
return self._std_dims
|
|
346
|
+
|
|
347
|
+
@std_dims.setter
|
|
348
|
+
@validate_type(str, allow_none=False)
|
|
349
|
+
@validate_emptiness()
|
|
350
|
+
def std_dims(self, val: str) -> None:
|
|
351
|
+
"""*std_dims* Sets the standardized dimensional expression.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
val (str): Standardized dimensional expression. e.g.: [L^(-1)*T^(2)]
|
|
355
|
+
|
|
356
|
+
Raises:
|
|
357
|
+
ValueError: If the standardized dimensional expression is empty.
|
|
358
|
+
"""
|
|
359
|
+
self._std_dims = val
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def std_units(self) -> Optional[str]:
|
|
363
|
+
"""*std_units* Get the standardized Unit of Measure.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Optional[str]: standardized Unit of Measure.
|
|
367
|
+
"""
|
|
368
|
+
return self._std_units
|
|
369
|
+
|
|
370
|
+
@std_units.setter
|
|
371
|
+
@validate_type(str, allow_none=False)
|
|
372
|
+
@validate_emptiness()
|
|
373
|
+
def std_units(self, val: str) -> None:
|
|
374
|
+
"""*std_units* Sets the standardized Unit of Measure.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
val (Optional[str]): standardized Unit of Measure.
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
ValueError: If standardized units are empty.
|
|
381
|
+
"""
|
|
382
|
+
self._std_units = val
|
|
383
|
+
|
|
384
|
+
def clear(self) -> None:
|
|
385
|
+
"""*clear()* Reset symbolic attributes to default values.
|
|
386
|
+
|
|
387
|
+
Resets dimensions, units, and all processed dimensional attributes.
|
|
388
|
+
"""
|
|
389
|
+
self._dims = ""
|
|
390
|
+
self._units = ""
|
|
391
|
+
self._std_dims = None
|
|
392
|
+
self._sym_exp = None
|
|
393
|
+
self._dim_col = []
|
|
394
|
+
self._std_units = ""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module serialization
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Serialization and parsing utilities for PyDASA.
|
|
7
|
+
|
|
8
|
+
This module provides utilities for:
|
|
9
|
+
- LaTeX expression parsing and conversion
|
|
10
|
+
- Symbol mapping and transformation
|
|
11
|
+
- Future: JSON, YAML serialization support
|
|
12
|
+
|
|
13
|
+
Modules:
|
|
14
|
+
**parser**: LaTeX parsing and Python conversion utilities
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from pydasa.serialization.parser import (
|
|
18
|
+
latex_to_python,
|
|
19
|
+
parse_latex,
|
|
20
|
+
create_latex_mapping,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"latex_to_python",
|
|
25
|
+
"parse_latex",
|
|
26
|
+
"create_latex_mapping",
|
|
27
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module latex.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Module for default global variables and comparison functions for use by all *PyDASA* and its Data Structs.
|
|
7
|
+
"""
|
|
8
|
+
# python native modules
|
|
9
|
+
# from dataclasses import dataclass
|
|
10
|
+
# from typing import TypeVar
|
|
11
|
+
from typing import Tuple, Dict
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
# custom modules
|
|
15
|
+
from sympy.parsing.latex import parse_latex
|
|
16
|
+
from sympy import Symbol, symbols
|
|
17
|
+
|
|
18
|
+
# import global variables
|
|
19
|
+
from pydasa.validations.patterns import LATEX_RE
|
|
20
|
+
|
|
21
|
+
# Global vars for special Latex symbos and functions to ignore
|
|
22
|
+
IGNORE_EXPR = {
|
|
23
|
+
"\\frac",
|
|
24
|
+
"\\sqrt",
|
|
25
|
+
"\\sin",
|
|
26
|
+
"\\cos",
|
|
27
|
+
"\\tan",
|
|
28
|
+
"\\log",
|
|
29
|
+
"\\exp"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Latex Parsing Functions
|
|
34
|
+
|
|
35
|
+
def latex_to_python(expr: str) -> str:
|
|
36
|
+
"""*latex_to_python()* Convert a LaTeX expression to a Python-compatible string.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
expr (str): The LaTeX expression to convert.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
str: The Python-compatible string.
|
|
43
|
+
"""
|
|
44
|
+
# Replace LaTeX subscript with Python style
|
|
45
|
+
if expr.isalnum():
|
|
46
|
+
return expr
|
|
47
|
+
# TODO this regex doesnt work, check latter
|
|
48
|
+
# ans = re.sub(r"\\([a-zA-Z]+)_{(\d+)}", r"\1_\2", expr)
|
|
49
|
+
alias = expr.replace("\\", "")
|
|
50
|
+
alias = alias.replace("_{", "_").replace("}", "")
|
|
51
|
+
return alias
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def extract_latex_vars(expr: str) -> Tuple[Dict[str, str], Dict[str, str]]:
|
|
55
|
+
"""*extract_latex_vars()* Extract variable names in LaTeX format with their Python equivalents.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
expr (str): The LaTeX expression to parse.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
tuple [dict]: A tuple containing two dictionaries:
|
|
62
|
+
- The first dictionary maps LaTeX variable names to their Python equivalents.
|
|
63
|
+
- The second dictionary maps Python variable names to their LaTeX equivalents.
|
|
64
|
+
"""
|
|
65
|
+
# Extract latex variable names with regex
|
|
66
|
+
matches = re.findall(LATEX_RE, expr)
|
|
67
|
+
|
|
68
|
+
# Filter out ignored LaTeX commands
|
|
69
|
+
matches = [m for m in matches if m not in IGNORE_EXPR]
|
|
70
|
+
|
|
71
|
+
# Create mappings both ways
|
|
72
|
+
latex_to_py = {}
|
|
73
|
+
py_to_latex = {}
|
|
74
|
+
|
|
75
|
+
for m in matches:
|
|
76
|
+
# Keep original LaTeX notation for external reference
|
|
77
|
+
latex_var = m
|
|
78
|
+
# Convert to Python style for internal use
|
|
79
|
+
py_var = m.lstrip("\\")
|
|
80
|
+
py_var = py_var.replace("_{", "_")
|
|
81
|
+
py_var = py_var.replace("}", "")
|
|
82
|
+
|
|
83
|
+
latex_to_py[latex_var] = py_var
|
|
84
|
+
py_to_latex[py_var] = latex_var
|
|
85
|
+
|
|
86
|
+
return latex_to_py, py_to_latex
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_latex_mapping(expr: str) -> Tuple[Dict[Symbol, Symbol], # symbol_map
|
|
90
|
+
Dict[str, Symbol], # py_symbol_map
|
|
91
|
+
Dict[str, str], # latex_to_py
|
|
92
|
+
Dict[str, str] # py_to_latex
|
|
93
|
+
]:
|
|
94
|
+
"""*create_latex_mapping()* Create a mapping between LaTeX symbols and Python symbols.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
expr (str): The LaTeX expression to parse.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
tuple[dict]: A tuple containing:
|
|
101
|
+
- A dictionary mapping LaTeX symbols to Python symbols for internal substitution.
|
|
102
|
+
- A dictionary mapping Python variable names to their corresponding sympy symbols for lambdify.
|
|
103
|
+
- A dictionary mapping LaTeX variable names to their Python equivalents.
|
|
104
|
+
- A dictionary mapping Python variable names to their LaTeX equivalents.
|
|
105
|
+
"""
|
|
106
|
+
# Get LaTeX<->Python variable mappings
|
|
107
|
+
latex_to_py, py_to_latex = extract_latex_vars(expr)
|
|
108
|
+
|
|
109
|
+
# Parse to get LaTeX symbols
|
|
110
|
+
sym_expr = parse_latex(expr)
|
|
111
|
+
|
|
112
|
+
# Create mapping for sympy substitution
|
|
113
|
+
symbol_map = {} # For internal substitution
|
|
114
|
+
py_symbol_map = {} # For lambdify
|
|
115
|
+
|
|
116
|
+
for latex_sym in sym_expr.free_symbols:
|
|
117
|
+
latex_name = str(latex_sym)
|
|
118
|
+
|
|
119
|
+
# Find corresponding Python name
|
|
120
|
+
for latex_var, py_var in latex_to_py.items():
|
|
121
|
+
# Check for various forms of equivalence
|
|
122
|
+
con1 = (latex_name == latex_var)
|
|
123
|
+
con2 = (latex_name == py_var)
|
|
124
|
+
con3 = (latex_name.replace("_{", "_").replace("}", "") == py_var)
|
|
125
|
+
if con1 or con2 or con3:
|
|
126
|
+
# Create symbol for this variable
|
|
127
|
+
sym = symbols(py_var)
|
|
128
|
+
# Store mappings
|
|
129
|
+
symbol_map[latex_sym] = sym # For substitution
|
|
130
|
+
py_symbol_map[py_var] = sym # For lambdify args
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
return symbol_map, py_symbol_map, latex_to_py, py_to_latex
|
|
File without changes
|
|
File without changes
|