gPCE-model 0.1.0__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.
- gPCE_model/__init__.py +5 -0
- gPCE_model/gpc_basis.py +189 -0
- gPCE_model/gpc_model.py +447 -0
- gPCE_model/multiindex.py +118 -0
- gpce_model-0.1.0.dist-info/METADATA +110 -0
- gpce_model-0.1.0.dist-info/RECORD +9 -0
- gpce_model-0.1.0.dist-info/WHEEL +5 -0
- gpce_model-0.1.0.dist-info/licenses/LICENSE +675 -0
- gpce_model-0.1.0.dist-info/top_level.txt +2 -0
gPCE_model/__init__.py
ADDED
gPCE_model/gpc_basis.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
""" Generic class for generalized polynomial chaos basis functions"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .multiindex import multiindex
|
|
5
|
+
import uncertain_variables as uv
|
|
6
|
+
|
|
7
|
+
class GpcBasis:
|
|
8
|
+
""" Generic class for generalized polynomial chaos basis functions
|
|
9
|
+
|
|
10
|
+
Attributes
|
|
11
|
+
----------
|
|
12
|
+
m : int
|
|
13
|
+
number of random variables
|
|
14
|
+
|
|
15
|
+
syschars : str or list of str
|
|
16
|
+
system character(s) of the gpc basis (e.g., 'H' for Hermite)
|
|
17
|
+
|
|
18
|
+
p : int
|
|
19
|
+
maximum total degree of the basis functions
|
|
20
|
+
|
|
21
|
+
I : numpy.array
|
|
22
|
+
multiindex set defining the basis functions
|
|
23
|
+
|
|
24
|
+
Methods
|
|
25
|
+
-------
|
|
26
|
+
__init__(Q, p=0, I="default", full_tensor=False, **kwargs)
|
|
27
|
+
Initialize the gpc basis with given parameters
|
|
28
|
+
|
|
29
|
+
__repr__()
|
|
30
|
+
Set how the gpc basis looks when printed
|
|
31
|
+
|
|
32
|
+
size()
|
|
33
|
+
Return the size of the multiindex set
|
|
34
|
+
|
|
35
|
+
evaluate(xi, dual=False)
|
|
36
|
+
Evaluate the gpc basis functions at given points xi
|
|
37
|
+
|
|
38
|
+
norm(do_sqrt=True)
|
|
39
|
+
Compute the norm of the basis functions """
|
|
40
|
+
|
|
41
|
+
# ---------------------Initialization---------------------------------------------------
|
|
42
|
+
def __init__(self, Q, p=0, I="default", full_tensor=False, **kwargs):
|
|
43
|
+
""" Initialize the gpc basis with given parameters
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
Q : VariableSet
|
|
48
|
+
VariableSet object defining the probabilistic variables
|
|
49
|
+
|
|
50
|
+
p : int, optional
|
|
51
|
+
Maximum total degree of the basis functions, by default 0
|
|
52
|
+
|
|
53
|
+
I : str, optional
|
|
54
|
+
Multiindex set defining the basis functions, by default "default"
|
|
55
|
+
|
|
56
|
+
full_tensor : bool, optional
|
|
57
|
+
If True, use full tensor product basis up to degree p, by default False """
|
|
58
|
+
|
|
59
|
+
m = Q.num_params()
|
|
60
|
+
self.m = m
|
|
61
|
+
|
|
62
|
+
self.syschars = Q.get_gpc_syschars()
|
|
63
|
+
self.p = p
|
|
64
|
+
|
|
65
|
+
if I == "default":
|
|
66
|
+
self.I = multiindex(self.m, p, full_tensor=full_tensor)
|
|
67
|
+
else:
|
|
68
|
+
self.I = I
|
|
69
|
+
|
|
70
|
+
# ---------------------set how gpc looks when printed ---------------------------------------------------
|
|
71
|
+
def __repr__(self):
|
|
72
|
+
""" Return a string representation of the GpcBasis object
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
repr_string : str
|
|
77
|
+
String representation of the GpcBasis object showing its attributes and their values """
|
|
78
|
+
|
|
79
|
+
attrs = vars(self)
|
|
80
|
+
repr_string = ', '.join("%s: %s" % item for item in attrs.items())
|
|
81
|
+
return repr_string
|
|
82
|
+
|
|
83
|
+
# ----------------------------------------- size --------------------------------------------------
|
|
84
|
+
def size(self):
|
|
85
|
+
""" Return the size of the multiindex set
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
size : tuple
|
|
90
|
+
Size of the multiindex set I """
|
|
91
|
+
|
|
92
|
+
size = self.I.shape
|
|
93
|
+
return size
|
|
94
|
+
|
|
95
|
+
# ----------------------------Evaluate basis functions ---------------------------------------------------
|
|
96
|
+
def evaluate(self, xi, dual=False):
|
|
97
|
+
""" Evaluate the gpc basis functions at given points xi
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
xi : array_like
|
|
102
|
+
Points at which to evaluate the basis functions
|
|
103
|
+
|
|
104
|
+
dual : bool, optional
|
|
105
|
+
If True, evaluate dual basis functions, by default False
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
y_j_alpha : numpy.array
|
|
110
|
+
Evaluated basis functions at points xi """
|
|
111
|
+
|
|
112
|
+
syschars = self.syschars
|
|
113
|
+
I = self.I
|
|
114
|
+
m = self.m
|
|
115
|
+
M = self.I.shape[0]
|
|
116
|
+
if xi.ndim == 1:
|
|
117
|
+
xi = xi.reshape(-1, 1)
|
|
118
|
+
k = xi.shape[0]
|
|
119
|
+
deg = max(self.I.flatten())
|
|
120
|
+
|
|
121
|
+
p = np.zeros([k, m, deg + 2])
|
|
122
|
+
p[:, :, 0] = np.zeros(xi.shape)
|
|
123
|
+
p[:, :, 1] = np.ones(xi.shape)
|
|
124
|
+
|
|
125
|
+
if len(syschars) == 1:
|
|
126
|
+
polysys = uv.syschar_to_polysys(syschars)
|
|
127
|
+
r = polysys.recur_coeff(deg)
|
|
128
|
+
for d in range(deg):
|
|
129
|
+
p[:, :, d + 2] = (r[d, 0] + xi * r[d, 1]) * p[:, :, d + 1] - r[d, 2] * p[:, :, d]
|
|
130
|
+
else:
|
|
131
|
+
for j, syschar in enumerate(syschars):
|
|
132
|
+
polysys = uv.syschar_to_polysys(syschar)
|
|
133
|
+
r = polysys.recur_coeff(deg)
|
|
134
|
+
for d in range(deg):
|
|
135
|
+
p[:, j, d + 2] = (r[d, 0] + xi[:, j] * r[d, 1]) * p[:, j, d + 1] - r[d, 2] * p[:, j, d]
|
|
136
|
+
|
|
137
|
+
y_j_alpha = np.ones([k, M])
|
|
138
|
+
for j in range(m):
|
|
139
|
+
y_j_alpha = y_j_alpha * p[:, j, I[:, j] + 1]
|
|
140
|
+
|
|
141
|
+
if dual:
|
|
142
|
+
nrm2 = self.norm(do_sqrt=False)
|
|
143
|
+
y_j_alpha = (y_j_alpha / nrm2.reshape(-1, 1)).transpose()
|
|
144
|
+
return y_j_alpha
|
|
145
|
+
|
|
146
|
+
# ------------------------Compute the norm of the basis functions-----------------------
|
|
147
|
+
def norm(self, do_sqrt=True):
|
|
148
|
+
""" Compute the norm of the basis functions
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
do_sqrt : bool, optional
|
|
153
|
+
If True, return the square root of the norm, by default True
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
norm_I : numpy.array
|
|
158
|
+
Norm of the basis functions """
|
|
159
|
+
|
|
160
|
+
syschars = self.syschars
|
|
161
|
+
I = self.I
|
|
162
|
+
m = self.m
|
|
163
|
+
M = self.I.shape[0]
|
|
164
|
+
|
|
165
|
+
if syschars == syschars.lower():
|
|
166
|
+
norm_I = np.ones([M, 1])
|
|
167
|
+
return norm_I
|
|
168
|
+
|
|
169
|
+
if len(syschars) == 1:
|
|
170
|
+
# max degree of univariate polynomials
|
|
171
|
+
deg = max(self.I.flatten())
|
|
172
|
+
polysys = uv.syschar_to_polysys(syschars)
|
|
173
|
+
nrm = polysys.sqnorm(range(deg + 1))
|
|
174
|
+
norm2_I = np.prod(nrm[I].reshape(I.shape), axis=1)
|
|
175
|
+
|
|
176
|
+
else:
|
|
177
|
+
norm2_I = np.ones([M])
|
|
178
|
+
for j in range(m):
|
|
179
|
+
deg = max(I[:, j])
|
|
180
|
+
polysys = uv.syschar_to_polysys(syschars[j])
|
|
181
|
+
nrm2 = polysys.sqnorm(np.arange(deg + 1))
|
|
182
|
+
norm2_I = norm2_I * nrm2[I[:, j]]
|
|
183
|
+
if do_sqrt:
|
|
184
|
+
norm_I = np.sqrt(norm2_I)
|
|
185
|
+
else:
|
|
186
|
+
norm_I = norm2_I
|
|
187
|
+
|
|
188
|
+
return norm_I
|
|
189
|
+
|
gPCE_model/gpc_model.py
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
""" Generic class for generalized polynomial chaos expansion (gPCE) model"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from .gpc_basis import GpcBasis
|
|
6
|
+
from sklearn.metrics import mean_squared_error
|
|
7
|
+
from scipy.sparse import csr_matrix
|
|
8
|
+
import copy
|
|
9
|
+
import shap
|
|
10
|
+
|
|
11
|
+
# ##########################################################################################
|
|
12
|
+
# GPC MODEL
|
|
13
|
+
# ‘Generalized Polynomial Chaos Expansion’
|
|
14
|
+
# ##########################################################################################
|
|
15
|
+
class GpcModel:
|
|
16
|
+
""" Generic class for generalized polynomial chaos expansion (gPCE) model
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
basis : GpcBasis
|
|
21
|
+
gPCE basis functions
|
|
22
|
+
|
|
23
|
+
Q : VariableSet
|
|
24
|
+
VariableSet object defining the probabilistic variables
|
|
25
|
+
|
|
26
|
+
u_alpha_i : numpy.array
|
|
27
|
+
Coefficients of the gPCE model
|
|
28
|
+
|
|
29
|
+
p : int
|
|
30
|
+
Maximum total degree of the basis functions
|
|
31
|
+
|
|
32
|
+
Methods
|
|
33
|
+
-------
|
|
34
|
+
__init__(Q, p=0, I="default", full_tensor=False, **kwargs)
|
|
35
|
+
Initialize the gPCE model with given parameters
|
|
36
|
+
|
|
37
|
+
__repr__()
|
|
38
|
+
Return a string representation of the GpcModel object
|
|
39
|
+
|
|
40
|
+
compute_coeffs_by_regression(q_k_j, u_k_i)
|
|
41
|
+
Compute gPCE coefficients using regression method
|
|
42
|
+
|
|
43
|
+
compute_coeffs_by_projection(q_k_j, u_k_i, w_k)
|
|
44
|
+
Compute gPCE coefficients using projection method
|
|
45
|
+
|
|
46
|
+
predict(q_k_j)
|
|
47
|
+
Predict model response at given parameter points q_k_j
|
|
48
|
+
|
|
49
|
+
train(q_train, y_train)
|
|
50
|
+
Train the gPCE model using training data
|
|
51
|
+
|
|
52
|
+
train_and_evaluate(q_train, y_train, q_val, y_val)
|
|
53
|
+
Train the gPCE model and evaluate on validation data
|
|
54
|
+
|
|
55
|
+
mean()
|
|
56
|
+
Compute the mean of the gPCE model
|
|
57
|
+
|
|
58
|
+
variance()
|
|
59
|
+
Compute the variance of the gPCE model
|
|
60
|
+
|
|
61
|
+
compute_partial_vars(model_obj, max_index=1)
|
|
62
|
+
Compute partial variances and Sobol indices up to specified order
|
|
63
|
+
|
|
64
|
+
get_shap_values(predict_fn, q, forced=False, explainer_type="kernelexplainer")
|
|
65
|
+
Compute SHAP values for the gPCE model predictions
|
|
66
|
+
|
|
67
|
+
to_jsonld(model_id='v0')
|
|
68
|
+
Export the gPCE model metadata to JSON-LD format """
|
|
69
|
+
|
|
70
|
+
def __init__(self, Q, p=0, I="default", full_tensor=False, **kwargs):
|
|
71
|
+
""" Initialize the gPCE model with given parameters
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
Q : VariableSet
|
|
76
|
+
VariableSet object defining the probabilistic variables
|
|
77
|
+
|
|
78
|
+
p : int, optional
|
|
79
|
+
Maximum total degree of the basis functions, by default 0
|
|
80
|
+
|
|
81
|
+
I : str, optional
|
|
82
|
+
Multiindex set defining the basis functions, by default "default"
|
|
83
|
+
|
|
84
|
+
full_tensor : bool, optional
|
|
85
|
+
If True, use full tensor product basis up to degree p, by default False"""
|
|
86
|
+
|
|
87
|
+
self.basis = GpcBasis(Q, p=p, I="default", full_tensor=False)
|
|
88
|
+
self.Q = Q
|
|
89
|
+
self.u_alpha_i = []
|
|
90
|
+
self.p = p
|
|
91
|
+
|
|
92
|
+
def __repr__(self):
|
|
93
|
+
""" Return a string representation of the GpcModel object
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
repr_string : str
|
|
98
|
+
String representation of the GpcModel object showing its attributes and their values """
|
|
99
|
+
|
|
100
|
+
attrs = vars(self)
|
|
101
|
+
repr_string = ', '.join("%s: %s" % item for item in attrs.items())
|
|
102
|
+
return repr_string
|
|
103
|
+
|
|
104
|
+
def compute_coeffs_by_regression(self, q_k_j, u_k_i):
|
|
105
|
+
""" Compute gPCE coefficients using regression method
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
q_k_j : array_like
|
|
110
|
+
Input parameter samples
|
|
111
|
+
|
|
112
|
+
u_k_i : array_like
|
|
113
|
+
Responses corresponding to input samples"""
|
|
114
|
+
|
|
115
|
+
xi_k_j = self.Q.params2germ(q_k_j)
|
|
116
|
+
phi_k_alpha = self.basis.evaluate(xi_k_j)
|
|
117
|
+
u_alpha_i = np.matmul(np.linalg.pinv(phi_k_alpha), u_k_i)
|
|
118
|
+
self.u_alpha_i = u_alpha_i
|
|
119
|
+
|
|
120
|
+
def compute_coeffs_by_projection(self, q_k_j, u_k_i, w_k):
|
|
121
|
+
""" Compute gPCE coefficients using projection method
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
q_k_j : array_like
|
|
126
|
+
Input parameter samples
|
|
127
|
+
|
|
128
|
+
u_k_i : array_like
|
|
129
|
+
Responses corresponding to input samples
|
|
130
|
+
|
|
131
|
+
w_k : array_like
|
|
132
|
+
Weights for the projection method"""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
xi_k_j = self.Q.params2germ(q_k_j)
|
|
136
|
+
phi_k_alpha = self.basis.evaluate(xi_k_j)
|
|
137
|
+
u_alpha_i = np.matmul(phi_k_alpha, np.diag(w_k), u_k_i)
|
|
138
|
+
self.u_alpha_i = u_alpha_i
|
|
139
|
+
|
|
140
|
+
def predict(self, q_k_j):
|
|
141
|
+
""" Predict model response at given parameter points q_k_j
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
q_k_j : array_like
|
|
146
|
+
Input parameter samples
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
u_k_i : numpy.array
|
|
151
|
+
Predicted responses corresponding to input samples"""
|
|
152
|
+
|
|
153
|
+
xi_k_j = self.Q.params2germ(q_k_j)
|
|
154
|
+
phi_k_alpha = self.basis.evaluate(xi_k_j)
|
|
155
|
+
u_k_i = np.matmul(phi_k_alpha, self.u_alpha_i)
|
|
156
|
+
if u_k_i.ndim == 1: # Check if the array is 1D
|
|
157
|
+
u_k_i = u_k_i.reshape(1, -1) # Reshape to (1, x)
|
|
158
|
+
return u_k_i
|
|
159
|
+
|
|
160
|
+
def train(self, q_train, y_train):
|
|
161
|
+
""" Train the gPCE model using training data
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
q_train : array_like
|
|
166
|
+
Training input parameter samples
|
|
167
|
+
|
|
168
|
+
y_train : array_like
|
|
169
|
+
Training responses corresponding to input samples
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
mse : float
|
|
174
|
+
Mean squared error on the training data """
|
|
175
|
+
|
|
176
|
+
self.compute_coeffs_by_regression(q_train, y_train)
|
|
177
|
+
y = self.predict(q_train)
|
|
178
|
+
mse = mean_squared_error(y_train, y)
|
|
179
|
+
print(f"Validation MSE: {mse:.4f}")
|
|
180
|
+
return mse
|
|
181
|
+
|
|
182
|
+
def train_and_evaluate(self, q_train, y_train, q_val, y_val):
|
|
183
|
+
""" Train the gPCE model and evaluate on validation data
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
q_train : array_like
|
|
188
|
+
Training input parameter samples
|
|
189
|
+
|
|
190
|
+
y_train : array_like
|
|
191
|
+
Training responses corresponding to input samples
|
|
192
|
+
|
|
193
|
+
q_val : array_like
|
|
194
|
+
Validation input parameter samples
|
|
195
|
+
|
|
196
|
+
y_val : array_like
|
|
197
|
+
Validation responses corresponding to input samples
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
mse_tr : float
|
|
202
|
+
Mean squared error on the training data
|
|
203
|
+
|
|
204
|
+
mse_vl : float
|
|
205
|
+
Mean squared error on the validation data """
|
|
206
|
+
|
|
207
|
+
self.compute_coeffs_by_regression(q_train, y_train)
|
|
208
|
+
y_tr_pred = self.predict(q_train)
|
|
209
|
+
y_vl_pred = self.predict(q_val)
|
|
210
|
+
mse_tr = mean_squared_error(y_train, y_tr_pred)
|
|
211
|
+
mse_vl = mean_squared_error(y_val, y_vl_pred)
|
|
212
|
+
return mse_tr, mse_vl
|
|
213
|
+
|
|
214
|
+
def mean(self):
|
|
215
|
+
""" Compute the mean of the gPCE model
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
means : numpy.array
|
|
220
|
+
Mean of the gPCE model """
|
|
221
|
+
|
|
222
|
+
coeffs = self.u_alpha_i
|
|
223
|
+
means = coeffs[0, :]
|
|
224
|
+
return means
|
|
225
|
+
|
|
226
|
+
def variance(self):
|
|
227
|
+
""" Compute the variance of the gPCE model
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
variances : numpy.array
|
|
232
|
+
Variance of the gPCE model """
|
|
233
|
+
|
|
234
|
+
coeffs = self.u_alpha_i
|
|
235
|
+
variances = np.sum(coeffs[1:, :]**2, axis=0)
|
|
236
|
+
return variances
|
|
237
|
+
|
|
238
|
+
def compute_partial_vars(self, model_obj, max_index=1):
|
|
239
|
+
""" Compute partial variances and Sobol indices up to specified order
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
model_obj : GpcModel
|
|
244
|
+
gPCE model object
|
|
245
|
+
|
|
246
|
+
max_index : int, optional
|
|
247
|
+
Maximum Sobol order to consider, by default 1
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
partial_var_df : pandas.DataFrame
|
|
252
|
+
DataFrame of partial variances for each QoI and Sobol index
|
|
253
|
+
|
|
254
|
+
sobol_index_df : pandas.DataFrame
|
|
255
|
+
DataFrame of Sobol indices for each QoI and Sobol index
|
|
256
|
+
|
|
257
|
+
total_var : numpy.array
|
|
258
|
+
Total variance of each QoI """
|
|
259
|
+
|
|
260
|
+
a_i_alpha = model_obj.model.u_alpha_i.T
|
|
261
|
+
V_a_orig = model_obj.model.basis
|
|
262
|
+
QoI_names = model_obj.QoI_names
|
|
263
|
+
|
|
264
|
+
# Get the multiindex set and convert to a logical array
|
|
265
|
+
V_a = copy.deepcopy(V_a_orig)
|
|
266
|
+
I_a = V_a.I #210x6
|
|
267
|
+
I_s = I_a.astype(bool) #210x6
|
|
268
|
+
|
|
269
|
+
# Identify the alpha=0 term (mean) to exclude from total variance
|
|
270
|
+
is_alpha0 = np.all(I_a == 0, axis=1)
|
|
271
|
+
|
|
272
|
+
# Calculate total variance excluding alpha=0
|
|
273
|
+
a_i_alpha_nonzero = a_i_alpha[:, ~is_alpha0]
|
|
274
|
+
sqr_norm_nonzero = V_a_orig.norm(do_sqrt=False)[~is_alpha0]
|
|
275
|
+
var_row_nonzero = np.multiply(a_i_alpha_nonzero**2, sqr_norm_nonzero.transpose())
|
|
276
|
+
total_var = np.sum(var_row_nonzero, axis=1)[:, np.newaxis]
|
|
277
|
+
|
|
278
|
+
# Get rid of indices with too high an order and the mean
|
|
279
|
+
sobol_order = np.sum(I_s, axis=1)
|
|
280
|
+
ind = (sobol_order > 0) & (sobol_order <= max_index)
|
|
281
|
+
|
|
282
|
+
I_a = I_a[ind, :]
|
|
283
|
+
I_s = I_s[ind, :]
|
|
284
|
+
a_i_alpha = a_i_alpha[:, ind]
|
|
285
|
+
V_a.I = I_a
|
|
286
|
+
|
|
287
|
+
# Calculate variance of the a_i_alpha * F_alpha polynomials
|
|
288
|
+
sqr_norm = V_a.norm(do_sqrt=False)
|
|
289
|
+
var_row = np.multiply(a_i_alpha**2, sqr_norm.transpose())
|
|
290
|
+
|
|
291
|
+
# Get unique rows from Sobol indices
|
|
292
|
+
I_s, _, ind2 = np.unique(I_s, axis=0, return_index=True, return_inverse=True)
|
|
293
|
+
M = V_a.size()[0]
|
|
294
|
+
U = csr_matrix((np.ones(M), (np.arange(M), ind2)), shape=(M, len(I_s)))
|
|
295
|
+
|
|
296
|
+
# Compute the partial variance
|
|
297
|
+
partial_var = var_row @ U.toarray()
|
|
298
|
+
|
|
299
|
+
# Sort partial variances by Sobol order
|
|
300
|
+
order_criterion = np.column_stack([np.sum(I_s, axis=1), np.flip(I_s, axis=1)])
|
|
301
|
+
sortind = np.lexsort([order_criterion[:, i] for i in reversed(range(order_criterion.shape[1]))])
|
|
302
|
+
I_s = I_s[sortind]
|
|
303
|
+
partial_var = partial_var[:, sortind]
|
|
304
|
+
|
|
305
|
+
indexed_param_names = gpc_multiindex2param_names(I_s, self.Q.param_names())
|
|
306
|
+
|
|
307
|
+
# Compute the Sobol indices
|
|
308
|
+
sobol_index = np.divide(partial_var, total_var)
|
|
309
|
+
|
|
310
|
+
partial_var_df = pd.DataFrame(partial_var, columns=indexed_param_names, index=QoI_names)
|
|
311
|
+
sobol_index_df = pd.DataFrame(sobol_index, columns=indexed_param_names, index=QoI_names)
|
|
312
|
+
|
|
313
|
+
return partial_var_df, sobol_index_df, total_var
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_shap_values(self, predict_fn, q, forced=False, explainer_type="kernelexplainer"):
|
|
317
|
+
""" Compute SHAP values for the gPCE model predictions
|
|
318
|
+
|
|
319
|
+
Parameters
|
|
320
|
+
----------
|
|
321
|
+
predict_fn : function
|
|
322
|
+
Prediction function of the gPCE model
|
|
323
|
+
|
|
324
|
+
q : array_like
|
|
325
|
+
Input parameter samples
|
|
326
|
+
|
|
327
|
+
forced : bool, optional
|
|
328
|
+
If True, force re-computation of SHAP values, by default False
|
|
329
|
+
|
|
330
|
+
explainer_type : str, optional
|
|
331
|
+
Type of SHAP explainer to use, by default "kernelexplainer"
|
|
332
|
+
|
|
333
|
+
Returns
|
|
334
|
+
-------
|
|
335
|
+
shap_values : numpy.array
|
|
336
|
+
SHAP values for the gPCE model predictions """
|
|
337
|
+
|
|
338
|
+
if explainer_type == "kernelexplainer":
|
|
339
|
+
if hasattr(self, 'explainer') == False or forced == True:
|
|
340
|
+
explainer = shap.KernelExplainer(predict_fn, q)
|
|
341
|
+
self.explainer = explainer
|
|
342
|
+
shap_values = self.explainer(q)
|
|
343
|
+
return shap_values
|
|
344
|
+
|
|
345
|
+
def to_jsonld(self, model_id='v0'):
|
|
346
|
+
""" Export the gPCE model metadata to JSON-LD format
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
model_id : str, optional
|
|
351
|
+
Identifier for the model, by default 'v0'
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
jsonld : dict
|
|
356
|
+
JSON-LD representation of the gPCE model metadata"""
|
|
357
|
+
|
|
358
|
+
jsonld = {
|
|
359
|
+
"@context": {
|
|
360
|
+
"mls": "https://ml-schema.github.io/documentation/mls.html",
|
|
361
|
+
"rdfs": "http://www.w3.org/2000/01/rdf-schema#"
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
"@id": f"https://example.org/models/{model_id}",
|
|
365
|
+
"@type": "mls:Model",
|
|
366
|
+
"mls:implementsAlgorithm": {
|
|
367
|
+
"@id": 'https://en.wikipedia.org/wiki/Generalized_polynomial_chaos',
|
|
368
|
+
"@type": "mls:Algorithm",
|
|
369
|
+
"rdfs:label": "Generalized Polynomial Chaos",
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
"mls:hasHyperParameter": [
|
|
373
|
+
{
|
|
374
|
+
"@type": "mls:HyperParameterSetting",
|
|
375
|
+
"mls:hasParameterName": "p",
|
|
376
|
+
"mls:hasParameterValue": str(self.p)
|
|
377
|
+
}
|
|
378
|
+
],
|
|
379
|
+
|
|
380
|
+
"mls:hasInput": [
|
|
381
|
+
{
|
|
382
|
+
"@type": "mls:Feature",
|
|
383
|
+
"mls:featureName": name,
|
|
384
|
+
"mls:hasDistribution": {
|
|
385
|
+
"@type": "mls:Distribution",
|
|
386
|
+
"mls:distributionType": dist.get_dist_type(),
|
|
387
|
+
"mls:params": str(dist.get_dist_params()),
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
for (name, dist) in self.Q.params.items()
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return jsonld
|
|
395
|
+
|
|
396
|
+
# ##########################################################################################
|
|
397
|
+
# UTILS
|
|
398
|
+
# ##########################################################################################
|
|
399
|
+
|
|
400
|
+
def gpc_multiindex2param_names(I_s, param_names):
|
|
401
|
+
""" Translates a multi-index set to a list of indexed parameter names.
|
|
402
|
+
|
|
403
|
+
Parameters
|
|
404
|
+
----------
|
|
405
|
+
I_s : array_like
|
|
406
|
+
Multi-index set (rows are indices, columns correspond to dimensions).
|
|
407
|
+
|
|
408
|
+
param_names : list of str
|
|
409
|
+
List of parameter names.
|
|
410
|
+
|
|
411
|
+
Returns
|
|
412
|
+
-------
|
|
413
|
+
indexed_param_names : list of str
|
|
414
|
+
List of indexed parameter names. """
|
|
415
|
+
|
|
416
|
+
indexed_param_names = [None] * I_s.shape[0] # Initialize output list
|
|
417
|
+
max_d = np.max(np.sum(I_s, axis=1)) # Maximum Sobol order
|
|
418
|
+
min_d = np.min(np.sum(I_s, axis=1)) # Minimum Sobol order
|
|
419
|
+
|
|
420
|
+
ind_start = 0
|
|
421
|
+
for i in range(min_d, max_d + 1):
|
|
422
|
+
# Extract rows corresponding to the current degree (i)
|
|
423
|
+
I_s_i = I_s[np.sum(I_s, axis=1) == i, :]
|
|
424
|
+
l_i = I_s_i.shape[0]
|
|
425
|
+
|
|
426
|
+
# Find indices where I_s_i equals 1
|
|
427
|
+
indr, indc = np.where(I_s[np.sum(I_s, axis=1) == i, :] == 1)
|
|
428
|
+
|
|
429
|
+
# Sort and reshape indices into groups
|
|
430
|
+
IND = np.column_stack((indr, indc))
|
|
431
|
+
IND = IND[np.argsort(IND[:, 0])]
|
|
432
|
+
IND = IND[:, 1].reshape((l_i, i)) # Extract column indices
|
|
433
|
+
|
|
434
|
+
# Map indices to parameter names
|
|
435
|
+
param_names_i = ["".join([param_names[idx] for idx in row]) for row in IND]
|
|
436
|
+
|
|
437
|
+
# Add spaces between parameter names if degree > 1
|
|
438
|
+
if i != 1:
|
|
439
|
+
p_names_i = []
|
|
440
|
+
for row in IND:
|
|
441
|
+
p_names_i.append(" ".join(param_names[idx] for idx in row))
|
|
442
|
+
indexed_param_names[ind_start:ind_start + l_i] = p_names_i
|
|
443
|
+
else:
|
|
444
|
+
indexed_param_names[ind_start:ind_start + l_i] = param_names_i
|
|
445
|
+
|
|
446
|
+
ind_start += l_i
|
|
447
|
+
return indexed_param_names
|