def __init__(self, parent=None): Frame.__init__(self, parent, bg="yellow") self.pack(expand=YES, fill=BOTH) # number of points to sample when manySteps function called self.manyStepsN = 1000 # initialize the density from which to sample self.d = MyBeta() # initialize data members related to MCMC proposal # delta is the width of the proposal window centered # around current value self.delta = window_width self.x = 0.5 # will be reset in rescalePlot # initialize SAMC-related data members self.using_samc = True self.biased_pi = True self.m = None self.levels = None self.loglevels = None self.theta = None self.true_pi = None self.est_pi = None self.sample_size = None self.gain = 1.0 self.t0 = 10.0 self.limit_log_theta = 100.0 * math.log(10.0) # initialize miscellaneous data members self._step_num_within_step = 0 self._just_point = False self.plotter = None self.n_func_evals = 0 # set up pseuodorandom number generator self.r = Lot() # self.r.setSeed(13579) print "seed is =", self.r.getSeed() # create a frame to hold the menu buttons menuf = Frame(self, bg="magenta") menuf.pack(expand=NO, fill=X) # create the File menu button self.filemb = Menubutton(menuf, text="File", relief=RAISED, anchor=W, borderwidth=0) self.filemb.pack(expand=NO, fill=X, side=LEFT) self.filemb.menu = Menu(self.filemb, tearoff=0) self.filemb["menu"] = self.filemb.menu self.filemb.menu.add_command(label="Quit", command=self.quit) # create the Sample menu button self.samplemb = Menubutton(menuf, text="Sample", relief=RAISED, anchor=W, borderwidth=0) self.samplemb.pack(expand=YES, fill=X, side=LEFT) self.samplemb.menu = Menu(self.samplemb, tearoff=1) self.samplemb["menu"] = self.samplemb.menu self.samplemb.menu.add_command(label="Toggle between SAMC and MCMC (s)", command=self.toggleSAMC) self.samplemb.menu.add_command(label="One step (n)", command=self.oneStep) self.samplemb.menu.add_command(label="Many steps (m)", command=self.manySteps) self.samplemb.menu.add_command(label="Many steps using slice sampler (M)", command=self.manyStepsSliceSampler) self.samplemb.menu.add_command(label="Biased SAMC (b)", command=self.biasedSAMC) self.samplemb.menu.add_command(label="Unbiased SAMC (u)", command=self.unbiasedSAMC) self.samplemb.menu.add_command(label="Reset (r)", command=self.reset) self.samplemb.menu.add_command(label="Use Beta (B)", command=self.switchToBeta) self.samplemb.menu.add_command(label="Use Bimodal (I)", command=self.switchToBimodal) self.samplemb.menu.add_command(label="Use Exponential (E)", command=self.switchToExpon) # bind some keys to the application (doesn't matter which widget has focus # when you use bind_all) self.bind_all("<KeyPress-s>", self.keybdToggleSAMC) self.bind_all("<KeyPress-n>", self.keybdOneStep) self.bind_all("<KeyPress-m>", self.keybdManySteps) self.bind_all("<Shift-KeyPress-M>", self.keybdManyStepsSliceSampler) self.bind_all("<KeyPress-b>", self.keybdBiasedSAMC) self.bind_all("<KeyPress-u>", self.keybdUnbiasedSAMC) self.bind_all("<KeyPress-r>", self.keybdReset) self.bind_all("<KeyPress-q>", self.keybdQuit) self.bind_all("<Shift-KeyPress-B>", self.keybdSwitchToBeta) self.bind_all("<Shift-KeyPress-I>", self.keybdSwitchToBimodal) self.bind_all("<Shift-KeyPress-E>", self.keybdSwitchToExpon) # create the canvas # func_to_plot should return density, not log density self.plotter = Plotter(parent=self, func_to_plot=self.d.getPDF) self.plotter.pack(side=TOP, expand=YES, fill=BOTH) # create a status bar self.status_label = Label(self, justify=LEFT, relief=SUNKEN, height=1, anchor=W, text="Ready") self.status_label.pack(side=TOP, expand=NO, fill=X) self.switchToBimodal()
class SAMCDemo(Frame): def __init__(self, parent=None): Frame.__init__(self, parent, bg="yellow") self.pack(expand=YES, fill=BOTH) # number of points to sample when manySteps function called self.manyStepsN = 1000 # initialize the density from which to sample self.d = MyBeta() # initialize data members related to MCMC proposal # delta is the width of the proposal window centered # around current value self.delta = window_width self.x = 0.5 # will be reset in rescalePlot # initialize SAMC-related data members self.using_samc = True self.biased_pi = True self.m = None self.levels = None self.loglevels = None self.theta = None self.true_pi = None self.est_pi = None self.sample_size = None self.gain = 1.0 self.t0 = 10.0 self.limit_log_theta = 100.0 * math.log(10.0) # initialize miscellaneous data members self._step_num_within_step = 0 self._just_point = False self.plotter = None self.n_func_evals = 0 # set up pseuodorandom number generator self.r = Lot() # self.r.setSeed(13579) print "seed is =", self.r.getSeed() # create a frame to hold the menu buttons menuf = Frame(self, bg="magenta") menuf.pack(expand=NO, fill=X) # create the File menu button self.filemb = Menubutton(menuf, text="File", relief=RAISED, anchor=W, borderwidth=0) self.filemb.pack(expand=NO, fill=X, side=LEFT) self.filemb.menu = Menu(self.filemb, tearoff=0) self.filemb["menu"] = self.filemb.menu self.filemb.menu.add_command(label="Quit", command=self.quit) # create the Sample menu button self.samplemb = Menubutton(menuf, text="Sample", relief=RAISED, anchor=W, borderwidth=0) self.samplemb.pack(expand=YES, fill=X, side=LEFT) self.samplemb.menu = Menu(self.samplemb, tearoff=1) self.samplemb["menu"] = self.samplemb.menu self.samplemb.menu.add_command(label="Toggle between SAMC and MCMC (s)", command=self.toggleSAMC) self.samplemb.menu.add_command(label="One step (n)", command=self.oneStep) self.samplemb.menu.add_command(label="Many steps (m)", command=self.manySteps) self.samplemb.menu.add_command(label="Many steps using slice sampler (M)", command=self.manyStepsSliceSampler) self.samplemb.menu.add_command(label="Biased SAMC (b)", command=self.biasedSAMC) self.samplemb.menu.add_command(label="Unbiased SAMC (u)", command=self.unbiasedSAMC) self.samplemb.menu.add_command(label="Reset (r)", command=self.reset) self.samplemb.menu.add_command(label="Use Beta (B)", command=self.switchToBeta) self.samplemb.menu.add_command(label="Use Bimodal (I)", command=self.switchToBimodal) self.samplemb.menu.add_command(label="Use Exponential (E)", command=self.switchToExpon) # bind some keys to the application (doesn't matter which widget has focus # when you use bind_all) self.bind_all("<KeyPress-s>", self.keybdToggleSAMC) self.bind_all("<KeyPress-n>", self.keybdOneStep) self.bind_all("<KeyPress-m>", self.keybdManySteps) self.bind_all("<Shift-KeyPress-M>", self.keybdManyStepsSliceSampler) self.bind_all("<KeyPress-b>", self.keybdBiasedSAMC) self.bind_all("<KeyPress-u>", self.keybdUnbiasedSAMC) self.bind_all("<KeyPress-r>", self.keybdReset) self.bind_all("<KeyPress-q>", self.keybdQuit) self.bind_all("<Shift-KeyPress-B>", self.keybdSwitchToBeta) self.bind_all("<Shift-KeyPress-I>", self.keybdSwitchToBimodal) self.bind_all("<Shift-KeyPress-E>", self.keybdSwitchToExpon) # create the canvas # func_to_plot should return density, not log density self.plotter = Plotter(parent=self, func_to_plot=self.d.getPDF) self.plotter.pack(side=TOP, expand=YES, fill=BOTH) # create a status bar self.status_label = Label(self, justify=LEFT, relief=SUNKEN, height=1, anchor=W, text="Ready") self.status_label.pack(side=TOP, expand=NO, fill=X) self.switchToBimodal() def getLevelIndex(self, logf): """ Suppose f = 0.6 (but note that log(f), not f, should be passed into this function), and supposing also that these are the levels: level 4 (= m - 1) -------- 1.00 level 3 -------- 0.75 level 2 <-- 0.6 -------- 0.50 level 1 -------- 0.25 level 0 then this function would return 2. """ for i in range(self.m - 1): if logf < self.loglevels[i]: return i return self.m - 1 def takeStep(self, suppress_repaint=False): """ This function proposes a new step and shows the results as a point on the plot. """ # propose a new value for self.x u = self.r.uniform() x0 = (self.x - self.delta / 2.0) + self.delta * u if x0 < 0.0: x0 = -x0 logf0 = self.d.getLnPDF(x0) logf = self.d.getLnPDF(self.x) logR = logf0 - logf # adjust logR if using SAMC x_index = self.getLevelIndex(logf) x0_index = self.getLevelIndex(logf0) if self.using_samc: logR += self.theta[x_index] - self.theta[x0_index] logu = math.log(self.r.uniform()) if logu < logR: self.x = x0 logf = logf0 x_index = x0_index self.est_pi[x0_index] += 1 else: self.est_pi[x_index] += 1 self.sample_size += 1 # update weights if using SAMC for i in range(self.m): if x_index == i: self.theta[i] += self.gain * (1.0 - self.true_pi[i]) else: self.theta[i] += self.gain * (0.0 - self.true_pi[i]) # point (x,y) is the point ultimately chosen along the horizontal slice x = self.x fx = self.d.getPDF(self.x) u = self.r.uniform() if self.using_samc: if (x_index == self.m - 1) or (self.levels[x_index] > fx): y_hi = fx else: y_hi = self.levels[x_index] if x_index > 0: y_lo = self.levels[x_index - 1] else: y_lo = 0.0 y = y_lo + (y_hi - y_lo) * u else: y = fx * u self.plotter.points.append((x, y)) if not suppress_repaint: self.plotter.repaint() def _getSAMCLnPDF(self, x): """ Computes the log of the weighted PDF. For use when using slice sampler to sample from the SAMC target distribution. """ logf = self.d.getLnPDF(x) if self.using_samc: x_index = self.getLevelIndex(logf) q = self.theta[x_index] else: q = 0.0 return logf - q def takeStepSliceSampler(self, suppress_repaint=False): """ This function uses the slice sampler to draw a new value and shows the results as a point on the plot. """ # propose a new value for self.x self.x = self.s.sample() logf = self.d.getLnPDF(self.x) # update weights and counts if using SAMC if self.using_samc: x_index = self.getLevelIndex(logf) # update counts self.est_pi[x_index] += 1 self.sample_size += 1 # update weights for i in range(self.m): if x_index == i: self.theta[i] += self.gain * (1.0 - self.true_pi[i]) else: self.theta[i] += self.gain * (0.0 - self.true_pi[i]) # point (x,y) is the point ultimately chosen along the horizontal slice fx = self.d.getPDF(self.x) u = self.r.uniform() if self.using_samc: if (x_index == self.m - 1) or (self.levels[x_index] > fx): y_hi = fx else: y_hi = self.levels[x_index] if x_index > 0: y_lo = self.levels[x_index - 1] else: y_lo = 0.0 y = y_lo + (y_hi - y_lo) * u else: y = fx * u self.plotter.points.append((self.x, y)) if not suppress_repaint: self.plotter.repaint() def toggleSAMC(self): if self.using_samc: self.modifyStatus("Switched to MCMC") self.using_samc = False else: self.modifyStatus("Switched to SAMC") self.using_samc = True self.reset() def keybdToggleSAMC(self, event): self.toggleSAMC() def oneStep(self): self.takeStep() self.modifyStatus("Took 1 step (%d total steps so far)" % self.sample_size) def keybdOneStep(self, event): self.oneStep() def biasedSAMC(self): self.modifyStatus("Lowest energy level will now be visited twice as often as other levels") self.using_samc = True self.biased_pi = True self.reset() def keybdBiasedSAMC(self, event): self.biasedSAMC() def unbiasedSAMC(self): self.modifyStatus("Each energy level will now be visited equally") self.using_samc = True self.biased_pi = False self.reset() def keybdUnbiasedSAMC(self, event): self.unbiasedSAMC() def switchToBeta(self): self.modifyStatus("Switching to Beta(1.5) distribution") self.d = MyBeta() self.reset() def switchToExpon(self): self.modifyStatus("Switching to Exponential(2.0) distribution") self.d = MyExpon() self.reset() def switchToBimodal(self): self.modifyStatus("Switching to a distribution that is an equal mixture of Beta(2,19) and Beta(19,2)") self.d = MyBimodal() self.reset() def keybdSwitchToBeta(self, event): self.switchToBeta() def keybdSwitchToBimodal(self, event): self.switchToBimodal() def keybdSwitchToExpon(self, event): self.switchToExpon() def manySteps(self): self.modifyStatus("Patience...") for x in range(self.manyStepsN): self.takeStep(suppress_repaint=True) self.plotter.repaint() self.modifyStatus("Took %d steps (%d total steps so far)" % (self.manyStepsN, self.sample_size)) def keybdManySteps(self, event): self.manySteps() def manyStepsSliceSampler(self): self.modifyStatus("Patience...") for x in range(self.manyStepsN): self.takeStepSliceSampler(suppress_repaint=True) self.plotter.repaint() self.modifyStatus( "Took %d steps using slice sampler (%d total steps so far)" % (self.manyStepsN, self.sample_size) ) def keybdManyStepsSliceSampler(self, event): self.manyStepsSliceSampler() # def reset(self): # self.x = starting_x # self.modifyStatus('Ready') # self.initLevels() # if self.plotter: # try: # pw, ph, plotm = self.plotter.plotw, self.plotter.ploth, self.plotter.plotm # except: # pw, ph, plotm = 800, 600, 40 # self.plotter.resize(pw, ph, plotm) # self.plotter.reset() def keybdReset(self, event): self.reset() # next two functions cause the application to terminate def quit(self): Frame.quit(self) def keybdQuit(self, event): self.quit() def modifyStatus(self, msg): self.status_label.config(text=msg) self.status_label.update_idletasks() def initLevels(self, ymin, ymax, nlevels): """ If self.biased_pi is True, lowest energy level is made twice as probable as all other energy levels. If self.biased_pi is False, all energy levels have the same probability. """ # set up energy levels for this density # boundary between level 0 and 1 is log(ymin) # other boundaries divided evenly between log(ymin) and log(ymax) log_ymax = math.log(ymax) log_ymin = math.log(ymin) yincr = (log_ymax - log_ymin) / float(num_levels - 1) self.d.energy_levels = [] for i in range(num_levels - 1): self.d.energy_levels.append(log_ymin + yincr * float(i)) self.loglevels = self.d.energy_levels self.m = len(self.loglevels) + 1 self.levels = [math.exp(v) for v in self.loglevels] self.theta = [0.0] * self.m if self.biased_pi: self.true_pi = [1.0] * self.m self.true_pi[0] = 2.0 denom = sum(self.true_pi) self.true_pi = [p / denom for p in self.true_pi] else: self.true_pi = [1.0 / float(self.m)] * self.m self.est_pi = [0] * self.m self.sample_size = 0 def reset(self): """ Called after switching the probabilty density to explore. """ # self.plotter.func should be function that returns a probability density, # not a log density self.plotter.func = self.d.getPDF # self.f and self.ff should be functions that return the natural log of a # probability density, or the natural log of a function proportional to a # probability density # self.f = AdHocDensity(self.d.getLnPDF) self.f = AdHocDensity(self._getSAMCLnPDF) # create a slice sampler self.s = SliceSampler(self.r, self.f) # create the status label self.modifyStatus("Ready") # grab a few samples to use in scaling the plot (number to generate is passed # to the rescalePlot function) self.rescalePlot(1000) # reset statistics such as average slice width, number of function evalutations, etc. w = self.s.getSliceUnitWidth() self.s.setSliceUnitWidth(w / 3.0) self.s.resetDiagnostics() def rescalePlot(self, n): """ The rescalePlot function draws n samples from the target distribution to determine the approximate limits of the main part of the density function. It then sets the plotter's x and y axis ranges accordingly """ # temporarily turn off samc for purposes of rescaling the plot prev_using_samc = self.using_samc self.using_samc = False smallest = getEffectiveLnZero() largest = -smallest ymin = 0.0 # always zero ymin_not_zero = None # smallest y value seen that is greater than zero ymax = smallest xmin = largest xmax = smallest for i in range(n): x = self.s.sample() if x < xmin: xmin = x if x > xmax: xmax = x y = math.exp(self.f(x)) if y > ymax: ymax = y if (ymin_not_zero is None) or (y > 0.0 and y < ymin_not_zero): ymin_not_zero = y # restore using_samc setting self.using_samc = prev_using_samc # start x off at a random point between xmin and xmax self.x = xmin + (xmax - xmin) * self.r.uniform() # store the energy levels for this distribution self.initLevels(ymin_not_zero, ymax, num_levels) # make sure plot is wide enough to accommodate the worst-case scenario # in terms of positioning the initial interval of width w w = self.s.getSliceUnitWidth() self.plotter.setXRange(xmin - w, xmax + w, (xmax - xmin + 2 * w) / 5.0) self.plotter.setYRange(ymin, ymax, (ymax - ymin) / 5.0) try: pw, ph, plotm = self.plotter.plotw, self.plotter.ploth, self.plotter.plotm except: pw, ph, plotm = 800, 600, 40 self.plotter.resize(pw, ph, plotm) self.plotter.reset()