easyvvuq.analysis.ssc_analysis

ANALYSIS CLASS FOR THE SSC SAMPLER

  1"""
  2ANALYSIS CLASS FOR THE SSC SAMPLER
  3"""
  4
  5import numpy as np
  6import pickle
  7import copy
  8from easyvvuq import OutputType
  9from .base import BaseAnalysisElement
 10from .results import AnalysisResults
 11# import logging
 12# from scipy.special import comb
 13import pandas as pd
 14import matplotlib.pyplot as plt
 15from matplotlib.patches import Polygon
 16
 17__author__ = "Wouter Edeling"
 18__copyright__ = """
 19
 20    Copyright 2018 Robin A. Richardson, David W. Wright
 21
 22    This file is part of EasyVVUQ
 23
 24    EasyVVUQ is free software: you can redistribute it and/or modify
 25    it under the terms of the Lesser GNU General Public License as published by
 26    the Free Software Foundation, either version 3 of the License, or
 27    (at your option) any later version.
 28
 29    EasyVVUQ is distributed in the hope that it will be useful,
 30    but WITHOUT ANY WARRANTY; without even the implied warranty of
 31    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 32    Lesser GNU General Public License for more details.
 33
 34    You should have received a copy of the Lesser GNU General Public License
 35    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 36
 37"""
 38__license__ = "LGPL"
 39
 40
 41class SSCAnalysisResults(AnalysisResults):
 42    # def _get_sobols_first(self, qoi, input_):
 43    #     raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_first'])
 44    #     result = raw_dict[AnalysisResults._to_tuple(qoi)][input_]
 45    #     try:
 46    #         return np.array([float(result)])
 47    #     except TypeError:
 48    #         return np.array(result)
 49
 50    def supported_stats(self):
 51        """Types of statistics supported by the describe method.
 52
 53        Returns
 54        -------
 55        list of str
 56        """
 57        return ['mean', 'var', 'std']
 58
 59    def _describe(self, qoi, statistic):
 60        if statistic in self.supported_stats():
 61            return self.raw_data['statistical_moments'][qoi][statistic]
 62        else:
 63            raise NotImplementedError
 64
 65    def surrogate(self):
 66        """Return a SSC surrogate model.
 67
 68        Returns
 69        -------
 70        A function that takes a dictionary of parameter - value pairs and returns
 71        a dictionary with the results (same output as decoder).
 72        """
 73        def surrogate_fn(inputs):
 74            def swap(x):
 75                if x.size > 1:
 76                    return list(x)
 77                else:
 78                    return x[0]
 79            values = np.squeeze(np.array([inputs[key] for key in self.inputs])).T
 80            results = dict([(qoi, swap(self.surrogate_(qoi, values))) for qoi in self.qois])
 81            return results
 82        return surrogate_fn
 83
 84
 85class SSCAnalysis(BaseAnalysisElement):
 86    """
 87    SSc analysis class.
 88    """
 89
 90    def __init__(self, sampler=None, qoi_cols=None):
 91        """
 92        Parameters
 93        ----------
 94        sampler : SSCSampler
 95            Sampler used to initiate the SSC analysis
 96        qoi_cols : list or None
 97            Column names for quantities of interest (for which analysis is
 98            performed).
 99        """
