Example #1
0
    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."""
Example #2
0
    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.")
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
    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 
Example #8
0
    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
Example #9
0
    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!")
Example #10
0
    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)
Example #11
0
    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)