class MetropolisHastings(MCMC): @beartype def __init__( self, pdf_target: Union[Callable, list[Callable]] = None, log_pdf_target: Union[Callable, list[Callable]] = None, args_target: tuple = None, burn_length: Annotated[int, Is[lambda x: x >= 0]] = 0, jump: int = 1, dimension: int = None, seed: list = None, save_log_pdf: bool = False, concatenate_chains: bool = True, n_chains: int = None, proposal: Distribution = None, proposal_is_symmetric: bool = False, random_state: RandomStateType = None, nsamples: PositiveInteger = None, nsamples_per_chain: PositiveInteger = None, ): """ Metropolis-Hastings algorithm :cite:`MCMC1` :cite:`MCMC2` :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 **x**, which are the point(s) at which to evaluate the pdf. Within :class:`.MCMC` the pdf_target is evaluated as: :code:`p(x) = pdf_target(x, \*args_target)` where **x** is a :class:`numpy.ndarray of shape :code:`(nsamples, dimension)` and `args_target` are additional positional arguments that are provided to :class:`.MCMC` via its `args_target` input. If `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 **x** is a :class:`numpy.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). Same comments as for input `pdf_target`. :param args_target: Positional arguments of the pdf / log-pdf target function. See `pdf_target` :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 dimension: A scalar value defining the dimension of target density function. Either `dimension` and `n_chains` or `seed` must be provided. :param seed: Seed of the Markov chain(s), shape :code:`(n_chains, dimension)`. Default: :code:`zeros(n_chains x dimension)`. If seed is not provided, both n_chains and dimension 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 :code:`(nsamples * n_chains, dimension)` if :any:`True`, :code:`(nsamples, n_chains, dimension)` if :any:`False`. Default: :any:`True` :param n_chains: The number of Markov chains to generate. Either dimension and `n_chains` or `seed` must be provided. :param proposal: Proposal distribution, must have a log_pdf/pdf and rvs method. Default: standard multivariate normal :param proposal_is_symmetric: Indicates whether the proposal distribution is symmetric, affects computation of acceptance probability alpha Default: :any:`False`, set to :any:`True` if default proposal is used :param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`. :param nsamples: Number of samples to generate. :param nsamples_per_chain: Number of samples to generate per chain. """ self.nsamples = nsamples self.nsamples_per_chain = nsamples_per_chain super().__init__( pdf_target=pdf_target, log_pdf_target=log_pdf_target, args_target=args_target, dimension=dimension, seed=seed, burn_length=burn_length, jump=jump, save_log_pdf=save_log_pdf, concatenate_chains=concatenate_chains, random_state=random_state, n_chains=n_chains, ) self.logger = logging.getLogger(__name__) # Initialize algorithm specific inputs self.proposal = proposal self.proposal_is_symmetric = proposal_is_symmetric if self.proposal is None: if self.dimension is None: raise ValueError( "UQpy: Either input proposal or dimension must be provided." ) from UQpy.distributions import JointIndependent, Normal self.proposal = JointIndependent([Normal()] * self.dimension) self.proposal_is_symmetric = True else: self._check_methods_proposal(self.proposal) self.logger.info("\nUQpy: Initialization of " + self.__class__.__name__ + " algorithm complete.") if (nsamples is not None) or (nsamples_per_chain is not None): self.run( nsamples=nsamples, nsamples_per_chain=nsamples_per_chain, ) def run_one_iteration(self, current_state: np.ndarray, current_log_pdf: np.ndarray): """ Run one iteration of the mcmc chain for MH algorithm, starting at current state - see :class:`MCMC` class. """ # Sample candidate candidate = current_state + self.proposal.rvs( nsamples=self.n_chains, random_state=self.random_state) # Compute log_pdf_target of candidate sample log_p_candidate = self.evaluate_log_target(candidate) # Compute acceptance ratio if self.proposal_is_symmetric: # proposal is symmetric log_ratios = log_p_candidate - current_log_pdf else: # If the proposal is non-symmetric, one needs to account for it in computing acceptance ratio log_proposal_ratio = self.proposal.log_pdf( candidate - current_state) - self.proposal.log_pdf(current_state - candidate) log_ratios = log_p_candidate - current_log_pdf - log_proposal_ratio # Compare candidate with current sample and decide or not to keep the candidate (loop over nc chains) accept_vec = np.zeros( (self.n_chains, ) ) # this vector will be used to compute accept_ratio of each chain unif_rvs = (Uniform().rvs(nsamples=self.n_chains, random_state=self.random_state).reshape( (-1, ))) for nc, (cand, log_p_cand, r_) in enumerate(zip(candidate, log_p_candidate, log_ratios)): accept = np.log(unif_rvs[nc]) < r_ if accept: current_state[nc, :] = cand current_log_pdf[nc] = log_p_cand accept_vec[nc] = 1.0 # Update the acceptance rate self._update_acceptance_rate(accept_vec) return current_state, current_log_pdf
# %% md # # Create a distribution object, generate samples and evaluate the function at the samples. # %% np.random.seed(1) dist_1 = Uniform(loc=-5.12, scale=10.24) dist_2 = Uniform(loc=-5.12, scale=10.24) marg = [dist_1, dist_2] joint = JointIndependent(marginals=marg) n_samples = 100 x = joint.rvs(n_samples) y = function(x[:, 0], x[:, 1]) # %% md # # Visualize the 2D function. # %% xmin, xmax = -6, 6 ymin, ymax = -6, 6 X1 = np.linspace(xmin, xmax, 50) X2 = np.linspace(ymin, ymax, 50) X1_, X2_ = np.meshgrid(X1, X2) # grid of points f = function(X1_, X2_)
# %% np.random.seed(1) dist_1 = Uniform(loc=0, scale=2 * np.pi) dist_2 = Uniform(loc=0, scale=1) marg = [dist_1] * 4 marg_1 = [dist_2] * 4 marg.extend(marg_1) joint = JointIndependent(marginals=marg) n_samples = 9000 x = joint.rvs(n_samples) y = function(x) # %% md # # Create an object from the PCE class. Compute PCE coefficients using least squares regression. # %% max_degree = 6 polynomial_basis = TotalDegreeBasis(joint, max_degree) least_squares = LeastSquareRegression() pce = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=least_squares) pce.fit(x, y)
# %% errors = [] # construct PCE surrogate models for max_degree in range(1, 6): print('Total degree: ', max_degree) polynomial_basis = TotalDegreeBasis(joint, max_degree) print('Size of basis:', polynomial_basis.polynomials_number) # training data sampling_coeff = 5 print('Sampling coefficient: ', sampling_coeff) np.random.seed(42) n_samples = math.ceil(sampling_coeff * polynomial_basis.polynomials_number) print('Training data: ', n_samples) xx = joint.rvs(n_samples) yy = np.array([analytical_eigenvalues_2d(dim_out, x[0], x[1]) for x in xx]) # fit model least_squares = LeastSquareRegression() pce_metamodel = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=least_squares) pce_metamodel.fit(xx, yy) # coefficients # print('PCE coefficients: ', pce.C) # validation errors np.random.seed(999) n_samples = 1000 x_val = joint.rvs(n_samples)
# %% # input distributions dist = Uniform(loc=0, scale=1) marg = [dist] * 3 joint = JointIndependent(marginals=marg) # %% md # # Compute reference mean and variance values using Monte Carlo sampling. # %% # reference moments via Monte Carlo Sampling n_samples_mc = 1000000 xx = joint.rvs(n_samples_mc) yy = function(xx) mean_ref = yy.mean() var_ref = yy.var() # %% md # # Create validation data sets, to be used later to estimate the accuracy of the PCE. # %% # validation data sets n_samples_val = 1000 xx_val = joint.rvs(n_samples_val) yy_val = function(xx_val)
# %% md # # We must now compute the PCE coefficients. For that we first need a training sample of input random variable # realizations and the corresponding model outputs. These two data sets form what is also known as an # ''experimental design''. In case of adaptive construction of PCE by the best model selection algorithm, size of # ED is given apriori and the most suitable basis functions are adaptively selected. # %% # create training data sample_size = 500 print('Size of experimental design:', sample_size) # realizations of random inputs xx_train = joint.rvs(sample_size) # corresponding model outputs yy_train = np.array([ishigami(x) for x in xx_train]) # %% md # # We now fit the PCE coefficients by solving a regression problem. Here we opt for the _np.linalg.lstsq_ method, # which is based on the _dgelsd_ solver of LAPACK. This original PCE class will be used for further selection of # the best basis functions. # %% # fit model least_squares = LeastSquareRegression() pce = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=least_squares)
pf_stretch = np.zeros((ntrials, 1)) cov1_stretch = np.zeros((ntrials, 1)) cov2_stretch = np.zeros((ntrials, 1)) m = np.ones(2) m[0] = 5 m[1] = 125 C = np.eye(2) C[0, 0] = 1 C[1, 1] = 20**2 for i in range(ntrials): m1 = PythonModel(model_script='local_Resonance_pfn.py', model_object_name="RunPythonModel") model = RunModel(model=m1) dist = MultivariateNormal(mean=m, cov=C) xx = dist.rvs(nsamples=1000, random_state=123) xx1 = dist.rvs(nsamples=100, random_state=123) sampling = Stretch(dimension=2, n_chains=100, log_pdf_target=dist.log_pdf) x_ss_stretch = SubsetSimulation( sampling=sampling, runmodel_object=model, conditional_probability=0.1, nsamples_per_subset=1000, samples_init=xx, ) pf_stretch[i] = x_ss_stretch.failure_probability cov1_stretch[i] = x_ss_stretch.independent_chains_CoV cov2_stretch[i] = x_ss_stretch.dependent_chains_CoV print(pf_stretch)