Exemple #1
0
    def __init__(self, parent=None):
        Frame.__init__(self, parent, bg='yellow')
        self.pack(expand=YES, fill=BOTH)

        self.adaptMode = "simple" # 'yconditional'   # choices are: simple, neal, or yconditional
        self.adaptSimpleParam = 0.25
        self.adaptNealParam = 2.0
        self.adaptYCondParam = 1.3
        self.adaptYCondFromEnds = 0.25
        
        self.manyStepsN = 1000  # number of points to sample when manySteps function called
        #self.total_steps = 0
        
        self.n_func_evals = 0
        
        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='One step (n)', command=self.oneStep)
        self.samplemb.menu.add_command(label='Partial Step (d)', command=self.detailedStep)
        self.samplemb.menu.add_command(label='Many steps (m)', command=self.manySteps)
        self.samplemb.menu.add_command(label='Adapt (a)', command=self.adaptSampler)
        self.samplemb.menu.add_command(label='Reset (r)', command=self.reset)
        self.samplemb.menu.add_command(label='Partial Back Step (b)', command=self.oneStep)
        self.samplemb.menu.add_command(label='Use Beta (A)', command=self.switchToBeta)
        self.samplemb.menu.add_command(label='Use Bimodal (O)', 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-o>", self.keybdOverrelaxedOneStep)
        self.bind_all("<KeyPress-n>", self.keybdOneStep)
        self.bind_all("<KeyPress-d>", self.keybdDetailedStep)
        self.bind_all("<KeyPress-b>", self.keybdBackDetailedStep)
        self.bind_all("<Shift-KeyPress-N>", self.keybdManySteps)
        self.bind_all("<KeyPress-m>", self.keybdManySteps)
        self.bind_all("<KeyPress-a>", self.keybdAdaptSampler)
        self.bind_all("<KeyPress-r>", self.keybdReset)
        self.bind_all("<KeyPress-q>", self.keybdQuit)
        self.bind_all("<Shift-KeyPress-A>", self.keybdSwitchToBeta)
        self.bind_all("<Shift-KeyPress-O>", self.keybdSwitchToBimodal)
        self.bind_all("<Shift-KeyPress-E>", self.keybdSwitchToExpon)

        self._step_num_within_step = 0
        self._just_point = False
        # configure event is bound only to the main frame
        #self.bind("<Configure>", self.resizing)
        self.plotter = None
        
        self.d = MyBeta(1.5)
        # 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)
        self.switchToBeta()
Exemple #2
0
class SliceViewer(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent, bg='yellow')
        self.pack(expand=YES, fill=BOTH)

        self.adaptMode = "simple" # 'yconditional'   # choices are: simple, neal, or yconditional
        self.adaptSimpleParam = 0.25
        self.adaptNealParam = 2.0
        self.adaptYCondParam = 1.3
        self.adaptYCondFromEnds = 0.25
        
        self.manyStepsN = 1000  # number of points to sample when manySteps function called
        #self.total_steps = 0
        
        self.n_func_evals = 0
        
        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='One step (n)', command=self.oneStep)
        self.samplemb.menu.add_command(label='Partial Step (d)', command=self.detailedStep)
        self.samplemb.menu.add_command(label='Many steps (m)', command=self.manySteps)
        self.samplemb.menu.add_command(label='Adapt (a)', command=self.adaptSampler)
        self.samplemb.menu.add_command(label='Reset (r)', command=self.reset)
        self.samplemb.menu.add_command(label='Partial Back Step (b)', command=self.oneStep)
        self.samplemb.menu.add_command(label='Use Beta (A)', command=self.switchToBeta)
        self.samplemb.menu.add_command(label='Use Bimodal (O)', 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-o>", self.keybdOverrelaxedOneStep)
        self.bind_all("<KeyPress-n>", self.keybdOneStep)
        self.bind_all("<KeyPress-d>", self.keybdDetailedStep)
        self.bind_all("<KeyPress-b>", self.keybdBackDetailedStep)
        self.bind_all("<Shift-KeyPress-N>", self.keybdManySteps)
        self.bind_all("<KeyPress-m>", self.keybdManySteps)
        self.bind_all("<KeyPress-a>", self.keybdAdaptSampler)
        self.bind_all("<KeyPress-r>", self.keybdReset)
        self.bind_all("<KeyPress-q>", self.keybdQuit)
        self.bind_all("<Shift-KeyPress-A>", self.keybdSwitchToBeta)
        self.bind_all("<Shift-KeyPress-O>", self.keybdSwitchToBimodal)
        self.bind_all("<Shift-KeyPress-E>", self.keybdSwitchToExpon)

        self._step_num_within_step = 0
        self._just_point = False
        # configure event is bound only to the main frame
        #self.bind("<Configure>", self.resizing)
        self.plotter = None
        
        self.d = MyBeta(1.5)
        # 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)
        self.switchToBeta()


    def _densityChanged(self):
        self.plotter.func = self.d.getPDF
        self.f = AdHocDensity(self.d.getLnPDF)
        
        # self.f should be function that returns natural log of a probability density, or
        # the natural log of a function proportional to a probability density
        self.s = SliceSampler(self.r, self.f)

        
        # create the status label
        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)
        
        # 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()
        self.reset()


    # 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 
    def rescalePlot(self, n):
        smallest = getEffectiveLnZero()
        largest = -smallest
        ymin = 0.0 # always 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
        # 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)
        print 'range of x is [%f, %f]' % (xmin, xmax)
        print 'range of y is [%f, %f]' % (ymin, ymax)

    def takeStep(self, show_slice=False, detailed=False, suppress_repaint=False):
        """
        If detailed is False, this function displays an entire slice sampling step.
        If detailed is True, this function shows the details of a single slice sampling step.
        That is, it first shows the vertical slice, then the point chosen for the horizontal
        slice, then the horizontal slice, etc.
        """
        target_step = 1
        if (not detailed) or self._step_num_within_step == 0:
            #self.total_steps += 1
            # Tell the slice sampler to take one step and return all the information
            # about that step
            self.curr = self.s.debugSample()
        if not detailed:
            self._step_num_within_step = 1000000
            
        # debugSample returns a tuple with the following information about the 
        # slice sampling step just completed:
        #   0: sampled x
        #   1: x-coord of vertical slice
        #   2: y-coord of top of vertical slice (y-coord of bottom of vertical slice always 0.0)
        #   3: x-coord of left edge of horizontal slice
        #   4: x-coord of right edge of horizontal slice
        #   5: y-coord of horizontal slice
        #   6: horizontal slice interval width
        #   7+: x-coord of failed sampling attempts (y-coord all equal to element 5)
        
        # point (x0,y0) is the top of the vertical slice
        x0 = self.curr[1]
        y0 = math.exp(self.curr[2])
        
        # point (x,y) is the point ultimately chosen along the horizontal slice
        x = self.curr[0]
        y = math.exp(self.curr[5])
        
        # xleft and xright are the x-coordinates of the left and right ends of the 
        # horizontal slice
        xleft = self.s.getOrigLeftEdgeOfSlice()
        xright = self.s.getOrigRightEdgeOfSlice()
        
        #slice_extent = float(self.curr[4] - self.curr[3])
        #outf = file('yw.txt', 'a+')
        #outf.write('%d\t%f\t%f\n' % (self.total_steps, y, slice_extent))
        #outf.close()                    
        
        # xincr is the slice unit width
        xincr = self.curr[6]
        
        # curr_len is the length of the list returned by the slice sampler's debugSample method
        # This information helps us tell how many failed attempts there were
        curr_len = len(self.curr)
        
        # Determine the number of horiz. slice units needed
        unit_width = self.s.getSliceUnitWidth()
        assert xincr == unit_width
        n_units = int(0.5 + (xright - xleft)/xincr)
        
        # Get number of function evaluations needed for the current slice sample
        # At minimum, four function evaluations are required:
        #   1. re-evaluate current point (density has probably changed since last update)
        #   2. check left edge to see if it is outside slice
        #   3. check right edge to see if it is outside slice
        #   4. check sampled point to see if it is valid
        prev_n_func_evals = self.n_func_evals
        self.n_func_evals = self.s.getNumFuncEvals()
        extra_evals = self.n_func_evals - prev_n_func_evals - 4
        n_samples = self.s.getNumSamples()
        efficiency = (float(self.n_func_evals)/float(n_samples)) - 4.0
        #self.status_label.config(text='unit width=%.2f, units=%d, extra evals=%d, extra evals/sample=%.1f' % (unit_width, n_units, extra_evals, efficiency))
        if show_slice:
            self.plotter.repaint()
            if not self._just_point:
                self._step_num_within_step += 1
                
                # Plot the vertical slice
                self.status_label.config(text='vertical slice')
                if self._step_num_within_step == target_step:
                    self.plotter.plotLine(x0, 0.0, x0, y0)
                    return
                target_step += 1
    
                n_failed = curr_len - 7
                #print 'n_failed=%d' % (n_failed)
                #print 'self.s.getNumSamples()=', self.s.getNumSamples()
                #print 'self.s.getNumFailedSamples()=', self.s.getNumFailedSamples()
                #print 'self.s.getNumFuncEvals()=', self.s.getNumFuncEvals()
                if self.adaptMode == 'yconditional':
                    mode = self.s.getMode()
                    mode_height = math.exp(self.s.getLnDensityAtMode())
                    w0 = self.s.calcW(mode_height)
                    w1 = self.s.calcW(0.0)
                    left_x0 = mode - w0/2.0
                    left_y0 = mode_height
                    left_x1 = mode - w1/2.0
                    left_y1 = 0.0
                    right_x0 = mode + w0/2.0
                    right_y0 = mode_height
                    right_x1 = mode + w1/2.0
                    right_y1 = 0.0
                    self.plotter.plotLine(left_x0, left_y0, left_x1, left_y1, "green")
                    self.plotter.plotLine(right_x0, right_y0, right_x1, right_y1, "green")
                
                # Plot an arrow indicating where the horizontal slice will be located
                if self._step_num_within_step == target_step:
                    self.plotter.plotLine(x0, 0.0, x0, y0)
                    self.plotter.plotLeftPointingArrow(x0, y, color="white", shaft_length=10, arrowhead_length=5)
                    self.status_label.config(text='location of horizontal slice')
                    return
                target_step += 1
                
                # Plot the unit that spans the vertical slice
                n_increases = int(0.5 + (xright - xleft)/xincr) # int((xright - xleft)//xincr)
        
                growingSlice = self._step_num_within_step < target_step + n_increases
                if growingSlice:
                    nunits, n_increases, first_unit_index = self.plotter.plotOneSliceUnit(xleft, xright, xincr, x0, y, which=None, showTicks=True)

                    assert nunits == n_units, 'nunits = %d, n_units = %d' %(nunits, n_units)
                    self.status_label.config(text='first unit randomly positioned around vertical slice')
                    if self._step_num_within_step == target_step:
                        return
                    target_step += 1
                    
                    # Plot the units to the right of the first unit
                    curr_index = first_unit_index + 1 
                    while curr_index  < n_increases :
                        self.plotter.plotOneSliceUnit(xleft, xright, xincr, x0, y, curr_index) 
                        if self._step_num_within_step == target_step:
                            self.status_label.config(text='add units to right until density bracketed')
                            return
                        target_step += 1
                        curr_index += 1
                        
                    # Plot the units to the left of the first unit
                    curr_index = first_unit_index - 1 
                    while curr_index >= 0:
                        self.plotter.plotOneSliceUnit(xleft, xright, xincr, x0, y, curr_index)                
                        if self._step_num_within_step == target_step:
                            self.status_label.config(text='add units to left until density bracketed')
                            return
                        target_step += 1
                        curr_index -= 1
                else:
                    target_step += n_increases
                left_border, right_border = self.plotter.getExpandedSliceDim(xleft, xright, xincr)
                if self._step_num_within_step == target_step:
                    self.plotter.plotCurrentSliceWidth(left_border, right_border, x0, y)
                    self.status_label.config(text='Now we have the full slice that we will sample from')
                    return
                target_step += 1
                if n_failed > 0:
                    for i in range(n_failed):
                        for j in (0,1):
                            xfail = self.curr[i + 7]
                            xft = self.plotter.xtranslate(xfail)
                            if j == 1:
                                if x0 < xfail:
                                    right_border = xft
                                else:
                                    left_border = xft
                            if not self._just_point:
                                self.plotter.plotPoint(xfail, y, 'red')                                
                            if self._step_num_within_step == target_step:
                                self.plotter.plotCurrentSliceWidth(left_border, right_border, x0, y)
                                if j == 0:
                                    self.status_label.config(text='sample attempt failed')
                                elif j == 1:
                                    self.status_label.config(text='We can crop the edges of the slice')                                
                                return
                            target_step += 1
            
            # this is the last thing to plot, so we reset to _step_num_within_step to 0
            self.plotter.plotPoint(x, y, 'cyan')
            if self._just_point:
                self._step_num_within_step = 0
                self._just_point = False
                self.plotter.points.append((x,y))
            else:
                self.plotter.plotCurrentSliceWidth(left_border, right_border, x0, y)
                self._just_point = True
            self.status_label.config(text='successful sample at x=%.3f' % x)
            #print 'xleft=%f, xright=%f, xincr=%f\n' % (xleft, xright, xincr)
        else:
            self.plotter.points.append((x,y))
            if not suppress_repaint:
                self.plotter.repaint()

    def takeOverrelaxedStep(self, show_slice=False):
        self.curr = self.s.debugOverrelaxedSample()
        # 0: sampled x
        # 1: x-coord of vertical slice
        # 2: y-coord of top of vertical slice (y-coord of bottom of vertical slice always 0.0)
        # 3: x-coord of left edge of horizontal slice
        # 4: x-coord of right edge of horizontal slice
        # 5: y-coord of horizontal slice
        x = self.curr[0]
        y = math.exp(self.curr[5])
        x0 = self.curr[1]
        y0 = math.exp(self.curr[2])
        self.plotter.points.append((x,y))
        if show_slice:
            self.plotter.repaint()
            xleft = self.curr[3] # self.s.getLeftEdgeOfSlice()
            xright = self.curr[4] # self.s.getRightEdgeOfSlice()
            xincr = xright - xleft
            self.plotter.plotSlice(xleft, xright, xincr, y)
            self.plotter.plotLine(x0, 0.0, x0, y0)
            self.plotter.plotPoint(x, y, 'cyan')

    #def resizing(self, event):
    #    print "resizing: x=%f, y=%f" % (event.width, event.height)

    # next two functions result in a single sample and show details of the slice sampling process
    def oneStep(self, detailed=False, show_slice=False):
        self.takeStep(show_slice=show_slice, detailed=detailed)
        
    def keybdOneStep(self, event):
        self.oneStep(detailed=False)

    def detailedStep(self):
        self.oneStep(detailed=True, show_slice=True)

    def detailedBackStep(self):
        if self._step_num_within_step < 1:
            return
        self._step_num_within_step = max(1, self._step_num_within_step - 2)
        self.detailedStep()

    def switchToBeta(self):
        self.d = MyBeta(1.5)
        self._densityChanged()

    def switchToExpon(self):
        self.d = MyExpon()
        self._densityChanged()
        
    def switchToBimodal(self):
        self.d = MyBimodal()
        self._densityChanged()
       

    def keybdDetailedStep(self, event):
        self.detailedStep()

    def keybdBackDetailedStep(self, event):
        self.detailedBackStep()
    
    def keybdSwitchToBeta(self, event):
        self.switchToBeta()

    def keybdSwitchToBimodal(self, event):
        self.switchToBimodal()

    def keybdSwitchToExpon(self, event):
        self.switchToExpon()


