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
pydasa/__init__.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
PyDASA Package
|
|
4
|
+
===========================================
|
|
5
|
+
Main package initializer for PyDASA library, exposing core functionalities, data structures, dimensional analysis tools, unit conversion utilities, and workflow management components.
|
|
6
|
+
"""
|
|
7
|
+
# expose version
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
|
|
10
|
+
# expose imports
|
|
11
|
+
# exposing analytics modules
|
|
12
|
+
from .analysis.scenario import Sensitivity
|
|
13
|
+
from .analysis.simulation import MonteCarlo
|
|
14
|
+
|
|
15
|
+
# TODO conversion still in development
|
|
16
|
+
# exposing unit conversion modules
|
|
17
|
+
# from .context.conversion import UnitStandarizer
|
|
18
|
+
# from .context.system import MeasureSystem
|
|
19
|
+
# from .context.measurements import Unit
|
|
20
|
+
|
|
21
|
+
# exposing pi-theorem/dimensional analysis modules
|
|
22
|
+
from .dimensional.buckingham import Coefficient
|
|
23
|
+
from .dimensional.fundamental import Dimension
|
|
24
|
+
from .dimensional.vaschy import Schema
|
|
25
|
+
from .dimensional.model import Matrix
|
|
26
|
+
|
|
27
|
+
# exposing core modules
|
|
28
|
+
# exposing basic elements/variables modules
|
|
29
|
+
from .elements.parameter import Variable
|
|
30
|
+
# exposing parser/io modules
|
|
31
|
+
from .core.io import load, save
|
|
32
|
+
|
|
33
|
+
# exposing custom data structure modules
|
|
34
|
+
# TODO measurement still in development
|
|
35
|
+
# lists
|
|
36
|
+
from .structs.lists.arlt import ArrayList
|
|
37
|
+
from .structs.lists.sllt import SingleLinkedList
|
|
38
|
+
from .structs.lists.ndlt import Node, SLNode, DLNode
|
|
39
|
+
# from .structs.lists.dllt import DoubleLinkedList
|
|
40
|
+
# tables
|
|
41
|
+
from .structs.tables.htme import MapEntry
|
|
42
|
+
from .structs.tables.scht import Bucket, SCHashTable
|
|
43
|
+
|
|
44
|
+
# exposing validation, error and decorator modules
|
|
45
|
+
# exposing workflow modules
|
|
46
|
+
from .workflows.influence import SensitivityAnalysis
|
|
47
|
+
from .workflows.practical import MonteCarloSimulation
|
|
48
|
+
# from .workflows.phenomena import AnalysisEngine
|
|
49
|
+
|
|
50
|
+
# asserting all imports
|
|
51
|
+
# asserting analytics modules
|
|
52
|
+
assert Sensitivity
|
|
53
|
+
assert MonteCarlo
|
|
54
|
+
|
|
55
|
+
# asserting unit conversion modules
|
|
56
|
+
# assert UnitStandarizer
|
|
57
|
+
# assert MeasureSystem
|
|
58
|
+
# assert Unit
|
|
59
|
+
# asserting pi-theorem/dimensional analysis modules
|
|
60
|
+
assert Coefficient
|
|
61
|
+
assert Dimension
|
|
62
|
+
assert Schema
|
|
63
|
+
assert Matrix
|
|
64
|
+
# asserting elements/variables modules
|
|
65
|
+
assert Variable
|
|
66
|
+
# asserting parser/io modules
|
|
67
|
+
assert load
|
|
68
|
+
assert save
|
|
69
|
+
# asserting custom data structure modules
|
|
70
|
+
# lists
|
|
71
|
+
assert ArrayList
|
|
72
|
+
assert SingleLinkedList
|
|
73
|
+
assert Node
|
|
74
|
+
assert SLNode
|
|
75
|
+
assert DLNode
|
|
76
|
+
# tables
|
|
77
|
+
assert MapEntry
|
|
78
|
+
assert Bucket
|
|
79
|
+
assert SCHashTable
|
|
80
|
+
# asserting workflow modules
|
|
81
|
+
assert SensitivityAnalysis
|
|
82
|
+
assert MonteCarloSimulation
|
|
83
|
+
# assert AnalysisEngine
|
|
84
|
+
|
|
85
|
+
# Define __all__ for wildcard imports
|
|
86
|
+
__all__ = [
|
|
87
|
+
"__version__",
|
|
88
|
+
"Sensitivity",
|
|
89
|
+
"MonteCarlo",
|
|
90
|
+
# "UnitStandarizer",
|
|
91
|
+
# "MeasureSystem",
|
|
92
|
+
# "Unit",
|
|
93
|
+
"Coefficient",
|
|
94
|
+
"Dimension",
|
|
95
|
+
"Schema",
|
|
96
|
+
"Matrix",
|
|
97
|
+
"Variable",
|
|
98
|
+
"load",
|
|
99
|
+
"save",
|
|
100
|
+
"SensitivityAnalysis",
|
|
101
|
+
"MonteCarloSimulation",
|
|
102
|
+
# "AnalysisEngine",
|
|
103
|
+
]
|
pydasa/_version.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Version information
|
|
2
|
+
# version format: [MAYOR].[MINOR].[PATCH]
|
|
3
|
+
# PATCH: for bug fixes and minor improvements, third-party library updates, etc.
|
|
4
|
+
# MINOR: for new features and enhancements, backward-compatible changes
|
|
5
|
+
# MAYOR: for incompatible API changes
|
|
6
|
+
__version__ = "0.4.7"
|
|
File without changes
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module scenario.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Module for sensitivity analysis in *PyDASA*.
|
|
7
|
+
|
|
8
|
+
This module provides the Sensitivity class for performing sensitivity analysis on dimensional coefficients derived from dimensional analysis.
|
|
9
|
+
|
|
10
|
+
Classes:
|
|
11
|
+
|
|
12
|
+
**Sensitivity**: Performs sensitivity analysis on dimensional coefficients in *PyDASA*.
|
|
13
|
+
|
|
14
|
+
*IMPORTANT:* Based on the theory from:
|
|
15
|
+
# H.Gorter, *Dimensionalanalyse: Eine Theoririe der physikalischen Dimensionen mit Anwendungen*
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Optional, List, Dict, Any, Callable
|
|
21
|
+
|
|
22
|
+
# Third-party modules
|
|
23
|
+
import numpy as np
|
|
24
|
+
# import sympy as sp
|
|
25
|
+
from sympy import diff, lambdify # , symbols
|
|
26
|
+
from SALib.sample.fast_sampler import sample
|
|
27
|
+
from SALib.analyze.fast import analyze
|
|
28
|
+
|
|
29
|
+
# Import validation base classes
|
|
30
|
+
from pydasa.core.basic import Foundation
|
|
31
|
+
|
|
32
|
+
# Import validation decorators
|
|
33
|
+
from pydasa.validations.decorators import validate_choices, validate_pattern, validate_custom
|
|
34
|
+
|
|
35
|
+
# Import related classes
|
|
36
|
+
from pydasa.dimensional.buckingham import Coefficient
|
|
37
|
+
# from pydasa.core.parameter import Variable
|
|
38
|
+
from pydasa.core.setup import Frameworks
|
|
39
|
+
from pydasa.core.setup import AnaliticMode
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Import utils
|
|
43
|
+
from pydasa.serialization.parser import parse_latex
|
|
44
|
+
from pydasa.serialization.parser import create_latex_mapping
|
|
45
|
+
from pydasa.serialization.parser import latex_to_python
|
|
46
|
+
|
|
47
|
+
# Import configuration
|
|
48
|
+
from pydasa.core.setup import PYDASA_CFG
|
|
49
|
+
from pydasa.validations.patterns import LATEX_RE
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Sensitivity(Foundation):
|
|
54
|
+
# FIXME clean code, some vars and types are inconsistent
|
|
55
|
+
"""**Sensitivity** class for analyzing variable impacts in *PyDASA*.
|
|
56
|
+
|
|
57
|
+
Performs sensitivity analysis on dimensionless coefficients to determine which variables have the most significant impact on the system behavior.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
Foundation: Foundation class for validation of symbols and frameworks.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
# Identification and Classification
|
|
64
|
+
name (str): User-friendly name of the sensitivity analysis.
|
|
65
|
+
description (str): Brief summary of the sensitivity analysis.
|
|
66
|
+
_idx (int): Index/precedence of the sensitivity analysis.
|
|
67
|
+
_sym (str): Symbol representation (LaTeX or alphanumeric).
|
|
68
|
+
_alias (str): Python-compatible alias for use in code.
|
|
69
|
+
_fwk (str): Frameworks context (PHYSICAL, COMPUTATION, SOFTWARE, CUSTOM).
|
|
70
|
+
_cat (str): Category of analysis (SYM, NUM).
|
|
71
|
+
|
|
72
|
+
# Expression Management
|
|
73
|
+
_pi_expr (str): LaTeX expression to analyze.
|
|
74
|
+
_sym_func (Callable): Sympy function of the sensitivity.
|
|
75
|
+
_exe_func (Callable): Executable function for numerical evaluation.
|
|
76
|
+
_variables (Dict[str, Variable]): Variable symbols in the expression.
|
|
77
|
+
_symbols (Dict[str, Any]): Python symbols for the variables.
|
|
78
|
+
_aliases (Dict[str, Any]): Variable aliases for use in code.
|
|
79
|
+
|
|
80
|
+
# Analysis Configuration
|
|
81
|
+
var_bounds (List[List[float]]): Min/max bounds for each variable.
|
|
82
|
+
var_values (Dict[str, float]): Values for symbolic analysis.
|
|
83
|
+
var_ranges (np.ndarray): Sample value range for numerical analysis.
|
|
84
|
+
n_samples (int): Number of samples for analysis.
|
|
85
|
+
|
|
86
|
+
# Results
|
|
87
|
+
results (Dict[str, Any]): Analysis results.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
# Category attribute
|
|
91
|
+
# :attr: _cat
|
|
92
|
+
_cat: str = AnaliticMode.SYM.value
|
|
93
|
+
"""Category of sensitivity analysis (SYM, NUM)."""
|
|
94
|
+
|
|
95
|
+
# Expression properties
|
|
96
|
+
# :attr: _pi_expr
|
|
97
|
+
_pi_expr: Optional[str] = None
|
|
98
|
+
"""LaTeX expression to analyze."""
|
|
99
|
+
|
|
100
|
+
# :attr: _sym_func
|
|
101
|
+
_sym_func: Optional[Callable] = None
|
|
102
|
+
"""Sympy function of the sensitivity."""
|
|
103
|
+
|
|
104
|
+
# :attr: _exe_func
|
|
105
|
+
_exe_func: Optional[Callable] = None
|
|
106
|
+
"""Executable function for numerical evaluation."""
|
|
107
|
+
|
|
108
|
+
# :attr: _variables
|
|
109
|
+
_variables: Dict[str, Any] = field(default_factory=dict)
|
|
110
|
+
"""Variable symbols in the expression."""
|
|
111
|
+
|
|
112
|
+
# :attr: _symbols
|
|
113
|
+
_symbols: Dict[str, Any] = field(default_factory=dict)
|
|
114
|
+
"""Python symbols for the variables."""
|
|
115
|
+
|
|
116
|
+
# :attr: _aliases
|
|
117
|
+
_aliases: Dict[str, Any] = field(default_factory=dict)
|
|
118
|
+
"""Variable aliases for use in code."""
|
|
119
|
+
|
|
120
|
+
# :attr: _latex_to_py
|
|
121
|
+
_latex_to_py: Dict[str, str] = field(default_factory=dict)
|
|
122
|
+
"""Mapping from LaTeX symbols to Python-compatible names."""
|
|
123
|
+
|
|
124
|
+
# :attr: _py_to_latex
|
|
125
|
+
_py_to_latex: Dict[str, str] = field(default_factory=dict)
|
|
126
|
+
"""Mapping from Python-compatible names to LaTeX symbols."""
|
|
127
|
+
|
|
128
|
+
# Analysis configuration
|
|
129
|
+
# :attr: var_bounds
|
|
130
|
+
var_bounds: List[List[float]] = field(default_factory=list)
|
|
131
|
+
"""Min/max bounds for each variable."""
|
|
132
|
+
|
|
133
|
+
# :attr: var_values
|
|
134
|
+
var_values: Dict[str, float] = field(default_factory=dict)
|
|
135
|
+
"""Values for symbolic analysis."""
|
|
136
|
+
|
|
137
|
+
# :attr: var_domains
|
|
138
|
+
var_domains: Optional[np.ndarray] = None
|
|
139
|
+
"""Sample domain (inputs) for numerical analysis."""
|
|
140
|
+
|
|
141
|
+
# :attr: var_ranges
|
|
142
|
+
var_ranges: Optional[np.ndarray] = None
|
|
143
|
+
"""Sample value range (results) for numerical analysis."""
|
|
144
|
+
|
|
145
|
+
# :attr: n_samples
|
|
146
|
+
n_samples: int = 1000
|
|
147
|
+
"""Number of samples for analysis."""
|
|
148
|
+
|
|
149
|
+
# Results
|
|
150
|
+
# :attr: results
|
|
151
|
+
results: Dict[str, Any] = field(default_factory=dict)
|
|
152
|
+
"""Analysis results."""
|
|
153
|
+
|
|
154
|
+
def _validate_callable(self, value: Any, field_name: str) -> None:
|
|
155
|
+
"""Custom validator to ensure value is callable.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
value: The value to validate.
|
|
159
|
+
field_name: Name of the field being validated.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: If value is not callable.
|
|
163
|
+
"""
|
|
164
|
+
if not callable(value):
|
|
165
|
+
raise ValueError(f"Sympy function must be callable. Provided: {type(value)}")
|
|
166
|
+
|
|
167
|
+
def __post_init__(self) -> None:
|
|
168
|
+
"""*__post_init__()* Initializes the sensitivity analysis. Validates basic properties, sets default values, and processes the expression if provided.
|
|
169
|
+
"""
|
|
170
|
+
# Initialize from base class
|
|
171
|
+
super().__post_init__()
|
|
172
|
+
|
|
173
|
+
# Set default symbol if not specified
|
|
174
|
+
if not self._sym:
|
|
175
|
+
self._sym = f"SANSYS_\\Pi_{{{self._idx}}}" if self._idx >= 0 else "SANSYS_\\Pi_{}"
|
|
176
|
+
# Set default Python alias if not specified
|
|
177
|
+
if not self._alias:
|
|
178
|
+
self._alias = latex_to_python(self._sym)
|
|
179
|
+
|
|
180
|
+
# Set name and description if not already set
|
|
181
|
+
if not self.name:
|
|
182
|
+
self.name = f"{self._sym} Sensitivity"
|
|
183
|
+
if not self.description:
|
|
184
|
+
self.description = f"Sensitivity analysis for {self._sym}"
|
|
185
|
+
|
|
186
|
+
if self._pi_expr:
|
|
187
|
+
# Parse the expression
|
|
188
|
+
self._parse_expression(self._pi_expr)
|
|
189
|
+
|
|
190
|
+
def _validate_analysis_ready(self) -> None:
|
|
191
|
+
"""*_validate_analysis_ready()* Checks if the analysis can be performed.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If the variables are missing.
|
|
195
|
+
ValueError: If the python-compatible variables are missing.
|
|
196
|
+
ValueError: If the symbolic expression is missing.
|
|
197
|
+
"""
|
|
198
|
+
if not self._variables:
|
|
199
|
+
raise ValueError("No variables found in the expression.")
|
|
200
|
+
if not self._aliases:
|
|
201
|
+
raise ValueError("No Python aliases found for variables.")
|
|
202
|
+
if not self._sym_func:
|
|
203
|
+
raise ValueError("No expression has been defined for analysis.")
|
|
204
|
+
|
|
205
|
+
def set_coefficient(self, coef: Coefficient) -> None:
|
|
206
|
+
"""*set_coefficient()* Configure analysis from a coefficient.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
coef (Coefficient): Dimensionless coefficient to analyze.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If the coefficient doesn't have a valid expression.
|
|
213
|
+
"""
|
|
214
|
+
if not coef.pi_expr:
|
|
215
|
+
raise ValueError("Coefficient does not have a valid expression.")
|
|
216
|
+
|
|
217
|
+
# Set expression
|
|
218
|
+
self._pi_expr = coef.pi_expr
|
|
219
|
+
# parse coefficient expresion
|
|
220
|
+
if coef._pi_expr:
|
|
221
|
+
self._parse_expression(self._pi_expr)
|
|
222
|
+
|
|
223
|
+
def _parse_expression(self, expr: str) -> None:
|
|
224
|
+
"""*_parse_expression()* Parse the LaTeX expression into a sympy function.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
expr (str): LaTeX expression to parse.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
ValueError: If the expression cannot be parsed.
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
# Parse the expression
|
|
234
|
+
self._sym_func = parse_latex(expr)
|
|
235
|
+
|
|
236
|
+
# Create symbol mapping
|
|
237
|
+
maps = create_latex_mapping(expr)
|
|
238
|
+
self._symbols = maps[0]
|
|
239
|
+
self._aliases = maps[1]
|
|
240
|
+
self._latex_to_py = maps[2]
|
|
241
|
+
self._py_to_latex = maps[3]
|
|
242
|
+
|
|
243
|
+
# Substitute LaTeX symbols with Python symbols
|
|
244
|
+
for latex_sym, py_sym in self._symbols.items():
|
|
245
|
+
self._sym_func = self._sym_func.subs(latex_sym, py_sym)
|
|
246
|
+
|
|
247
|
+
# Get Python variable names
|
|
248
|
+
# self._variables = sorted(self._aliases.keys())
|
|
249
|
+
fsyms = self._sym_func.free_symbols
|
|
250
|
+
self._variables = sorted([str(s) for s in fsyms])
|
|
251
|
+
|
|
252
|
+
# """
|
|
253
|
+
# # OLD code, first version, keep for reference!!!
|
|
254
|
+
# self.results = {
|
|
255
|
+
# var: lambdify(self._variables, diff(self._sym_fun, var), "numpy")(
|
|
256
|
+
# *[vals[v] for v in self.variables]
|
|
257
|
+
# )
|
|
258
|
+
# for var in self._variables
|
|
259
|
+
# }
|
|
260
|
+
# """
|
|
261
|
+
except Exception as e:
|
|
262
|
+
_msg = f"Failed to parse expression: {str(e)}"
|
|
263
|
+
raise ValueError(_msg)
|
|
264
|
+
|
|
265
|
+
def analyze_symbolically(self,
|
|
266
|
+
vals: Dict[str, float]) -> Dict[str, float]:
|
|
267
|
+
"""*analyze_symbolically()* Perform symbolic sensitivity analysis.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
vals (Dict[str, float]): Dictionary mapping variable names to values.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Dict[str, float]: Sensitivity results for each variable.
|
|
274
|
+
"""
|
|
275
|
+
# # parse the coefficient expression
|
|
276
|
+
# self._parse_expression(self._pi_expr)
|
|
277
|
+
|
|
278
|
+
# save variable values for the analysis
|
|
279
|
+
self.var_values = vals
|
|
280
|
+
|
|
281
|
+
# Check that all required variables are provided
|
|
282
|
+
var_lt = [str(v) for v in self._latex_to_py]
|
|
283
|
+
missing_vars = set(var_lt) - set(list(vals.keys()))
|
|
284
|
+
if missing_vars:
|
|
285
|
+
_msg = f"Missing values for variables: {missing_vars}"
|
|
286
|
+
raise ValueError(_msg)
|
|
287
|
+
|
|
288
|
+
# Validate analysis readiness
|
|
289
|
+
self._validate_analysis_ready()
|
|
290
|
+
|
|
291
|
+
# trying symbolic coefficient sensitivity analysis
|
|
292
|
+
try:
|
|
293
|
+
py_to_latex = self._py_to_latex
|
|
294
|
+
results = dict()
|
|
295
|
+
functions = dict()
|
|
296
|
+
if self._variables:
|
|
297
|
+
for var in self._variables:
|
|
298
|
+
# Create lambdify function using Python symbols
|
|
299
|
+
expr = diff(self._sym_func, var)
|
|
300
|
+
aliases = [self._aliases[v] for v in self._variables]
|
|
301
|
+
# self._exe_func = lambdify(aliases, expr, "numpy")
|
|
302
|
+
func = lambdify(aliases, expr, "numpy")
|
|
303
|
+
functions[py_to_latex[var]] = func
|
|
304
|
+
|
|
305
|
+
# Convert back to LaTeX variables for result keys
|
|
306
|
+
val_args = [vals[py_to_latex[v]] for v in self._variables]
|
|
307
|
+
res = functions[py_to_latex[var]](*val_args)
|
|
308
|
+
results[py_to_latex[var]] = res
|
|
309
|
+
|
|
310
|
+
self._exe_func = functions
|
|
311
|
+
self.results = results
|
|
312
|
+
return self.results
|
|
313
|
+
|
|
314
|
+
except Exception as e:
|
|
315
|
+
_msg = f"Error calculating sensitivity for {var}: {str(e)}"
|
|
316
|
+
raise ValueError(_msg)
|
|
317
|
+
|
|
318
|
+
def analyze_numerically(self,
|
|
319
|
+
vals: List[str],
|
|
320
|
+
bounds: List[List[float]],
|
|
321
|
+
n_samples: int = 1000) -> Dict[str, Any]:
|
|
322
|
+
"""*analyze_numerically()* Perform numerical sensitivity analysis.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
vals (List[str]): List of variable names to analyze.
|
|
326
|
+
bounds (List[List[float]]): Bounds for each variable [min, max].
|
|
327
|
+
n_samples (int, optional): Number of samples to use. Defaults to 1000.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Dict[str, Any]: Detailed sensitivity analysis results.
|
|
331
|
+
"""
|
|
332
|
+
# # parse the coefficient expression
|
|
333
|
+
# self._parse_expression(self._pi_expr)
|
|
334
|
+
|
|
335
|
+
# Validate analysis readiness
|
|
336
|
+
self._validate_analysis_ready()
|
|
337
|
+
|
|
338
|
+
# trying numeric coefficient sensitivity analysis
|
|
339
|
+
try:
|
|
340
|
+
# Validate bounds length matches number of variables
|
|
341
|
+
if len(bounds) != len(self._variables):
|
|
342
|
+
_msg = f"Expected {len(self._variables)} "
|
|
343
|
+
_msg += f"bounds (one per variable), got {len(bounds)}"
|
|
344
|
+
raise ValueError(_msg)
|
|
345
|
+
|
|
346
|
+
# Set number of samples
|
|
347
|
+
self.n_samples = n_samples
|
|
348
|
+
# Store bounds
|
|
349
|
+
self.var_bounds = bounds
|
|
350
|
+
|
|
351
|
+
results = dict()
|
|
352
|
+
if self._variables:
|
|
353
|
+
|
|
354
|
+
# Set up problem definition for SALib
|
|
355
|
+
problem = {
|
|
356
|
+
"num_vars": len(vals),
|
|
357
|
+
"names": self.variables,
|
|
358
|
+
"bounds": bounds,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Generate samples (domain)
|
|
362
|
+
self.var_domains = sample(problem, n_samples)
|
|
363
|
+
_len = len(self._variables)
|
|
364
|
+
self.var_domains = self.var_domains.reshape(-1, _len)
|
|
365
|
+
|
|
366
|
+
# Create lambdify function using Python symbols
|
|
367
|
+
aliases = [self._aliases[v] for v in self._variables]
|
|
368
|
+
self._exe_func = lambdify(aliases, self._sym_func, "numpy")
|
|
369
|
+
|
|
370
|
+
# Evaluate function at sample points
|
|
371
|
+
Y = np.apply_along_axis(lambda v: self._exe_func(*v),
|
|
372
|
+
1, self.var_domains)
|
|
373
|
+
self.var_ranges = Y.reshape(-1, 1)
|
|
374
|
+
|
|
375
|
+
# Perform FAST (Fourier Amplitude Sensitivity Test) analysis
|
|
376
|
+
results = analyze(problem, Y)
|
|
377
|
+
|
|
378
|
+
# Convert back to LaTeX variables for result keys
|
|
379
|
+
if "names" in results:
|
|
380
|
+
py_to_latex = self._py_to_latex
|
|
381
|
+
results["names"] = [py_to_latex.get(v, v) for v in results["names"]]
|
|
382
|
+
|
|
383
|
+
self.results = results
|
|
384
|
+
return self.results
|
|
385
|
+
|
|
386
|
+
except Exception as e:
|
|
387
|
+
_msg = f"Error calculating sensitivity: {str(e)}"
|
|
388
|
+
raise ValueError(_msg)
|
|
389
|
+
|
|
390
|
+
# Property getters and setters
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def cat(self) -> str:
|
|
394
|
+
"""*cat* Get the analysis category.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
str: Category (SYM, NUM).
|
|
398
|
+
"""
|
|
399
|
+
return self._cat
|
|
400
|
+
|
|
401
|
+
@cat.setter
|
|
402
|
+
@validate_choices(PYDASA_CFG.analitic_modes, case_sensitive=False)
|
|
403
|
+
def cat(self, val: str) -> None:
|
|
404
|
+
"""*cat* Set the analysis category.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
val (str): Category value.
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
ValueError: If category is invalid.
|
|
411
|
+
"""
|
|
412
|
+
self._cat = val.upper()
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def pi_expr(self) -> Optional[str]:
|
|
416
|
+
"""*pi_expr* Get the expression to analyze.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Optional[str]: LaTeX expression.
|
|
420
|
+
"""
|
|
421
|
+
return self._pi_expr
|
|
422
|
+
|
|
423
|
+
@pi_expr.setter
|
|
424
|
+
@validate_pattern(LATEX_RE, allow_alnum=True)
|
|
425
|
+
def pi_expr(self, val: str) -> None:
|
|
426
|
+
"""*pi_expr* Set the expression to analyze.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
val (str): LaTeX expression.
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
ValueError: If expression is invalid.
|
|
433
|
+
"""
|
|
434
|
+
# Update expression
|
|
435
|
+
self._pi_expr = val
|
|
436
|
+
|
|
437
|
+
# Parse expression
|
|
438
|
+
self._parse_expression(self._pi_expr)
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def sym_func(self) -> Optional[Callable]:
|
|
442
|
+
"""*sym_func* Get the symbolic function.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Optional[Callable]: Symbolic expression.
|
|
446
|
+
"""
|
|
447
|
+
return self._sym_func
|
|
448
|
+
|
|
449
|
+
@sym_func.setter
|
|
450
|
+
@validate_custom(lambda self, val: self._validate_callable(val, "sym_func"))
|
|
451
|
+
def sym_func(self, val: Callable) -> None:
|
|
452
|
+
"""*sym_func* Set the symbolic function.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
val (Callable): Symbolic function.
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
ValueError: If function is not callable.
|
|
459
|
+
"""
|
|
460
|
+
self._sym_func = val
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def exe_func(self) -> Optional[Callable]:
|
|
464
|
+
"""*exe_func* Get the executable function.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Optional[Callable]: Executable function for numerical evaluation.
|
|
468
|
+
"""
|
|
469
|
+
return self._exe_func
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def variables(self) -> Dict[str, Any]:
|
|
473
|
+
"""*variables* Get the variables in the expression.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
Dict[str, Any]:: Variable symbols.
|
|
477
|
+
"""
|
|
478
|
+
return self._variables
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def symbols(self) -> Dict[str, Any]:
|
|
482
|
+
"""*symbols* Get the Python symbols for the variables.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
Dict[str, Any]: Dictionary mapping variable names to sympy symbols.
|
|
486
|
+
"""
|
|
487
|
+
return self._symbols
|
|
488
|
+
|
|
489
|
+
@property
|
|
490
|
+
def aliases(self) -> Dict[str, Any]:
|
|
491
|
+
"""*aliases* Get the Python aliases for the variables.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Dict[str, Any]:: Python-compatible variable names.
|
|
495
|
+
"""
|
|
496
|
+
return self._aliases
|
|
497
|
+
|
|
498
|
+
def clear(self) -> None:
|
|
499
|
+
"""*clear()* Reset all attributes to default values. Resets all sensitivity analysis properties to their initial state.
|
|
500
|
+
"""
|
|
501
|
+
# Reset base class attributes
|
|
502
|
+
self._idx = -1
|
|
503
|
+
self._sym = "SANSYS_\\Pi_{}"
|
|
504
|
+
self._alias = ""
|
|
505
|
+
self._fwk = Frameworks.PHYSICAL.value
|
|
506
|
+
self.name = ""
|
|
507
|
+
self.description = ""
|
|
508
|
+
|
|
509
|
+
# Reset sensitivity-specific attributes
|
|
510
|
+
self._cat = AnaliticMode.SYM.value
|
|
511
|
+
self._pi_expr = None
|
|
512
|
+
self._sym_func = None
|
|
513
|
+
self._exe_func = None
|
|
514
|
+
self._variables = []
|
|
515
|
+
self._symbols = {}
|
|
516
|
+
self._aliases = {}
|
|
517
|
+
self.var_bounds = []
|
|
518
|
+
self.var_values = {}
|
|
519
|
+
self.var_domains = None
|
|
520
|
+
self.var_ranges = None
|
|
521
|
+
self.n_samples = 1000
|
|
522
|
+
self.results = {}
|
|
523
|
+
|
|
524
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
525
|
+
"""*to_dict()* Convert sensitivity analysis to dictionary representation.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Dict[str, Any]: Dictionary representation of sensitivity analysis.
|
|
529
|
+
"""
|
|
530
|
+
return {
|
|
531
|
+
"name": self.name,
|
|
532
|
+
"description": self.description,
|
|
533
|
+
"idx": self._idx,
|
|
534
|
+
"sym": self._sym,
|
|
535
|
+
"alias": self._alias,
|
|
536
|
+
"fwk": self._fwk,
|
|
537
|
+
"cat": self._cat,
|
|
538
|
+
"pi_expr": self._pi_expr,
|
|
539
|
+
"variables": self._variables,
|
|
540
|
+
"symbols": self._symbols,
|
|
541
|
+
"aliases": self._aliases,
|
|
542
|
+
"var_bounds": self.var_bounds,
|
|
543
|
+
"var_values": self.var_values,
|
|
544
|
+
"var_domains": self.var_domains,
|
|
545
|
+
"var_ranges": self.var_ranges,
|
|
546
|
+
"n_samples": self.n_samples,
|
|
547
|
+
"results": self.results
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
@classmethod
|
|
551
|
+
def from_dict(cls, data: Dict[str, Any]) -> Sensitivity:
|
|
552
|
+
"""*from_dict()* Create sensitivity analysis from dictionary representation.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
data (Dict[str, Any]): Dictionary representation of sensitivity analysis.
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
Sensitivity: New sensitivity analysis instance.
|
|
559
|
+
"""
|
|
560
|
+
# Create basic instance
|
|
561
|
+
instance = cls(
|
|
562
|
+
_name=data.get("name", ""),
|
|
563
|
+
description=data.get("description", ""),
|
|
564
|
+
_idx=data.get("idx", -1),
|
|
565
|
+
_sym=data.get("sym", ""),
|
|
566
|
+
_cat=data.get("cat", AnaliticMode.SYM.value),
|
|
567
|
+
_fwk=data.get("fwk", Frameworks.PHYSICAL.value),
|
|
568
|
+
_alias=data.get("alias", ""),
|
|
569
|
+
_pi_expr=data.get("pi_expr", None),
|
|
570
|
+
_variables=data.get("variables", {}),
|
|
571
|
+
_symbols=data.get("symbols", {}),
|
|
572
|
+
_aliases=data.get("aliases", {}),
|
|
573
|
+
var_bounds=data.get("var_bounds", []),
|
|
574
|
+
var_values=data.get("var_values", {}),
|
|
575
|
+
var_domains=data.get("var_domains", None),
|
|
576
|
+
var_ranges=data.get("var_ranges", None),
|
|
577
|
+
n_samples=data.get("n_samples", 1000),
|
|
578
|
+
# results=data.get("results", {})
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Set additional properties if available
|
|
582
|
+
if "results" in data:
|
|
583
|
+
instance.results = data["results"]
|
|
584
|
+
return instance
|