From 671fad7e02364ed9198c9b3bf686de63a73aa6cd Mon Sep 17 00:00:00 2001 From: Farid Mohammadi <farid.mohammadi@iws.uni-stuttgart.de> Date: Wed, 14 Sep 2022 16:08:40 +0200 Subject: [PATCH] [examples] add OHagan example. --- examples/OHagan-function/OHagan.py | 58 +++++ examples/OHagan-function/data/M.csv | 15 ++ .../data/sparse_solver_comparison.py | 240 ++++++++++++++++++ .../OHagan-function/data/valid_outputs.npy | Bin 0 -> 928 bytes .../OHagan-function/data/valid_samples.npy | Bin 0 -> 12128 bytes examples/OHagan-function/test_OHagan.py | 197 ++++++++++++++ 6 files changed, 510 insertions(+) create mode 100644 examples/OHagan-function/OHagan.py create mode 100644 examples/OHagan-function/data/M.csv create mode 100644 examples/OHagan-function/data/sparse_solver_comparison.py create mode 100644 examples/OHagan-function/data/valid_outputs.npy create mode 100644 examples/OHagan-function/data/valid_samples.npy create mode 100644 examples/OHagan-function/test_OHagan.py diff --git a/examples/OHagan-function/OHagan.py b/examples/OHagan-function/OHagan.py new file mode 100644 index 000000000..71a58e092 --- /dev/null +++ b/examples/OHagan-function/OHagan.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 19 08:56:21 2018 + +@author: farid +""" +import numpy as np + + +def OHagan(xx, *args): + """ + Oakley & O'Hagan (2004) Function + + This function's a-coefficients are chosen so that 5 of the input variables + contribute significantly to the output variance, 5 have a much smaller + effect, and the remaining 5 have almost no effect on the output variance. + + O'Hagan, 2004, Probabilistic sensitivity analysis of complex models: a + Bayesian approach J. R. Statist. Soc. B (2004) 66, Part 3, pp. 751-769. + + Parameters + ---------- + xx : array of shape (n_samples, n_params) + Input parameter sets. + + Returns + ------- + output: dict + Ourput value. + + """ + n_samples, n_params = xx.shape + + # Read M + M = np.loadtxt('data/M.csv', delimiter=',') + + a_1 = np.array([0.0118, 0.0456, 0.2297, 0.0393, 0.1177, 0.3865, 0.3897, + 0.6061, 0.6159, 0.4005, 1.0741, 1.1474, 0.7880, 1.1242, + 1.1982]) + a_2 = np.array([0.4341, 0.0887, 0.0512, 0.3233, 0.1489, 1.0360, 0.9892, + 0.9672, 0.8977, 0.8083, 1.8426, 2.4712, 2.3946, 2.0045, + 2.2621]) + a_3 = np.array([0.1044, 0.2057, 0.0774, 0.2730, 0.1253, 0.7526, 0.8570, + 1.0331, 0.8388, 0.7970, 2.2145, 2.0382, 2.4004, 2.0541, + 1.9845]) + + # Compute response + xx_M = np.dot(xx, M) + last_term = np.sum(xx_M * xx, axis=1) + + y = np.dot(xx, a_1) + np.dot(np.sin(xx), a_2) + np.dot(np.cos(xx), a_3) + y += last_term + + # Prepare output dict using standard bayesvalidrox format + output = {'x_values': np.zeros(1), 'Z': y.T} + + return output diff --git a/examples/OHagan-function/data/M.csv b/examples/OHagan-function/data/M.csv new file mode 100644 index 000000000..98ec96817 --- /dev/null +++ b/examples/OHagan-function/data/M.csv @@ -0,0 +1,15 @@ +-0.022482886,-0.18501666,0.13418263,0.36867264,0.17172785,0.13651143,-0.44034404,-0.081422854,0.71321025,-0.44361072,0.50383394,-0.024101458,-0.045939684,0.21666181,0.055887417 +0.2565963,0.053792287,0.25800381,0.23795905,-0.59125756,-0.081627077,-0.28749073,0.41581639,0.49752241,0.083893165,-0.11056683,0.033222351,-0.13979497,-0.031020556,-0.22318721 +-0.055999811,0.19542252,0.095529005,-0.2862653,-0.14441303,0.22369356,0.14527412,0.28998481,0.2310501,-0.31929879,-0.29039128,-0.20956898,0.43139047,0.024429152,0.044904409 +0.66448103,0.43069872,0.29924645,-0.16202441,-0.31479544,-0.39026802,0.17679822,0.057952663,0.17230342,0.13466011,-0.3527524,0.25146896,-0.018810529,0.36482392,-0.32504618 +-0.121278,0.12463327,0.10656519,0.046562296,-0.21678617,0.19492172,-0.065521126,0.024404669,-0.09682886,0.19366196,0.33354757,0.31295994,-0.083615456,-0.25342082,0.37325717 +-0.2837623,-0.32820154,-0.10496068,-0.22073452,-0.13708154,-0.14426375,-0.11503319,0.22424151,-0.030395022,-0.51505615,0.017254978,0.038957118,0.36069184,0.30902452,0.050030193 +-0.077875893,0.003745656,0.88685604,-0.26590028,-0.079325357,-0.042734919,-0.18653782,-0.35604718,-0.17497421,0.088699956,0.40025886,-0.055979693,0.13724479,0.21485613,-0.011265799 +-0.09229473,0.59209563,0.031338285,-0.033080861,-0.24308858,-0.099798547,0.034460195,0.095119813,-0.3380162,0.0063860024,-0.61207299,0.081325416,0.88683114,0.14254905,0.14776204 +-0.13189434,0.52878496,0.12652391,0.045113625,0.58373514,0.37291503,0.11395325,-0.29479222,-0.57014085,0.46291592,-0.094050179,0.13959097,-0.38607402,-0.4489706,-0.14602419 +0.058107658,-0.32289338,0.093139162,0.072427234,-0.56919401,0.52554237,0.23656926,-0.011782016,0.071820601,0.078277291,-0.13355752,0.22722721,0.14369455,-0.45198935,-0.55574794 +0.66145875,0.34633299,0.14098019,0.51882591,-0.28019898,-0.1603226,-0.068413337,-0.20428242,0.069672173,0.23112577,-0.044368579,-0.16455425,0.21620977,0.0042702105,-0.087399014 +0.31599556,-0.027551859,0.13434254,0.13497371,0.05400568,-0.17374789,0.17525393,0.060258929,-0.17914162,-0.31056619,-0.25358691,0.025847535,-0.43006001,-0.62266361,-0.033996882 +-0.29038151,0.03410127,0.034903413,-0.12121764,0.026030714,-0.33546274,-0.41424111,0.05324838,-0.27099455,-0.026251302,0.41024137,0.26636349,0.15582891,-0.18666254,0.019895831 +-0.24388652,-0.44098852,0.012618825,0.24945112,0.071101888,0.24623792,0.17484502,0.0085286769,0.2514707,-0.14659862,-0.08462515,0.36931333,-0.29955293,0.1104436,-0.75690139 +0.041494323,-0.25980564,0.46402128,-0.36112127,-0.94980789,-0.16504063,0.0030943325,0.052792942,0.22523648,0.38390366,0.45562427,-0.18631744,0.0082333995,0.16670803,0.16045688 diff --git a/examples/OHagan-function/data/sparse_solver_comparison.py b/examples/OHagan-function/data/sparse_solver_comparison.py new file mode 100644 index 000000000..5245bb89d --- /dev/null +++ b/examples/OHagan-function/data/sparse_solver_comparison.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Sat Sep 10 09:44:05 2022 + +@author: farid +""" + +import numpy as np +import joblib +import os +import scipy.stats as st + +import sys +sys.path.append("../../../src/bayesvalidrox/") + +from pylink.pylink import PyLinkForwardModel +from surrogate_models.inputs import Input +from surrogate_models.surrogate_models import MetaModel +from post_processing.post_processing import PostProcessing +from bayes_inference.bayes_inference import BayesInference +from bayes_inference.discrepancy import Discrepancy +import matplotlib +matplotlib.use('agg') +from matplotlib.backends.backend_pdf import PdfPages +import matplotlib.ticker as ticker +from matplotlib.offsetbox import AnchoredText +from matplotlib.patches import Patch +import matplotlib.pyplot as plt +# Load the mplstyle +plt.style.use(os.path.join( + os.path.split(__file__)[0], + '../../../src/bayesvalidrox/', 'bayesvalidrox.mplstyle')) + + +def plot_seq_design_diagnostics(meta_model, util_funcs, + ref_BME_KLD=None, save_fig=True): + """ + Plots the Bayesian Model Evidence (BME) and Kullback-Leibler divergence + (KLD) for the sequential design. + + Parameters + ---------- + ref_BME_KLD : array, optional + Reference BME and KLD . The default is `None`. + save_fig : bool, optional + Whether to save the figures. The default is `True`. + + Returns + ------- + None. + + """ + n_init_samples = meta_model.ExpDesign.n_init_samples + n_total_samples = meta_model.ExpDesign.X.shape[0] + + if save_fig: + newpath = f'{path}/boxplot_{model_name}/' + if not os.path.exists(newpath): + os.makedirs(newpath) + + plotList = ['Modified LOO error', 'Validation error', 'KLD', 'BME', + 'RMSEMean', 'RMSEStd', 'Hellinger distance'] + seqList = [meta_model.SeqModifiedLOO, meta_model.seqValidError, + meta_model.SeqKLD, meta_model.SeqBME, meta_model.seqRMSEMean, + meta_model.seqRMSEStd, meta_model.SeqDistHellinger] + + markers = ('x', 'o', 'd', '*', '+') + colors = ('k', 'darkgreen', 'b', 'navy', 'darkred') + + # Plot the evolution of the diagnostic criteria of the + # Sequential Experimental Design. + for plotidx, plot in enumerate(plotList): + fig, ax = plt.subplots(figsize=(27, 15)) + seq_dict = seqList[plotidx] + name_util = list(seq_dict.keys()) + + if len(name_util) == 0: + continue + + # Box plot when Replications have been detected. + if any(int(name.split("rep_", 1)[1]) > 1 for name in name_util): + # Extract the values from dict + sorted_seq_opt = {} + # Number of replications + n_reps = meta_model.ExpDesign.n_replication + + # Get the list of utility function names + # Handle if only one UtilityFunction is provided + if not isinstance(util_funcs, list): + util_funcs = [util_funcs] + + for util in util_funcs: + sortedSeq = {} + # min number of runs available from reps + n_runs = min([seq_dict[f'{util}_rep_{i+1}'].shape[0] + for i in range(n_reps)]) + + for runIdx in range(n_runs): + values = [] + for key in seq_dict.keys(): + if util in key: + values.append(seq_dict[key][runIdx].mean()) + sortedSeq['SeqItr_'+str(runIdx)] = np.array(values) + sorted_seq_opt[util] = sortedSeq + + # BoxPlot + def draw_plot(data, labels, edge_color, fill_color, idx): + pos = labels - (4*idx-6) + bp = plt.boxplot(data, positions=pos, labels=labels, + patch_artist=True, sym='', widths=3) + + ax.plot(pos, np.median(data, axis=0), lw=4, color=fill_color[idx]) + + elements = ['boxes', 'whiskers', 'fliers', 'means', + 'medians', 'caps'] + for element in elements: + plt.setp(bp[element], color=edge_color[idx], alpha=0.6) + + for patch in bp['boxes']: + patch.set(facecolor=fill_color[idx], alpha=0.6) + + if meta_model.ExpDesign.n_new_samples != 1: + step1 = meta_model.ExpDesign.n_new_samples + step2 = 1 + else: + step1 = 10 + step2 = 10 + edge_color = ['red', 'blue', 'green', 'black'] + fill_color = ['tan', 'cyan', 'lightgreen', 'grey'] + plot_label = plot + # Plot for different Utility Functions + for idx, util in enumerate(util_funcs): + all_errors = np.empty((n_reps, 0)) + + for key in list(sorted_seq_opt[util].keys()): + errors = sorted_seq_opt.get(util, {}).get(key)[:, None] + all_errors = np.hstack((all_errors, errors)) + + # Special cases for BME and KLD + if plot == 'KLD' or plot == 'BME': + # BME convergence if refBME is provided + if ref_BME_KLD is not None: + if plot == 'BME': + refValue = ref_BME_KLD[0] + plot_label = r'BME/BME$^{Ref.}$' + if plot == 'KLD': + refValue = ref_BME_KLD[1] + plot_label = '$D_{KL}[p(\\theta|y_*),p(\\theta)]'\ + ' / D_{KL}^{Ref.}[p(\\theta|y_*), '\ + 'p(\\theta)]$' + + # Difference between BME/KLD and the ref. values + all_errors = np.divide(all_errors, + np.full((all_errors.shape), + refValue)) + + # Plot baseline for zero, i.e. no difference + plt.axhline(y=1.0, xmin=0, xmax=1, c='green', + ls='--', lw=2) + + # Plot each UtilFuncs + labels = np.arange(n_init_samples, n_total_samples+1, step1) + draw_plot(all_errors[:, ::step2], labels, edge_color, + fill_color, idx) + # labels = np.array([10, 30, 50, 70, 90, 120, 150, 200]) + # indices = [0, 20, 40, 60, 80, 110, 140, 190] + # draw_plot(all_errors[:, indices], labels, edge_color, + # fill_color, idx) + + plt.xticks(labels) + # Set the major and minor locators + # ax.xaxis.set_major_locator(ticker.AutoLocator()) + # ax.xaxis.set_minor_locator(ticker.AutoMinorLocator()) + # ax.xaxis.grid(True, which='major', linestyle='-') + # ax.xaxis.grid(True, which='minor', linestyle='--') + + # Shade + for center in labels[::2]: + ax.axvspan(center-8, center+8, alpha=0.1, color='grey') + + # Legend + legend_elements = [] + for idx, util in enumerate(util_funcs): + legend_elements.append(Patch(facecolor=fill_color[idx], + edgecolor=edge_color[idx], + label=util)) + plt.legend(handles=legend_elements[::-1], loc='best') + + if plot != 'BME' and plot != 'KLD': + plt.yscale('log') + plt.autoscale(True) + # ax.yaxis.set_minor_locator(ticker.LogLocator(numticks=999, subs="auto")) + ax.yaxis.grid(True, which='minor', linestyle='--') + plt.xlabel('\\# of training samples', fontsize=f_size) + plt.ylabel(plot_label, fontsize=f_size) + # plt.title(plot) + plt.xticks(fontsize=f_size) + plt.yticks(fontsize=f_size) + + if save_fig: + # save the current figure + plot_name = plot.replace(' ', '_') + fig.savefig( + f'{newpath}/boxplot_solver_ishigami_{plot_name}.pdf', + bbox_inches='tight' + ) + # Destroy the current plot + plt.clf() + return + + +if __name__ == "__main__": + # Set variables + model_name = 'borehole' + solvers = ['BCS', 'FastARD', 'OMP', 'OLS'] + path = f'/home/farid/bwSyncShare/Scientific_LH2/Promotion/dissertation/surrogate/data-borehole/' + f_size = 45 + + all_loo_errors = {} + all_valid_errors = {} + for solver in solvers: + # reading the data from the file + with open(f"{path}/{solver}/PCEModel_{model_name}.pkl", "rb") as input: + meta_model = joblib.load(input) + + # Update name and Concatenate + all_valid_errors.update({key.replace('ALM', solver): value + for key, value in + meta_model.seqValidError.items() + }) + all_loo_errors.update({key.replace('ALM', solver): value + for key, value in + meta_model.SeqModifiedLOO.items() + }) + meta_model.seqValidError = all_valid_errors + meta_model.SeqModifiedLOO = all_loo_errors + + # Plot box plot + plot_seq_design_diagnostics(meta_model, solvers) diff --git a/examples/OHagan-function/data/valid_outputs.npy b/examples/OHagan-function/data/valid_outputs.npy new file mode 100644 index 0000000000000000000000000000000000000000..6b0f28b72e05d71f6a0c7a677f416e9493301ac0 GIT binary patch literal 928 zcmbWr{ZrEg0LO72hwflO!C*6B$mSk#5AqnsJ`@M&sf>sYhl<F9h%)A8J9bDdxG7w^ zy97i<41_CFh*9TC4ESnP2sn8dmJguLP;WF;&e1Gr*}u^HxA&`lOZH~jR(CgzTP3@& zNL`>}zr|p$FOFvm80_M5)d5w$BClLkSoEK#=I>J%y>fL)zOv}mbHpN%fFb4z7*&k_ z8JXzV_U2%P7<+v#KfW1CL(siUbAN6yYPA#e($51RT~0_naFh$%(?50<HzXqdGo!hF zyF<$5yrz+vV({*1x3ia?`s48))tSpIDk{Ir)qMO+0_%m+l_Sb9_~;z+$i@{oS}}Dl zF)IWqB0sBm`}BbJ!)Hc&U;=!<AX#fpt;6~EHtPPlE{5{ONYecwr_@=^A9`LR0$r*1 zN+&bX{uiTfp5_ONE~#g-n}>!COR`A=85&FdxC;J6TwT`92%!k|=kk6US>nLNQ|o(< zFo4Qmq<IJ-cE@d<Gqy!Q^=LA9p@IXeeZMgFfhSS}gjv36Tx2u*q6X?BVbctoNh84s zsr&6#8ij`knz`z#&eg<8EqzvKj6~F-<k<^?K<LNKS5+=PBukC0S-o`RSPu5z$*>Hp zZ|1MD?^%o5YZmX+A`y=NetSalvkwf?X1npA0IL`Qxf#M3EU5+uM-3q`B3d)n9ga_F z^aNKs4>{yTzJo_W%*h=$n%)t>Ua0uwl8%R!k4)2^3JN&K(w0N5BBXsSORk>h;o^&a zOPh|4GKYKrrqf&`pIIB~*A@w?T-9W2iN@GR@n3OcgV5@HQsOM<!i%|X?~KI*hZY42 zyMlzppw&;3s#!?C^~Ubw%vj9p&IYUyyAeB2EL6yC4EP@2Ok4PYPRP5weI7Qt!z1c> z88@7X&b|ikX8&09T-|meEQwCMU(h*jc{>8r;)nC!1d4F4g@46R6@&BEf?rM3{y4J# zPC<rR0?{$s@MKdc@j<xnw<%>D?3kMM>Dt7E!XzB+x*dltwcROqX>pjL-jHiMXvlr( zypmVS0#QRU6EoI<o!-497gdP(zweGwOZ<p0#bU+OAQfao=aBB21SYSJ_9k8g_LZJ$ z*~Oy3fzIv;{Tig(UK$uNEO;naUDQW~LuO)Uj$UUXGw09DAHQdz!6i?0MGH~eo4q_d O_c~g`67OrYp2WYvI)AVL literal 0 HcmV?d00001 diff --git a/examples/OHagan-function/data/valid_samples.npy b/examples/OHagan-function/data/valid_samples.npy new file mode 100644 index 0000000000000000000000000000000000000000..2a2a38e1a1718d94f39243fac3d0f047fdd870d1 GIT binary patch literal 12128 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 literal 0 HcmV?d00001 diff --git a/examples/OHagan-function/test_OHagan.py b/examples/OHagan-function/test_OHagan.py new file mode 100644 index 000000000..4715ecc52 --- /dev/null +++ b/examples/OHagan-function/test_OHagan.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This test deals with the surrogate modeling of a Ishigami function. + +You will see how to: + Check the quality of your regression model + Perform sensitivity analysis via Sobol Indices + +Author: Farid Mohammadi, M.Sc. +E-Mail: farid.mohammadi@iws.uni-stuttgart.de +Department of Hydromechanics and Modelling of Hydrosystems (LH2) +Institute for Modelling Hydraulic and Environmental Systems (IWS), University +of Stuttgart, www.iws.uni-stuttgart.de/lh2/ +Pfaffenwaldring 61 +70569 Stuttgart + +Created on Wed Jul 10 2019 + +""" + +import numpy as np +import joblib + +# import bayesvalidrox +# Add BayesValidRox path +import sys +sys.path.append("../../src/bayesvalidrox/") + +from pylink.pylink import PyLinkForwardModel +from surrogate_models.inputs import Input +from surrogate_models.surrogate_models import MetaModel +from post_processing.post_processing import PostProcessing +from bayes_inference.bayes_inference import BayesInference +from bayes_inference.discrepancy import Discrepancy +import matplotlib +matplotlib.use('agg') + +if __name__ == "__main__": + + # ===================================================== + # ============= COMPUTATIONAL MODEL ================ + # ===================================================== + Model = PyLinkForwardModel() + + # Define model options + Model.link_type = 'Function' + Model.py_file = 'OHagan' + Model.name = 'OHagan' + + Model.Output.names = ['Z'] + + # ===================================================== + # ========= PROBABILISTIC INPUT MODEL ============== + # ===================================================== + Inputs = Input() + + n_dim = 15 + + for i in range(n_dim): + Inputs.add_marginals() + Inputs.Marginals[i].name = "$\\theta_{"+str(i+1)+"}$" + Inputs.Marginals[i].dist_type = 'normal' + Inputs.Marginals[i].parameters = [0, 1] + + # ===================================================== + # ====== POLYNOMIAL CHAOS EXPANSION METAMODELS ====== + # ===================================================== + MetaModelOpts = MetaModel(Inputs, Model) + + # Select your metamodel method + # 1) PCE (Polynomial Chaos Expansion) 2) aPCE (arbitrary PCE) + # 3) GPE (Gaussian Process Emulator) + MetaModelOpts.meta_model_type = 'aPCE' + + # ------------------------------------------------ + # ------------- PCE Specification ---------------- + # ------------------------------------------------ + # Select the sparse least-square minimization method for + # the PCE coefficients calculation: + # 1)OLS: Ordinary Least Square 2)BRR: Bayesian Ridge Regression + # 3)LARS: Least angle regression 4)ARD: Bayesian ARD Regression + # 5)FastARD: Fast Bayesian ARD Regression + # 6)BCS: Bayesian Compressive Sensing + # 7)OMP: Orthogonal Matching Pursuit + # 8)VBL: Variational Bayesian Learning + # 9)EBL: Emperical Bayesian Learning + MetaModelOpts.pce_reg_method = 'FastARD' + + # Bootstraping + # 1) normal 2) fast + MetaModelOpts.bootstrap_method = 'fast' + MetaModelOpts.n_bootstrap_itrs = 1#00 + + # Specify the max degree to be compared by the adaptive algorithm: + # The degree with the lowest Leave-One-Out cross-validation (LOO) + # error (or the highest score=1-LOO)estimator is chosen as the final + # metamodel. pce_deg accepts degree as a scalar or a range. + MetaModelOpts.pce_deg = 7 + + # q-quasi-norm 0<q<1 (default=1) + MetaModelOpts.pce_q_norm = 0.5 + + # Print summary of the regression results + # MetaModelOpts.verbose = True + + # ------------------------------------------------ + # ------ Experimental Design Configuration ------- + # ------------------------------------------------ + MetaModelOpts.add_ExpDesign() + + # One-shot (normal) or Sequential Adaptive (sequential) Design + MetaModelOpts.ExpDesign.method = 'sequential' + MetaModelOpts.ExpDesign.n_init_samples = 100 + + # Sampling methods + # 1) random 2) latin_hypercube 3) sobol 4) halton 5) hammersley 6) korobov + # 7) chebyshev(FT) 8) grid(FT) 9) nested_grid(FT) 10)user + MetaModelOpts.ExpDesign.sampling_method = 'user' + + # Provide the experimental design object with a hdf5 file + MetaModelOpts.ExpDesign.hdf5_file = 'ExpDesign_OHagan_orig.hdf5' + + # Sequential experimental design (needed only for sequential ExpDesign) + MetaModelOpts.ExpDesign.n_new_samples = 1 + MetaModelOpts.ExpDesign.n_max_samples = 500 # 150 + MetaModelOpts.ExpDesign.mod_LOO_threshold = 1e-16 + + # ------------------------------------------------ + # ------- Sequential Design configuration -------- + # ------------------------------------------------ + # 1) None 2) 'equal' 3)'epsilon-decreasing' 4) 'adaptive' + MetaModelOpts.ExpDesign.tradeoff_scheme = None + # MetaModelOpts.ExpDesign.n_replication = 50 + # -------- Exploration ------ + # 1)'Voronoi' 2)'random' 3)'latin_hypercube' 4)'dual annealing' + MetaModelOpts.ExpDesign.explore_method = 'latin_hypercube' + + # Use when 'dual annealing' chosen + MetaModelOpts.ExpDesign.max_func_itr = 200 + + # Use when 'Voronoi' or 'random' or 'latin_hypercube' chosen + MetaModelOpts.ExpDesign.n_canddidate = 10000 + MetaModelOpts.ExpDesign.n_cand_groups = 4 + + # -------- Exploitation ------ + # 1)'BayesOptDesign' 2)'VarOptDesign' 3)'alphabetic' 4)'Space-filling' + MetaModelOpts.ExpDesign.exploit_method = 'Space-filling' + + # BayesOptDesign -> when data is available + # 1)DKL (Kullback-Leibler Divergence) 2)DPP (D-Posterior-percision) + # 3)APP (A-Posterior-percision) + # MetaModelOpts.ExpDesign.util_func = 'DKL' + + # VarBasedOptDesign -> when data is not available + # Only with Vornoi >>> 1)Entropy 2)EIGF, 3)ALM, 4)LOOCV + MetaModelOpts.ExpDesign.util_func = 'ALM' + + # alphabetic + # 1)D-Opt (D-Optimality) 2)A-Opt (A-Optimality) + # 3)K-Opt (K-Optimality) + # MetaModelOpts.ExpDesign.util_func = 'D-Opt' + + MetaModelOpts.valid_samples = np.load("data/valid_samples.npy") + MetaModelOpts.valid_model_runs = { + 'Z': np.load("data/valid_outputs.npy") + } + # >>>>>>>>>>>>>>>>>>>>>> Build Surrogate <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + # Adaptive sparse arbitrary polynomial chaos expansion + # PCEModel = MetaModelOpts.create_metamodel() + from surrogate_models.meta_model_engine import MetaModelEngine + meta_model_engine = MetaModelEngine(MetaModelOpts) + meta_model_engine.run() + PCEModel = meta_model_engine.MetaModel + + # Save PCE models + with open(f'PCEModel_{Model.name}.pkl', 'wb') as output: + joblib.dump(PCEModel, output, 2) + + # ===================================================== + # ========= POST PROCESSING OF METAMODELS =========== + # ===================================================== + PostPCE = PostProcessing(PCEModel) + + # Plot to check validation visually. + PostPCE.valid_metamodel(n_samples=200) + + # PostPCE.eval_PCEmodel_3D() + # Compute and print RMSE error + PostPCE.check_accuracy(n_samples=3000) + + # Plot the evolution of the KLD,BME, and Modified LOOCV error + if MetaModelOpts.ExpDesign.method == 'sequential': + PostPCE.plot_seq_design_diagnostics() + + # Plot the sobol indices + total_sobol = PostPCE.sobol_indices(plot_type='bar') -- GitLab