# next two functions result in a single overrelaxed sample and show details of the slice sampling process
    def overrelaxedOneStep(self):
        self.takeOverrelaxedStep(True)
        
    def keybdOverrelaxedOneStep(self, event):
        self.overrelaxedOneStep()
        
    # next two functions result in many samples but do not show details of the slice sampling process
    def manySteps(self):
        for x in range(self.manyStepsN):
            self.takeStep(suppress_repaint=True)
        self.plotter.repaint()
            
    def keybdManySteps(self, event):
        self.manySteps()

    # next two functions clear the canvas and start the sampling over from scratch        
    def reset(self):
        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 adjust the slide unit width based on previous slice widths
    def adaptSampler(self):
        if self.adaptMode == 'simple':
            # average cropped slice width is divided by self.adaptSimpleParam to get new unit width
            w = self.s.adaptSimple(self.adaptSimpleParam)
            self.status_label.config(text='Simple adapt mode: multiplier = %f, new unit width = %f' % (self.adaptSimpleParam, w))
        elif self.adaptMode == 'neal':
            # average distance between successive sampled points times self.adaptNealParam is used as new unit width
            w = self.s.adaptNeal(self.adaptNealParam)
            self.status_label.config(text='Neal adapt mode: multiplier = %f, new unit width = %f' % (self.adaptNealParam, w))
        elif self.adaptMode == 'yconditional':
            # new unit width depends on how cropped slice interval varies with y (the value chosen along
            # interval from 0 to f(x) that defines the vertical height of the slice). If this mode is in
            # force, w is changed every time a sample is drawn
            self.s.adaptYConditional(self.adaptYCondFromEnds, self.adaptYCondParam)
            self.status_label.config(text='y-conditional adapt mode: from_ends = %f, multiplier = %f' % (self.adaptYCondFromEnds, self.adaptYCondParam))
        self.s.resetDiagnostics()

    def keybdAdaptSampler(self, event):
        self.adaptSampler()

    # next two functions cause the application to terminate
    def quit(self):
        Frame.quit(self)

    def keybdQuit(self, event):
        self.quit()