def collapsed_sample_f(self, theta,n,A,f):
     """
     Use a collapsed Gibbs sampler to update the block assignments 
     by integrating out the block-to-block connection probabilities B.
     Since this is a Beta-Bernoulli model the posterior can be computed
     in closed form and the integral can be computed analytically.
     """
     (B,pi) = theta
     A = A.astype(np.bool)
     zother = np.array(f).astype(np.int)
     
     # P(A|z) \propto 
     #    \prod_{r1}\prod_{r2} Beta(m(r1,r2)+b1,\hat{m}(r1,r2)+b0) /
     #                           Beta(b1,b0)
     # 
     # Switching z changes the product over r1 and the product over r2
     
     # Compute the posterior distribution over blocks
     
     # TODO: This literal translation of the log prob is O(R^3)
     # But it can almost certainly be sped up to O(R^2)
     ln_pi_post = np.log(pi)
     for r in np.arange(self.R):
         zother[n] = r
         for r1 in np.arange(self.R):
             for r2 in np.arange(self.R):
                 # Look at outgoing edges under z[n] = r
                 Ar1r2 = A[np.ix_(zother==r1,zother==r2)]
                 mr1r2 = np.sum(Ar1r2)
                 hat_mr1r2 = Ar1r2.size - mr1r2
                 
                 ln_pi_post[r] += betaln(mr1r2+self.b1, hat_mr1r2+self.b0) - \
                                  betaln(self.b1,self.b0)
                             
         zn = log_sum_exp_sample(ln_pi_post)
     
     return zn
    def naive_sample_f(self, theta, n, A, f, beta=1.0):
        """
        Naively Gibbs sample z given B and pi
        """
        (B,pi) = theta
        A = A.astype(np.bool)
        zother = np.array(f).astype(np.int)
        
        # Compute the posterior distribution over blocks
        ln_pi_post = np.log(pi)
        
        rrange = np.arange(self.R)
        for r in rrange:
            zother[n] = r
            # Block IDs of nodes we connect to 
            o1 = A[n,:]
            if np.any(A[n,:]):
                ln_pi_post[r] += beta * np.sum(np.log(B[np.ix_([r],zother[o1])]))
            
            # Block IDs of nodes we don't connect to
            o2 = np.logical_not(A[n,:])
            if np.any(o2):
                ln_pi_post[r] += beta * np.sum(np.log(1-B[np.ix_([r],zother[o2])]))
            
            # Block IDs of nodes that connect to us
            i1 = A[:,n]
            if np.any(i1):
                ln_pi_post[r] += beta * np.sum(np.log(B[np.ix_(zother[i1],[r])]))

            # Block IDs of nodes that do not connect to us
            i2 = np.logical_not(A[:,n])
            if np.any(i2):
                ln_pi_post[r] += beta * np.sum(np.log(1-B[np.ix_(zother[i2],[r])]))
            
        zn = log_sum_exp_sample(ln_pi_post)
        
        return zn