100
101        if sampler is None:
102            msg = 'SSC analysis requires a paired sampler to be passed'
103            raise RuntimeError(msg)
104
105        if qoi_cols is None:
106            raise RuntimeError("Analysis element requires a list of "
107                               "quantities of interest (qoi)")
108
109        self.qoi_cols = qoi_cols
110        self.output_type = OutputType.SUMMARY
111        self.sampler = sampler
112
113    def element_name(self):
114        """Name for this element for logging purposes"""
115        return "SSC_Analysis"
116
117    def element_version(self):
118        """Version of this element for logging purposes"""
119        return "0.1"
120
121    def save_state(self, filename):
122        """
123        Saves the complete state of the analysis object to a pickle file,
124        except the sampler object (self.sampler).
125
126        Parameters
127        ----------
128        filename : string
129            name to the file to write the state to
130        """
131        print("Saving analysis state to %s" % filename)
132        # make a copy of the state, and do not store the sampler as well
133        state = copy.copy(self.__dict__)
134        del state['sampler']
135        with open(filename, 'wb') as fp:
136            pickle.dump(state, fp)
137
138    def load_state(self, filename):
139        """
140        Loads the complete state of the analysis object from a
141        pickle file, stored using save_state.
142
143        Parameters
144        ----------
145        filename : string
146            name of the file to load
147        """
148        print("Loading analysis state from %s" % filename)
149        with open(filename, 'rb') as fp:
150            state = pickle.load(fp)
151        for key in state.keys():
152            self.__dict__[key] = state[key]
153
154    def analyse(self, data_frame=None, compute_moments=True, n_mc=20000):
155        """
156        Perform SSC analysis on input `data_frame`.
157
158        Parameters
159        ----------
160        data_frame : pandas.DataFrame
161            Input data for analysis.
162        compute_moments : bool, optional.
163            Compute the first 2 moments. Default is True.
164
165        Returns
166        -------
167        results : dict
168            A dictionary containing the statistical moments.
169        """
170
171        if data_frame is None:
172            raise RuntimeError("Analysis element needs a data frame to "
173                               "analyse")
174        elif isinstance(data_frame, pd.DataFrame) and data_frame.empty:
175            raise RuntimeError(
176                "No data in data frame passed to analyse element")
177
178        self.load_samples(data_frame)
179        # # size of one code sample
180        # # TODO: change this to include QoI of different size
181        # self.N_qoi = self.samples[qoi_cols[0]][0].size
182
183        # Compute descriptive statistics for each quantity of interest
184        results = {'statistical_moments': {}}
185
186        if compute_moments:
187            for qoi_k in self.qoi_cols:
188                mean_k, var_k = self.get_moments(qoi_k, n_mc=n_mc)
189                std_k = var_k ** 0.5
190                # compute statistical moments
191                results['statistical_moments'][qoi_k] = {'mean': mean_k,
192                                                         'var': var_k,
193                                                         'std': std_k}
194
195        results = SSCAnalysisResults(raw_data=results, samples=data_frame,
196                                     qois=self.qoi_cols, inputs=list(self.sampler.vary.get_keys()))
197        results.surrogate_ = self.surrogate
198        return results
199
200    def load_samples(self, data_frame):
201        """
202        Extract output values for each quantity of interest from Dataframe.
203
204        Parameters
205        ----------
206        data_frame : EasyVVUQ (pandas) data frame
207            The code samples from the EasyVVUQ data frame.
208
209        Returns
210        -------
211        None.
212
213        """
214        print('Loading samples...')
215        qoi_cols = self.qoi_cols
216        samples = {k: [] for k in qoi_cols}
217        for k in qoi_cols:
218            for run_id in data_frame[('run_id', 0)].unique():
219                values = data_frame.loc[data_frame[('run_id', 0)] == run_id][k].values
220                samples[k].append(values.flatten())
221            samples[k] = np.array(samples[k])
222        self.samples = samples
223        print('done')
224
225    def get_moments(self, qoi, n_mc):
226        """
227        Compute the mean and variance through Monte Carlo sampling of the SSC
228        surrogate. Independent random inputs samples are drawn though the
229        SSC sampler object.
230
231        Parameters
232        ----------
233        qoi : string
234            The name of the QoI.
235        n_mc : int
236            The number of Monte Carlo samples.
237
238        Returns
239        -------
240        mean : array
241            The mean of qoi.
242        var : array
243            The variance of qoi.
244
245        """
246        print('Computing mean and variance...')
247        Xi = self.sampler.sample_inputs(n_mc)
248        rvs = [self.surrogate(qoi, xi) for xi in Xi]
249        mean = np.mean(rvs)
250        var = np.var(rvs)
251        print('done.')
252        return np.array([mean]), np.array([var])
253
254    def update_surrogate(self, qoi, data_frame, max_LEC_jobs=4, n_mc_LEC=5,
255                         max_ENO_jobs=4):
256        """
257        Update the SSC surrogate given new data. Given an EasyVVUQ dataframe,
258        check the LEC condition, and compute the ENO interpolation stencils.
259
260        Parameters
261        ----------
262        qoi : string
263            The name of the QoI on the basis of which the sampling plan
264            is refined.
265        data_frame : EasyVVUQ (pandas) data frame
266            The code samples from the EasyVVUQ data frame.
267        max_LEC_jobs : int, optional
268            The number of LEC checks to perform in parallel. The default is 4.
269        n_mc_LEC : int, optional
270            The number of surrogate evaluations used in the LEC check.
271            The default is 5.
272        max_LEC_jobs : int, optional
273            The number of ENO stencils to compute in parallel. The default is 4.
274
275        Returns
276        -------
277        None. Stores the polynomials orders, interpolation stencils and
278        the simplex probabilities in analysis.p_j, analysis.S_j and
279        analysis.prob_j respectively.
280
281        """
282
283        # the number of code evaluations
284        n_s = self.sampler.n_samples
285        # the number of simplex elements
286        n_e = self.sampler.n_elements
287
288        # load the EasyVVUQ data frame
289        self.load_samples(data_frame)
290        # code outputs
291        # v = np.array(self.samples[qoi])
292        v = self.samples[qoi]
293
294        # find the max polynomial order and set the p_j = pmax
295        pmax = self.sampler.find_pmax(n_s)
296        if pmax > self.sampler.pmax_cutoff:
297            pmax = self.sampler.pmax_cutoff
298            print('Max. polynomial order set by hand to = ' + str(pmax))
299        else:
300            print('Max. polynomial order allowed by n_s = ' + str(pmax))
301
302        # polynomial order per simplex elememt
303        p_j = (np.ones(n_e) * pmax).astype('int')
304
305        # compute nearest neighbour stencils
306        S_j = self.sampler.compute_stencil_j()
307
308        # check the LEC condition of all stencil
309        res_LEC = self.sampler.check_LEC(p_j, v, S_j,
310                                         n_mc=n_mc_LEC,
311                                         max_jobs=max_LEC_jobs)
312        # updated polynomial order, stencil and el_idx are the element indices
313        # per interpolation stencil
314        p_j = res_LEC['p_j']
315        S_j = res_LEC['S_j']
316        el_idx = res_LEC['el_idx']
317
318        # convert the nearest-neighbour stencils to ENO stencils
319        S_j, p_j, el_idx = self.sampler.compute_ENO_stencil(p_j, S_j, el_idx,
320                                                            max_jobs=max_ENO_jobs)
321        # store polynomial orders and stencils
322        self.p_j = p_j
323        self.S_j = S_j
324
325        print("Updated polynomials orders = %s" % p_j)
326
327        # compute the simplex probabilities
328        self.prob_j = self.sampler.compute_probability()
329
330    def adapt_locally(self, n_new_samples=1):
331        """
332        Locally refine the sampling plan based on the SSC geometric
333        refinement measure.
334
335        Parameters
336        ----------
337        n_new_samples : int, optional
338            The number of new code evaulations to perform. The default is 1.
339
340        Returns
341        -------
342        None. Updates the Delaunay triangulation of the SSC sampler with
343        the new points. A new ensemble must be executed next.
344
345        """
346
347        if n_new_samples > self.sampler.n_elements:
348            n_new_samples = self.sampler.n_elements
349
350        # compute the refinement measures
351        eps_bar_j, vol_j = self.sampler.compute_eps_bar_j(self.p_j, self.prob_j)
352
353        # rank elements according to eps_bar_j
354        refine_idx = np.flipud(np.argsort(eps_bar_j))
355
356        # find the elements at the hypercube boundaries
357        bound_simplices = self.sampler.find_boundary_simplices()
358
359        # store which edges are refined, to prevent refining the same edge twice
360        # during the same interation
361        refined_edges = []
362
363        # the refinement locations
364        xi_k_jref = np.zeros([n_new_samples, self.sampler.n_dimensions])
365
366        i = 0
367        j = 0
368
369        # determine n_new_samples locations
370        while i < n_new_samples:
371
372            if np.in1d(refine_idx[j], bound_simplices) == False:
373                # compute the subvertices of the element chosen for refinement
374                sub_vertices = self.sampler.compute_sub_simplex_vertices(refine_idx[j])
375                # draw a random sample from the sub simplex
376                xi_k_jref[i, :] = self.sampler.sample_simplex(1, sub_vertices)
377                # if interior is refined: no danger of refining the same edge twice
378                already_refined = False
379            else:
380                print('refining edge')
381
382                xi_k_jref[i, :], refined_edges, already_refined =  \
383                    self.sampler.sample_simplex_edge(refine_idx[j], refined_edges)
384
385            if not already_refined:
386                i += 1
387                j += 1
388            else:
389                print('Edge already refined, selecting another sample.')
390                j += 1
391
392        self.sampler.update_Delaunay(xi_k_jref)
393
394    def surrogate(self, qoi, xi):
395        """
396        Evaluate the SSC surrogate at xi.
397
398        Parameters
399        ----------
400        qoi : string
401            Name of the QoI.
402        xi : array, shape (n_xi,)
403            The location in the input space at which to evaluate the
404            surrogate.
405
406        Returns
407        -------
408        array
409            The surrogate output at xi
410
411        """
412
413        if not isinstance(xi, np.ndarray):
414            xi = np.array([xi])
415
416        surr = self.sampler.surrogate(xi, self.S_j, self.p_j, self.samples[qoi])
417        return np.array([surr])
418
419    def get_sample_array(self, qoi):
420        """
421        Parameters
422        ----------
423        qoi : str
424            name of quantity of interest
425
426        Returns
427        -------
428        array of all samples of qoi
429        """
430
431        return self.samples[qoi]
432
433    def plot_grid(self):
434        """
435        Plot the 1D or 2D sampling plan and color code the simplices according
436        to their polynomial order.
437
438        Returns
439        -------
440        None.
441
442        """
443
444        assert self.sampler.n_xi == 1 or self.sampler.n_xi == 2, \
445            "Only works for 1D and 2D problems"
446
447        tri = self.sampler.tri
448        colors = ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3",
449                  "#fdb462", "#b3de69", "#fccde5", "#d9d9d9"]
450
451        if self.sampler.n_xi == 2:
452            # plot the delaunay grid and color it according to the local p_j
453            fig = plt.figure()
454            ax = fig.add_subplot(111, xlabel=r'$\xi_1$', ylabel=r'$\xi_2$',
455                                 xlim=[self.sampler.corners[0][0],
456                                       self.sampler.corners[0][1]],
457                                 ylim=[self.sampler.corners[1][0],
458                                       self.sampler.corners[1][1]])
459
460            for p in range(np.max(self.p_j)):
461                idx = (self.p_j == p + 1).nonzero()[0]
462                first = True
463                for i in idx:
464                    vertices = tri.points[tri.simplices[i]]
465                    if first:
466                        pg = Polygon(vertices, facecolor=colors[p], edgecolor='k',
467                                     label=r'$p_j = %d$' % (p + 1))
468                        first = False
469                    else:
470                        pg = Polygon(vertices, facecolor=colors[p], edgecolor='k')
471
472                    ax.add_patch(pg)
473
474            leg = plt.legend(loc=0)
475            leg.set_draggable(True)
476
477        elif self.sampler.n_xi == 1:
478            fig = plt.figure()
479            ax = fig.add_subplot(111, xlabel='cell center',
480                                 ylabel=r'polynomial order %s' % '$p_j$')
481            ax.plot(self.sampler.compute_xi_center_j(), self.p_j, 'o')
482            ax.vlines(self.sampler.compute_xi_center_j(), np.zeros(self.sampler.n_elements),
483                      self.p_j, linestyles='dashed')
484            ax.vlines(self.sampler.tri.points, -0.05 * np.max(self.p_j),
485                      0.05 * np.max(self.p_j))
486
487        plt.tight_layout()
488        plt.show()
class SSCAnalysisResults(easyvvuq.analysis.results.AnalysisResults):
42class SSCAnalysisResults(AnalysisResults):
43    # def _get_sobols_first(self, qoi, input_):
44    #     raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_first'])
45    #     result = raw_dict[AnalysisResults._to_tuple(qoi)][input_]
46    #     try:
47    #         return np.array([float(result)])
48    #     except TypeError:
49    #         return np.array(result)
50
51    def supported_stats(self):
52        """Types of statistics supported by the describe method.
53
54        Returns
55        -------
56        list of str
57        """
58        return ['mean', 'var', 'std']
59
60    def _describe(self, qoi, statistic):
61        if statistic in self.supported_stats():
62            return self.raw_data['statistical_moments'][qoi][statistic]
63        else:
64            raise NotImplementedError
65
66    def surrogate(self):
67        """Return a SSC surrogate model.
68
69        Returns
70        -------
71        A function that takes a dictionary of parameter - value pairs and returns
72        a dictionary with the results (same output as decoder).
73        """
74        def surrogate_fn(inputs):
75            def swap(x):
76                if x.size > 1:
77                    return list(x)
78                else:
79                    return x[0]
80            values = np.squeeze(np.array([inputs[key] for key in self.inputs])).T
81            results = dict([(qoi, swap(self.surrogate_(qoi, values))) for qoi in self.qois])
82            return results
83        return surrogate_fn

