UQPyL 2.1.6__cp313-cp313-win_amd64.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 (202) hide show
  1. UQPyL/__init__.py +33 -0
  2. UQPyL/analysis/__init__.py +21 -0
  3. UQPyL/analysis/base.py +231 -0
  4. UQPyL/analysis/delta.py +2 -0
  5. UQPyL/analysis/fast.py +2 -0
  6. UQPyL/analysis/mars.py +2 -0
  7. UQPyL/analysis/methods/__init__.py +21 -0
  8. UQPyL/analysis/methods/delta.py +249 -0
  9. UQPyL/analysis/methods/fast.py +146 -0
  10. UQPyL/analysis/methods/mars.py +98 -0
  11. UQPyL/analysis/methods/morris.py +144 -0
  12. UQPyL/analysis/methods/rbd_fast.py +120 -0
  13. UQPyL/analysis/methods/rsa.py +149 -0
  14. UQPyL/analysis/methods/sobol.py +246 -0
  15. UQPyL/analysis/morris.py +2 -0
  16. UQPyL/analysis/rbd_fast.py +2 -0
  17. UQPyL/analysis/rsa.py +2 -0
  18. UQPyL/analysis/runtime/__init__.py +15 -0
  19. UQPyL/analysis/runtime/reader.py +138 -0
  20. UQPyL/analysis/runtime/result.py +153 -0
  21. UQPyL/analysis/runtime/storage.py +131 -0
  22. UQPyL/analysis/runtime/verbose.py +159 -0
  23. UQPyL/analysis/sobol.py +1 -0
  24. UQPyL/calibration/__init__.py +16 -0
  25. UQPyL/calibration/base.py +195 -0
  26. UQPyL/calibration/methods/__init__.py +11 -0
  27. UQPyL/calibration/methods/es.py +99 -0
  28. UQPyL/calibration/methods/glue.py +73 -0
  29. UQPyL/calibration/methods/ies.py +140 -0
  30. UQPyL/calibration/methods/sufi2.py +185 -0
  31. UQPyL/calibration/reader.py +95 -0
  32. UQPyL/calibration/runtime.py +284 -0
  33. UQPyL/calibration/util.py +159 -0
  34. UQPyL/core/__init__.py +23 -0
  35. UQPyL/core/parameter_store.py +30 -0
  36. UQPyL/core/params.py +19 -0
  37. UQPyL/core/runtime.py +91 -0
  38. UQPyL/core/runtime_reader.py +38 -0
  39. UQPyL/core/runtime_session.py +22 -0
  40. UQPyL/core/runtime_storage.py +67 -0
  41. UQPyL/doe/__init__.py +14 -0
  42. UQPyL/doe/base.py +118 -0
  43. UQPyL/doe/methods/__init__.py +17 -0
  44. UQPyL/doe/methods/fast.py +132 -0
  45. UQPyL/doe/methods/full_fact.py +94 -0
  46. UQPyL/doe/methods/lhs.py +209 -0
  47. UQPyL/doe/methods/morris.py +132 -0
  48. UQPyL/doe/methods/random.py +46 -0
  49. UQPyL/doe/methods/saltelli.py +158 -0
  50. UQPyL/doe/methods/sobol.py +116 -0
  51. UQPyL/inference/__init__.py +16 -0
  52. UQPyL/inference/base.py +334 -0
  53. UQPyL/inference/chain.py +37 -0
  54. UQPyL/inference/methods/__init__.py +13 -0
  55. UQPyL/inference/methods/amh.py +205 -0
  56. UQPyL/inference/methods/demc.py +185 -0
  57. UQPyL/inference/methods/dream_zs.py +321 -0
  58. UQPyL/inference/methods/mh.py +156 -0
  59. UQPyL/inference/methods/mh_gibbs.py +189 -0
  60. UQPyL/inference/runtime/__init__.py +14 -0
  61. UQPyL/inference/runtime/reader.py +115 -0
  62. UQPyL/inference/runtime/result.py +246 -0
  63. UQPyL/inference/runtime/storage.py +194 -0
  64. UQPyL/inference/runtime/verbose.py +322 -0
  65. UQPyL/optimization/__init__.py +20 -0
  66. UQPyL/optimization/base.py +173 -0
  67. UQPyL/optimization/core/__init__.py +6 -0
  68. UQPyL/optimization/core/constraint.py +110 -0
  69. UQPyL/optimization/core/crowding_distance.py +56 -0
  70. UQPyL/optimization/core/ga_operator.py +105 -0
  71. UQPyL/optimization/core/non_dominated_sort.py +63 -0
  72. UQPyL/optimization/core/tournament.py +24 -0
  73. UQPyL/optimization/core/uniform_point.py +56 -0
  74. UQPyL/optimization/expensive/__init__.py +9 -0
  75. UQPyL/optimization/expensive/asmo.py +149 -0
  76. UQPyL/optimization/expensive/ego.py +153 -0
  77. UQPyL/optimization/expensive/moasmo.py +203 -0
  78. UQPyL/optimization/metric/__init__.py +3 -0
  79. UQPyL/optimization/metric/gd.py +16 -0
  80. UQPyL/optimization/metric/hv.py +138 -0
  81. UQPyL/optimization/metric/igd.py +16 -0
  82. UQPyL/optimization/moea/__init__.py +5 -0
  83. UQPyL/optimization/moea/moea_d.py +169 -0
  84. UQPyL/optimization/moea/nsga_ii.py +149 -0
  85. UQPyL/optimization/moea/nsga_iii.py +237 -0
  86. UQPyL/optimization/moea/rvea.py +172 -0
  87. UQPyL/optimization/population.py +186 -0
  88. UQPyL/optimization/runtime/__init__.py +4 -0
  89. UQPyL/optimization/runtime/reader.py +203 -0
  90. UQPyL/optimization/runtime/result.py +274 -0
  91. UQPyL/optimization/runtime/storage.py +224 -0
  92. UQPyL/optimization/runtime/verbose.py +384 -0
  93. UQPyL/optimization/soea/__init__.py +8 -0
  94. UQPyL/optimization/soea/abc.py +273 -0
  95. UQPyL/optimization/soea/csa.py +203 -0
  96. UQPyL/optimization/soea/de.py +129 -0
  97. UQPyL/optimization/soea/ga.py +115 -0
  98. UQPyL/optimization/soea/ml_sce_ua.py +183 -0
  99. UQPyL/optimization/soea/pso.py +174 -0
  100. UQPyL/optimization/soea/sce_ua.py +189 -0
  101. UQPyL/problem/__init__.py +36 -0
  102. UQPyL/problem/base.py +236 -0
  103. UQPyL/problem/decorators.py +52 -0
  104. UQPyL/problem/eval.py +23 -0
  105. UQPyL/problem/model_problem.py +225 -0
  106. UQPyL/problem/mop/DTLZ.py +658 -0
  107. UQPyL/problem/mop/ZDT.py +211 -0
  108. UQPyL/problem/mop/__init__.py +2 -0
  109. UQPyL/problem/problem.py +79 -0
  110. UQPyL/problem/sop/__init__.py +5 -0
  111. UQPyL/problem/sop/single_constraint_problem.py +50 -0
  112. UQPyL/problem/sop/single_simple_problem.py +519 -0
  113. UQPyL/problem/space.py +148 -0
  114. UQPyL/surrogate/__init__.py +20 -0
  115. UQPyL/surrogate/auto_tuner.py +212 -0
  116. UQPyL/surrogate/base.py +357 -0
  117. UQPyL/surrogate/gp/__init__.py +2 -0
  118. UQPyL/surrogate/gp/gaussian_process.py +263 -0
  119. UQPyL/surrogate/gp/kernel/__init__.py +6 -0
  120. UQPyL/surrogate/gp/kernel/base_kernel.py +34 -0
  121. UQPyL/surrogate/gp/kernel/c_kernel_.py +28 -0
  122. UQPyL/surrogate/gp/kernel/dot_kernel_.py +28 -0
  123. UQPyL/surrogate/gp/kernel/matern_kernel.py +95 -0
  124. UQPyL/surrogate/gp/kernel/rbf_kernel.py +34 -0
  125. UQPyL/surrogate/gp/kernel/rq_kernel.py +45 -0
  126. UQPyL/surrogate/kriging/__init__.py +2 -0
  127. UQPyL/surrogate/kriging/kernel/__init__.py +4 -0
  128. UQPyL/surrogate/kriging/kernel/base_kernel.py +29 -0
  129. UQPyL/surrogate/kriging/kernel/cubic_kernel.py +29 -0
  130. UQPyL/surrogate/kriging/kernel/exp_kernel.py +26 -0
  131. UQPyL/surrogate/kriging/kernel/guass_kernel.py +26 -0
  132. UQPyL/surrogate/kriging/kriging.py +356 -0
  133. UQPyL/surrogate/mars/__init__.py +1 -0
  134. UQPyL/surrogate/mars/core/__init__.py +4 -0
  135. UQPyL/surrogate/mars/core/_basis.cp313-win_amd64.pyd +0 -0
  136. UQPyL/surrogate/mars/core/_basis.pxd +239 -0
  137. UQPyL/surrogate/mars/core/_basis.pyx +1114 -0
  138. UQPyL/surrogate/mars/core/_forward.cp313-win_amd64.pyd +0 -0
  139. UQPyL/surrogate/mars/core/_forward.pxd +93 -0
  140. UQPyL/surrogate/mars/core/_forward.pyx +579 -0
  141. UQPyL/surrogate/mars/core/_knot_search.cp313-win_amd64.pyd +0 -0
  142. UQPyL/surrogate/mars/core/_knot_search.pxd +94 -0
  143. UQPyL/surrogate/mars/core/_knot_search.pyx +677 -0
  144. UQPyL/surrogate/mars/core/_pruning.cp313-win_amd64.pyd +0 -0
  145. UQPyL/surrogate/mars/core/_pruning.pxd +25 -0
  146. UQPyL/surrogate/mars/core/_pruning.pyx +215 -0
  147. UQPyL/surrogate/mars/core/_qr.cp313-win_amd64.pyd +0 -0
  148. UQPyL/surrogate/mars/core/_qr.pxd +34 -0
  149. UQPyL/surrogate/mars/core/_qr.pyx +264 -0
  150. UQPyL/surrogate/mars/core/_record.cp313-win_amd64.pyd +0 -0
  151. UQPyL/surrogate/mars/core/_record.pxd +66 -0
  152. UQPyL/surrogate/mars/core/_record.pyx +332 -0
  153. UQPyL/surrogate/mars/core/_types.cp313-win_amd64.pyd +0 -0
  154. UQPyL/surrogate/mars/core/_types.pxd +5 -0
  155. UQPyL/surrogate/mars/core/_types.pyx +5 -0
  156. UQPyL/surrogate/mars/core/_util.cp313-win_amd64.pyd +0 -0
  157. UQPyL/surrogate/mars/core/_util.pxd +23 -0
  158. UQPyL/surrogate/mars/core/_util.pyx +94 -0
  159. UQPyL/surrogate/mars/mars.py +733 -0
  160. UQPyL/surrogate/metric.py +54 -0
  161. UQPyL/surrogate/poly.py +62 -0
  162. UQPyL/surrogate/rbf/__init__.py +2 -0
  163. UQPyL/surrogate/rbf/kernel/__init__.py +14 -0
  164. UQPyL/surrogate/rbf/kernel/base_kernel.py +69 -0
  165. UQPyL/surrogate/rbf/kernel/cubic_kernel.py +30 -0
  166. UQPyL/surrogate/rbf/kernel/gaussian_kernel.py +19 -0
  167. UQPyL/surrogate/rbf/kernel/linear_kernel.py +26 -0
  168. UQPyL/surrogate/rbf/kernel/multiquadric_kernel.py +27 -0
  169. UQPyL/surrogate/rbf/kernel/thin_plate_spline_kernel.py +34 -0
  170. UQPyL/surrogate/rbf/radial_basis_function.py +183 -0
  171. UQPyL/surrogate/regression/__init__.py +2 -0
  172. UQPyL/surrogate/regression/lasso/__init__.py +8 -0
  173. UQPyL/surrogate/regression/lasso/cython_utils.pxd +54 -0
  174. UQPyL/surrogate/regression/lasso/cython_utils.pyx +472 -0
  175. UQPyL/surrogate/regression/lasso/lasso.cp313-win_amd64.pyd +0 -0
  176. UQPyL/surrogate/regression/lasso/lasso_fast.pyx +852 -0
  177. UQPyL/surrogate/regression/linear_regression.py +235 -0
  178. UQPyL/surrogate/regression/polynomial_regression.py +135 -0
  179. UQPyL/surrogate/scaler.py +86 -0
  180. UQPyL/surrogate/setting.py +352 -0
  181. UQPyL/surrogate/split.py +60 -0
  182. UQPyL/surrogate/svr/__init__.py +1 -0
  183. UQPyL/surrogate/svr/core/__init__.py +7 -0
  184. UQPyL/surrogate/svr/core/libsvm_interface.cp313-win_amd64.pyd +0 -0
  185. UQPyL/surrogate/svr/core/libsvm_interface.cpp +145 -0
  186. UQPyL/surrogate/svr/core/svm.cpp +3313 -0
  187. UQPyL/surrogate/svr/core/svm.h +106 -0
  188. UQPyL/surrogate/svr/support_vector_machine.py +196 -0
  189. UQPyL/surrogate/util/__init__.py +0 -0
  190. UQPyL/surrogate/util/boxmin.py +118 -0
  191. UQPyL/surrogate/util/lbfgsb.py +45 -0
  192. UQPyL/viz/__init__.py +23 -0
  193. UQPyL/viz/analysis.py +68 -0
  194. UQPyL/viz/common.py +25 -0
  195. UQPyL/viz/inference.py +135 -0
  196. UQPyL/viz/optimization.py +244 -0
  197. UQPyL/viz/surrogate.py +41 -0
  198. uqpyl-2.1.6.dist-info/METADATA +342 -0
  199. uqpyl-2.1.6.dist-info/RECORD +202 -0
  200. uqpyl-2.1.6.dist-info/WHEEL +5 -0
  201. uqpyl-2.1.6.dist-info/licenses/LICENSE.md +21 -0
  202. uqpyl-2.1.6.dist-info/top_level.txt +1 -0
