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 ADDED
@@ -0,0 +1,5 @@
1
+ __version__ = '0.1.0'
2
+
3
+ from .gpc_model import *
4
+ from .gpc_basis import *
5
+ from .multiindex import *
@@ -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
+
@@ -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