Contains the analysis results.

Parameters
  • raw_data (obj): An arbitrary object that contains raw analysis data.
  • samples (pandas DataFrame): Collated samples.
  • qois (list of str): List of qoi names used during the analysis.
  • inputs (list of str): List of input names used during the analysis.
def supported_stats(self):
51    def supported_stats(self):
52        """Types of statistics supported by the describe method.
53
54        Returns
55        -------
56        list of str
57        """
58        return ['mean', 'var', 'std']

Types of statistics supported by the describe method.

Returns
  • list of str
def surrogate(self):
66    def surrogate(self):
67        """Return a SSC surrogate model.
68
69        Returns
70        -------
71        A function that takes a dictionary of parameter - value pairs and returns
72        a dictionary with the results (same output as decoder).
73        """
74        def surrogate_fn(inputs):
75            def swap(x):
76                if x.size > 1:
77                    return list(x)
78                else:
79                    return x[0]
80            values = np.squeeze(np.array([inputs[key] for key in self.inputs])).T
81            results = dict([(qoi, swap(self.surrogate_(qoi, values))) for qoi in self.qois])
82            return results
83        return surrogate_fn

Return a SSC surrogate model.

Returns
  • A function that takes a dictionary of parameter - value pairs and returns
  • a dictionary with the results (same output as decoder).