UQPyL/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """UQPyL package entry."""
2
+
3
+ import importlib
4
+ from types import ModuleType
5
+
6
+ doe = importlib.import_module(__name__ + ".doe")
7
+
8
+ problem = importlib.import_module(__name__ + ".problem")
9
+ viz = importlib.import_module(__name__ + ".viz")
10
+
11
+ def _import_submodule(name: str) -> ModuleType:
12
+ return importlib.import_module(f"{__name__}.{name}")
13
+
14
+
15
+ surrogate = _import_submodule("surrogate")
16
+ optimization = _import_submodule("optimization")
17
+ analysis = _import_submodule("analysis")
18
+ inference = _import_submodule("inference")
19
+ calibration = _import_submodule("calibration")
20
+
21
+ __version__ = "2.1.7"
22
+ __author__ = "wmtSky"
23
+
24
+ __all__=[
25
+ "problem",
26
+ "surrogate",
27
+ "optimization",
28
+ "analysis",
29
+ "doe",
30
+ "inference",
31
+ "calibration",
32
+ "viz"
33
+ ]
@@ -0,0 +1,21 @@
1
+ from .methods.delta import DeltaTest
2
+ from .methods.fast import FAST
3
+ from .methods.morris import Morris
4
+ from .methods.rbd_fast import RBDFAST
5
+ from .methods.rsa import RSA
6
+ from .methods.sobol import Sobol
7
+
8
+ try:
9
+ from .methods.mars import MARS
10
+ except Exception:
11
+ MARS = None
12
+
13
+ __all__ = [
14
+ "DeltaTest",
15
+ "FAST",
16
+ "MARS",
17
+ "Morris",
18
+ "RBDFAST",
19
+ "RSA",
20
+ "Sobol",
21
+ ]
UQPyL/analysis/base.py ADDED
@@ -0,0 +1,231 @@
1
+ import abc
2
+ import time
3
+ from typing import List, Union
4
+
5
+ import numpy as np
6
+
7
+ from ..core.params import Params
8
+ from ..core.runtime_session import RunSession
9
+ from ..problem import ProblemABC as Problem
10
+ from .runtime import AnaState, SqliteStorage, Verbose
11
+
12
+ AnaIndex = Union[str, int, List[int]]
13
+
14
+ class AnalysisABC(metaclass=abc.ABCMeta):
15
+ """
16
+ Abstract base class for analysis methods.
17
+ Shared workflow and utilities for sensitivity analysis methods.
18
+ """
19
+
20
+ def __init__(self, verboseFlag: bool = False, logFlag: bool = False, saveFlag: bool = False):
21
+ """
22
+ Initialize the analysis base class.
23
+
24
+ Args:
25
+ verboseFlag: Whether to print compact runtime summaries.
26
+ logFlag: Whether to write a log file.
27
+ saveFlag: Whether to persist results to sqlite.
28
+ """
29
+
30
+ # Set flags for verbosity, logging, and saving
31
+ self.verboseFlag = verboseFlag
32
+ self.logFlag = logFlag
33
+ self.saveFlag = saveFlag
34
+
35
+ # Initialize settings and results
36
+ self.setting = Params()
37
+ self.params = self.setting
38
+ self.result = AnaState(self)
39
+ self.state = self.result
40
+ self.storage = None
41
+ self.session: RunSession | None = None
42
+ self.runId = None
43
+
44
+ def set(self, key, value):
45
+ """
46
+ Set an analysis parameter.
47
+
48
+ Args:
49
+ key: Parameter name.
50
+ value: Parameter value.
51
+ """
52
+
53
+ self.setting.set(key, value)
54
+
55
+ def get(self, *args):
56
+ """
57
+ Retrieve one or more analysis parameters.
58
+
59
+ Args:
60
+ *args: Parameter names.
61
+
62
+ Returns:
63
+ The requested parameter value or values.
64
+ """
65
+
66
+ return self.setting.get(*args)
67
+
68
+ def setProblem(self, problem: Problem):
69
+ """
70
+ Set the problem instance for the analysis.
71
+
72
+ Args:
73
+ problem: Problem instance defining the input and output space.
74
+ """
75
+
76
+ self.problem = problem
77
+
78
+ def setup(self, problem):
79
+ self.setProblem(problem)
80
+ self.result.reset()
81
+ self.state = self.result
82
+ self.runId = None
83
+ self.session = None
84
+ Verbose.setupContext(self, problem)
85
+ if self.saveFlag:
86
+ rootDir = getattr(problem, "workDir", None) or Verbose.workDir
87
+ self.storage = SqliteStorage(rootDir)
88
+ self.session = self.storage.create_run(self)
89
+ self.runId = self.session.run_id
90
+
91
+ def finalize(self):
92
+ result = self.state.buildResult()
93
+ if self.saveFlag and self.session is not None:
94
+ self.storage.saveResult(self.session, result)
95
+ self.storage.close(self.session)
96
+ self.session = None
97
+ Verbose.printConclusion(self, result)
98
+ if self.logFlag:
99
+ Verbose.saveLog(self)
100
+ return result
101
+
102
+ def analyze(self, problem, *args, **kwargs):
103
+ """
104
+ Run the analysis workflow and return the final `AnaResult`.
105
+
106
+ Expected public inputs follow the unified protocol:
107
+ `analyze(problem, X, Y=None, meta=None, target="objs", index="all")`.
108
+ Here `target` is the semantic label of `Y`, and when `Y` is not
109
+ provided it also selects which problem output block to evaluate.
110
+ """
111
+ meta = kwargs.get("meta")
112
+ if meta is not None:
113
+ self.checkMeta(meta)
114
+ self.setup(problem)
115
+ Verbose.printSettings(self)
116
+ start = time.perf_counter()
117
+ self._analyzeCore(problem, *args, **kwargs)
118
+ self.state.runtime = time.perf_counter() - start
119
+ return self.finalize()
120
+
121
+ def checkMeta(self, meta):
122
+ """
123
+ Validate sampling metadata produced by `sampleWithMeta()`.
124
+ """
125
+ return None
126
+
127
+ def check_Y(self, X, Y, target: str = 'objs', index: AnaIndex = 'all'):
128
+ """
129
+ Resolve and slice analysis outputs.
130
+
131
+ `target` labels the meaning of `Y`, typically `objs` or `cons`.
132
+ If `Y` is not provided, `target` also selects which problem output
133
+ block should be evaluated. If `index` is not `'all'`, only the
134
+ selected output columns are kept.
135
+ """
136
+ if Y is None:
137
+ Y = self.evaluate(X, target=target)
138
+
139
+ if index != 'all':
140
+ indices = self._normalize_index(index)
141
+ try:
142
+ Y = Y[:, indices]
143
+ except Exception:
144
+ raise ValueError("Please check the index you set!")
145
+
146
+ return Y
147
+
148
+ def recordResult(self, X, Y, res, target: str = 'objs', meta=None):
149
+ self.result.record(X, Y, res, target=target, meta=meta)
150
+ for metric in self.result.metrics:
151
+ for i, target in enumerate(metric.rowLabels):
152
+ self.record(target, metric.name, metric.colLabels, metric.values[i])
153
+
154
+ def record(self, target, indicator, labels, values):
155
+ """
156
+ Record the analysis results.
157
+
158
+ Args:
159
+ target: Output label such as `obj1` or `con1`.
160
+ indicator: Metric name.
161
+ labels: Input variable labels.
162
+ values: Metric values.
163
+ """
164
+
165
+ self.result.verbose.setdefault(target, {})
166
+ self.result.verbose[target].setdefault(indicator, {})
167
+
168
+ for label, v in zip(labels, values):
169
+ self.result.verbose[target][indicator][label] = v
170
+
171
+ self.result.verbose[target][indicator]['array'] = np.array(values)
172
+
173
+
174
+ def __check_X_Y__(self, X, Y):
175
+ """
176
+ Check input and output arrays.
177
+
178
+ Args:
179
+ X: Input matrix.
180
+ Y: Output matrix.
181
+
182
+ Returns:
183
+ The validated `X` and `Y`.
184
+ """
185
+
186
+ if not isinstance(X, np.ndarray) and X is not None:
187
+ raise TypeError("X must be an instance of np.ndarray or None!")
188
+
189
+ if not isinstance(Y, np.ndarray) and Y is not None:
190
+ raise TypeError("Y must be an instance of np.ndarray or None!")
191
+
192
+ if Y.ndim == 1:
193
+ Y = Y.reshape(-1, 1)
194
+
195
+ return X, Y
196
+
197
+ def evaluate(self, X, target: str = 'objs'):
198
+ """
199
+ Evaluate the problem with the given input data.
200
+
201
+ Args:
202
+ X: Input matrix.
203
+ target: Semantic output label to evaluate, typically `objs` or `cons`.
204
+
205
+ Returns:
206
+ The requested output matrix.
207
+ """
208
+
209
+ if target not in ('objs', 'cons'):
210
+ raise ValueError("Target must be 'objs' or 'cons'!")
211
+
212
+ evalRes = self.problem.evaluate(X, target=target)
213
+ Y = evalRes.objs if target == 'objs' else evalRes.cons
214
+ if Y is None:
215
+ raise ValueError(f"Problem does not provide target '{target}'.")
216
+ return Y
217
+
218
+ def _normalize_index(self, index: AnaIndex):
219
+ """
220
+ Normalize output column selection into a list of integers.
221
+ """
222
+ if isinstance(index, int):
223
+ return [index]
224
+ if isinstance(index, (list, tuple, np.ndarray)):
225
+ return list(index)
226
+ raise ValueError("Index must be 'all', an integer, or a list of integers!")
227
+
228
+ @abc.abstractmethod
229
+ def _analyzeCore(self, problem, *args, **kwargs):
230
+ pass
231
+
@@ -0,0 +1,2 @@
1
+ from ..optimization.soea import GA
2
+ from .methods.delta import *
UQPyL/analysis/fast.py ADDED
@@ -0,0 +1,2 @@
1
+ from .methods.fast import *
2
+
UQPyL/analysis/mars.py ADDED
@@ -0,0 +1,2 @@
1
+ from .methods.mars import *
2
+
@@ -0,0 +1,21 @@
1
+ from .delta import DeltaTest
2
+ from .fast import FAST
3
+ from .morris import Morris
4
+ from .rbd_fast import RBDFAST
5
+ from .rsa import RSA
6
+ from .sobol import Sobol
7
+
8
+ try:
9
+ from .mars import MARS
10
+ except Exception:
11
+ MARS = None
12
+
13
+ __all__ = [
14
+ "DeltaTest",
15
+ "FAST",
16
+ "MARS",
17
+ "Morris",
18
+ "RBDFAST",
19
+ "RSA",
20
+ "Sobol",
21
+ ]
@@ -0,0 +1,249 @@
1
+ # Delta test
2
+ import numpy as np
3
+ import sys
4
+ from scipy.spatial import KDTree
5
+ from typing import Optional
6
+
7
+ from ..base import AnaIndex, AnalysisABC
8
+ from ...problem import ProblemABC, Problem
9
+
10
+ class DeltaTest(AnalysisABC):
11
+ """
12
+ Delta Test
13
+ Non-parametric sensitivity analysis based on nearest-neighbor prediction error.
14
+
15
+ Examples:
16
+ >>> from UQPyL.doe import LHS
17
+ >>> delta_method = DeltaTest(nNeighbors=2)
18
+ >>> X = LHS('classic').sample(problem, 1000)
19
+ >>> res = delta_method.analyze(problem, X, target="objs")
20
+ >>> print(res)
21
+
22
+ References:
23
+ [1] E. Eirola et al, Using the Delta Test for Variable Selection,
24
+ Artificial Neural Networks, 2008.
25
+ [2] SALib, https://github.com/SALib/SALib
26
+ """
27
+
28
+ name = "DeltaTest"
29
+
30
+ def __init__(self, nNeighbors: int = 2,
31
+ verboseFlag: bool = True, logFlag: bool = False, saveFlag: bool = False):
32
+ """
33
+ Initialize the Delta Test method.
34
+
35
+ Args:
36
+ nNeighbors: Number of nearest neighbors used by the delta estimate.
37
+ verboseFlag: Whether to print compact runtime summaries.
38
+ logFlag: Whether to write a log file.
39
+ saveFlag: Whether to persist results to sqlite.
40
+ """
41
+ super().__init__(verboseFlag, logFlag, saveFlag)
42
+ self.set("nNeighbors", nNeighbors)
43
+
44
+ def _analyzeCore(self, problem, X: np.ndarray, Y: Optional[np.ndarray] = None, meta: Optional[dict] = None,
45
+ target: str = 'objs', index: AnaIndex = 'all') -> None:
46
+ """
47
+ Run the Delta Test on the provided samples.
48
+
49
+ Args:
50
+ problem: Analysis problem.
51
+ X: Input sample matrix.
52
+ Y: Optional output matrix corresponding to `X`.
53
+ meta: Optional sampling metadata for persistence only.
54
+ target: Semantic label of `Y`, typically `objs` or `cons`.
55
+ index: Output column selection.
56
+ """
57
+
58
+ # Set the problem instance for analysis
59
+ self.setProblem(problem)
60
+
61
+ # Evaluate the problem if Y is not provided
62
+ Y = self.check_Y(X, Y, target, index)
63
+
64
+ X, Y = self.__check_X_Y__(X, Y)
65
+ nInput = problem.nInput
66
+ numY = Y.shape[1]
67
+ nNeighbors = self.get("nNeighbors")
68
+
69
+ outputLabel = "obj" if target == "objs" else "con"
70
+
71
+ S1 = np.zeros((numY, nInput))
72
+ S1_norm = np.zeros((numY, nInput))
73
+ row_label = [f"{outputLabel}{i+1}" for i in range(numY)]
74
+ col_label_1 = problem.xLabels
75
+
76
+ for i in range(numY):
77
+ Y_i = Y[:, i:i+1]
78
+ base = self._cal_delta(X, Y_i, nNeighbors)
79
+ for j in range(nInput):
80
+ XSub = np.delete(X, [j], axis=1)
81
+ deltaWithoutVar = self._cal_delta(XSub, Y_i, nNeighbors)
82
+ S1[i, j] = deltaWithoutVar - base
83
+
84
+ total = np.sum(S1[i])
85
+ if np.isclose(total, 0.0):
86
+ S1_norm[i] = 0.0
87
+ else:
88
+ S1_norm[i] = S1[i] / total
89
+
90
+ res = [('S1', S1, row_label, col_label_1, 'decsDim1'), ('S1_norm', S1_norm, row_label, col_label_1, 'decsDim1')]
91
+
92
+ self.recordResult(X, Y, res, target=target, meta=meta)
93
+
94
+ return None
95
+
96
+ def findCombEA(self, problem, X: np.ndarray, Y: Optional[np.ndarray] = None,
97
+ FEs: int = 10000,
98
+ verboseFlag: bool = True, saveFlag: bool = True):
99
+ """
100
+ Find the best combination using Evolutionary Algorithm.
101
+
102
+ Args:
103
+ problem: Analysis problem.
104
+ X: Input sample matrix.
105
+ Y: Optional output matrix corresponding to `X`.
106
+ FEs: Maximum number of function evaluations.
107
+ verboseFlag: Whether the helper GA should print progress.
108
+ saveFlag: Whether the helper GA should persist results.
109
+
110
+ Returns:
111
+ The optimization result returned by the configured GA.
112
+ """
113
+ # Set the problem instance for analysis
114
+ self.setProblem(problem)
115
+
116
+ # Retrieve the number of nearest neighbors for analysis
117
+ nNeighbors = self.get('nNeighbors')
118
+
119
+ # Evaluate outputs if Y is not provided
120
+ if Y is None:
121
+ Y = self.evaluate(X, target="objs")
122
+
123
+ X, Y = self.__check_X_Y__(X, Y)
124
+
125
+ @ProblemABC.singleFunc
126
+ def objective(x_):
127
+ """
128
+ Minimize the delta value.
129
+
130
+ Args:
131
+ x_: Binary array indicating selected variables.
132
+
133
+ Returns:
134
+ The delta value for the selected variables.
135
+ """
136
+ x_ = x_.astype(int)
137
+ Indices = np.where(x_ == 1)[0]
138
+ XSub = X[:, Indices]
139
+
140
+ if np.sum(x_) == 0:
141
+ return np.inf
142
+ else:
143
+ return self._cal_delta(XSub, Y, nNeighbors)
144
+
145
+ # Create the optimization problem
146
+ nInput = problem.nInput
147
+ nObj = 1
148
+ ub = [1] * nInput
149
+ lb = [0] * nInput
150
+ varType = [1] * nInput
151
+
152
+ problem = Problem(nInput=nInput, nObj=nObj, ub=ub, lb=lb,
153
+ varType=varType, objFunc=objective, optType='min')
154
+
155
+ publicModule = sys.modules.get("UQPyL.analysis.delta")
156
+ gaClass = getattr(publicModule, "GA", None) if publicModule is not None else None
157
+ if gaClass is None:
158
+ from ...optimization.soea import GA as gaClass
159
+
160
+ # Initialize the GA
161
+ ga = gaClass(maxFEs=FEs, verboseFlag=verboseFlag, saveFlag=saveFlag)
162
+
163
+ # Run the GA
164
+ res = ga.run(problem)
165
+
166
+ return res
167
+
168
+ def findCombVio(self, problem, X: np.ndarray, Y: Optional[np.ndarray] = None):
169
+ """
170
+ Find the best combination using a brute-force approach.
171
+
172
+ Args:
173
+ problem: Analysis problem.
174
+ X: Input sample matrix.
175
+ Y: Optional output matrix corresponding to `X`.
176
+
177
+ Returns:
178
+ Labels of the selected variables.
179
+ """
180
+
181
+ from itertools import product
182
+
183
+ # Set the problem instance for analysis
184
+ self.setProblem(problem)
185
+
186
+ nInput = problem.nInput
187
+
188
+ # Retrieve the number of nearest neighbors for analysis
189
+ nNeighbors = self.get('nNeighbors')
190
+
191
+ # Evaluate outputs if Y is not provided
192
+ if Y is None:
193
+ Y = self.evaluate(X, target="objs")
194
+
195
+ X, Y = self.__check_X_Y__(X, Y)
196
+
197
+ # Generate all possible combinations of input variables
198
+ combinations = list(product([0, 1], repeat=nInput))
199
+
200
+ # Initialize an array to store objective values for each combination
201
+ objs = np.zeros((len(combinations), 1))
202
+
203
+ # Evaluate each combination
204
+ for i in range(len(combinations)):
205
+ x_ = np.array(combinations[i])
206
+ Indices = np.where(x_ == 1)[0]
207
+ XSub = X[:, Indices]
208
+
209
+ if np.sum(x_) == 0:
210
+ objs[i] = np.inf
211
+ else:
212
+ objs[i] = self._cal_delta(XSub, Y, nNeighbors)
213
+
214
+ # Find the best combination based on the objective values
215
+ best_index = np.argmin(objs)
216
+ best_combination = combinations[best_index]
217
+
218
+ # Return the labels of the most sensitive variables
219
+ return [problem.xLabels[i] for i in range(nInput) if best_combination[i] == 1]
220
+
221
+ def _cal_delta(self, X: np.ndarray, Y: np.ndarray, nNeighbors: int):
222
+ """
223
+ Calculate the Delta value using KDTree for nearest neighbor search.
224
+
225
+ Args:
226
+ X: Input data array.
227
+ Y: Output data array.
228
+ nNeighbors: Number of nearest neighbors to consider.
229
+
230
+ Returns:
231
+ The calculated delta value.
232
+ """
233
+ N, _ = X.shape
234
+
235
+ # Build a KDTree for fast nearest neighbor search
236
+ tree = KDTree(X)
237
+
238
+ # Query the nearest neighbors for each point
239
+ _, neighbors_indices = tree.query(X, k=nNeighbors + 1) # +1 to include the point itself
240
+
241
+ # Exclude the point itself from the neighbors
242
+ neighbors_indices = neighbors_indices[:, 1:]
243
+
244
+ Delta = 0
245
+ for i in range(N):
246
+ d = (Y[i] - Y[neighbors_indices[i]])**2
247
+ Delta += float(np.mean(d))
248
+
249
+ return Delta / (nNeighbors * N)