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,529 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module phenomena.py
4
+ ===========================================
5
+
6
+ Module for **AnalysisEngine** to orchestrate dimensional analysis workflows in *PyDASA*.
7
+
8
+ This module provides the **AnalysisEngine** class serves as the main entry point and workflow for *PyDASA's* dimensional analysis capabilities setting up the dimensional domain, solving the dimensional matrix, and coefficient generation.
9
+
10
+ Classes:
11
+ **AnalysisEngine**: Main workflow class for dimensional analysis and coefficient generation.
12
+
13
+ *IMPORTANT:* Based on the theory from:
14
+
15
+ # H.Gorter, *Dimensionalanalyse: Eine Theoririe der physikalischen Dimensionen mit Anwendungen*
16
+ """
17
+ # Standard library imports
18
+ from __future__ import annotations
19
+ from dataclasses import dataclass, field
20
+ from typing import Dict, Optional, Any, Union, Tuple
21
+
22
+ # Import validation base classes
23
+ from pydasa.core.basic import Foundation
24
+
25
+ # Import related classes
26
+ from pydasa.elements.parameter import Variable
27
+ from pydasa.dimensional.buckingham import Coefficient
28
+ from pydasa.dimensional.model import Matrix
29
+ from pydasa.dimensional.vaschy import Schema
30
+
31
+ # Import utils
32
+ from pydasa.validations.error import inspect_var
33
+ from pydasa.serialization.parser import latex_to_python
34
+
35
+ # Import validation decorators
36
+ from pydasa.validations.decorators import validate_type
37
+ # from pydasa.validations.decorators import validate_custom
38
+
39
+ # Import global configuration
40
+ from pydasa.core.setup import Frameworks # , PYDASA_CFG
41
+
42
+ # custom type hinting
43
+ Variables = Union[Dict[str, Variable], Dict[str, Any]]
44
+ Coefficients = Union[Dict[str, Coefficient], Dict[str, Any]]
45
+ FDUs = Union[str, Dict[str, Any], Schema]
46
+
47
+
48
+ @dataclass
49
+ class AnalysisEngine(Foundation):
50
+ """**AnalysisEngine** class for orchestrating dimensional analysis workflows in *PyDASA*.
51
+
52
+ Main entry point that coordinates dimensional matrix solving and coefficient generation.
53
+ Also known as DimProblem.
54
+
55
+ Attributes:
56
+ # Identification and Classification
57
+ name (str): User-friendly name of the problem.
58
+ description (str): Brief summary of the problem.
59
+ _idx (int): Index/precedence of the problem.
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
+
64
+ # Problem Components
65
+ _variables (Dict[str, Variable]): All dimensional variables in the problem.
66
+ _schema (Optional[Schema]): Dimensional framework schema for the problem.
67
+ _model (Optional[Matrix]): Dimensional matrix for analysis.
68
+
69
+ # Generated Results
70
+ _coefficients (Dict[str, Coefficient]): Generated dimensionless coefficients.
71
+
72
+ # Workflow State
73
+ _is_solved (bool): Whether the dimensional matrix has been solved.
74
+ """
75
+
76
+ # Problem components
77
+ # :attr: _variables
78
+ _variables: Dict[str, Variable] = field(default_factory=dict)
79
+ """Dictionary of all dimensional variables in the problem."""
80
+
81
+ # :attr: _schema
82
+ _schema: Optional[FDUs] = None
83
+ """Dimensional framework schema (manages FDUs). Always a Schema object after initialization."""
84
+
85
+ # :attr: _model
86
+ _model: Optional[Matrix] = None
87
+ """Dimensional matrix for Buckingham Pi analysis."""
88
+
89
+ # Generated results
90
+ # :attr: _coefficients
91
+ _coefficients: Dict[str, Coefficient] = field(default_factory=dict)
92
+ """Dictionary of generated dimensionless coefficients."""
93
+
94
+ # Workflow state
95
+ # :attr: _is_solved
96
+ _is_solved: bool = False
97
+ """Flag indicating if dimensional matrix has been solved."""
98
+
99
+ def __post_init__(self) -> None:
100
+ """*__post_init__()* Initializes the solver.
101
+
102
+ Validates basic properties and sets up default values.
103
+ """
104
+ # Initialize from base class
105
+ super().__post_init__()
106
+
107
+ # Set default symbol if not specified
108
+ if not self._sym:
109
+ self._sym = f"Solver_{{{self._idx}}}" if self._idx >= 0 else "Solver_{-1}"
110
+
111
+ if not self._alias:
112
+ self._alias = latex_to_python(self._sym)
113
+
114
+ # Set name and description if not already set
115
+ if not self.name:
116
+ self.name = f"Dimensional Analysis Engine {self._idx}"
117
+
118
+ if not self.description:
119
+ self.description = "Solves dimensional analysis using the Buckingham Pi-Theorem."
120
+
121
+ # Initialize schema with default PHYSICAL framework
122
+ # The setter will handle conversion if _schema was set to a string/dict in __init__
123
+ # Ensure _schema is a Schema object
124
+ # Handle the case where _schema might be set to a string, dict, or None during initialization
125
+ if not isinstance(self._schema, Schema):
126
+ if self._schema in (None, Frameworks.PHYSICAL.value):
127
+ # Default to PHYSICAL framework
128
+ self._schema = Schema(_fwk=Frameworks.PHYSICAL.value)
129
+ elif isinstance(self._schema, str):
130
+ # Convert string to Schema
131
+ self._schema = Schema(_fwk=self._schema.upper())
132
+ elif isinstance(self._schema, dict):
133
+ # Convert dict to Schema
134
+ self._schema = Schema.from_dict(self._schema)
135
+ # else:
136
+ # # Fallback to default
137
+ # self._schema = Schema(_fwk=Frameworks.PHYSICAL.value)
138
+
139
+ # # Initialize schema with default PHYSICAL framework
140
+ # self._schema = Schema(_fwk=Frameworks.PHYSICAL.value)
141
+
142
+ # # Convert default schema string to Schema object
143
+ # # if isinstance(self._schema, str):
144
+ # # self._schema = Schema(_fwk=self._schema)
145
+ # if self._schema is not Frameworks.PHYSICAL.value:
146
+ # if isinstance(self._schema, str):
147
+ # self.schema = Schema(self._schema)
148
+
149
+ # # Initialize with default PHYSICAL framework if not already set
150
+ # if not hasattr(self, "_schema") or self._schema is None:
151
+ # self._schema = Schema(_fwk=Frameworks.PHYSICAL.value)
152
+
153
+ # ========================================================================
154
+ # Validation Methods
155
+ # ========================================================================
156
+
157
+ def _validate_dict(self,
158
+ dt: Dict[str, Any],
159
+ exp_type: Union[type, Tuple[type, ...]]) -> bool:
160
+ """*_validate_dict()* Validates a dictionary with expected value types.
161
+
162
+ Args:
163
+ dt (Dict[str, Any]): Dictionary to validate.
164
+ exp_type (Union[type, Tuple[type, ...]]): Expected type(s) for dictionary values.
165
+
166
+ Raises:
167
+ ValueError: If the object is not a dictionary.
168
+ ValueError: If the dictionary is empty.
169
+ ValueError: If the dictionary contains values of unexpected types.
170
+
171
+ Returns:
172
+ bool: True if the dictionary is valid.
173
+ """
174
+ # variable inspection
175
+ var_name = inspect_var(dt)
176
+
177
+ # Validate is dictionary
178
+ if not isinstance(dt, dict):
179
+ _msg = f"{var_name} must be a dictionary. "
180
+ _msg += f"Provided: {type(dt).__name__}"
181
+ raise ValueError(_msg)
182
+
183
+ # Validate not empty
184
+ if len(dt) == 0:
185
+ _msg = f"{var_name} cannot be empty. "
186
+ _msg += f"Provided: {dt}"
187
+ raise ValueError(_msg)
188
+
189
+ # Convert list to tuple for isinstance()
190
+ type_check = exp_type if isinstance(exp_type, tuple) else (exp_type,) if not isinstance(exp_type, tuple) else exp_type
191
+
192
+ # Validate value types
193
+ if not all(isinstance(v, type_check) for v in dt.values()):
194
+ # Format expected types for error message
195
+ if isinstance(exp_type, tuple):
196
+ type_names = " or ".join(t.__name__ for t in exp_type)
197
+ else:
198
+ type_names = exp_type.__name__
199
+
200
+ actual_types = [type(v).__name__ for v in dt.values()]
201
+ _msg = f"{var_name} must contain {type_names} values. "
202
+ _msg += f"Provided: {actual_types}"
203
+ raise ValueError(_msg)
204
+
205
+ return True
206
+
207
+ # ========================================================================
208
+ # Property Getters and Setters
209
+ # ========================================================================
210
+
211
+ @property
212
+ def variables(self) -> Dict[str, Variable]:
213
+ """*variables* Get the dictionary of variables.
214
+
215
+ Returns:
216
+ Dict[str, Variable]: Dictionary of variables.
217
+ """
218
+ return self._variables.copy()
219
+
220
+ @variables.setter
221
+ @validate_type(dict, Variable, allow_none=False)
222
+ def variables(self, val: Variables) -> None:
223
+ """*variables* Set the dictionary of variables.
224
+
225
+ Args:
226
+ val (Variables): Dictionary of variables (Variable objects or dicts).
227
+
228
+ Raises:
229
+ ValueError: If dictionary is invalid or contains invalid values.
230
+ """
231
+ # Convert dict values to Variable objects if needed
232
+ converted = {}
233
+ for key, value in val.items():
234
+ # if value is already a Variable, keep it
235
+ if isinstance(value, Variable):
236
+ converted[key] = value
237
+ # if value is a dict, convert to Variable
238
+ elif isinstance(value, dict):
239
+ # Convert dict to Variable
240
+ converted[key] = Variable.from_dict(value)
241
+ else:
242
+ _msg = f"Input '{key}' must be type 'Variable' or 'dict'. "
243
+ _msg += f"Provided: {type(value).__name__}"
244
+ raise ValueError(_msg)
245
+
246
+ self._variables = converted
247
+ self._is_solved = False # Reset solve state
248
+
249
+ @property
250
+ def schema(self) -> Schema:
251
+ """*schema* Get the dimensional framework schema.
252
+
253
+ Returns:
254
+ Optional[Schema]: Dimensional framework schema.
255
+ """
256
+ return self._schema
257
+
258
+ @schema.setter
259
+ @validate_type(str, dict, Schema, allow_none=False)
260
+ def schema(self, val: Union[str, dict, Schema]) -> None:
261
+ """*schema* Set the dimensional framework schema.
262
+
263
+ Args:
264
+ val (Union[str, dict, Schema]): Dimensional framework schema.
265
+
266
+ Raises:
267
+ ValueError: If string is not a valid framework name or dict is invalid.
268
+ TypeError: If val is not a valid type.
269
+ """
270
+ # if schema is a string, convert to Schema
271
+ if isinstance(val, str):
272
+ self._schema = Schema(_fwk=val.upper())
273
+ # if schema is a dict, convert to Schema
274
+ elif isinstance(val, dict):
275
+ self._schema = Schema.from_dict(val)
276
+ # if schema is already a Schema, keep it
277
+ elif isinstance(val, Schema):
278
+ self._schema = val
279
+ else:
280
+ _msg = "Input must be type 'str', 'dict', or 'Schema'. "
281
+ _msg += f"Provided: {type(val).__name__}"
282
+ raise TypeError(_msg)
283
+
284
+ @property
285
+ def matrix(self) -> Optional[Matrix]:
286
+ """*matrix* Get the dimensional matrix.
287
+
288
+ Returns:
289
+ Optional[Matrix]: Dimensional matrix.
290
+ """
291
+ return self._model
292
+
293
+ @matrix.setter
294
+ @validate_type(Matrix, allow_none=True)
295
+ def matrix(self, val: Optional[Matrix]) -> None:
296
+ """*matrix* Set the dimensional matrix.
297
+
298
+ Args:
299
+ val (Optional[Matrix]): Dimensional matrix.
300
+ """
301
+ self._model = val
302
+ if val is not None:
303
+ self._is_solved = False # Reset solve state
304
+
305
+ @property
306
+ def coefficients(self) -> Dict[str, Coefficient]:
307
+ """*coefficients* Get the generated coefficients.
308
+
309
+ Returns:
310
+ Dict[str, Coefficient]: Dictionary of coefficients.
311
+ """
312
+ return self._coefficients.copy()
313
+
314
+ @coefficients.setter
315
+ @validate_type(dict, Coefficient, allow_none=False)
316
+ def coefficients(self, val: Coefficients) -> None:
317
+ """*coefficients* Set the generated coefficients.
318
+
319
+ Args:
320
+ val (Coefficients): Dictionary of coefficients (Coefficient objects or dicts).
321
+
322
+ Raises:
323
+ ValueError: If dictionary is invalid or contains invalid values.
324
+ """
325
+ # Convert dict values to Coefficient objects if needed
326
+ converted = {}
327
+ for key, value in val.items():
328
+ # if value is already a Coefficient, keep it
329
+ if isinstance(value, Coefficient):
330
+ converted[key] = value
331
+ # if value is a dict, convert to Coefficient
332
+ elif isinstance(value, dict):
333
+ # Convert dict to Coefficient
334
+ converted[key] = Coefficient.from_dict(value)
335
+ else:
336
+ _msg = f"Input '{key}' must be type 'Coefficient' or 'dict'. "
337
+ _msg += f"Provided: {type(value).__name__}"
338
+ raise ValueError(_msg)
339
+
340
+ self._coefficients = converted
341
+ self._is_solved = False # Reset solve state
342
+
343
+ @property
344
+ def is_solved(self) -> bool:
345
+ """*is_solved* Check if dimensional matrix has been solved.
346
+
347
+ Returns:
348
+ bool: True if solved, False otherwise.
349
+ """
350
+ return self._is_solved
351
+
352
+ # ========================================================================
353
+ # Workflow Methods
354
+ # ========================================================================
355
+
356
+ def create_matrix(self, **kwargs) -> None:
357
+ """*create_matrix()* Create and configure dimensional matrix.
358
+
359
+ Creates a Matrix object from the current variables and optional parameters.
360
+
361
+ Args:
362
+ **kwargs: Optional keyword arguments to pass to Matrix constructor.
363
+
364
+ Returns:
365
+ Matrix: Configured dimensional matrix.
366
+
367
+ Raises:
368
+ ValueError: If variables are not set.
369
+ """
370
+ if not self._variables:
371
+ raise ValueError("Variables must be set before creating matrix.")
372
+
373
+ # Create matrix with variables
374
+ self._model = Matrix(
375
+ _idx=self.idx,
376
+ _fwk=self._fwk,
377
+ _schema=self._schema,
378
+ _variables=self._variables,
379
+ # **kwargs
380
+ )
381
+
382
+ self._is_solved = False # Reset solve state
383
+ # return self._model
384
+
385
+ def solve(self) -> Dict[str, Coefficient]:
386
+ """*solve()* Solve the dimensional matrix and generate coefficients.
387
+
388
+ Performs dimensional analysis using the Buckingham Pi theorem to generate
389
+ dimensionless coefficients.
390
+
391
+ Returns:
392
+ Dict[str, Coefficient]: Dictionary of generated coefficients.
393
+
394
+ Raises:
395
+ ValueError: If matrix is not created.
396
+ RuntimeError: If solving fails.
397
+ """
398
+ if self._model is None:
399
+ raise ValueError("Matrix must be created before solving. Call create_matrix() first.")
400
+
401
+ try:
402
+ # Solve the matrix (generate coefficients)
403
+ self._model.create_matrix()
404
+ self._model.solve_matrix()
405
+ # self._model.solve()
406
+
407
+ # Extract generated coefficients from matrix
408
+ self._coefficients = self._model.coefficients
409
+ self._is_solved = True
410
+ return self._coefficients.copy()
411
+
412
+ except Exception as e:
413
+ _msg = f"Failed to solve dimensional matrix: {str(e)}"
414
+ raise RuntimeError(_msg) from e
415
+
416
+ def run_analysis(self) -> Dict[str, Any]:
417
+ """*run_analysis()* Execute complete dimensional analysis workflow. Convenience method that runs the entire workflow: create matrix and solve.
418
+
419
+ Returns:
420
+ Dict[str, Any]: Dictionary of generated dimensionless coefficient in native python format
421
+
422
+ Raises:
423
+ ValueError: If variables are not set.
424
+ """
425
+ # Step 1: Create matrix if not already created
426
+ if self._model is None:
427
+ self.create_matrix()
428
+
429
+ # Step 2: Solve and return coefficients
430
+ # return self.solve()
431
+ # Create + Solve matrix
432
+ coefficients = self.solve()
433
+ results = {k: v.to_dict() for k, v in coefficients.items()}
434
+ return results
435
+
436
+ # ========================================================================
437
+ # Utility Methods
438
+ # ========================================================================
439
+
440
+ def reset(self) -> None:
441
+ """*reset()* Reset the solver state.
442
+
443
+ Clears all generated results, keeping only the input variables.
444
+ """
445
+ self._model = None
446
+ self._coefficients.clear()
447
+ self._is_solved = False
448
+
449
+ def clear(self) -> None:
450
+ """*clear()* Reset all attributes to default values.
451
+
452
+ Resets all solver properties to their initial state, including variables.
453
+ """
454
+ self._variables.clear()
455
+ self._schema = Schema(_fwk=Frameworks.PHYSICAL.value)
456
+ self.reset()
457
+
458
+ def to_dict(self) -> Dict[str, Any]:
459
+ """*to_dict()* Convert solver state to dictionary.
460
+
461
+ Returns:
462
+ Dict[str, Any]: Dictionary representation of solver state.
463
+ """
464
+ return {
465
+ "name": self.name,
466
+ "description": self.description,
467
+ "idx": self._idx,
468
+ "sym": self._sym,
469
+ "alias": self._alias,
470
+ "fwk": self._fwk,
471
+ "variables": {
472
+ k: v.to_dict() for k, v in self._variables.items()
473
+ },
474
+ "coefficients": {
475
+ k: v.to_dict() for k, v in self._coefficients.items()
476
+ },
477
+ "is_solved": self._is_solved,
478
+ }
479
+
480
+ @classmethod
481
+ def from_dict(cls, data: Dict[str, Any]) -> AnalysisEngine:
482
+ """*from_dict()* Create a AnalysisEngine instance from a dictionary.
483
+
484
+ Args:
485
+ data (Dict[str, Any]): Dictionary containing the solver"s state.
486
+
487
+ Returns:
488
+ AnalysisEngine: New instance of AnalysisEngine.
489
+ """
490
+ # Create instance with basic attributes
491
+ instance = cls(
492
+ _name=data.get("name", ""),
493
+ description=data.get("description", ""),
494
+ _idx=data.get("idx", -1),
495
+ _sym=data.get("sym", ""),
496
+ _alias=data.get("alias", ""),
497
+ _fwk=data.get("fwk", ""),
498
+ )
499
+
500
+ # Set variables
501
+ vars_data = data.get("variables", {})
502
+ if vars_data:
503
+ vars_dict = {k: Variable.from_dict(v) for k, v in vars_data.items()}
504
+ instance.variables = vars_dict
505
+
506
+ # Set coefficients
507
+ coefs_data = data.get("coefficients", {})
508
+ if coefs_data:
509
+ coefs_dict = {k: Coefficient.from_dict(v) for k, v in coefs_data.items()}
510
+ instance._coefficients = coefs_dict
511
+
512
+ # Set state flags
513
+ instance._is_solved = data.get("is_solved", False)
514
+
515
+ return instance
516
+
517
+ def __repr__(self) -> str:
518
+ """*__repr__()* String representation of solver.
519
+
520
+ Returns:
521
+ str: String representation.
522
+ """
523
+ status = "solved" if self._is_solved else "not solved"
524
+ coef_count = len(self._coefficients)
525
+
526
+ return (f"AnalysisEngine(name={self.name!r}, "
527
+ f"variables={len(self._variables)}, "
528
+ f"coefficients={coef_count}, "
529
+ f"status={status})")