class SSCAnalysis(easyvvuq.analysis.base.BaseAnalysisElement):
 86class SSCAnalysis(BaseAnalysisElement):
 87    """
 88    SSc analysis class.
 89    """
 90
 91    def __init__(self, sampler=None, qoi_cols=None):
 92        """
 93        Parameters
 94        ----------
 95        sampler : SSCSampler
 96            Sampler used to initiate the SSC analysis
 97        qoi_cols : list or None
 98            Column names for quantities of interest (for which analysis is
 99            performed).
100        """
101
102        if sampler is None:
103            msg = 'SSC analysis requires a paired sampler to be passed'
104            raise RuntimeError(msg)
105
106        if qoi_cols is None:
107            raise RuntimeError("Analysis element requires a list of "
108                               "quantities of interest (qoi)")
109
110        self.qoi_cols = qoi_cols
111        self.output_type = OutputType.SUMMARY
112        self.sampler = sampler
113
114    def element_name(self):
115        """Name for this element for logging purposes"""
116        return "SSC_Analysis"
117
118    def element_version(self):
119        """Version of this element for logging purposes"""
120        return "0.1"
121
122    def save_state(self, filename):
123        """
124        Saves the complete state of the analysis object to a pickle file,
125        except the sampler object (self.sampler).
126
127        Parameters
128        ----------
129        filename : string
130            name to the file to write the state to
131        """
132        print("Saving analysis state to %s" % filename)
133        # make a copy of the state, and do not store the sampler as well
134        state = copy.copy(self.__dict__)
135        del state['sampler']
136        with open(filename, 'wb') as fp:
137            pickle.dump(state, fp)
138
139    def load_state(self, filename):
140        """
141        Loads the complete state of the analysis object from a
142        pickle file, stored using save_state.
143
144        Parameters
145        ----------
146        filename : string
147            name of the file to load
148        """
149        print("Loading analysis state from %s" % filename)
150        with open(filename, 'rb') as fp:
151            state = pickle.load(fp)
152        for key in state.keys():
153            self.__dict__[key] = state[key]
154
155    def analyse(self, data_frame=None, compute_moments=True, n_mc=20000):
156        """
157        Perform SSC analysis on input `data_frame`.
158
159        Parameters
160        ----------
161        data_frame : pandas.DataFrame
162            Input data for analysis.
163        compute_moments : bool, optional.
164            Compute the first 2 moments. Default is True.
165
166        Returns
167        -------
168        results : dict
169            A dictionary containing the statistical moments.
170        """
171
172        if data_frame is None:
173            raise RuntimeError("Analysis element needs a data frame to "
174                               "analyse")
175        elif isinstance(data_frame, pd.DataFrame) and data_frame.empty:
176            raise RuntimeError(
177                "No data in data frame passed to analyse element")
178
179        self.load_samples(data_frame)
180        # # size of one code sample
181        # # TODO: change this to include QoI of different size
182        # self.N_qoi = self.samples[qoi_cols[0]][0].size
183
184        # Compute descriptive statistics for each quantity of interest
185        results = {'statistical_moments': {}}
186
187        if compute_moments:
188            for qoi_k in self.qoi_cols:
189                mean_k, var_k = self.get_moments(qoi_k, n_mc=n_mc)
190                std_k = var_k ** 0.5
191                # compute statistical moments
192                results['statistical_moments'][qoi_k] = {'mean': mean_k,
193                                                         'var': var_k,
194                                                         'std': std_k}
195
196        results = SSCAnalysisResults(raw_data=results, samples=data_frame,
197                                     qois=self.qoi_cols, inputs=list(self.sampler.vary.get_keys()))
198        results.surrogate_ = self.surrogate
199        return results
200
201    def load_samples(self, data_frame):
202        """
203        Extract output values for each quantity of interest from Dataframe.
204
205        Parameters
206        ----------
207        data_frame : EasyVVUQ (pandas) data frame
208            The code samples from the EasyVVUQ data frame.
209
210        Returns
211        -------
212        None.
213
214        """
215        print('Loading samples...')
216        qoi_cols = self.qoi_cols
217        samples = {k: [] for k in qoi_cols}
218        for k in qoi_cols:
219            for run_id in data_frame[('run_id', 0)].unique():
220                values = data_frame.loc[data_frame[('run_id', 0)] == run_id][k].values
221                samples[k].append(values.flatten())
222            samples[k] = np.array(samples[k])
223        self.samples = samples
224        print('done')
225
226    def get_moments(self, qoi, n_mc):
227        """
228        Compute the mean and variance through Monte Carlo sampling of the SSC
229        surrogate. Independent random inputs samples are drawn though the
230        SSC sampler object.
231
232        Parameters
233        ----------
234        qoi : string
235            The name of the QoI.
236        n_mc : int
237            The number of Monte Carlo samples.
238
239        Returns
240        -------
241        mean : array
242            The mean of qoi.
243        var : array
244            The variance of qoi.
245
246        """
247        print('Computing mean and variance...')
248        Xi = self.sampler.sample_inputs(n_mc)
249        rvs = [self.surrogate(qoi, xi) for xi in Xi]
250        mean = np.mean(rvs)
251        var = np.var(rvs)
252        print('done.')
253        return np.array([mean]), np.array([var])
254
255    def update_surrogate(self, qoi, data_frame, max_LEC_jobs=4, n_mc_LEC=5,
256                         max_ENO_jobs=4):
257        """
258        Update the SSC surrogate given new data. Given an EasyVVUQ dataframe,
259        check the LEC condition, and compute the ENO interpolation stencils.
260
261        Parameters
262        ----------
263        qoi : string
264            The name of the QoI on the basis of which the sampling plan
265            is refined.
266        data_frame : EasyVVUQ (pandas) data frame
267            The code samples from the EasyVVUQ data frame.
268        max_LEC_jobs : int, optional
269            The number of LEC checks to perform in parallel. The default is 4.
270        n_mc_LEC : int, optional
271            The number of surrogate evaluations used in the LEC check.
272            The default is 5.
273        max_LEC_jobs : int, optional
274            The number of ENO stencils to compute in parallel. The default is 4.
275
276        Returns
277        -------
278        None. Stores the polynomials orders, interpolation stencils and
279        the simplex probabilities in analysis.p_j, analysis.S_j and
280        analysis.prob_j respectively.
281
282        """
283
284        # the number of code evaluations
285        n_s = self.sampler.n_samples
286        # the number of simplex elements
287        n_e = self.sampler.n_elements
288
289        # load the EasyVVUQ data frame
290        self.load_samples(data_frame)
291        # code outputs
292        # v = np.array(self.samples[qoi])
293        v = self.samples[qoi]
294
295        # find the max polynomial order and set the p_j = pmax
296        pmax = self.sampler.find_pmax(n_s)
297        if pmax > self.sampler.pmax_cutoff:
298            pmax = self.sampler.pmax_cutoff
299            print('Max. polynomial order set by hand to = ' + str(pmax))
300        else:
301            print('Max. polynomial order allowed by n_s = ' + str(pmax))
302
303        # polynomial order per simplex elememt
304        p_j = (np.ones(n_e) * pmax).astype('int')
305
306        # compute nearest neighbour stencils
307        S_j = self.sampler.compute_stencil_j()
308
309        # check the LEC condition of all stencil
310        res_LEC = self.sampler.check_LEC(p_j, v, S_j,
311                                         n_mc=n_mc_LEC,
312                                         max_jobs=max_LEC_jobs)
313        # updated polynomial order, stencil and el_idx are the element indices
314        # per interpolation stencil
315        p_j = res_LEC['p_j']
316        S_j = res_LEC['S_j']
317        el_idx = res_LEC['el_idx']
318
319        # convert the nearest-neighbour stencils to ENO stencils
320        S_j, p_j, el_idx = self.sampler.compute_ENO_stencil(p_j, S_j, el_idx,
321                                                            max_jobs=max_ENO_jobs)
322        # store polynomial orders and stencils
323        self.p_j = p_j
324        self.S_j = S_j
325
326        print("Updated polynomials orders = %s" % p_j)
327
328        # compute the simplex probabilities
329        self.prob_j = self.sampler.compute_probability()
330
331    def adapt_locally(self, n_new_samples=1):
332        """
333        Locally refine the sampling plan based on the SSC geometric
334        refinement measure.
335
336        Parameters
337        ----------
338        n_new_samples : int, optional
339            The number of new code evaulations to perform. The default is 1.
340
341        Returns
342        -------
343        None. Updates the Delaunay triangulation of the SSC sampler with
344        the new points. A new ensemble must be executed next.
345
346        """
347
348        if n_new_samples > self.sampler.n_elements:
349            n_new_samples = self.sampler.n_elements
350
351        # compute the refinement measures
352        eps_bar_j, vol_j = self.sampler.compute_eps_bar_j(self.p_j, self.prob_j)
353
354        # rank elements according to eps_bar_j
355        refine_idx = np.flipud(np.argsort(eps_bar_j))
356
357        # find the elements at the hypercube boundaries
358        bound_simplices = self.sampler.find_boundary_simplices()
359
360        # store which edges are refined, to prevent refining the same edge twice
361        # during the same interation
362        refined_edges = []
363
364        # the refinement locations
365        xi_k_jref = np.zeros([n_new_samples, self.sampler.n_dimensions])
366
367        i = 0
368        j = 0
369
370        # determine n_new_samples locations
371        while i < n_new_samples:
372
373            if np.in1d(refine_idx[j], bound_simplices) == False:
374                # compute the subvertices of the element chosen for refinement
375                sub_vertices = self.sampler.compute_sub_simplex_vertices(refine_idx[j])
376                # draw a random sample from the sub simplex
377                xi_k_jref[i, :] = self.sampler.sample_simplex(1, sub_vertices)
378                # if interior is refined: no danger of refining the same edge twice
379                already_refined = False
380            else:
381                print('refining edge')
382
383                xi_k_jref[i, :], refined_edges, already_refined =  \
384                    self.sampler.sample_simplex_edge(refine_idx[j], refined_edges)
385
386            if not already_refined:
387                i += 1
388                j += 1
389            else:
390                print('Edge already refined, selecting another sample.')
391                j += 1
392
393        self.sampler.update_Delaunay(xi_k_jref)
394
395    def surrogate(self, qoi, xi):
396        """
397        Evaluate the SSC surrogate at xi.
398
399        Parameters
400        ----------
401        qoi : string
402            Name of the QoI.
403        xi : array, shape (n_xi,)
404            The location in the input space at which to evaluate the
405            surrogate.
406
407        Returns
408        -------
409        array
410            The surrogate output at xi
411
412        """
413
414        if not isinstance(xi, np.ndarray):
415            xi = np.array([xi])
416
417        surr = self.sampler.surrogate(xi, self.S_j, self.p_j, self.samples[qoi])
418        return np.array([surr])
419
420    def get_sample_array(self, qoi):
421        """
422        Parameters
423        ----------
424        qoi : str
425            name of quantity of interest
426
427        Returns
428        -------
429        array of all samples of qoi
430        """
431
432        return self.samples[qoi]
433
434    def plot_grid(self):
435        """
436        Plot the 1D or 2D sampling plan and color code the simplices according
437        to their polynomial order.
438
439        Returns
440        -------
441        None.
442
443        """
444
445        assert self.sampler.n_xi == 1 or self.sampler.n_xi == 2, \
446            "Only works for 1D and 2D problems"
447
448        tri = self.sampler.tri
449        colors = ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3",
450                  "#fdb462", "#b3de69", "#fccde5", "#d9d9d9"]
451
452        if self.sampler.n_xi == 2:
453            # plot the delaunay grid and color it according to the local p_j
454            fig = plt.figure()
455            ax = fig.add_subplot(111, xlabel=r'$\xi_1$', ylabel=r'$\xi_2$',
456                                 xlim=[self.sampler.corners[0][0],
457                                       self.sampler.corners[0][1]],
458                                 ylim=[self.sampler.corners[1][0],
459                                       self.sampler.corners[1][1]])
460
461            for p in range(np.max(self.p_j)):
462                idx = (self.p_j == p + 1).nonzero()[0]
463                first = True
464                for i in idx:
465                    vertices = tri.points[tri.simplices[i]]
466                    if first:
467                        pg = Polygon(vertices, facecolor=colors[p], edgecolor='k',
468                                     label=r'$p_j = %d$' % (p + 1))
469                        first = False
470                    else:
471                        pg = Polygon(vertices, facecolor=colors[p], edgecolor='k')
472
473                    ax.add_patch(pg)
474
475            leg = plt.legend(loc=0)
476            leg.set_draggable(True)
477
478        elif self.sampler.n_xi == 1:
479            fig = plt.figure()
480            ax = fig.add_subplot(111, xlabel='cell center',
481                                 ylabel=r'polynomial order %s' % '$p_j$')
482            ax.plot(self.sampler.compute_xi_center_j(), self.p_j, 'o')
483            ax.vlines(self.sampler.compute_xi_center_j(), np.zeros(self.sampler.n_elements),
484                      self.p_j, linestyles='dashed')
485            ax.vlines(self.sampler.tri.points, -0.05 * np.max(self.p_j),
486                      0.05 * np.max(self.p_j))
487
488        plt.tight_layout()
489        plt.show()

