Sample
        Generate samples for posterior distribution using Gauss-Newton 
        proposal parameters
    Inputs : 
        n_samples :
            number of samples to generate
    Hidden Outputs :
        chain  :
            chain of samples
        n_samples :
            length of chain
        n_accepted :
            number of proposals accepted
        step_count :
            count of the steps accepted

def _sample(self, n_samples):
        """
    Sample
        Generate samples for posterior distribution using Gauss-Newton 
        proposal parameters
    Inputs : 
        n_samples :
            number of samples to generate
    Hidden Outputs :
        chain  :
            chain of samples
        n_samples :
            length of chain
        n_accepted :
            number of proposals accepted
        step_count :
            count of the steps accepted
        """
        try : 
            n_samples = int(n_samples)
        except :
            raise TypeError("number of samples has to be an integer")
            exit()

        # fetch info
        X = self._proposal_params(self._X)
        k_max = self._max_steps

        # initialize 
        chain = np.zeros((n_samples, self._n)) 
        n_accepted = 0
        step_count = np.zeros(k_max+2)

        # begin outer loop
        for i in xrange(n_samples):
            accepted  = False       # check if sample is accepted
            r_        = [1]         # list of step sizes
            Z_        = [X]         # initialize list of Z s
            self._r_  = r_ 
            log_P_z_x = 0. + X['log_p'] 

            k = 0 # back-off steps taken so far
            while k <= k_max:
                # get proposal
                chi_z = False
                while not chi_z:
                    z = multi_normal(X, r_[-1])
                    chi_z, f_z, J_z = self._f(z)
                Z = self._proposal_params({'x':z,'f':f_z,'J':J_z})
                Z_.append(Z)
                self._Z_ = Z_

                log_P_z_x += log_K(Z, X, r_[-1])

                # N is the Numerator of the acceptance, N = P_x_z
                self._N_is_0 = False # check to see if N = 0, to use in _log_P
                log_N = self._log_P(X, Z, k)

                # calculating acceptance probability
                if self._N_is_0 == True :
                    A_z_x = 0.
                elif log_N >= log_P_z_x :
                    A_z_x = 1.
                else :
                    A_z_x = np.exp(log_N - log_P_z_x)

                # acceptance rejection
                if  np.random.rand() <= A_z_x:
                    accepted = True
                    break
                else : 
                    log_P_z_x += np.log(1. - A_z_x)
                    self._back_off()
                    k += 1                     
            # end of steps for loop
            if accepted == True :
                chain[i,:] = z[:,0] 
                X = Z
                # for statistics
                n_accepted += 1    
                step_count[k+1] += 1
            else :
                chain[i,:]  = X['x'][:,0]
                # for statistics
                step_count[0] += 1
        # end outer loop

        # update stored info
        self._X = X

        # outputs
        if self._n_samples == 0 :
            self._chain = chain
            self._step_count = step_count
        else :
            self._chain = np.append(self._chain, chain, axis=0)
            self._step_count = np.add(self._step_count, step_count)
        self._n_samples += n_samples
        self._n_accepted += n_accepted