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,765 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module practical.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Module for **MonteCarloSimulation** to manage the Monte Carlo experiments in *PyDASA*.
|
|
7
|
+
|
|
8
|
+
This module provides classes for managing Monte Carlo simulations for sensitivity analysis of dimensionless coefficients.
|
|
9
|
+
|
|
10
|
+
Classes:
|
|
11
|
+
**MonteCarloSimulation**: Manages Monte Carlo simulations analysis, including configuration and execution of the experiments.
|
|
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, List, Any, Tuple, Union, Optional
|
|
21
|
+
# import random
|
|
22
|
+
# import re, Optional, Callable
|
|
23
|
+
|
|
24
|
+
# Third-party imports
|
|
25
|
+
import numpy as np
|
|
26
|
+
from numpy.typing import NDArray
|
|
27
|
+
|
|
28
|
+
# Import validation base classes
|
|
29
|
+
from pydasa.core.basic import Foundation
|
|
30
|
+
|
|
31
|
+
# Import related classes
|
|
32
|
+
from pydasa.elements.parameter import Variable
|
|
33
|
+
from pydasa.dimensional.buckingham import Coefficient
|
|
34
|
+
from pydasa.analysis.simulation import MonteCarlo
|
|
35
|
+
|
|
36
|
+
# Import utils
|
|
37
|
+
from pydasa.validations.error import inspect_var
|
|
38
|
+
from pydasa.serialization.parser import latex_to_python
|
|
39
|
+
|
|
40
|
+
# Import validation decorators
|
|
41
|
+
from pydasa.validations.decorators import validate_type
|
|
42
|
+
from pydasa.validations.decorators import validate_custom
|
|
43
|
+
from pydasa.validations.decorators import validate_range
|
|
44
|
+
from pydasa.validations.decorators import validate_choices
|
|
45
|
+
|
|
46
|
+
# Import global configuration
|
|
47
|
+
# from pydasa.core.parameter import Variable
|
|
48
|
+
# from pydasa.core.setup import Frameworks
|
|
49
|
+
from pydasa.core.setup import AnaliticMode
|
|
50
|
+
# Import configuration
|
|
51
|
+
from pydasa.core.setup import PYDASA_CFG
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class MonteCarloSimulation(Foundation):
|
|
56
|
+
"""**MonteCarloSimulation** class for managing Monte Carlo simulations in *PyDASA*.
|
|
57
|
+
|
|
58
|
+
Manages the creation, configuration, and execution of Monte Carlo simulations of dimensionless coefficients.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
Foundation: Foundation class for validation of symbols and frameworks.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
# Identification and Classification
|
|
65
|
+
name (str): User-friendly name of the sensitivity handler.
|
|
66
|
+
description (str): Brief summary of the sensitivity handler.
|
|
67
|
+
_idx (int): Index/precedence of the sensitivity handler.
|
|
68
|
+
_sym (str): Symbol representation (LaTeX or alphanumeric).
|
|
69
|
+
_alias (str): Python-compatible alias for use in code.
|
|
70
|
+
_fwk (str): Frameworks context (PHYSICAL, COMPUTATION, SOFTWARE, CUSTOM).
|
|
71
|
+
_cat (str): Category of analysis (SYM, NUM).
|
|
72
|
+
|
|
73
|
+
# Simulation Components
|
|
74
|
+
_variables (Dict[str, Variable]): all available parameters/variables in the model (*Variable*).
|
|
75
|
+
_coefficients (Dict[str, Coefficient]): all available coefficients in the model (*Coefficient*).
|
|
76
|
+
_distributions (Dict[str, Dict[str, Any]]): all distribution functions used in the simulations.
|
|
77
|
+
_experiments (int): Number of simulation to run. Default is -1.
|
|
78
|
+
|
|
79
|
+
# Simulation Results
|
|
80
|
+
_simulations (Dict[str, MonteCarlo]): all Monte Carlo simulations performed.
|
|
81
|
+
_results (Dict[str, Any]): all results from the simulations.
|
|
82
|
+
_shared_cache (Dict[str, NDArray[np.float64]]): In-memory cache for simulation data between coefficients.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
# Identification and Classification
|
|
86
|
+
# :attr: _cat
|
|
87
|
+
_cat: str = AnaliticMode.NUM.value
|
|
88
|
+
"""Category of sensitivity analysis (SYM, NUM)."""
|
|
89
|
+
|
|
90
|
+
# Variable management
|
|
91
|
+
# :attr: _variables
|
|
92
|
+
_variables: Dict[str, Variable] = field(default_factory=dict)
|
|
93
|
+
"""Dictionary of all parameters/variables in the model (*Variable*)."""
|
|
94
|
+
|
|
95
|
+
# :attr: _coefficients
|
|
96
|
+
_coefficients: Dict[str, Coefficient] = field(default_factory=dict)
|
|
97
|
+
"""Dictionary of all coefficients in the model (*Coefficient*)."""
|
|
98
|
+
|
|
99
|
+
# Simulation configuration
|
|
100
|
+
# :attr: _distributions
|
|
101
|
+
_distributions: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
|
102
|
+
"""Variable sampling distributions and specifications for simulations (specific name, parameters, and function)."""
|
|
103
|
+
|
|
104
|
+
# :attr: _experiments
|
|
105
|
+
_experiments: int = -1
|
|
106
|
+
"""Number of simulation to run."""
|
|
107
|
+
|
|
108
|
+
# Simulation Management
|
|
109
|
+
# :attr: _shared_cache
|
|
110
|
+
_shared_cache: Dict[str, NDArray[np.float64]] = field(default_factory=dict)
|
|
111
|
+
"""In-memory cache for simulation data between coefficients."""
|
|
112
|
+
|
|
113
|
+
# :attr: _simulations
|
|
114
|
+
_simulations: Dict[str, MonteCarlo] = field(default_factory=dict)
|
|
115
|
+
"""Dictionary of Monte Carlo simulations."""
|
|
116
|
+
|
|
117
|
+
# :attr: _results
|
|
118
|
+
_results: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
|
119
|
+
"""Consolidated results of the Monte Carlo simulations."""
|
|
120
|
+
|
|
121
|
+
def __post_init__(self) -> None:
|
|
122
|
+
"""*__post_init__()* Initializes the Monte Carlo handler."""
|
|
123
|
+
# Initialize from base class
|
|
124
|
+
super().__post_init__()
|
|
125
|
+
|
|
126
|
+
# Set default symbol if not specified
|
|
127
|
+
if not self._sym:
|
|
128
|
+
self._sym = f"MCH_{{\\Pi_{{{self._idx}}}}}" if self._idx >= 0 else "MCH_\\Pi_{-1}"
|
|
129
|
+
|
|
130
|
+
if not self._alias:
|
|
131
|
+
self._alias = latex_to_python(self._sym)
|
|
132
|
+
|
|
133
|
+
# Set name and description if not already set
|
|
134
|
+
if not self.name:
|
|
135
|
+
self.name = f"Monte Carlo Simulation Handler {self._idx}"
|
|
136
|
+
|
|
137
|
+
if not self.description:
|
|
138
|
+
coef_keys = ", ".join(self._coefficients.keys()) if self._coefficients else "no coefficients"
|
|
139
|
+
self.description = f"Manages Monte Carlo simulations for [{coef_keys}] coefficients."
|
|
140
|
+
|
|
141
|
+
# Ensure mem_cache is always initialized
|
|
142
|
+
if self._shared_cache is None:
|
|
143
|
+
self._shared_cache = {}
|
|
144
|
+
|
|
145
|
+
def config_simulations(self) -> None:
|
|
146
|
+
"""*config_simulations()* Configures distributions and simulations if not already set."""
|
|
147
|
+
if len(self._distributions) == 0:
|
|
148
|
+
self._config_distributions()
|
|
149
|
+
if len(self._simulations) == 0:
|
|
150
|
+
self._config_simulations()
|
|
151
|
+
|
|
152
|
+
# ========================================================================
|
|
153
|
+
# Foundation Methods
|
|
154
|
+
# ========================================================================
|
|
155
|
+
|
|
156
|
+
def _validate_dict(self,
|
|
157
|
+
dt: Dict[str, Any],
|
|
158
|
+
exp_type: Union[type, Tuple[type, ...]]) -> bool:
|
|
159
|
+
"""*_validate_dict()* Validates a dictionary with expected value types.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
dt (Dict[str, Any]): Dictionary to validate.
|
|
163
|
+
exp_type (Union[type, Tuple[type, ...]]): Expected type(s) for dictionary values.
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
ValueError: If the object is not a dictionary.
|
|
167
|
+
ValueError: If the dictionary is empty.
|
|
168
|
+
ValueError: If the dictionary contains values of unexpected types.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
bool: True if the dictionary is valid.
|
|
172
|
+
"""
|
|
173
|
+
# variable inspection
|
|
174
|
+
var_name = inspect_var(dt)
|
|
175
|
+
|
|
176
|
+
# Validate is dictionary
|
|
177
|
+
if not isinstance(dt, dict):
|
|
178
|
+
_msg = f"{var_name} must be a dictionary. "
|
|
179
|
+
_msg += f"Provided: {type(dt).__name__}"
|
|
180
|
+
raise ValueError(_msg)
|
|
181
|
+
|
|
182
|
+
# Validate not empty
|
|
183
|
+
if len(dt) == 0:
|
|
184
|
+
_msg = f"{var_name} cannot be empty. "
|
|
185
|
+
_msg += f"Provided: {dt}"
|
|
186
|
+
raise ValueError(_msg)
|
|
187
|
+
|
|
188
|
+
# Convert list to tuple for isinstance()
|
|
189
|
+
type_check = exp_type if isinstance(exp_type, tuple) else (exp_type,) if not isinstance(exp_type, tuple) else exp_type
|
|
190
|
+
|
|
191
|
+
# Validate value types
|
|
192
|
+
if not all(isinstance(v, type_check) for v in dt.values()):
|
|
193
|
+
# Format expected types for error message
|
|
194
|
+
if isinstance(exp_type, tuple):
|
|
195
|
+
type_names = " or ".join(t.__name__ for t in exp_type)
|
|
196
|
+
else:
|
|
197
|
+
type_names = exp_type.__name__
|
|
198
|
+
|
|
199
|
+
actual_types = [type(v).__name__ for v in dt.values()]
|
|
200
|
+
_msg = f"{var_name} must contain {type_names} values. "
|
|
201
|
+
_msg += f"Provided: {actual_types}"
|
|
202
|
+
raise ValueError(_msg)
|
|
203
|
+
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def _validate_coefficient_vars(self,
|
|
207
|
+
coef: Coefficient,
|
|
208
|
+
pi_sym: str) -> Dict[str, Any]:
|
|
209
|
+
"""*_validate_coefficient_vars()* Validates and returns coefficient's var_dims.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
coef (Coefficient): The coefficient to validate.
|
|
213
|
+
pi_sym (str): The coefficient symbol for error messages.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Dict[str, Any]: The validated var_dims dictionary.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ValueError: If var_dims is None or missing.
|
|
220
|
+
"""
|
|
221
|
+
if not hasattr(coef, 'var_dims'):
|
|
222
|
+
_msg = f"Coefficient '{pi_sym}' missing var_dims attribute."
|
|
223
|
+
raise ValueError(_msg)
|
|
224
|
+
|
|
225
|
+
var_dims = coef.var_dims
|
|
226
|
+
if var_dims is None:
|
|
227
|
+
_msg = f"Coefficient '{pi_sym}' has None var_dims. "
|
|
228
|
+
_msg += "Ensure the coefficient was properly initialized."
|
|
229
|
+
raise ValueError(_msg)
|
|
230
|
+
|
|
231
|
+
if not isinstance(var_dims, dict):
|
|
232
|
+
_msg = f"Coefficient '{pi_sym}' var_dims must be a dictionary. "
|
|
233
|
+
_msg += f"Got: {type(var_dims).__name__}"
|
|
234
|
+
raise TypeError(_msg)
|
|
235
|
+
|
|
236
|
+
return var_dims
|
|
237
|
+
|
|
238
|
+
# ========================================================================
|
|
239
|
+
# Configuration Methods
|
|
240
|
+
# ========================================================================
|
|
241
|
+
|
|
242
|
+
def _config_distributions(self) -> None:
|
|
243
|
+
"""*_config_distributions()* Creates the Monte Carlo distributions for each variable.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
ValueError: If the distribution specifications are invalid.
|
|
247
|
+
"""
|
|
248
|
+
# Clear existing distributions
|
|
249
|
+
self._distributions.clear()
|
|
250
|
+
|
|
251
|
+
# Validate variables exist before processing
|
|
252
|
+
if not self._variables:
|
|
253
|
+
_msg = "Cannot configure distributions: no variables defined."
|
|
254
|
+
raise ValueError(_msg)
|
|
255
|
+
|
|
256
|
+
for var in self._variables.values():
|
|
257
|
+
sym = var.sym
|
|
258
|
+
|
|
259
|
+
# Skip if already configured
|
|
260
|
+
if sym in self._distributions:
|
|
261
|
+
continue
|
|
262
|
+
|
|
263
|
+
# Collect specs for better error reporting
|
|
264
|
+
specs = {
|
|
265
|
+
"dist_type": var.dist_type,
|
|
266
|
+
"dist_params": var.dist_params,
|
|
267
|
+
"dist_func": var.dist_func
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# Validate distribution specifications
|
|
271
|
+
if not any(specs.values()):
|
|
272
|
+
_msg = f"Invalid distribution for variable '{sym}'. "
|
|
273
|
+
_msg += f"Incomplete specifications provided: {specs}"
|
|
274
|
+
raise ValueError(_msg)
|
|
275
|
+
|
|
276
|
+
# Store distribution configuration
|
|
277
|
+
self._distributions[sym] = {
|
|
278
|
+
"depends": var.depends,
|
|
279
|
+
"dtype": var.dist_type,
|
|
280
|
+
"params": var.dist_params,
|
|
281
|
+
"func": var.dist_func
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
def _get_distributions(self,
|
|
285
|
+
var_keys: List[str]) -> Dict[str, Dict[str, Any]]:
|
|
286
|
+
"""*_get_distributions()* Retrieves the distribution specifications for a list of variable keys.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
var_keys (List[str]): List of variable keys.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Dict[str, Dict[str, Any]]: Dictionary of distribution specifications.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If required distributions are missing.
|
|
296
|
+
"""
|
|
297
|
+
# Filter distributions for requested variables
|
|
298
|
+
dist = {k: v for k, v in self._distributions.items() if k in var_keys}
|
|
299
|
+
|
|
300
|
+
# Warn about missing distributions
|
|
301
|
+
missing = [k for k in var_keys if k not in dist]
|
|
302
|
+
|
|
303
|
+
if missing:
|
|
304
|
+
_msg = f"Missing distributions for variables: {missing}. "
|
|
305
|
+
_msg += "Ensure _config_distributions() has been called."
|
|
306
|
+
raise ValueError(_msg)
|
|
307
|
+
|
|
308
|
+
return dist
|
|
309
|
+
|
|
310
|
+
def _get_dependencies(self, var_keys: List[str]) -> Dict[str, List[str]]:
|
|
311
|
+
"""*_get_dependencies()* Retrieves variable dependencies for a list of variable keys.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
var_keys (List[str]): List of variable keys.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Dict[str, List[str]]: Dictionary mapping variable symbols to their dependencies.
|
|
318
|
+
"""
|
|
319
|
+
deps = {
|
|
320
|
+
k: v.depends for k, v in self._variables.items() if k in var_keys
|
|
321
|
+
}
|
|
322
|
+
return deps
|
|
323
|
+
|
|
324
|
+
def _init_shared_cache(self) -> None:
|
|
325
|
+
"""*_init_shared_cache()* Initialize shared cache for all variables."""
|
|
326
|
+
# Only initialize if experiments is positive
|
|
327
|
+
if self._experiments < 0:
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
# Initialize cache for each variable once
|
|
331
|
+
for var_sym in self._variables.keys():
|
|
332
|
+
self._shared_cache[var_sym] = np.full((self._experiments, 1),
|
|
333
|
+
np.nan,
|
|
334
|
+
dtype=np.float64)
|
|
335
|
+
|
|
336
|
+
def _config_simulations(self) -> None:
|
|
337
|
+
"""*_config_simulations()* Sets up Monte Carlo simulation objects for each coefficient to be analyzed.
|
|
338
|
+
|
|
339
|
+
Creates a MonteCarlo instance for each coefficient with appropriate distributions and dependencies.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
ValueError: If coefficients or variables are not properly configured.
|
|
343
|
+
"""
|
|
344
|
+
# Validate prerequisites
|
|
345
|
+
if not self._coefficients:
|
|
346
|
+
_msg = "Cannot configure simulations: no coefficients defined."
|
|
347
|
+
raise ValueError(_msg)
|
|
348
|
+
|
|
349
|
+
if not self._variables:
|
|
350
|
+
_msg = "Cannot configure simulations: no variables defined."
|
|
351
|
+
raise ValueError(_msg)
|
|
352
|
+
|
|
353
|
+
if not self._distributions:
|
|
354
|
+
_msg = "Cannot configure simulations: distributions not defined. "
|
|
355
|
+
raise ValueError(_msg)
|
|
356
|
+
|
|
357
|
+
# Clear existing simulations
|
|
358
|
+
self._simulations.clear()
|
|
359
|
+
|
|
360
|
+
# Initialize shared cache once
|
|
361
|
+
if not self._shared_cache:
|
|
362
|
+
self._init_shared_cache()
|
|
363
|
+
|
|
364
|
+
# Create simulations for each coefficient
|
|
365
|
+
for i, (pi, coef) in enumerate(self._coefficients.items()):
|
|
366
|
+
# Validate coefficient before processing
|
|
367
|
+
var_dims = self._validate_coefficient_vars(coef, pi)
|
|
368
|
+
|
|
369
|
+
# Extract variables from the coefficient's expression
|
|
370
|
+
vars_in_coef = list(var_dims.keys())
|
|
371
|
+
|
|
372
|
+
# Skip coefficients with no variables
|
|
373
|
+
if not vars_in_coef:
|
|
374
|
+
_msg = f"Coefficient '{pi}' has no variables in expression. Skipping simulation."
|
|
375
|
+
print(f"Warning: {_msg}")
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
# Create Monte Carlo simulation
|
|
380
|
+
sim = MonteCarlo(
|
|
381
|
+
_idx=i,
|
|
382
|
+
_sym=f"MC_{{{coef.sym}}}",
|
|
383
|
+
_fwk=self._fwk,
|
|
384
|
+
_cat=self._cat,
|
|
385
|
+
_pi_expr=coef.pi_expr,
|
|
386
|
+
_coefficient=coef,
|
|
387
|
+
_variables=self._variables,
|
|
388
|
+
# _simul_cache=self._shared_cache,
|
|
389
|
+
_experiments=self._experiments,
|
|
390
|
+
_name=f"Monte Carlo Simulation for {coef.name}",
|
|
391
|
+
description=f"Monte Carlo simulation for {coef.sym}",
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Configure with coefficient
|
|
395
|
+
sim.set_coefficient(coef)
|
|
396
|
+
|
|
397
|
+
# Get distributions with validation
|
|
398
|
+
sim._distributions = self._get_distributions(vars_in_coef)
|
|
399
|
+
sim._dependencies = self._get_dependencies(vars_in_coef)
|
|
400
|
+
|
|
401
|
+
# CRITICAL: Share the cache reference
|
|
402
|
+
sim._simul_cache = self._shared_cache
|
|
403
|
+
|
|
404
|
+
# Add to simulations dictionary
|
|
405
|
+
self._simulations[pi] = sim
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
_msg = f"Failed to create simulation for '{pi}': {str(e)}"
|
|
409
|
+
raise RuntimeError(_msg) from e
|
|
410
|
+
|
|
411
|
+
# ========================================================================
|
|
412
|
+
# Simulation Execution Methods
|
|
413
|
+
# ========================================================================
|
|
414
|
+
|
|
415
|
+
def simulate(self, n_samples: Optional[int] = None) -> None:
|
|
416
|
+
"""*simulate()* Runs the Monte Carlo simulations.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
n_samples (Optional[int]): Number of samples to generate.
|
|
420
|
+
If None, uses self._experiments value. Defaults to None.
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ValueError: If simulations are not configured.
|
|
424
|
+
ValueError: If a required simulation is not found.
|
|
425
|
+
"""
|
|
426
|
+
# Validate simulations exist
|
|
427
|
+
if not self._simulations:
|
|
428
|
+
_msg = "No simulations configured. Call config_simulations() first."
|
|
429
|
+
raise ValueError(_msg)
|
|
430
|
+
|
|
431
|
+
# Use default if not specified
|
|
432
|
+
if n_samples is not None:
|
|
433
|
+
self._experiments = n_samples
|
|
434
|
+
|
|
435
|
+
# Validate n_samples
|
|
436
|
+
if self._experiments < 1:
|
|
437
|
+
_msg = f"Experiments must be positive. Got: {n_samples}"
|
|
438
|
+
raise ValueError(_msg)
|
|
439
|
+
|
|
440
|
+
# ✅ Initialize shared cache BEFORE running simulations
|
|
441
|
+
if not self._shared_cache:
|
|
442
|
+
for var_sym in self._variables.keys():
|
|
443
|
+
self._shared_cache[var_sym] = np.full((self._experiments, 1),
|
|
444
|
+
np.nan,
|
|
445
|
+
dtype=np.float64)
|
|
446
|
+
|
|
447
|
+
# print("----------")
|
|
448
|
+
# print(f"_shared_cache keys: {self._shared_cache.keys()}")
|
|
449
|
+
# # ✅ Assign shared cache to ALL simulations
|
|
450
|
+
# for sim in self._simulations.values():
|
|
451
|
+
# sim._simul_cache = self._shared_cache
|
|
452
|
+
|
|
453
|
+
results = {}
|
|
454
|
+
|
|
455
|
+
for sym in self._coefficients:
|
|
456
|
+
# Get the simulation object
|
|
457
|
+
sim = self._simulations.get(sym)
|
|
458
|
+
if not sim:
|
|
459
|
+
_msg = f"Simulation for coefficient '{sym}' not found. "
|
|
460
|
+
_msg += "Ensure _config_simulations() completed successfully."
|
|
461
|
+
raise ValueError(_msg)
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
# print("-----------------------------------")
|
|
465
|
+
# print(f"_shared_cache status:\n {self._shared_cache}")
|
|
466
|
+
# print("-----------------------------------")
|
|
467
|
+
|
|
468
|
+
# ✅ Use shared cache
|
|
469
|
+
sim._simul_cache = self._shared_cache
|
|
470
|
+
|
|
471
|
+
# Run the simulation
|
|
472
|
+
sim.run(self._experiments)
|
|
473
|
+
|
|
474
|
+
# Store comprehensive results
|
|
475
|
+
res = {
|
|
476
|
+
"inputs": sim.inputs,
|
|
477
|
+
"results": sim.results,
|
|
478
|
+
"statistics": sim.statistics,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# Store results
|
|
482
|
+
results[sym] = res
|
|
483
|
+
|
|
484
|
+
except Exception as e:
|
|
485
|
+
_msg = f"Simulation failed for coefficient '{sym}': {str(e)}"
|
|
486
|
+
raise RuntimeError(_msg) from e
|
|
487
|
+
|
|
488
|
+
self._results = results
|
|
489
|
+
|
|
490
|
+
# ========================================================================
|
|
491
|
+
# Getter Methods
|
|
492
|
+
# ========================================================================
|
|
493
|
+
|
|
494
|
+
def get_simulation(self, name: str) -> MonteCarlo:
|
|
495
|
+
"""*get_simulation()* Get a simulation by name.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
name (str): Name of the simulation.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
MonteCarlo: The requested simulation.
|
|
502
|
+
|
|
503
|
+
Raises:
|
|
504
|
+
ValueError: If the simulation doesn't exist.
|
|
505
|
+
"""
|
|
506
|
+
if name not in self._simulations:
|
|
507
|
+
available = ", ".join(self._simulations.keys())
|
|
508
|
+
_msg = f"Simulation '{name}' does not exist. "
|
|
509
|
+
_msg += f"Available: {available}"
|
|
510
|
+
raise ValueError(_msg)
|
|
511
|
+
|
|
512
|
+
return self._simulations[name]
|
|
513
|
+
|
|
514
|
+
def get_distribution(self, name: str) -> Dict[str, Any]:
|
|
515
|
+
"""*get_distribution()* Get the distribution by name.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
name (str): Name of the distribution.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
Dict[str, Any]: The requested distribution.
|
|
522
|
+
|
|
523
|
+
Raises:
|
|
524
|
+
ValueError: If the distribution doesn't exist.
|
|
525
|
+
"""
|
|
526
|
+
if name not in self._distributions:
|
|
527
|
+
available = ", ".join(self._distributions.keys())
|
|
528
|
+
_msg = f"Distribution '{name}' does not exist. "
|
|
529
|
+
_msg += f"Available: {available}"
|
|
530
|
+
raise ValueError(_msg)
|
|
531
|
+
|
|
532
|
+
return self._distributions[name]
|
|
533
|
+
|
|
534
|
+
def get_results(self, name: str) -> Dict[str, Any]:
|
|
535
|
+
"""*get_results()* Get the results of a simulation by name.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
name (str): Name of the simulation.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
Dict[str, Any]: The results of the requested simulation.
|
|
542
|
+
|
|
543
|
+
Raises:
|
|
544
|
+
ValueError: If the results for the simulation don't exist.
|
|
545
|
+
"""
|
|
546
|
+
if name not in self._results:
|
|
547
|
+
available = ", ".join(self._results.keys())
|
|
548
|
+
_msg = f"Results for simulation '{name}' do not exist. "
|
|
549
|
+
_msg += f"Available: {available}"
|
|
550
|
+
raise ValueError(_msg)
|
|
551
|
+
|
|
552
|
+
return self._results[name]
|
|
553
|
+
|
|
554
|
+
# ========================================================================
|
|
555
|
+
# Property Getters and Setters
|
|
556
|
+
# ========================================================================
|
|
557
|
+
|
|
558
|
+
@property
|
|
559
|
+
def cat(self) -> str:
|
|
560
|
+
"""*cat* Get the analysis category.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
str: Category (SYM, NUM, HYB).
|
|
564
|
+
"""
|
|
565
|
+
return self._cat
|
|
566
|
+
|
|
567
|
+
@cat.setter
|
|
568
|
+
@validate_type(str)
|
|
569
|
+
@validate_choices(PYDASA_CFG.analitic_modes)
|
|
570
|
+
def cat(self, val: str) -> None:
|
|
571
|
+
"""*cat* Set the analysis category.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
val (str): Category value.
|
|
575
|
+
|
|
576
|
+
Raises:
|
|
577
|
+
ValueError: If category is invalid.
|
|
578
|
+
"""
|
|
579
|
+
self._cat = val.upper()
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def variables(self) -> Dict[str, Variable]:
|
|
583
|
+
"""*variables* Get the list of variables.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Dict[str, Variable]: Dictionary of variables.
|
|
587
|
+
"""
|
|
588
|
+
return self._variables.copy()
|
|
589
|
+
|
|
590
|
+
@variables.setter
|
|
591
|
+
@validate_type(dict)
|
|
592
|
+
@validate_custom(lambda self, val: self._validate_dict(val, Variable))
|
|
593
|
+
def variables(self, val: Dict[str, Variable]) -> None:
|
|
594
|
+
"""*variables* Set the list of variables.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
val (Dict[str, Variable]): Dictionary of variables.
|
|
598
|
+
|
|
599
|
+
Raises:
|
|
600
|
+
ValueError: If dictionary is invalid.
|
|
601
|
+
"""
|
|
602
|
+
self._variables = val
|
|
603
|
+
|
|
604
|
+
# Clear existing analyses since variables changed
|
|
605
|
+
self._simulations.clear()
|
|
606
|
+
self._distributions.clear()
|
|
607
|
+
self._results.clear()
|
|
608
|
+
|
|
609
|
+
@property
|
|
610
|
+
def coefficients(self) -> Dict[str, Coefficient]:
|
|
611
|
+
"""*coefficients* Get the dictionary of coefficients.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
Dict[str, Coefficient]: Dictionary of coefficients.
|
|
615
|
+
"""
|
|
616
|
+
return self._coefficients.copy()
|
|
617
|
+
|
|
618
|
+
@coefficients.setter
|
|
619
|
+
@validate_type(dict)
|
|
620
|
+
@validate_custom(lambda self, val: self._validate_dict(val, Coefficient))
|
|
621
|
+
def coefficients(self, val: Dict[str, Coefficient]) -> None:
|
|
622
|
+
"""*coefficients* Set the dictionary of coefficients.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
val (Dict[str, Coefficient]): Dictionary of coefficients.
|
|
626
|
+
|
|
627
|
+
Raises:
|
|
628
|
+
ValueError: If dictionary is invalid.
|
|
629
|
+
"""
|
|
630
|
+
self._coefficients = val
|
|
631
|
+
|
|
632
|
+
# Clear existing analyses since coefficients changed
|
|
633
|
+
self._simulations.clear()
|
|
634
|
+
self._results.clear()
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def experiments(self) -> int:
|
|
638
|
+
"""*experiments* Get the number of experiments.
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
int: Number of experiments to run.
|
|
642
|
+
"""
|
|
643
|
+
return self._experiments
|
|
644
|
+
|
|
645
|
+
@experiments.setter
|
|
646
|
+
@validate_type(int)
|
|
647
|
+
@validate_range(min_value=1)
|
|
648
|
+
def experiments(self, val: int) -> None:
|
|
649
|
+
"""*experiments* Set the number of experiments.
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
val (int): Number of experiments.
|
|
653
|
+
|
|
654
|
+
Raises:
|
|
655
|
+
ValueError: If value is not positive.
|
|
656
|
+
"""
|
|
657
|
+
self._experiments = val
|
|
658
|
+
|
|
659
|
+
@property
|
|
660
|
+
def simulations(self) -> Dict[str, MonteCarlo]:
|
|
661
|
+
"""*simulations* Get the dictionary of Monte Carlo simulations.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Dict[str, MonteCarlo]: Dictionary of Monte Carlo simulations.
|
|
665
|
+
"""
|
|
666
|
+
return self._simulations.copy()
|
|
667
|
+
|
|
668
|
+
@property
|
|
669
|
+
def results(self) -> Dict[str, Dict[str, Any]]:
|
|
670
|
+
"""*results* Get the Monte Carlo results.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
Dict[str, Dict[str, Any]]: Monte Carlo results.
|
|
674
|
+
"""
|
|
675
|
+
return self._results.copy()
|
|
676
|
+
|
|
677
|
+
# ========================================================================
|
|
678
|
+
# Utility Methods
|
|
679
|
+
# ========================================================================
|
|
680
|
+
|
|
681
|
+
def clear(self) -> None:
|
|
682
|
+
"""*clear()* Reset all attributes to default values.
|
|
683
|
+
|
|
684
|
+
Resets all handler properties to their initial state.
|
|
685
|
+
"""
|
|
686
|
+
# Reset base class attributes
|
|
687
|
+
# super().clear()
|
|
688
|
+
|
|
689
|
+
# Reset specific attributes
|
|
690
|
+
self._simulations.clear()
|
|
691
|
+
self._distributions.clear()
|
|
692
|
+
self._results.clear()
|
|
693
|
+
self._shared_cache.clear()
|
|
694
|
+
|
|
695
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
696
|
+
"""*to_dict()* Convert the handler's state to a dictionary.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
Dict[str, Any]: Dictionary representation of the handler's state.
|
|
700
|
+
"""
|
|
701
|
+
return {
|
|
702
|
+
"name": self.name,
|
|
703
|
+
"description": self.description,
|
|
704
|
+
"idx": self._idx,
|
|
705
|
+
"sym": self._sym,
|
|
706
|
+
"alias": self._alias,
|
|
707
|
+
"fwk": self._fwk,
|
|
708
|
+
"cat": self._cat,
|
|
709
|
+
"experiments": self._experiments,
|
|
710
|
+
"variables": {
|
|
711
|
+
k: v.to_dict() for k, v in self._variables.items()
|
|
712
|
+
},
|
|
713
|
+
"coefficients": {
|
|
714
|
+
k: v.to_dict() for k, v in self._coefficients.items()
|
|
715
|
+
},
|
|
716
|
+
"simulations": {
|
|
717
|
+
k: v.to_dict() for k, v in self._simulations.items()
|
|
718
|
+
},
|
|
719
|
+
"results": self._results
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
@classmethod
|
|
723
|
+
def from_dict(cls, data: Dict[str, Any]) -> MonteCarloSimulation:
|
|
724
|
+
"""*from_dict()* Create a MonteCarloSimulation instance from a dictionary.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
data (Dict[str, Any]): Dictionary containing the handler's state.
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
MonteCarloSimulation: New instance of MonteCarloSimulation.
|
|
731
|
+
"""
|
|
732
|
+
# Create instance with basic attributes
|
|
733
|
+
instance = cls(
|
|
734
|
+
_name=data.get("name", ""),
|
|
735
|
+
description=data.get("description", ""),
|
|
736
|
+
_idx=data.get("idx", -1),
|
|
737
|
+
_sym=data.get("sym", ""),
|
|
738
|
+
_alias=data.get("alias", ""),
|
|
739
|
+
_fwk=data.get("fwk", ""),
|
|
740
|
+
_cat=data.get("cat", AnaliticMode.NUM.value),
|
|
741
|
+
_experiments=data.get("experiments", -1)
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
# Set variables
|
|
745
|
+
vars_data = data.get("variables", {})
|
|
746
|
+
if vars_data:
|
|
747
|
+
vars_dict = {k: Variable.from_dict(v) for k, v in vars_data.items()}
|
|
748
|
+
instance.variables = vars_dict
|
|
749
|
+
|
|
750
|
+
# Set coefficients
|
|
751
|
+
coefs_data = data.get("coefficients", {})
|
|
752
|
+
if coefs_data:
|
|
753
|
+
coefs_dict = {k: Coefficient.from_dict(v) for k, v in coefs_data.items()}
|
|
754
|
+
instance.coefficients = coefs_dict
|
|
755
|
+
|
|
756
|
+
# Configure simulations if we have variables and coefficients
|
|
757
|
+
if vars_data and coefs_data:
|
|
758
|
+
instance.config_simulations()
|
|
759
|
+
|
|
760
|
+
# Set results if available
|
|
761
|
+
results_data = data.get("results", {})
|
|
762
|
+
if results_data:
|
|
763
|
+
instance._results = results_data
|
|
764
|
+
|
|
765
|
+
return instance
|