SSc analysis class.

SSCAnalysis(sampler=None, qoi_cols=None)
 91    def __init__(self, sampler=None, qoi_cols=None):
 92        """
 93        Parameters
 94        ----------
 95        sampler : SSCSampler
 96            Sampler used to initiate the SSC analysis
 97        qoi_cols : list or None
 98            Column names for quantities of interest (for which analysis is
 99            performed).
100        """
101
102        if sampler is None:
103            msg = 'SSC analysis requires a paired sampler to be passed'
104            raise RuntimeError(msg)
105
106        if qoi_cols is None:
107            raise RuntimeError("Analysis element requires a list of "
108                               "quantities of interest (qoi)")
109
110        self.qoi_cols = qoi_cols
111        self.output_type = OutputType.SUMMARY
112        self.sampler = sampler
Parameters
  • sampler (SSCSampler): Sampler used to initiate the SSC analysis
  • qoi_cols (list or None): Column names for quantities of interest (for which analysis is performed).
qoi_cols
output_type
sampler
def element_name(self):
114    def element_name(self):
115        """Name for this element for logging purposes"""
116        return "SSC_Analysis"

Name for this element for logging purposes

def element_version(self):
118    def element_version(self):
119        """Version of this element for logging purposes"""
120        return "0.1"

Version of this element for logging purposes

