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.
- UQPyL/__init__.py +33 -0
- UQPyL/analysis/__init__.py +21 -0
- UQPyL/analysis/base.py +231 -0
- UQPyL/analysis/delta.py +2 -0
- UQPyL/analysis/fast.py +2 -0
- UQPyL/analysis/mars.py +2 -0
- UQPyL/analysis/methods/__init__.py +21 -0
- UQPyL/analysis/methods/delta.py +249 -0
- UQPyL/analysis/methods/fast.py +146 -0
- UQPyL/analysis/methods/mars.py +98 -0
- UQPyL/analysis/methods/morris.py +144 -0
- UQPyL/analysis/methods/rbd_fast.py +120 -0
- UQPyL/analysis/methods/rsa.py +149 -0
- UQPyL/analysis/methods/sobol.py +246 -0
- UQPyL/analysis/morris.py +2 -0
- UQPyL/analysis/rbd_fast.py +2 -0
- UQPyL/analysis/rsa.py +2 -0
- UQPyL/analysis/runtime/__init__.py +15 -0
- UQPyL/analysis/runtime/reader.py +138 -0
- UQPyL/analysis/runtime/result.py +153 -0
- UQPyL/analysis/runtime/storage.py +131 -0
- UQPyL/analysis/runtime/verbose.py +159 -0
- UQPyL/analysis/sobol.py +1 -0
- UQPyL/calibration/__init__.py +16 -0
- UQPyL/calibration/base.py +195 -0
- UQPyL/calibration/methods/__init__.py +11 -0
- UQPyL/calibration/methods/es.py +99 -0
- UQPyL/calibration/methods/glue.py +73 -0
- UQPyL/calibration/methods/ies.py +140 -0
- UQPyL/calibration/methods/sufi2.py +185 -0
- UQPyL/calibration/reader.py +95 -0
- UQPyL/calibration/runtime.py +284 -0
- UQPyL/calibration/util.py +159 -0
- UQPyL/core/__init__.py +23 -0
- UQPyL/core/parameter_store.py +30 -0
- UQPyL/core/params.py +19 -0
- UQPyL/core/runtime.py +91 -0
- UQPyL/core/runtime_reader.py +38 -0
- UQPyL/core/runtime_session.py +22 -0
- UQPyL/core/runtime_storage.py +67 -0
- UQPyL/doe/__init__.py +14 -0
- UQPyL/doe/base.py +118 -0
- UQPyL/doe/methods/__init__.py +17 -0
- UQPyL/doe/methods/fast.py +132 -0
- UQPyL/doe/methods/full_fact.py +94 -0
- UQPyL/doe/methods/lhs.py +209 -0
- UQPyL/doe/methods/morris.py +132 -0
- UQPyL/doe/methods/random.py +46 -0
- UQPyL/doe/methods/saltelli.py +158 -0
- UQPyL/doe/methods/sobol.py +116 -0
- UQPyL/inference/__init__.py +16 -0
- UQPyL/inference/base.py +334 -0
- UQPyL/inference/chain.py +37 -0
- UQPyL/inference/methods/__init__.py +13 -0
- UQPyL/inference/methods/amh.py +205 -0
- UQPyL/inference/methods/demc.py +185 -0
- UQPyL/inference/methods/dream_zs.py +321 -0
- UQPyL/inference/methods/mh.py +156 -0
- UQPyL/inference/methods/mh_gibbs.py +189 -0
- UQPyL/inference/runtime/__init__.py +14 -0
- UQPyL/inference/runtime/reader.py +115 -0
- UQPyL/inference/runtime/result.py +246 -0
- UQPyL/inference/runtime/storage.py +194 -0
- UQPyL/inference/runtime/verbose.py +322 -0
- UQPyL/optimization/__init__.py +20 -0
- UQPyL/optimization/base.py +173 -0
- UQPyL/optimization/core/__init__.py +6 -0
- UQPyL/optimization/core/constraint.py +110 -0
- UQPyL/optimization/core/crowding_distance.py +56 -0
- UQPyL/optimization/core/ga_operator.py +105 -0
- UQPyL/optimization/core/non_dominated_sort.py +63 -0
- UQPyL/optimization/core/tournament.py +24 -0
- UQPyL/optimization/core/uniform_point.py +56 -0
- UQPyL/optimization/expensive/__init__.py +9 -0
- UQPyL/optimization/expensive/asmo.py +149 -0
- UQPyL/optimization/expensive/ego.py +153 -0
- UQPyL/optimization/expensive/moasmo.py +203 -0
- UQPyL/optimization/metric/__init__.py +3 -0
- UQPyL/optimization/metric/gd.py +16 -0
- UQPyL/optimization/metric/hv.py +138 -0
- UQPyL/optimization/metric/igd.py +16 -0
- UQPyL/optimization/moea/__init__.py +5 -0
- UQPyL/optimization/moea/moea_d.py +169 -0
- UQPyL/optimization/moea/nsga_ii.py +149 -0
- UQPyL/optimization/moea/nsga_iii.py +237 -0
- UQPyL/optimization/moea/rvea.py +172 -0
- UQPyL/optimization/population.py +186 -0
- UQPyL/optimization/runtime/__init__.py +4 -0
- UQPyL/optimization/runtime/reader.py +203 -0
- UQPyL/optimization/runtime/result.py +274 -0
- UQPyL/optimization/runtime/storage.py +224 -0
- UQPyL/optimization/runtime/verbose.py +384 -0
- UQPyL/optimization/soea/__init__.py +8 -0
- UQPyL/optimization/soea/abc.py +273 -0
- UQPyL/optimization/soea/csa.py +203 -0
- UQPyL/optimization/soea/de.py +129 -0
- UQPyL/optimization/soea/ga.py +115 -0
- UQPyL/optimization/soea/ml_sce_ua.py +183 -0
- UQPyL/optimization/soea/pso.py +174 -0
- UQPyL/optimization/soea/sce_ua.py +189 -0
- UQPyL/problem/__init__.py +36 -0
- UQPyL/problem/base.py +236 -0
- UQPyL/problem/decorators.py +52 -0
- UQPyL/problem/eval.py +23 -0
- UQPyL/problem/model_problem.py +225 -0
- UQPyL/problem/mop/DTLZ.py +658 -0
- UQPyL/problem/mop/ZDT.py +211 -0
- UQPyL/problem/mop/__init__.py +2 -0
- UQPyL/problem/problem.py +79 -0
- UQPyL/problem/sop/__init__.py +5 -0
- UQPyL/problem/sop/single_constraint_problem.py +50 -0
- UQPyL/problem/sop/single_simple_problem.py +519 -0
- UQPyL/problem/space.py +148 -0
- UQPyL/surrogate/__init__.py +20 -0
- UQPyL/surrogate/auto_tuner.py +212 -0
- UQPyL/surrogate/base.py +357 -0
- UQPyL/surrogate/gp/__init__.py +2 -0
- UQPyL/surrogate/gp/gaussian_process.py +263 -0
- UQPyL/surrogate/gp/kernel/__init__.py +6 -0
- UQPyL/surrogate/gp/kernel/base_kernel.py +34 -0
- UQPyL/surrogate/gp/kernel/c_kernel_.py +28 -0
- UQPyL/surrogate/gp/kernel/dot_kernel_.py +28 -0
- UQPyL/surrogate/gp/kernel/matern_kernel.py +95 -0
- UQPyL/surrogate/gp/kernel/rbf_kernel.py +34 -0
- UQPyL/surrogate/gp/kernel/rq_kernel.py +45 -0
- UQPyL/surrogate/kriging/__init__.py +2 -0
- UQPyL/surrogate/kriging/kernel/__init__.py +4 -0
- UQPyL/surrogate/kriging/kernel/base_kernel.py +29 -0
- UQPyL/surrogate/kriging/kernel/cubic_kernel.py +29 -0
- UQPyL/surrogate/kriging/kernel/exp_kernel.py +26 -0
- UQPyL/surrogate/kriging/kernel/guass_kernel.py +26 -0
- UQPyL/surrogate/kriging/kriging.py +356 -0
- UQPyL/surrogate/mars/__init__.py +1 -0
- UQPyL/surrogate/mars/core/__init__.py +4 -0
- UQPyL/surrogate/mars/core/_basis.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_basis.pxd +239 -0
- UQPyL/surrogate/mars/core/_basis.pyx +1114 -0
- UQPyL/surrogate/mars/core/_forward.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_forward.pxd +93 -0
- UQPyL/surrogate/mars/core/_forward.pyx +579 -0
- UQPyL/surrogate/mars/core/_knot_search.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_knot_search.pxd +94 -0
- UQPyL/surrogate/mars/core/_knot_search.pyx +677 -0
- UQPyL/surrogate/mars/core/_pruning.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_pruning.pxd +25 -0
- UQPyL/surrogate/mars/core/_pruning.pyx +215 -0
- UQPyL/surrogate/mars/core/_qr.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_qr.pxd +34 -0
- UQPyL/surrogate/mars/core/_qr.pyx +264 -0
- UQPyL/surrogate/mars/core/_record.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_record.pxd +66 -0
- UQPyL/surrogate/mars/core/_record.pyx +332 -0
- UQPyL/surrogate/mars/core/_types.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_types.pxd +5 -0
- UQPyL/surrogate/mars/core/_types.pyx +5 -0
- UQPyL/surrogate/mars/core/_util.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/mars/core/_util.pxd +23 -0
- UQPyL/surrogate/mars/core/_util.pyx +94 -0
- UQPyL/surrogate/mars/mars.py +733 -0
- UQPyL/surrogate/metric.py +54 -0
- UQPyL/surrogate/poly.py +62 -0
- UQPyL/surrogate/rbf/__init__.py +2 -0
- UQPyL/surrogate/rbf/kernel/__init__.py +14 -0
- UQPyL/surrogate/rbf/kernel/base_kernel.py +69 -0
- UQPyL/surrogate/rbf/kernel/cubic_kernel.py +30 -0
- UQPyL/surrogate/rbf/kernel/gaussian_kernel.py +19 -0
- UQPyL/surrogate/rbf/kernel/linear_kernel.py +26 -0
- UQPyL/surrogate/rbf/kernel/multiquadric_kernel.py +27 -0
- UQPyL/surrogate/rbf/kernel/thin_plate_spline_kernel.py +34 -0
- UQPyL/surrogate/rbf/radial_basis_function.py +183 -0
- UQPyL/surrogate/regression/__init__.py +2 -0
- UQPyL/surrogate/regression/lasso/__init__.py +8 -0
- UQPyL/surrogate/regression/lasso/cython_utils.pxd +54 -0
- UQPyL/surrogate/regression/lasso/cython_utils.pyx +472 -0
- UQPyL/surrogate/regression/lasso/lasso.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/regression/lasso/lasso_fast.pyx +852 -0
- UQPyL/surrogate/regression/linear_regression.py +235 -0
- UQPyL/surrogate/regression/polynomial_regression.py +135 -0
- UQPyL/surrogate/scaler.py +86 -0
- UQPyL/surrogate/setting.py +352 -0
- UQPyL/surrogate/split.py +60 -0
- UQPyL/surrogate/svr/__init__.py +1 -0
- UQPyL/surrogate/svr/core/__init__.py +7 -0
- UQPyL/surrogate/svr/core/libsvm_interface.cp313-win_amd64.pyd +0 -0
- UQPyL/surrogate/svr/core/libsvm_interface.cpp +145 -0
- UQPyL/surrogate/svr/core/svm.cpp +3313 -0
- UQPyL/surrogate/svr/core/svm.h +106 -0
- UQPyL/surrogate/svr/support_vector_machine.py +196 -0
- UQPyL/surrogate/util/__init__.py +0 -0
- UQPyL/surrogate/util/boxmin.py +118 -0
- UQPyL/surrogate/util/lbfgsb.py +45 -0
- UQPyL/viz/__init__.py +23 -0
- UQPyL/viz/analysis.py +68 -0
- UQPyL/viz/common.py +25 -0
- UQPyL/viz/inference.py +135 -0
- UQPyL/viz/optimization.py +244 -0
- UQPyL/viz/surrogate.py +41 -0
- uqpyl-2.1.6.dist-info/METADATA +342 -0
- uqpyl-2.1.6.dist-info/RECORD +202 -0
- uqpyl-2.1.6.dist-info/WHEEL +5 -0
- uqpyl-2.1.6.dist-info/licenses/LICENSE.md +21 -0
- 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
|
+
|
UQPyL/analysis/delta.py
ADDED
UQPyL/analysis/fast.py
ADDED
UQPyL/analysis/mars.py
ADDED
|
@@ -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)
|