def __init__( self, nodes: Union[list, Numpy2DFloatArray] = None, nsamples: PositiveInteger = None, random_state: RandomStateType = None, ): """ Generate uniform random samples inside an n-dimensional simplex. :param nodes: The vertices of the simplex. :param nsamples: The number of samples to be generated inside the simplex. If `nsamples` is provided when the object is defined, the :meth:`run` method will be called automatically. If `nsamples` is not provided when the object is defined, the user must invoke the :meth:`run` method and specify `nsamples`. :param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. """ self.samples: NumpyFloatArray = None self.nodes = np.atleast_2d(nodes) self.nsamples = nsamples if self.nodes.shape[0] != self.nodes.shape[1] + 1: raise NotImplementedError( "UQpy: Size of simplex (nodes) is not consistent.") self.random_state = process_random_state(random_state) if nsamples is not None: self.run(nsamples=nsamples) """New random samples distributed uniformly inside the simplex."""
def run(self, nsamples: PositiveInteger, random_state: RandomStateType = None): """ Execute the random sampling in the :class:`.MonteCarloSampling` class. The :meth:`run` method is the function that performs random sampling in the :class:`.MonteCarloSampling` class. If `nsamples` is provided, the :meth:`run` method is automatically called when the :class:`MonteCarloSampling` object is defined. The user may also call the :meth:`run` method directly to generate samples. The :meth:`run` method of the :class:`.MonteCarloSampling` class can be invoked many times and each time the generated samples are appended to the existing samples. :param nsamples: Number of samples to be drawn from each distribution. If the :meth:`run` method is invoked multiple times, the newly generated samples will be appended to the existing samples. :param random_state: Random seed used to initialize the pseudo-random number generator. """ # Check if a random_state is provided. self.random_state = (process_random_state(random_state) if random_state is not None else self.random_state) self.logger.info("UQpy: Running Monte Carlo Sampling.") if isinstance(self.dist_object, list): temp_samples = [] for i in range(len(self.dist_object)): if hasattr(self.dist_object[i], "rvs"): temp_samples.append(self.dist_object[i].rvs( nsamples=nsamples, random_state=self.random_state)) else: raise ValueError("UQpy: rvs method is missing.") self.x = [] for j in range(nsamples): y = [temp_samples[k][j] for k in range(len(self.dist_object))] self.x.append(np.array(y)) elif hasattr(self.dist_object, "rvs"): temp_samples = self.dist_object.rvs(nsamples=nsamples, random_state=self.random_state) self.x = temp_samples if self.samples is None: if isinstance(self.dist_object, list) and self.array is True: self.samples = np.hstack(np.array(self.x)).T else: self.samples = np.array(self.x) elif isinstance(self.dist_object, list) and self.array is True: self.samples = np.concatenate( [self.samples, np.hstack(np.array(self.x)).T], axis=0) elif isinstance(self.dist_object, Distribution): self.samples = np.vstack([self.samples, self.x]) else: self.samples = np.vstack([self.samples, self.x]) self.nsamples = len(self.samples) self.logger.info("UQpy: Monte Carlo Sampling Complete.")
def __init__(self, pdf_target: Callable = None, log_pdf_target: Callable = None, args_target: tuple = None, proposal: Union[None, Distribution] = None, random_state: RandomStateType = None, nsamples: PositiveInteger = None): """ Sample from a user-defined target density using importance sampling. :param pdf_target: Callable that evaluates the pdf of the target distribution. Either `log_pdf_target` or `pdf_target` must be specified (the former is preferred). :param log_pdf_target: Callable that evaluates the log-pdf of the target distribution. Either `log_pdf_target` or `pdf_target` must be specified (the former is preferred). :param args_target: Positional arguments of the target log_pdf / pdf callable. :param proposal: Proposal to sample from. This :class:`.Distribution` object must have an :py:meth:`rvs` method and a `log_pdf` (or pdf) method. :param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. :param nsamples: Number of samples to generate - see :meth:`run` method. If not :any:`None`, the :py:meth:`run` method is called when the object is created. Default is :any:`None`. """ # Initialize proposal: it should have an rvs and log pdf or pdf method self.evaluate_log_target = None self.proposal = proposal self._args_target = args_target self.log_pdf_target = log_pdf_target self.pdf_target = pdf_target self.logger = logging.getLogger(__name__) self.random_state = process_random_state(random_state) # Initialize the samples and weights self.samples: NumpyFloatArray = None """Set of samples, :class:`numpy.ndarray` of shape :code:`(nsamples, dimensions)`""" self.unnormalized_log_weights: NumpyFloatArray = None """Unnormalized log weights, i.e., :code:`log_w(x) = log_target(x) - log_proposal(x)`, :class:`numpy.ndarray` of shape :code:`(nsamples, )`""" self.weights: NumpyFloatArray = None """Importance weights, weighted so that they sum up to 1, :class:`numpy.ndarray` of shape :code:`(nsamples, )` """ self.unweighted_samples: NumpyFloatArray = None """Set of un-weighted samples (useful for instance for plotting), computed by calling the :meth:`resample` method""" # Run IS if nsamples is provided if nsamples is not None and nsamples != 0: self.run(nsamples)
def __init__( self, distributions: Union[Distribution, list[Distribution]], nsamples: PositiveInteger, criterion: Criterion = Random(), random_state: RandomStateType = None ): """ Perform Latin hypercube sampling (LHS) of random variables. All distributions in :class:`LatinHypercubeSampling` must be independent. :class:`.LatinHypercubeSampling` does not generate correlated random variables. Therefore, for multi-variate designs the `distributions` must be a list of :class:`.DistributionContinuous1D` objects or an object of the :class:`.JointIndependent` class. :param distributions: List of :class:`.Distribution` objects corresponding to each random variable. :param nsamples: Number of samples to be drawn from each distribution. :param random_state: Random seed used to initialize the pseudo-random number generator. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. :param criterion: The criterion for pairing the generating sample points. This parameter must be of type :class:`.Criterion`. Options: 1. :class:`.Random` - completely random. \n 2. :class:`.Centered` - points only at the centre. \n 3. :class:`.MaxiMin` - maximizing the minimum distance between points. \n 4. :class:`.MinCorrelation` - minimizing the correlation between the points. \n 5. User-defined criterion class, by providing an implementation of the abstract class :class:`Criterion` """ self.random_state = process_random_state(random_state) self.distributions = distributions self.criterion = criterion self.nsamples = nsamples self.logger = logging.getLogger(__name__) self._samples: NumpyFloatArray = None if isinstance(self.distributions, list): self._samples = np.zeros([self.nsamples, len(self.distributions)]) elif isinstance(self.distributions, DistributionContinuous1D): self._samples = np.zeros([self.nsamples, 1]) elif isinstance(self.distributions, JointIndependent): self._samples = np.zeros([self.nsamples, len(self.distributions.marginals)]) self.samplesU01: NumpyFloatArray = np.zeros_like(self._samples) """The generated LHS samples on the unit hypercube.""" if self.nsamples is not None: self.run(self.nsamples)
def __init__( self, runmodel_object: RunModel, distributions: Union[JointIndependent, Union[list, tuple]], n_levels: Annotated[int, Is[lambda x: x >= 3]], delta: Union[float, int] = None, random_state: RandomStateType = None, n_trajectories: PositiveInteger = None, maximize_dispersion: bool = False, ): """ Compute sensitivity indices based on the Morris screening method. :param runmodel_object: The computational model. It should be of type :class:`.RunModel`. The output QoI can be a scalar or vector of length :code:`ny`, then the sensitivity indices of all :code:`ny` outputs are computed independently. :param distributions: List of :class:`.Distribution` objects corresponding to each random variable, or :class:`.JointIndependent` object (multivariate RV with independent marginals). :param n_levels: Number of levels that define the grid over the hypercube where evaluation points are sampled. Must be an integer :math:`\ge 3`. :param delta: Size of the jump between two consecutive evaluation points, must be a multiple of delta should be in :code:`{1/(n_levels-1), ..., 1-1/(n_levels-1)}`. Default: :math:`delta=\\frac{levels\_number}{2 * (levels\_number-1)}` if `n_levels` is even, :math:`delta=0.5` if n_levels is odd. :param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`. :param n_trajectories: Number of random trajectories, usually chosen between :math:`5` and :math:`10`. The number of model evaluations is :code:`n_trajectories * (d+1)`. If None, the `Morris` object is created but not run (see :py:meth:`run` method) :param maximize_dispersion: If :any:`True`, generate a large number of design trajectories and keep the ones that maximize dispersion between all trajectories, allows for a better coverage of the input space. Default :any:`False`. """ # Check RunModel object and distributions self.runmodel_object = runmodel_object marginals = (distributions.marginals if isinstance( distributions, JointIndependent) else distributions) self.icdfs = [getattr(dist, "icdf", None) for dist in marginals] if any(icdf is None for icdf in self.icdfs): raise ValueError( "At least one of the distributions provided has a None icdf") self.dimension = len(self.icdfs) if self.dimension != len(self.runmodel_object.model.var_names): raise ValueError( "The number of distributions provided does not match the number of RunModel variables" ) self.n_levels = n_levels self.delta = delta self.check_levels_delta() self.random_state = process_random_state(random_state) self.maximize_dispersion = maximize_dispersion self.trajectories_unit_hypercube: NumpyFloatArray = None """Trajectories in the unit hypercube, :class:`numpy.ndarray` of shape :code:`(n_trajectories, d+1, d)`""" self.trajectories_physical_space: NumpyFloatArray = None """Trajectories in the physical space, :class:`numpy.ndarray` of shape :code:`(n_trajectories, d+1, d)`""" self.elementary_effects: NumpyFloatArray = None """Elementary effects :math:`EE_{k}`, :class:`numpy.ndarray` of shape :code:`(n_trajectories, d, ny)`.""" self.mustar_indices: NumpyFloatArray = None """First Morris sensitivity index :math:`\mu_{k}^{\star}`, :class:`numpy.ndarray` of shape :code:`(d, ny)`""" self.sigma_indices: NumpyFloatArray = None """Second Morris sensitivity index :math:`\sigma_{k}`, :class:`numpy.ndarray` of shape :code:`(d, ny)`""" if n_trajectories is not None: self.run(n_trajectories)
def __init__( self, distributions: Union[Distribution, list[Distribution]], runmodel_object: RunModel, surrogate: SurrogateType, learning_function: LearningFunction, samples: Numpy2DFloatArray = None, nsamples: PositiveInteger = None, learning_nsamples: PositiveInteger = None, qoi_name: str = None, n_add: int = 1, random_state: RandomStateType = None, ): """ Adaptively sample for construction of a kriging surrogate for different objectives including reliability, optimization, and global fit. :param distributions: List of :class:`.Distribution` objects corresponding to each random variable. :param runmodel_object: A :class:`.RunModel` object, which is used to evaluate the model. :param surrogate: A kriging surrogate model, this object must have :meth:`fit` and :meth:`predict` methods. May be an object of the :py:meth:`UQpy` :class:`Kriging` class or an object of the :py:mod:`scikit-learn` :class:`GaussianProcessRegressor` :param learning_function: Learning function used as the selection criteria to identify new samples. :param samples: The initial samples at which to evaluate the model. Either `samples` or `nstart` must be provided. :param nsamples: Total number of samples to be drawn (including the initial samples). If `nsamples` and `samples` are provided when instantiating the class, the :meth:`run` method will automatically be called. If either `nsamples` or `samples` is not provided, :class:`.AdaptiveKriging` can be executed by invoking the :meth:`run` method and passing `nsamples`. :param learning_nsamples: Number of samples generated for evaluation of the learning function. Samples for the learning set are drawn using :class:`.LatinHypercubeSampling`. :param qoi_name: Name of the quantity of interest. If the quantity of interest is a dictionary, this is used to convert it to a list :param n_add: Number of samples to be added per iteration. :param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. """ # Initialize the internal variables of the class. self.runmodel_object = runmodel_object self.samples: Numpy2DFloatArray = np.array(samples) """contains the samples at which the model is evaluated.""" self.learning_nsamples = learning_nsamples self.initial_nsamples = None self.logger = logging.getLogger(__name__) self.qoi_name = qoi_name self.learning_function = learning_function self.learning_set = None self.dist_object = distributions self.nsamples = nsamples self.moments = None self.n_add = n_add self.indicator = False self.pf = [] self.cov_pf = [] self.dimension = 0 self.qoi = None self.prediction_model = None # Initialize and run preliminary error checks. self.dimension = len(distributions) if samples is not None and self.dimension != self.samples.shape[1]: raise NotImplementedError( "UQpy Error: Dimension of samples and distribution are inconsistent." ) if isinstance(distributions, list): for i in range(len(distributions)): if not isinstance(distributions[i], DistributionContinuous1D): raise TypeError( "UQpy: A DistributionContinuous1D object must be provided." ) elif not isinstance(distributions, (DistributionContinuous1D, JointIndependent)): raise TypeError( "UQpy: A DistributionContinuous1D or JointInd object must be provided." ) self.random_state = process_random_state(random_state) self.surrogate = surrogate self.logger.info( "UQpy: Adaptive Kriging - Running the initial sample set using RunModel." ) # Evaluate model at the training points if len(self.runmodel_object.qoi_list) == 0 and samples is not None: self.runmodel_object.run(samples=self.samples, append_samples=False) if samples is not None and len( self.runmodel_object.qoi_list) != self.samples.shape[0]: raise NotImplementedError( "UQpy: There should be no model evaluation or Number of samples and model " "evaluation in RunModel object should be same.") if self.nsamples is not None and samples is not None: self.run(nsamples=self.nsamples)
def __init__( self, dimension: Union[None, int] = None, pdf_target: Union[Callable, list[Callable], None] = None, log_pdf_target: Union[Callable, list[Callable], None] = None, args_target: Union[tuple, None] = None, seed: Union[list, None] = None, burn_length: Annotated[int, Is[lambda x: x >= 0]] = 0, jump: PositiveInteger = 1, n_chains: Union[None, int] = None, save_log_pdf: bool = False, concatenate_chains: bool = True, random_state: RandomStateType = None, ): """ Generate samples from arbitrary user-specified probability density function using Markov Chain Monte Carlo. This is the parent class for all MCMC algorithms. This parent class only provides the framework for MCMC and cannot be used directly for sampling. Sampling is done by calling the child class for the specific MCMC algorithm. :param dimension: A scalar value defining the dimension of target density function. Either *dimension* and *n_chains* or *seed* must be provided. :param pdf_target: Target density function from which to draw random samples. Either `pdf_target` or `log_pdf_target` must be provided (the latter should be preferred for better numerical stability). If `pdf_target` is a callable, it refers to the joint pdf to sample from, it must take at least one input :code:`x`, which are the point(s) at which to evaluate the pdf. Within MCMC the `pdf_target` is evaluated as: :code:`p(x) = pdf_target(x, *args_target)` where :code:`x` is a ndarray of shape :code:`(nsamples, dimension)` and `args_target` are additional positional arguments that are provided to MCMC via its `args_target` input. If :code:`pdf_target` is a list of callables, it refers to independent marginals to sample from. The marginal in dimension :code:`j` is evaluated as: :code:`p_j(xj) = pdf_target[j](xj, *args_target[j])` where :code:`x` is a ndarray of shape :code:`(nsamples, dimension)` :param log_pdf_target: Logarithm of the target density function from which to draw random samples. Either `pdf_target` or `log_pdf_target` must be provided (the latter should be preferred for better numerical stability). :param args_target: Positional arguments of the pdf / log-pdf target function. See `pdf_target` :param seed: Seed of the Markov chain(s), shape ``(nchains, dimension)``. Default: ``zeros(nchains, dimension)``. If `seed` is not provided, both `nchains` and `dimension` must be provided. :param burn_length: Length of burn-in - i.e., number of samples at the beginning of the chain to discard (note: no thinning during burn-in). Default is :math:`0`, no burn-in. :param jump: Thinning parameter, used to reduce correlation between samples. Setting :code:`jump=n` corresponds to skipping :code:`n-1` states between accepted states of the chain. Default is :math:`1` (no thinning). :param n_chains: The number of Markov chains to generate. Either `dimension` and `nchains` or `seed` must be provided. :param save_log_pdf: Boolean that indicates whether to save log-pdf values along with the samples. Default: :any:`False` :param concatenate_chains: Boolean that indicates whether to concatenate the chains after a run, i.e., samples are stored as an :class:`numpy.ndarray` of shape ``(nsamples * nchains, dimension)`` if :any:`True`, ``(nsamples, nchains, dimension)`` if :any:`False`. Default: :any:`True` :param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. """ self.burn_length, self.jump = burn_length, jump self._initialization_seed = seed self.seed = self._preprocess_seed(seed=seed, dimensions=dimension, n_chains=n_chains) self.n_chains, self.dimension = self.seed.shape self.evaluate_log_target: Callable = None """It is a callable that evaluates the log-pdf of the target distribution at a given point **x**""" self.evaluate_log_target_marginals: Callable = None """It is a callable that evaluates the log-pdf of the target marginal distributions at a given point **x**""" # Check target pdf self.save_log_pdf = save_log_pdf self.concatenate_chains = concatenate_chains self._random_state = random_state self.random_state = process_random_state(random_state) self.logger = logging.getLogger(__name__) self.log_pdf_target = log_pdf_target self.pdf_target = pdf_target self.args_target = args_target # Initialize a few more variables self.samples: NumpyFloatArray = None """Set of MCMC samples following the target distribution, :class:`numpy.ndarray` of shape :code:`(nsamples * n_chains, dimension)` or :code:`(nsamples, n_chains, dimension)` (see input `concatenate_chains`).""" self.log_pdf_values: NumpyFloatArray = None """Values of the log pdf for the accepted samples, :class:`numpy.ndarray` of shape :code:`(n_chains * nsamples,)` or :code:`(nsamples, n_chains)`""" self.acceptance_rate = [0.0] * self.n_chains self.samples_counter: int = 0 """Total number of samples; The :py:attr:`nsamples` attribute tallies the total number of generated samples. After each iteration, it is updated by :math:`1`. At the end of the simulation, the :py:attr:`nsamples` attribute equals the user-specified value for input :py:attr:`nsamples` given to the child class.""" self.nsamples_per_chain: int = 0 """Total number of samples per chain; Similar to the attribute :py:attr:`nsamples`, it is updated during iterations as new samples are saved.""" self.iterations_number: int = 0 # total nb of iterations, grows if you call run several times """Total number of iterations, updated on-the-fly as the algorithm proceeds. It is related to number of samples
def _run(self): """ Execute subset simulation This is an instance method that runs subset simulation. It is automatically called when the :class:`.SubsetSimulation` class is instantiated. """ step = 0 n_keep = int(self.conditional_probability * self.nsamples_per_subset) d12 = [] d22 = [] # Generate the initial samples - Level 0 # Here we need to make sure that we have good initial samples from the target joint density. if self.samples_init is None: self.logger.warning( "UQpy: You have not provided initial samples.\n Subset simulation is highly sensitive " "to the initial sample set. It is recommended that the user either:\n" "- Provide an initial set of samples (samples_init) known to follow the distribution; " "or\n - Provide a robust mcmc object that will draw independent initial samples from " "the distribution.") self.mcmc_objects[0].run(nsamples=self.nsamples_per_subset) self.samples.append(self.mcmc_objects[0].samples) else: self.samples.append(self.samples_init) # Run the model for the initial samples, sort them by their performance function, and identify the # conditional level self.runmodel_object.run(samples=np.atleast_2d(self.samples[step])) self.performance_function_per_level.append( np.squeeze(self.runmodel_object.qoi_list)) g_ind = np.argsort(self.performance_function_per_level[step]) self.performance_threshold_per_level.append( self.performance_function_per_level[step][g_ind[n_keep - 1]]) # Estimate coefficient of variation of conditional probability of first level d1, d2 = self._compute_coefficient_of_variation(step) d12.append(d1**2) d22.append(d2**2) self.logger.info( "UQpy: Subset Simulation, conditional level 0 complete.") while self.performance_threshold_per_level[ step] > 0 and step < self.max_level: # Increment the conditional level step += 1 # Initialize the samples and the performance function at the next conditional level self.samples.append(np.zeros_like(self.samples[step - 1])) self.samples[step][:n_keep] = self.samples[step - 1][g_ind[0:n_keep], :] self.performance_function_per_level.append( np.zeros_like(self.performance_function_per_level[step - 1])) self.performance_function_per_level[ step][:n_keep] = self.performance_function_per_level[step - 1][ g_ind[:n_keep]] # Unpack the attributes new_sampler = copy.deepcopy(self._sampling_class) new_sampler.seed = np.atleast_2d(self.samples[step][:n_keep, :]) new_sampler.n_chains = len( np.atleast_2d(self.samples[step][:n_keep, :])) new_sampler.random_state = process_random_state(self._random_state) self.mcmc_objects.append(new_sampler) # Set the number of samples to propagate each chain (n_prop) in the conditional level n_prop_test = (self.nsamples_per_subset / self.mcmc_objects[step].n_chains) if n_prop_test.is_integer(): n_prop = (self.nsamples_per_subset // self.mcmc_objects[step].n_chains) else: raise AttributeError( "UQpy: The number of samples per subset (nsamples_per_ss) must be an integer multiple of " "the number of mcmc chains.") # Propagate each chain n_prop times and evaluate the model to accept or reject. for i in range(n_prop - 1): # Propagate each chain if i == 0: self.mcmc_objects[step].run( nsamples=2 * self.mcmc_objects[step].n_chains) else: self.mcmc_objects[step].run( nsamples=self.mcmc_objects[step].n_chains) # Decide whether a new simulation is needed for each proposed state a = self.mcmc_objects[step].samples[i * n_keep:(i + 1) * n_keep, :] b = self.mcmc_objects[step].samples[(i + 1) * n_keep:(i + 2) * n_keep, :] test1 = np.equal(a, b) test = np.logical_and(test1[:, 0], test1[:, 1]) # Pull out the indices of the false values in the test list ind_false = [i for i, val in enumerate(test) if not val] # Pull out the indices of the true values in the test list ind_true = [i for i, val in enumerate(test) if val] # Do not run the model for those samples where the mcmc state remains unchanged. self.samples[step][[x + (i + 1) * n_keep for x in ind_true], :] = \ self.mcmc_objects[step].samples[ind_true, :] self.performance_function_per_level[step][[x + (i + 1) * n_keep for x in ind_true]] = \ self.performance_function_per_level[step][ind_true] # Run the model at each of the new sample points x_run = self.mcmc_objects[step].samples[ [x + (i + 1) * n_keep for x in ind_false], :] if x_run.size != 0: self.runmodel_object.run(samples=x_run) # Temporarily save the latest model runs g_temp = np.asarray( self.runmodel_object.qoi_list[-len(x_run):]) # Accept the states with g <= g_level ind_accept = np.where( g_temp <= self.performance_threshold_per_level[step - 1])[0] for ii in ind_accept: self.samples[step][(i + 1) * n_keep + ind_false[ii]] = x_run[ii] self.performance_function_per_level[step][ (i + 1) * n_keep + ind_false[ii]] = g_temp[ii] # Reject the states with g > g_level ind_reject = np.where( g_temp > self.performance_threshold_per_level[step - 1])[0] for ii in ind_reject: self.samples[step][(i + 1) * n_keep + ind_false[ii]] =\ self.samples[step][i * n_keep + ind_false[ii]] self.performance_function_per_level[step][(i + 1) * n_keep + ind_false[ii]] = \ self.performance_function_per_level[step][i * n_keep + ind_false[ii]] g_ind = np.argsort(self.performance_function_per_level[step]) self.performance_threshold_per_level.append( self.performance_function_per_level[step][g_ind[n_keep]]) # Estimate coefficient of variation of conditional probability of first level d1, d2 = self._compute_coefficient_of_variation(step) d12.append(d1**2) d22.append(d2**2) self.logger.info("UQpy: Subset Simulation, conditional level " + str(step) + " complete.") n_fail = len([ value for value in self.performance_function_per_level[step] if value < 0 ]) failure_probability = (self.conditional_probability**step * n_fail / self.nsamples_per_subset) probability_cov_independent = np.sqrt(np.sum(d12)) probability_cov_dependent = np.sqrt(np.sum(d22)) return failure_probability, probability_cov_independent, probability_cov_dependent
def __init__( self, runmodel_object: RunModel, sampling: MCMC, samples_init: np.ndarray = None, conditional_probability: Annotated[Union[float, int], Is[lambda x: 0 <= x <= 1]] = 0.1, nsamples_per_subset: int = 1000, max_level: int = 10, ): """ Perform Subset Simulation to estimate probability of failure. This class estimates probability of failure for a user-defined model using Subset Simulation. The class can use one of several mcmc algorithms to draw conditional samples. :param runmodel_object: The computational model. It should be of type :class:`.RunModel`. :param sampling: Specifies the :class:`MCMC` algorithm. :param samples_init: A set of samples from the specified probability distribution. These are the samples from the original distribution. They are not conditional samples. The samples must be an array of size :code:`samples_number_per_subset x dimension`. If `samples_init` is not specified, the :class:`.SubsetSimulation` class will use the :class:`.MCMC` class created with the aid of `sampling` to draw the initial samples. :param conditional_probability: Conditional probability for each conditional level. :param nsamples_per_subset: Number of samples to draw in each conditional level. :param max_level: Maximum number of allowable conditional levels. """ # Initialize other attributes self._sampling_class = sampling self._random_state = process_random_state(sampling._random_state) self.runmodel_object = runmodel_object self.samples_init = samples_init self.conditional_probability = conditional_probability self.nsamples_per_subset = nsamples_per_subset self.max_level = max_level self.logger = logging.getLogger(__name__) self.mcmc_objects = [sampling] self.samples: list = list() """A list of arrays containing the samples in each conditional level. The size of the list is equal to the number of levels.""" self.performance_function_per_level: list = [] """A list of arrays containing the evaluation of the performance function at each sample in each conditional level. The size of the list is equal to the number of levels.""" self.performance_threshold_per_level: list = [] """Threshold value of the performance function for each conditional level. The size of the list is equal to the number of levels.""" self.logger.info( "UQpy: Running Subset Simulation with mcmc of type: " + str(type(sampling))) self.failure_probability: float = None """Probability of failure estimate.""" self.independent_chains_CoV: float = None """Coefficient of variation of the probability of failure estimate assuming independent chains.""" self.dependent_chains_CoV: float = None """Coefficient of variation of the probability of failure estimate with dependent chains.""" [ self.failure_probability, self.independent_chains_CoV, self.dependent_chains_CoV ] = self._run() self.logger.info("UQpy: Subset Simulation Complete!")
def __init__( self, regression_model: Regression, correlation_model: Correlation, correlation_model_parameters: list, optimizer, bounds: list = None, optimize: bool = True, optimizations_number: int = 1, normalize: bool = True, random_state: RandomStateType = None, ): """ Κriging generates an Gaussian process regression-based surrogate model to predict the model output at new sample points. :param regression_model: `regression_model` specifies and evaluates the basis functions and their coefficients, which defines the trend of the model. Built-in options: :class:`Constant`, :class:`Linear`, :class:`Quadratic` :param correlation_model: `correlation_model` specifies and evaluates the correlation function. Built-in options: :class:`Exponential`, :class:`Gaussian`, :class:`Linear`, :class:`Spherical`, :class:`Cubic`, :class:`Spline` :param correlation_model_parameters: List or array of initial values for the correlation model hyperparameters/scale parameters. :param bounds: Bounds on the hyperparameters used to solve optimization problem to estimate maximum likelihood estimator. This should be a closed bound. Default: :math:`[0.001, 10^7]` for each hyperparameter. :param optimize: Indicator to solve MLE problem or not. If :any:'True' corr_model_params will be used as initial solution for optimization problem. Otherwise, correlation_model_parameters will be directly use as the hyperparamters. Default: :any:`True`. :param optimizations_number: Number of times MLE optimization problem is to be solved with a random starting point. Default: :math:`1`. :param normalize: Boolean flag used in case data normalization is required. :param optimizer: Object of the :class:`Optimizer` optimizer used during the Kriging surrogate. Default: :class:`.MinimizeOptimizer`. :param random_state: Random seed used to initialize the pseudo-random number generator. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. """ self.regression_model = regression_model self.correlation_model = correlation_model self.correlation_model_parameters = np.array( correlation_model_parameters) self.bounds = bounds self.optimizer = optimizer self.optimizations_number = optimizations_number self.optimize = optimize self.normalize = normalize self.logger = logging.getLogger(__name__) self.random_state = random_state # Variables are used outside the __init__ self.samples = None self.values = None self.sample_mean, self.sample_std = None, None self.value_mean, self.value_std = None, None self.rmodel, self.cmodel = None, None self.beta: list = None """Regression coefficients.""" self.gamma = None self.err_var: float = None """Variance of the Gaussian random process.""" self.F_dash = None self.C_inv = None self.G = None self.F, self.R = None, None if isinstance(self.optimizer, str): raise ValueError( "The optimization function provided a input parameter cannot be None." ) if optimizer._bounds is None: optimizer.update_bounds([[0.001, 10**7]] * self.correlation_model_parameters.shape[0]) self.jac = optimizer.supports_jacobian() self.random_state = process_random_state(random_state)
def __init__( self, distributions: Union[Distribution, list[Distribution]], nsamples: Optional[int] = None, random_state: RandomStateType = None, ): """ Perform Monte Carlo sampling (MCS) of random variables. :param distributions: Probability distribution of each random variable. :param nsamples: Number of samples to be drawn from each distribution. The :meth:`run` method is automatically called if `nsamples` is provided. If `nsamples` is not provided, then the :class:`.MonteCarloSampling` object is created but samples are not generated. :param random_state: Random seed used to initialize the pseudo-random number generator. If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the object itself can be passed directly. """ self.logger = logging.getLogger(__name__) self.random_state = process_random_state(random_state) self.list = False self.array = False self._process_distributions(distributions) self.samples: NumpyFloatArray = None """Generated samples. If a list of :class:`.DistributionContinuous1D` objects is provided for `distributions`, then `samples` is an :class:`numpy.ndarray` with ``samples.shape=(nsamples, len(distributions))``. If a :class:`.DistributionContinuous1D` object is provided for `distributions` then `samples` is an array with ``samples.shape=(nsamples, 1)``. If a :class:`.DistributionContinuousND` object is provided for `distributions` then `samples` is an array with ``samples.shape=(nsamples, ND)``. If a list of mixed :class:`.DistributionContinuous1D` and :class:`.DistributionContinuousND` objects is provided then `samples` is a list with ``len(samples)=nsamples`` and ``len(samples[i]) = len(distributions)``. """ self.x = None self.samplesU01: NumpyFloatArray = None """ Generated samples transformed to the unit hypercube. This attribute exists only if the :meth:`transform_u01` method is invoked by the user. If a list of :class:`.DistributionContinuous1D` objects is provided for `distributions`, then `samplesU01` is an :class:`numpy.ndarray` with ``samples.shape=(nsamples, len(distributions))``. If a :class:`.DistributionContinuous1D` object is provided for `distributions` then `samplesU01` is an array with ``samples.shape=(nsamples, 1)``. If a :class:`.DistributionContinuousND` object is provided for `distributions` then `samplesU01` is an array with ``samples.shape=(nsamples, ND)``. If a list of mixed :class:`.DistributionContinuous1D` and :class:`.DistributionContinuousND` objects is provided then `samplesU01` is a list with ``len(samples)=nsamples`` and ``len(samples[i]) = len(distributions)``. """ self.nsamples = nsamples # Run Monte Carlo sampling if nsamples is not None: self.run(nsamples=self.nsamples, random_state=self.random_state)