def save_state(self, filename):
122    def save_state(self, filename):
123        """
124        Saves the complete state of the analysis object to a pickle file,
125        except the sampler object (self.sampler).
126
127        Parameters
128        ----------
129        filename : string
130            name to the file to write the state to
131        """
132        print("Saving analysis state to %s" % filename)
133        # make a copy of the state, and do not store the sampler as well
134        state = copy.copy(self.__dict__)
135        del state['sampler']
136        with open(filename, 'wb') as fp:
137            pickle.dump(state, fp)

Saves the complete state of the analysis object to a pickle file, except the sampler object (self.sampler).

Parameters
  • filename (string): name to the file to write the state to
def load_state(self, filename):
139    def load_state(self, filename):
140        """
141        Loads the complete state of the analysis object from a
142        pickle file, stored using save_state.
143
144        Parameters
145        ----------
146        filename : string
147            name of the file to load
148        """
149        print("Loading analysis state from %s" % filename)
150        with open(filename, 'rb') as fp:
151            state = pickle.load(fp)
152        for key in state.keys():
153            self.__dict__[key] = state[key]

Loads the complete state of the analysis object from a pickle file, stored using save_state.

Parameters
  • filename (string): name of the file to load
def analyse(self, data_frame=None, compute_moments=True, n_mc=20000):
155    def analyse(self, data_frame=None, compute_moments=True, n_mc=20000):
156        """
157        Perform SSC analysis on input `data_frame`.
158
159        Parameters
160        ----------
161        data_frame : pandas.DataFrame
162            Input data for analysis.
163        compute_moments : bool, optional.
164            Compute the first 2 moments. Default is True.
165
166        Returns
167        -------
168        results : dict
169            A dictionary containing the statistical moments.
170        """
171
172        if data_frame is None:
173            raise RuntimeError("Analysis element needs a data frame to "
174                               "analyse")
175        elif isinstance(data_frame, pd.DataFrame) and data_frame.empty:
176            raise RuntimeError(
177                "No data in data frame passed to analyse element")
178
179        self.load_samples(data_frame)
180        # # size of one code sample
181        # # TODO: change this to include QoI of different size
182        # self.N_qoi = self.samples[qoi_cols[0]][0].size
183
184        # Compute descriptive statistics for each quantity of interest
185        results = {'statistical_moments': {}}
186
187        if compute_moments:
188            for qoi_k in self.qoi_cols:
189                mean_k, var_k = self.get_moments(qoi_k, n_mc=n_mc)
190                std_k = var_k ** 0.5
191                # compute statistical moments
192                results['statistical_moments'][qoi_k] = {'mean': mean_k,
193                                                         'var': var_k,
194                                                         'std': std_k}
195
196        results = SSCAnalysisResults(raw_data=results, samples=data_frame,
197                                     qois=self.qoi_cols, inputs=list(self.sampler.vary.get_keys()))
198        results.surrogate_ = self.surrogate
199        return results

Perform SSC analysis on input data_frame.

Parameters
  • data_frame (pandas.DataFrame): Input data for analysis.
  • compute_moments (bool, optional.): Compute the first 2 moments. Default is True.
