def displace(self, sample): """ Construct a set of displacement vectors for the random walk from a distribution with zero mean and my covariance """ # get my decomposed covariance Σ_chol = self.sigma_chol # build a set of random displacement vectors; note that, for convenience, this starts # out as (parameters x samples), i.e. the transpose of what we need δT = altar.matrix(shape=tuple(reversed(sample.shape))).random( pdf=self.uninormal) # multiply the displacement vectors by the decomposed covariance δT = altar.blas.dtrmm(Σ_chol.sideLeft, Σ_chol.lowerTriangular, Σ_chol.opNoTrans, Σ_chol.nonUnitDiagonal, 1, Σ_chol, δT) # allocate the transpose δ = altar.matrix(shape=sample.shape) # fill it δT.transpose(δ) # offset it by the original sample δ += sample # and return it return δ
def computeCp(self, theta_mean): """ Calculate Cp """ # grab the samples shape=(samples, parameters) parameters = self.parameters observations = self.observations nCmu = self.nCmu Cp = altar.matrix(shape=(observations, observations)) # calculate kv = altar.vector(shape=observations) cmu = self.Cmu kmu = self.Kmu Kp = altar.matrix(shape=(observations, nCmu)) for i in range(nCmu): # get kmu_i from list, shape=(observations, parameters) kmu_i = kmu[i] # kv = Kmu_i * thetha_mean # dgemv y = alpha Op(A) x + beta y altar.blas.dgemv(kmu_i.opNoTrans, 1.0, kmu_i, theta_mean, 0.0, kv) Kp.setColumn(i, kv) # KpC = Kp * Cmu KpC = altar.matrix(shape=(observations, nCmu)) altar.blas.dsymm(cmu.sideRight, cmu.upperTriangular, 1.0, cmu, Kp, 0.0, KpC) # Cp = KpC*Kp altar.blas.dgemm(KpC.opNoTrans, Kp.opTrans, 1.0, KpC, Kp, 0.0, Cp) # all done return Cp
def loadGF(self): """ Load the data in the input files into memory """ # grab the input dataspace ifs = self.ifs # first the green functions try: # get the path to the file gf = ifs[self.green] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing Green functions: no '{self.green}' in '{self.case}'") # and raise the exception again raise # if all goes well else: # allocate the matrix green = altar.matrix(shape=(self.observations, self.parameters)) # and load the file contents into memory green.load(gf.uri) # all done return green
def resampling(self, step): """ Rebuild the sample and its statistics sorted by the likelihood of the parameter values """ θOld = step.theta priorOld = step.prior dataOld = step.data postOld = step.posterior # allocate the new entities θ = altar.matrix(shape=θOld.shape) prior = altar.vector(shape=priorOld.shape) data = altar.vector(shape=dataOld.shape) posterior = altar.vector(shape=postOld.shape) # build a histogram for the new samples and convert it into a vector multi = self.computeSampleMultiplicities(step=step).values() # print(" histogram as vector:") # print(" counts: {}".format(tuple(multi))) # unique samples count unique_samples = 0 # sample count index = 0 # indices for kept samples indices = altar.vector(shape=multi.shape) # record kept sample indices for i in range(multi.shape): count = int(multi[i]) # if count is zero, skip if count == 0: continue # add the unique samples count unique_samples += 1 # duplicate indices for ic in range(count): indices[index] = i index += 1 # shuffle the indices indices.shuffle(rng=self.rng) self.info.log( f"resampling: unique samples {unique_samples} out of {multi.shape}" ) # print(" kept sample indices: {}".format(tuple(indices[i] for i in range(indices.shape)))) # copy theta, (prior, data, posterior) over according to the indices for i in range(indices.shape): # get the index for old samples old = int(indices[i]) # duplicate theta for param in range(step.parameters): θ[i, param] = θOld[old, param] prior[i] = priorOld[old] data[i] = dataOld[old] posterior[i] = postOld[old] # return the shuffled data return θ, (prior, data, posterior)
def dataobsBatch(self): """ Get a batch of duplicated dataobs """ if self.dataobs_batch is None: self.dataobs_batch = altar.matrix(shape=(self.samples, self.observations)) # for each sample for sample in range(self.samples): # make the corresponding column a copy of the data vector self.dataobs_batch.setColumn(sample, self.dataobs) return self.dataobs_batch
def alloc(cls, samples, parameters): """ Allocate storage for the parts of a cooling step """ # allocate the initial sample set theta = altar.matrix(shape=(samples, parameters)).zero() # allocate the likelihood vectors prior = altar.vector(shape=samples).zero() data = altar.vector(shape=samples).zero() posterior = altar.vector(shape=samples).zero() # build one of my instances and return it return cls(beta=0, theta=theta, likelihoods=(prior, data, posterior))
def loadInputs(self): """ Load the data in the input files into memory """ # grab the input dataspace ifs = self.ifs # get the displacement data try: # get the path to the file df = ifs[self.displacements] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing displacements: no '{self.displacements}' in '{self.case}'" ) # and raise the exception again raise # if all goes well, create a data sheet data = datasheet(name="displacements") # and populate it data.read(uri=df.uri) # adjust the number of observations self.observations = len(data) # finally, try to try: # get the file node with the data covariance node = ifs[self.covariance] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing data covariance matrix: no '{self.covariance}' in '{self.case}'" ) # and re-raise the exception raise # if all goes well else: # allocate the matrix covariance = altar.matrix(shape=[self.observations] * 2) # and load the contents into memory covariance.load(node.uri) # all done return data, covariance
def computeCovariance(self, step): """ Compute the parameter covariance Σ of the sample in {step} Σ = c_m^2 \sum_{i \in samples} \tilde{w}_{i} θ_i θ_i^T} - \bar{θ} \bar{θ}^Τ where \bar{θ} = \sum_{i \in samples} \tilde{w}_{i} θ_{i} The covariance Σ gets used to build a proposal pdf for the posterior """ # unpack what i need w = self.w # w is assumed normalized θ = step.theta # the current sample set # extract the number of samples and number of parameters samples = step.samples parameters = step.parameters # initialize the covariance matrix Σ = altar.matrix(shape=(parameters, parameters)).zero() # check the geometries assert w.shape == samples assert θ.shape == (samples, parameters) assert Σ.shape == (parameters, parameters) # calculate the weighted mean of every parameter across all samples θbar = altar.vector(shape=parameters) # for each parameter for j in range(parameters): # the jth column in θ has the value of this parameter in the various samples θbar[j] = θ.getColumn(j).mean(weights=w) # start filling out Σ for i in range(samples): # get the sample sample = θ.getRow(i) # form Σ += w[i] sample sample^T altar.blas.dsyr(Σ.lowerTriangular, w[i], sample, Σ) # subtract θbar θbar^T altar.blas.dsyr(Σ.lowerTriangular, -1, θbar, Σ) # fill the upper triangle for i in range(parameters): for j in range(i): Σ[j, i] = Σ[i, j] # condition the covariance matrix if self.check_positive_definiteness: self.conditionCovariance(Σ=Σ) # all done return Σ
def initializeResiduals(self, samples, data): """ Prime the matrix that will hold the residuals (G θ - d) for each sample by duplicating the observation vector as many times as there are samples """ # allocate the residual matrix r = altar.matrix(shape=(data.shape, samples)) # for each sample for sample in range(samples): # make the corresponding column a copy of the data vector r.setColumn(sample, data) # all done return r
def initialize(self, application): """ Initialize the state of the model given a {problem} specification """ # externals from math import sin, cos # chain up super().initialize(application=application) # initialize my parameter sets self.initializeParameterSets() # mount the directory with my input data self.ifs = self.mountInputDataspace(pfs=application.pfs) # load the data from the inputs into memory displacements, self.cd = self.loadInputs() # compute the normalization self.normalization = self.computeNormalization() # compute the inverse of the covariance matrix self.cd_inv = self.computeCovarianceInverse() # build the local representations self.points = [] self.d = altar.vector(shape=self.observations) self.los = altar.matrix(shape=(self.observations, 3)) self.oid = [] # populate them for obs, record in enumerate(displacements): # extract the observation id self.oid.append(record.oid) # extract the (x,y) coordinate of the observation point self.points.append((record.x, record.y)) # extract the observed displacement self.d[obs] = record.d # get the LOS angles theta = record.theta phi = record.phi # form the projection vectors and store them self.los[obs, 0] = sin(theta) * cos(phi) self.los[obs, 1] = sin(theta) * sin(phi) self.los[obs, 2] = cos(theta) # save the parameter meta data self.meta() # show me # self.show(job=application.job, channel=self.info) # all done return self
def rank(self, step): """ Rebuild the sample and its statistics sorted by the likelihood of the parameter values """ θOld = step.theta priorOld = step.prior dataOld = step.data postOld = step.posterior # allocate the new entities θ = altar.matrix(shape=θOld.shape) prior = altar.vector(shape=priorOld.shape) data = altar.vector(shape=dataOld.shape) posterior = altar.vector(shape=postOld.shape) # build a histogram for the new samples and convert it into a vector multi = self.computeSampleMultiplicities(step=step).values() # print(" histogram as vector:") # print(" counts: {}".format(tuple(multi))) # compute the permutation that would sort the frequency table according to the sample # multiplicity, in reverse order p = multi.sortIndirect().reverse() # print(" sorted: {}".format(tuple(p[i] for i in range(p.shape)))) # the number of samples we have processed done = 0 # start moving stuff around until we have built a complete sample set for i in range(p.shape): # the old sample index old = p[i] # and its multiplicity count = int(multi[old]) # if the count has dropped to zero, we are done if count == 0: break # otherwise, duplicate this sample {count} times for dupl in range(count): # update the samples for param in range(step.parameters): θ[done, param] = θOld[old, param] # update the log-likelihoods prior[done] = priorOld[old] data[done] = dataOld[old] posterior[done] = postOld[old] # update the number of processed samples done += 1 # print(i, old, count, done) # return the shuffled data return θ, (prior, data, posterior)
def loadData(self): """ load data and covariance """ # grab the input dataspace ifs = self.ifs # next, the observations try: # get the path to the file df = ifs[self.data_file] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log(f"missing observations: no '{self.data_file}' {ifs.path()}") # and raise the exception again raise # if all goes well else: # allocate the vector self.dataobs= altar.vector(shape=self.observations) # and load the file contents into memory self.dataobs.load(df.uri) if self.cd_file is not None: # finally, the data covariance try: # get the path to the file cf = ifs[self.cd_file] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log(f"missing data covariance matrix: no '{self.cd_file}'") # and raise the exception again raise # if all goes well else: # allocate the matrix self.cd = altar.matrix(shape=(self.observations, self.observations)) # and load the file contents into memory self.cd.load(cf.uri) else: # use a constant covariance self.cd = self.cd_std return
def __init__(self, **kwds): # chain up super().__init__(**kwds) # local names for the math functions log, π, cos, sin = math.log, math.pi, math.cos, math.sin # the number of model parameters dof = self.parameters # convert the central value into a vector; allocate peak = altar.vector(shape=dof) # and populate for index, value in enumerate(self.μ): peak[index] = value # the trigonometry cos_φ = cos(self.φ) sin_φ = sin(self.φ) # the eigenvalues λ0 = self.λ[0] λ1 = self.λ[1] # the eigenvalue inverses λ0_inv = 1 / λ0 λ1_inv = 1 / λ1 # build the inverse of the covariance matrix σ_inv = altar.matrix(shape=(dof, dof)) σ_inv[0, 0] = λ0_inv * cos_φ**2 + λ1_inv * sin_φ**2 σ_inv[1, 1] = λ1_inv * cos_φ**2 + λ0_inv * sin_φ**2 σ_inv[0, 1] = σ_inv[1, 0] = (λ1_inv - λ0_inv) * cos_φ * sin_φ # compute its determinant and store it σ_lndet = log(λ0 * λ1) # attach the characteristics of my pdf self.peak = peak self.σ_inv = σ_inv # the log-normalization self.normalization = -.5 * (dof * log(2 * π) + σ_lndet) # all done return
def __init__(self, beta, theta, likelihoods, sigma=None, **kwds): # chain up super().__init__(**kwds) # store the temperature self.beta = beta # store the sample set self.theta = theta # store the likelihoods self.prior, self.data, self.posterior = likelihoods # get the number of parameters dof = self.parameters # initialize the covariance matrix self.sigma = altar.matrix( shape=(dof, dof)).zero() if sigma is None else sigma # all done return
def evalDataLikelihood(self, theta, likelihood): """ calculate data likelihood and add it to step.prior or step.data """ # This method assumes that there is a forwardModelBatched defined # Otherwise, please define your own version of this method # create a matrix for the prediction (samples, observations) prediction = altar.matrix(shape=(self.samples, self.observations)) # survey forward model whether it computes residual or not returnResidual = self.return_residual # call forwardModel to calculate the data prediction or its difference between dataobs self.forwardModelBatched(theta=theta, prediction=prediction) # call data to calculate the l2 norm self.dataobs.evalLikelihood(prediction=prediction, likelihood=likelihood, residual=returnResidual) # all done return self
def dataLikelihood(self, model, step): """ Fill {step.data} with the likelihoods of the samples in {step.theta} given the available data. """ # grab my calculator source = self.source # compute the portion of the sample that belongs to this model θ = model.restrict(theta=step.theta) # allocate a matrix to hold the predicted displacements predicted = altar.matrix(shape=(step.samples, model.observations)) # compute the predicted displacements libmogi.displacements(source, θ.capsule, predicted.data) # compute the residuals (in place) libmogi.residuals(source, predicted.data) # get the norm norm = model.norm # the inverse of the data covariance matrix cd_inv = model.cd_inv # the normalization normalization = model.normalization # and the data likelihood vector dataLLK = step.data # find out how many samples in the set samples = θ.rows # go through the samples for sample in range(samples): # get the residuals residuals = predicted.getRow(sample) # compute the norm nrm = norm.eval(v=residuals, sigma_inv=cd_inv) # and normalize it llk = normalization - nrm**2 / 2 # store it dataLLK[sample] = llk # all done return self
def cuInitSample(self, theta): """ Fill my portion of {theta} with initial random values from my distribution. """ # use cpu to generate a batch of samples samples = theta.shape[0] parameters = self.parameters θ = altar.matrix(shape=(samples, parameters)) # grab the references for area/shear modulus area_patches = self.area_patches mu_patches = self.mu_patches # create a gaussian distribution to generate Mw for each sample gaussian_Mw = altar.pdf.gaussian(mean=self.Mw_mean, sigma=self.Mw_sigma, rng=self.rng) # create a dirichlet distribution to generate displacements alpha = altar.vector(shape=parameters).fill( 1) # power 0, or (alpha_i = 1) dirichlet_D = altar.pdf.dirichlet(alpha=alpha, rng=self.rng) # create a tempory vector for theta of samples theta_sample = altar.vector(shape=parameters) # get the range low, high = self.support # iterate through samples to initialize samples for sample in range(samples): within_range = False # iterate until all samples are within support while within_range is False: # assume within_range is true in the beginning within_range = True # generate a Mw sample Mw = gaussian_Mw.sample() # Pentiar = M0 = \sum (A_i D_i Mu_i) # 15 here is for GPa * Km^2, instead of Pa * m^2 Pentier = pow(10, 1.5 * Mw + 9.1 - 15) # if a negative sign is desired if self.slip_sign == 'negative': Pentier = -Pentier # generate a dirichlet sample \sum x_i = 1 dirichlet_D.vector(vector=theta_sample) # D_i = P * x_i /A_i for patch in range(parameters): theta_sample[patch] *= Pentier / (area_patches[patch] * mu_patches[patch]) # check the range if (theta_sample[patch] >= high or theta_sample[patch] <= low): within_range = False break # set theta θ.setRow(sample, theta_sample) # make a copy to gpu gθ = altar.cuda.matrix(source=θ, dtype=self.precision) # insert into theta according to the idx_range theta.insert(src=gθ, start=(0, self.idx_range[0])) # and return return self
def mogi(self): """ Synthesize displacements for a grid of stations given a specific source location and strength """ # get the stations stations = self.stations # dedcue the number of observations observations = len(stations) # make a source source = altar.models.mogi.source(x=self.x, y=self.y, d=self.d, dV=self.dV, nu=self.nu) # observe all displacements from the same angle for now theta = π / 4 # the azimuthal angle phi = π # the polar angle # build the common projection vector s = sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta) # allocate a matrix to hold the projections los = altar.matrix(shape=(observations, 3)) # go through the observations for obs in range(observations): # store the LOS vector los[obs, 0] = s[0] los[obs, 1] = s[1] los[obs, 2] = s[2] # compute the displacements u = source.displacements(locations=stations, los=los) # prepare the dataset # rows: one for each location # columns: observation id, u.s, x, y, theta, phi # observation id simulates data that come from different sources and therefore require # a different offset data = altar.models.mogi.data(name="displacements") # go through the observation locations for idx, (x, y) in enumerate(stations): # make a new entry in the data sheet observation = data.pyre_new() # western stations if x < 0: # come from a different data set observation.oid = 1 # than else: # eastern stations observation.oid = 0 # record the location of this observation observation.x = x observation.y = y # project the displacement observation.d = u[idx] # save the direction of the projection vector observation.theta = theta observation.phi = phi # the length of the data sheet is the number of observations observations = len(data) # allocate a matrix for the data correlation correlation = altar.matrix(shape=[observations] * 2).zero() # go through the observations for idx, observation in enumerate(data): # set the covariance to a fraction of the "observed" displacement correlation[idx, idx] = 1.0 #.01 * observation.d # all done return data, correlation
def loadInputs(self): """ Load the data in the input files into memory """ # grab the input dataspace ifs = self.ifs # first the green functions try: # get the path to the file gf = ifs[self.green] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing Green functions: no '{self.green}' in '{self.case}'") # and raise the exception again raise # if all goes well else: # allocate the matrix green = altar.matrix(shape=(self.observations, self.parameters)) # and load the file contents into memory green.load(gf.uri) # next, the observations try: # get the path to the file df = ifs[self.data] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing observations: no '{self.data}' in '{self.case}'") # and raise the exception again raise # if all goes well else: # allocate the vector data = altar.vector(shape=self.observations) # and load the file contents into memory data.load(df.uri) # finally, the data covariance try: # get the path to the file cf = ifs[self.cd] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing data covariance matrix: no '{self.cd}' in '{self.case}'" ) # and raise the exception again raise # if all goes well else: # allocate the matrix cd = altar.matrix(shape=(self.observations, self.observations)) # and load the file contents into memory cd.load(cf.uri) # all done return green, data, cd
def loadInputsCp(self): """ Load the additional data (for Cp problem) in the input files into memory """ # grab the input dataspace ifs = self.ifs # the covariance/uncertainty for model parameter Cmu try: # get the path to the file cmuf = ifs[self.cmu_file] # if the file doesn't exist except ifs.NotFoundError: # grab my error channel channel = self.error # complain channel.log( f"missing data covariance matrix: no '{self.cmu_file}' in '{self.case}'" ) # and raise the exception again raise # if all goes well else: # allocate the matrix cmu = altar.matrix(shape=(self.nCmu, self.nCmu)) # and load the file contents into memory cmu.load(cmuf.uri) # the sensitivity kernel, Kmu ususally nCmu = self.nCmu prefix, suffix = self.kmu_file.split("[n]") kmu = [] kmu_i = altar.matrix(shape=(self.observations, self.parameters)) for i in range(nCmu): kmufn = prefix + str(i + 1) + suffix try: kmuf = ifs[kmufn] except ifs.NotFoundErr: channel.log( f"missing sensitivity kernel: no '{kmufn}' in '{self.case}'" ) raise else: kmu_i.load(kmuf.uri) kmu.append(kmu_i) # the initial model try: # get the path to the file initModelf = ifs[self.initialModel_file] # if the file doesn't exist except ifs.NotFoundError: channel.log( f"missing initial model file: no '{initModelf}' in '{self.case}'" ) raise # if all goes well else: # and load the file contents into memory initModel = altar.vector(shape=self.parameters) initModel.load(initModelf.uri) # all done return kmu, cmu, initModel
def initialize(self, application): """ Initialize the state of the model given a {problem} specification """ # externals from math import sin, cos # chain up super().initialize(application=application) # initialize my parameter sets self.initializeParameterSets() # mount the directory with my input data self.ifs = self.mountInputDataspace(pfs=application.pfs) # load the data from the inputs into memory displacements, self.cd = self.loadInputs() # compute the normalization self.normalization = self.computeNormalization() # compute the inverse of the covariance matrix self.cd_inv = self.computeCovarianceInverse() # build the local representations self.points = [] self.d = altar.vector(shape=self.observations) self.los = altar.matrix(shape=(self.observations, 3)) self.oid = [] # populate them for obs, record in enumerate(displacements): # extract the observation id self.oid.append(record.oid) # extract the (x,y) coordinate of the observation point self.points.append((record.x, record.y)) # extract the observed displacement self.d[obs] = record.d # get the LOS angles theta = record.theta phi = record.phi # form the projection vectors and store them self.los[obs, 0] = sin(theta) * cos(phi) self.los[obs, 1] = sin(theta) * sin(phi) self.los[obs, 2] = cos(theta) # save the parameter meta data self.meta() # pick an implementation strategy # if the user has asked for CUDA support if application.job.gpus > 0: # attempt to try: # use the CUDA implementation from .CUDA import CUDA as strategy # if this fails except ImportError: # make a channel channel = application.error # complain raise channel.log("unable to find CUDA support") # if the user specified {fast} mode elif self.mode == "fast": # attempt to try: # get the fast strategy that involves a Reverso source implemented in C++ from .Fast import Fast as strategy # if this fails except ImportError: # make channel channel = application.error # complain raise channel.log("unable to find support for <fast> mode") # otherwise else: # get the strategy implemented in pure python from .Native import Native as strategy # initialize it and save it self.strategy = strategy().initialize(application=application, model=self) # show me # self.show(job=application.job, channel=self.info) # all done return self
def initialize(self, application): """ Initialize the state of the model given a problem specification """ # chain up super().initialize(application=application) # initialize the parameter sets self.initializeParameterSets() # mount the workspace self.ifs = self.mountInputDataspace(pfs=application.pfs) # load the data data = self.loadInputs() # prep to swallow the inputs self.ticks = [] self.d = altar.vector(shape=(3 * self.observations)) self.cd = altar.matrix(shape=(3 * self.observations, 3 * self.observations)).zero() # go through the data records for idx, rec in enumerate(data): # save the (t,x,y) triplet self.ticks.append((rec.t, rec.x, rec.y)) # save the three components of the displacements self.d[3 * idx + 0] = rec.uE self.d[3 * idx + 1] = rec.uN self.d[3 * idx + 2] = rec.uZ # populate the covariance matrix self.cd[3 * idx + 0, 3 * idx + 0] = rec.σE self.cd[3 * idx + 1, 3 * idx + 1] = rec.σN self.cd[3 * idx + 2, 3 * idx + 2] = rec.σZ # compute the normalization self.normalization = self.computeNormalization() # compute the inverse of the covariance matrix self.cd_inv = self.computeCovarianceInverse() # save the parameter meta data self.meta() # pick an implementation strategy # if the user specified {fast} mode if self.mode == "fast": # attempt to try: # get the fast strategy that involves a CDM source implemented in C++ from .Fast import Fast as strategy # if this fails except ImportError: # make channel channel = application.error # complain raise channel.log("unable to find support for <fast> mode") # otherwise else: # get the strategy implemented in pure python from .Native import Native as strategy # initialize it and save it self.strategy = strategy().initialize(application=application, model=self) # show me # self.show(job=application.job, channel=self.info) # all done return self
def walkChains(self, annealer, step): """ Run the Metropolis algorithm on the Markov chains """ # get the model model = annealer.model # and the event dispatcher dispatcher = annealer.dispatcher # unpack what i need from the cooling step β = step.beta θ = step.theta prior = step.prior data = step.data posterior = step.posterior # get the parameter covariance Σ_chol = self.sigma_chol # the sample geometry samples = step.samples parameters = step.parameters # a couple of functions from the math module exp = math.exp log = math.log # reset the accept/reject counters accepted = rejected = unlikely = 0 # allocate some vectors that we use throughout the following # candidate likelihoods cprior = altar.vector(shape=samples) cdata = altar.vector(shape=samples) cpost = altar.vector(shape=samples) # a fake covariance matrix for the candidate steps, just so we don't have to rebuild it # every time csigma = altar.matrix(shape=(parameters, parameters)) # the mask of samples rejected due to model constraint violations rejects = altar.vector(shape=samples) # and a vector with random numbers for the Metropolis acceptance dice = altar.vector(shape=samples) # step all chains together for step in range(self.steps): # notify we are advancing the chains dispatcher.notify(event=dispatcher.chainAdvanceStart, controller=annealer) # initialize the candidate sample by randomly displacing the current one cθ = self.displace(sample=θ) # initialize the likelihoods likelihoods = cprior.zero(), cdata.zero(), cpost.zero() # and the covariance matrix csigma.zero() # build a candidate state candidate = self.CoolingStep(beta=β, theta=cθ, likelihoods=likelihoods, sigma=csigma) # the random displacement may have generated candidates that are outside the # support of the model, so we must give it an opportunity to reject them; # notify we are starting the verification process dispatcher.notify(event=dispatcher.verifyStart, controller=annealer) # reset the mask and ask the model to verify the sample validity model.verify(step=candidate, mask=rejects.zero()) # make the candidate a consistent set by replacing the rejected samples with copies # of the originals from {θ} for index, flag in enumerate(rejects): # if this sample was rejected if flag: # copy the corresponding row from {θ} into {candidate} cθ.setRow(index, θ.getRow(index)) # notify that the verification process is finished dispatcher.notify(event=dispatcher.verifyFinish, controller=annealer) # compute the likelihoods model.likelihoods(annealer=annealer, step=candidate) # build a vector to hold the difference of the two posterior likelihoods diff = cpost.clone() # subtract the previous posterior diff -= posterior # randomize the Metropolis acceptance vector dice.random(self.uniform) # notify we are starting accepting samples dispatcher.notify(event=dispatcher.acceptStart, controller=annealer) # accept/reject: go through all the samples for sample in range(samples): # a candidate is rejected if the model considered it invalid if rejects[sample]: # nothing to do: θ, priorL, dataL, and postL contain the right statistics # for this sample; just update the rejection count rejected += 1 # and move on continue # a candidate is also rejected if the model considered it less likely than the # original and it wasn't saved by the {dice} if log(dice[sample]) > diff[sample]: # nothing to do: θ, priorL, dataL, and postL contain the right statistics # for this sample; just update the unlikely count unlikely += 1 # and move on continue # otherwise, update the acceptance count accepted += 1 # copy the candidate sample θ.setRow(sample, cθ.getRow(sample)) # and its likelihoods prior[sample] = cprior[sample] data[sample] = cdata[sample] posterior[sample] = cpost[sample] # notify we are done accepting samples dispatcher.notify(event=dispatcher.acceptFinish, controller=annealer) # notify we are done advancing the chains dispatcher.notify(event=dispatcher.chainAdvanceFinish, controller=annealer) # all done return accepted, rejected, unlikely