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