Returns
  • results (dict): A dictionary containing the statistical moments.
def load_samples(self, data_frame):
201    def load_samples(self, data_frame):
202        """
203        Extract output values for each quantity of interest from Dataframe.
204
205        Parameters
206        ----------
207        data_frame : EasyVVUQ (pandas) data frame
208            The code samples from the EasyVVUQ data frame.
209
210        Returns
211        -------
212        None.
213
214        """
215        print('Loading samples...')
216        qoi_cols = self.qoi_cols
217        samples = {k: [] for k in qoi_cols}
218        for k in qoi_cols:
219            for run_id in data_frame[('run_id', 0)].unique():
220                values = data_frame.loc[data_frame[('run_id', 0)] == run_id][k].values
221                samples[k].append(values.flatten())
222            samples[k] = np.array(samples[k])
223        self.samples = samples
224        print('done')

Extract output values for each quantity of interest from Dataframe.

Parameters
  • data_frame (EasyVVUQ (pandas) data frame): The code samples from the EasyVVUQ data frame.
Returns
  • None.
def get_moments(self, qoi, n_mc):
226    def get_moments(self, qoi, n_mc):
227        """
228        Compute the mean and variance through Monte Carlo sampling of the SSC
229        surrogate. Independent random inputs samples are drawn though the
230        SSC sampler object.
231
232        Parameters
233        ----------
234        qoi : string
235            The name of the QoI.
236        n_mc : int
237            The number of Monte Carlo samples.
238
239        Returns
240        -------
241        mean : array
242            The mean of qoi.
243        var : array
244            The variance of qoi.
245
246        """
247        print('Computing mean and variance...')
248        Xi = self.sampler.sample_inputs(n_mc)
249        rvs = [self.surrogate(qoi, xi) for xi in Xi]
250        mean = np.mean(rvs)
251        var = np.var(rvs)
252        print('done.')
253        return np.array([mean]), np.array([var])

Compute the mean and variance through Monte Carlo sampling of the SSC surrogate. Independent random inputs samples are drawn though the SSC sampler object.

Parameters
  • qoi (string): The name of the QoI.
  • n_mc (int): The number of Monte Carlo samples.
Returns
  • mean (array): The mean of qoi.
  • var (array): The variance of qoi.
def update_surrogate(self, qoi, data_frame, max_LEC_jobs=4, n_mc_LEC=5, max_ENO_jobs=4):
255    def update_surrogate(self, qoi, data_frame, max_LEC_jobs=4, n_mc_LEC=5,
256                         max_ENO_jobs=4):
257        """
258        Update the SSC surrogate given new data. Given an EasyVVUQ dataframe,
259        check the LEC condition, and compute the ENO interpolation stencils.
260
261        Parameters
262        ----------
263        qoi : string
264            The name of the QoI on the basis of which the sampling plan
265            is refined.
266        data_frame : EasyVVUQ (pandas) data frame
267            The code samples from the EasyVVUQ data frame.
268        max_LEC_jobs : int, optional
269            The number of LEC checks to perform in parallel. The default is 4.
270        n_mc_LEC : int, optional
271            The number of surrogate evaluations used in the LEC check.
272            The default is 5.
273        max_LEC_jobs : int, optional
274            The number of ENO stencils to compute in parallel. The default is 4.
275
276        Returns
277        -------
278        None. Stores the polynomials orders, interpolation stencils and
279        the simplex probabilities in analysis.p_j, analysis.S_j and
280        analysis.prob_j respectively.
281
282        """
283
284        # the number of code evaluations
285        n_s = self.sampler.n_samples
286        # the number of simplex elements
287        n_e = self.sampler.n_elements
288
289        # load the EasyVVUQ data frame
290        self.load_samples(data_frame)
291        # code outputs
292        # v = np.array(self.samples[qoi])
293        v = self.samples[qoi]
294
295        # find the max polynomial order and set the p_j = pmax
296        pmax = self.sampler.find_pmax(n_s)
297        if pmax > self.sampler.pmax_cutoff:
298            pmax = self.sampler.pmax_cutoff
299            print('Max. polynomial order set by hand to = ' + str(pmax))
300        else:
301            print('Max. polynomial order allowed by n_s = ' + str(pmax))
302
303        # polynomial order per simplex elememt
304        p_j = (np.ones(n_e) * pmax).astype('int')
305
306        # compute nearest neighbour stencils
307        S_j = self.sampler.compute_stencil_j()
308
309        # check the LEC condition of all stencil
310        res_LEC = self.sampler.check_LEC(p_j, v, S_j,
311                                         n_mc=n_mc_LEC,
312                                         max_jobs=max_LEC_jobs)
313        # updated polynomial order, stencil and el_idx are the element indices
314        # per interpolation stencil
315        p_j = res_LEC['p_j']
316        S_j = res_LEC['S_j']
317        el_idx = res_LEC['el_idx']
318
319        # convert the nearest-neighbour stencils to ENO stencils
320        S_j, p_j, el_idx = self.sampler.compute_ENO_stencil(p_j, S_j, el_idx,
321                                                            max_jobs=max_ENO_jobs)
322        # store polynomial orders and stencils
323        self.p_j = p_j
324        self.S_j = S_j
325
326        print("Updated polynomials orders = %s" % p_j)
327
328        # compute the simplex probabilities
329        self.prob_j = self.sampler.compute_probability()

Update the SSC surrogate given new data. Given an EasyVVUQ dataframe, check the LEC condition, and compute the ENO interpolation stencils.

Parameters
  • qoi (string): The name of the QoI on the basis of which the sampling plan is refined.
  • data_frame (EasyVVUQ (pandas) data frame): The code samples from the EasyVVUQ data frame.
  • max_LEC_jobs (int, optional): The number of LEC checks to perform in parallel. The default is 4.
  • n_mc_LEC (int, optional): The number of surrogate evaluations used in the LEC check. The default is 5.
  • max_LEC_jobs (int, optional): The number of ENO stencils to compute in parallel. The default is 4.
Returns
  • None. Stores the polynomials orders, interpolation stencils and
  • the simplex probabilities in analysis.p_j, analysis.S_j and
  • analysis.prob_j respectively.
def adapt_locally(self, n_new_samples=1):
331    def adapt_locally(self, n_new_samples=1):
332        """
333        Locally refine the sampling plan based on the SSC geometric
334        refinement measure.
335
336        Parameters
337        ----------
338        n_new_samples : int, optional
339            The number of new code evaulations to perform. The default is 1.
340
341        Returns
342        -------
343        None. Updates the Delaunay triangulation of the SSC sampler with
344        the new points. A new ensemble must be executed next.
345
346        """
347
348        if n_new_samples > self.sampler.n_elements:
349            n_new_samples = self.sampler.n_elements
350
351        # compute the refinement measures
352        eps_bar_j, vol_j = self.sampler.compute_eps_bar_j(self.p_j, self.prob_j)
353
354        # rank elements according to eps_bar_j
355        refine_idx = np.flipud(np.argsort(eps_bar_j))
356
357        # find the elements at the hypercube boundaries
358        bound_simplices = self.sampler.find_boundary_simplices()
359
360        # store which edges are refined, to prevent refining the same edge twice
361        # during the same interation
362        refined_edges = []
363
364        # the refinement locations
365        xi_k_jref = np.zeros([n_new_samples, self.sampler.n_dimensions])
366
367        i = 0
368        j = 0
369
370        # determine n_new_samples locations
371        while i < n_new_samples:
372
373            if np.in1d(refine_idx[j], bound_simplices) == False:
374                # compute the subvertices of the element chosen for refinement
375                sub_vertices = self.sampler.compute_sub_simplex_vertices(refine_idx[j])
376                # draw a random sample from the sub simplex
377                xi_k_jref[i, :] = self.sampler.sample_simplex(1, sub_vertices)
378                # if interior is refined: no danger of refining the same edge twice
379                already_refined = False
380            else:
381                print('refining edge')
382
383                xi_k_jref[i, :], refined_edges, already_refined =  \
384                    self.sampler.sample_simplex_edge(refine_idx[j], refined_edges)
385
386            if not already_refined:
387                i += 1
388                j += 1
389            else:
390                print('Edge already refined, selecting another sample.')
391                j += 1
392
393        self.sampler.update_Delaunay(xi_k_jref)

Locally refine the sampling plan based on the SSC geometric refinement measure.

Parameters
  • n_new_samples (int, optional): The number of new code evaulations to perform. The default is 1.
Returns
  • None. Updates the Delaunay triangulation of the SSC sampler with
  • the new points. A new ensemble must be executed next.
def surrogate(self, qoi, xi):
395    def surrogate(self, qoi, xi):
396        """
397        Evaluate the SSC surrogate at xi.
398
399        Parameters
400        ----------
401        qoi : string
402            Name of the QoI.
403        xi : array, shape (n_xi,)
404            The location in the input space at which to evaluate the
405            surrogate.
406
407        Returns
408        -------
409        array
410            The surrogate output at xi
411
412        """
413
414        if not isinstance(xi, np.ndarray):
415            xi = np.array([xi])
416
417        surr = self.sampler.surrogate(xi, self.S_j, self.p_j, self.samples[qoi])
418        return np.array([surr])

Evaluate the SSC surrogate at xi.

Parameters
  • qoi (string): Name of the QoI.
  • xi (array, shape (n_xi,)): The location in the input space at which to evaluate the surrogate.
Returns
  • array: The surrogate output at xi
def get_sample_array(self, qoi):
420    def get_sample_array(self, qoi):
421        """
422        Parameters
423        ----------
424        qoi : str
425            name of quantity of interest
426
427        Returns
428        -------
429        array of all samples of qoi
430        """
431
432        return self.samples[qoi]
Parameters
  • qoi (str): name of quantity of interest
Returns
  • array of all samples of qoi
def plot_grid(self):
434    def plot_grid(self):
435        """
436        Plot the 1D or 2D sampling plan and color code the simplices according
437        to their polynomial order.
438
439        Returns
440        -------
441        None.
442
443        """
444
445        assert self.sampler.n_xi == 1 or self.sampler.n_xi == 2, \
446            "Only works for 1D and 2D problems"
447
448        tri = self.sampler.tri
449        colors = ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3",
450                  "#fdb462", "#b3de69", "#fccde5", "#d9d9d9"]
451
452        if self.sampler.n_xi == 2:
453            # plot the delaunay grid and color it according to the local p_j
454            fig = plt.figure()
455            ax = fig.add_subplot(111, xlabel=r'$\xi_1$', ylabel=r'$\xi_2$',
456                                 xlim=[self.sampler.corners[0][0],
457                                       self.sampler.corners[0][1]],
458                                 ylim=[self.sampler.corners[1][0],
459                                       self.sampler.corners[1][1]])
460
461            for p in range(np.max(self.p_j)):
462                idx = (self.p_j == p + 1).nonzero()[0]
463                first = True
464                for i in idx:
465                    vertices = tri.points[tri.simplices[i]]
466                    if first:
467                        pg = Polygon(vertices, facecolor=colors[p], edgecolor='k',
468                                     label=r'$p_j = %d$' % (p + 1))
469                        first = False
470                    else:
471                        pg = Polygon(vertices, facecolor=colors[p], edgecolor='k')
472
473                    ax.add_patch(pg)
474
475            leg = plt.legend(loc=0)
476            leg.set_draggable(True)
477
478        elif self.sampler.n_xi == 1:
479            fig = plt.figure()
480            ax = fig.add_subplot(111, xlabel='cell center',
481                                 ylabel=r'polynomial order %s' % '$p_j$')
482            ax.plot(self.sampler.compute_xi_center_j(), self.p_j, 'o')
483            ax.vlines(self.sampler.compute_xi_center_j(), np.zeros(self.sampler.n_elements),
484                      self.p_j, linestyles='dashed')
485            ax.vlines(self.sampler.tri.points, -0.05 * np.max(self.p_j),
486                      0.05 * np.max(self.p_j))
487
488        plt.tight_layout()
489        plt.show()

Plot the 1D or 2D sampling plan and color code the simplices according to their polynomial order.

Returns
  • None.