jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def calc_fragment_hap_posterior(self, assigned_vars, bc_inds, allele1_log_probs, allele2_log_probs, mix_log_probs):
        """ Calculate the posterior distribution of the haplotype state of each fragment """

        bc_mix_prob = self.bc_mix_prob

        p1 = np.zeros(len(bc_inds), dtype=np.float64)
        p2 = np.zeros(len(bc_inds), dtype=np.float64)
        p_mix = np.zeros(len(bc_inds), dtype=np.float64)

        l1m = np.log(0.5 * (1.0 - bc_mix_prob))
        l_mix = np.log(bc_mix_prob)

        for (col_idx, assignment) in assigned_vars:
            if assignment == 0:
                p1 += allele1_log_probs[bc_inds,col_idx].toarray().squeeze()
                p2 += allele2_log_probs[bc_inds,col_idx].toarray().squeeze()
            elif assignment == 1:
                p1 += allele2_log_probs[bc_inds,col_idx].toarray().squeeze()
                p2 += allele1_log_probs[bc_inds,col_idx].toarray().squeeze()
            else:
                p1 += mix_log_probs[bc_inds, col_idx].toarray().squeeze()
                p2 += mix_log_probs[bc_inds, col_idx].toarray().squeeze()

            p_mix += mix_log_probs[bc_inds, col_idx].toarray().squeeze()
        p1 += l1m
        p2 += l1m
        p_mix += l_mix

        norm = scipy.misc.logsumexp([p1, p2, p_mix], axis=0)
        dists = np.exp(np.array([p1 - norm, p2 - norm, p_mix - norm])).transpose()
        return dists
//  [python]
def calc_fragment_hap_posterior(self, assigned_vars, bc_inds, allele1_log_probs, allele2_log_probs, mix_log_probs):
        """ Calculate the posterior distribution of the haplotype state of each fragment """

        bc_mix_prob = self.bc_mix_prob

        p1 = np.zeros(len(bc_inds), dtype=np.float64)
        p2 = np.zeros(len(bc_inds), dtype=np.float64)
        p_mix = np.zeros(len(bc_inds), dtype=np.float64)

        l1m = np.log(0.5 * (1.0 - bc_mix_prob))
        l_mix = np.log(bc_mix_prob)

        for (col_idx, assignment) in assigned_vars:
            if assignment == 0:
                p1 += allele1_log_probs[bc_inds,col_idx].toarray().squeeze()
                p2 += allele2_log_probs[bc_inds,col_idx].toarray().squeeze()
            elif assignment == 1:
                p1 += allele2_log_probs[bc_inds,col_idx].toarray().squeeze()
                p2 += allele1_log_probs[bc_inds,col_idx].toarray().squeeze()
            else:
                p1 += mix_log_probs[bc_inds, col_idx].toarray().squeeze()
                p2 += mix_log_probs[bc_inds, col_idx].toarray().squeeze()

            p_mix += mix_log_probs[bc_inds, col_idx].toarray().squeeze()
        p1 += l1m
        p2 += l1m
        p_mix += l_mix

        norm = scipy.special.logsumexp([p1, p2, p_mix], axis=0)
        dists = np.exp(np.array([p1 - norm, p2 - norm, p_mix - norm])).transpose()
        return dists
//  [python]
def save_negative_log_likelihood_fixed_pars(self, t):
        """For comparison with GP, fix sigma^2 at the MAP when doing the
        one-step-ahead prediction."""
        all_one_step_ahead_lklhoods_weighted = [
            (self.model_universe[m].one_step_ahead_predictive_log_probs_fixed_pars +
             self.model_and_run_length_log_distr[m,
                        self.model_and_run_length_log_distr[m,:]> -np.inf])
            for m in range(0, self.Q)]
             #self.model_universe[m].retained_run_lengths])
             #np.where(self.model_and_run_length_log_distr[m,:]> -np.inf)])

        summed_up = -misc.logsumexp([item for entry in
            all_one_step_ahead_lklhoods_weighted for item in entry])
        self.negative_log_likelihood_fixed_pars.append(summed_up)
//  [python]
def save_negative_log_likelihood_fixed_pars(self, t):
        """For comparison with GP, fix sigma^2 at the MAP when doing the
        one-step-ahead prediction."""
        all_one_step_ahead_lklhoods_weighted = [
            (self.model_universe[m].one_step_ahead_predictive_log_probs_fixed_pars +
             self.model_and_run_length_log_distr[m,
                        self.model_and_run_length_log_distr[m,:]> -np.inf])
            for m in range(0, self.Q)]
             #self.model_universe[m].retained_run_lengths])
             #np.where(self.model_and_run_length_log_distr[m,:]> -np.inf)])

        summed_up = -scipy.special.logsumexp([item for entry in
            all_one_step_ahead_lklhoods_weighted for item in entry])
        self.negative_log_likelihood_fixed_pars.append(summed_up)
//  [python]
def update_priors(self, t):
        """update the priors for each model in the model universe, provided
        that the boolean auto_prior_update is true"""
        for (m, model) in zip(range(0,self.Q), self.model_universe):
            if model.auto_prior_update == True:
                """We need to weigh quantities acc. to rld"""
                model_specific_rld = (self.model_and_run_length_log_distr[m,:] -
                    misc.logsumexp(self.model_and_run_length_log_distr[m,:]))
                model.prior_update(t, model_specific_rld)
//  [python]
def update_priors(self, t):
        """update the priors for each model in the model universe, provided
        that the boolean auto_prior_update is true"""
        for (m, model) in zip(range(0,self.Q), self.model_universe):
            if model.auto_prior_update == True:
                """We need to weigh quantities acc. to rld"""
                model_specific_rld = (self.model_and_run_length_log_distr[m,:] -
                    scipy.special.logsumexp(self.model_and_run_length_log_distr[m,:]))
                model.prior_update(t, model_specific_rld)
//  [python]
def gibbs_sample_inside_loop_i(self, i, anneal_temp=1): #, lms=1.):
        """
        Perform the inside loop of Gibbs sampling for data vector `i`.

        This is the inside of `gibbs_sample` and can be used by outside objects
        to perform only the inside loop part of the Gibbs sampling operation.
        The step in the loop is sample a new assignment for data vector `i`.
        The reason for not replacing the actual inner part of `gibbs_sample` by
        a call to this function is because this won't allow for caching the old
        component stats.
        """

        # Compute log probability of `X[i]` belonging to each component
        # (24.26) in Murphy, p. 843
        log_prob_z = self.lms * (
            np.ones(self.components.K_max)*np.log(
                float(self.alpha)/self.components.K_max + self.components.counts
                )
            )

        # (24.23) in Murphy, p. 842
        log_prob_z[:self.components.K] += self.components.log_post_pred(i)
        # Empty (unactive) components
        log_prob_z[self.components.K:] += self.components.log_prior(i)
        if anneal_temp != 1:
            log_prob_z = log_prob_z - logsumexp(log_prob_z)
            log_prob_z_anneal = 1./anneal_temp * log_prob_z - logsumexp(1./anneal_temp * log_prob_z)
            prob_z = np.exp(log_prob_z_anneal)
        else:
            prob_z = np.exp(log_prob_z - logsumexp(log_prob_z))
        # prob_z = np.exp(log_prob_z - logsumexp(log_prob_z))
        assert not np.isnan(np.sum(prob_z))

        # Sample the new component assignment for `X[i]`
        k = utils.draw(prob_z)

        # There could be several empty, unactive components at the end
        if k > self.components.K:
            k = self.components.K

        logger.debug("Adding item " + str(i) + " to acoustic model component " + str(k))
        self.components.add_item(i, k)
//  [python]
def gibbs_sample_inside_loop_i(self, i, anneal_temp=1): #, lms=1.):
        """
        Perform the inside loop of Gibbs sampling for data vector `i`.

        This is the inside of `gibbs_sample` and can be used by outside objects
        to perform only the inside loop part of the Gibbs sampling operation.
        The step in the loop is sample a new assignment for data vector `i`.
        The reason for not replacing the actual inner part of `gibbs_sample` by
        a call to this function is because this won't allow for caching the old
        component stats.
        """

        # Compute log probability of `X[i]` belonging to each component
        # (24.26) in Murphy, p. 843
        log_prob_z = self.lms * (
            np.ones(self.components.K_max)*np.log(
                float(self.alpha)/self.components.K_max + self.components.counts
                )
            )

        # (24.23) in Murphy, p. 842
        log_prob_z[:self.components.K] += self.components.log_post_pred(i)
        # Empty (unactive) components
        log_prob_z[self.components.K:] += self.components.log_prior(i)
        if anneal_temp != 1:
            log_prob_z = log_prob_z - scipy.special.logsumexp(log_prob_z)
            log_prob_z_anneal = 1./anneal_temp * log_prob_z - scipy.special.logsumexp(1./anneal_temp * log_prob_z)
            prob_z = np.exp(log_prob_z_anneal)
        else:
            prob_z = np.exp(log_prob_z - scipy.special.logsumexp(log_prob_z))
        # prob_z = np.exp(log_prob_z - logsumexp(log_prob_z))
        assert not np.isnan(np.sum(prob_z))

        # Sample the new component assignment for `X[i]`
        k = utils.draw(prob_z)

        # There could be several empty, unactive components at the end
        if k > self.components.K:
            k = self.components.K

        logger.debug("Adding item " + str(i) + " to acoustic model component " + str(k))
        self.components.add_item(i, k)
//  [python]
def normalise(a):
    """normalises the input probability vector to sum to one in the log domain.

    This is equivalent to a/sum(a) in the linear domain.
    """
    return a - sp.misc.logsumexp(a)
//  [python]
def normalise(a):
    """normalises the input probability vector to sum to one in the log domain.

    This is equivalent to a/sum(a) in the linear domain.
    """
    return a - sp.special.logsumexp(a)
//  [python]
def dot(a, b):
    """Computes dot product in the log domain.

    This is equivalent to a1*b1+a2*b2+... in the linear domain.
    """
    return sp.misc.logsumexp(a + b)
//  [python]
def dot(a, b):
    """Computes dot product in the log domain.

    This is equivalent to a1*b1+a2*b2+... in the linear domain.
    """
    return sp.special.logsumexp(a + b)
//  [python]
def CCML(ccpart,ccmat,mu,r,nu,s,row_alpha,col_alpha):
	lp = []
	
	ccmat = np.array(ccmat)

	state = ccpart.states[1]

	# loop through the states
	for state in ccpart.states:
		all_cols = state['col_parts']
		all_rows = state['row_parts']

		K = max(all_cols)+1
				
		lp_temp = lcrp(all_cols,col_alpha)
		for view in range(K):
			row_part = all_rows[view,:]
			lp_temp += lcrp(row_part,row_alpha)
			cols_view = np.nonzero(all_cols==view)[0]
			for col in cols_view:
				for cat in range(row_part.max()+1):
					X = ccmat[np.nonzero(row_part==cat)[0],col]
					lp_temp += NGML(X,mu,r,nu,s)

		lp.append(lp_temp);

	# return the normalized probabilities
	return lp-sp.misc.logsumexp(lp)
//  [python]
def CCML(ccpart,ccmat,mu,r,nu,s,row_alpha,col_alpha):
	lp = []
	
	ccmat = np.array(ccmat)

	state = ccpart.states[1]

	# loop through the states
	for state in ccpart.states:
		all_cols = state['col_parts']
		all_rows = state['row_parts']

		K = max(all_cols)+1
				
		lp_temp = lcrp(all_cols,col_alpha)
		for view in range(K):
			row_part = all_rows[view,:]
			lp_temp += lcrp(row_part,row_alpha)
			cols_view = np.nonzero(all_cols==view)[0]
			for col in cols_view:
				for cat in range(row_part.max()+1):
					X = ccmat[np.nonzero(row_part==cat)[0],col]
					lp_temp += NGML(X,mu,r,nu,s)

		lp.append(lp_temp);

	# return the normalized probabilities
	return lp-sp.special.logsumexp(lp)
//  [python]
def determine_chains_to_merge(chain_dirs,chain_inclusion_factor):
    '''
    Examines all of the trees output by each chain and reports which chains should
    be merged. Chains will meet the criteria if the log(sum(all_tree_likelihoods))
    is within some factor of the maximum of that value across chains.
    '''
    logSumLHs = []
    for chain_dir in chain_dirs:
        logLHs = []
        tree_zip_file = zipfile.ZipFile(os.path.join(chain_dir,'trees.zip'), mode = 'r')
        for tree_name in tree_zip_file.namelist():
            if tree_name.startswith("tree"):
                #the logged likelihood is in the names of the trees, just use that.
                logLHs.append(float(tree_name.split('_')[-1]))
        logSumLHs.append(scipy.misc.logsumexp(logLHs))

    # Check below assumes that LLH < 0, which it should always be. We need this
    # assumption for the idea that a "slightly worse" chain has a "slightly
    # more negative" LH to work.
    logSumLHs = np.array(logSumLHs)
    assert np.all(logSumLHs < 0)
    bestLogSumLH = np.max(logSumLHs)

    included_chains = []
    excluded_chains = []
    for cidx, logsumlh in enumerate(logSumLHs):
        if logsumlh >= chain_inclusion_factor * bestLogSumLH:
            included_chains.append((cidx, logsumlh))
        else:
            excluded_chains.append((cidx, logsumlh))

    assert len(included_chains) >= 1
    return (included_chains, excluded_chains)
//  [python]
def determine_chains_to_merge(chain_dirs,chain_inclusion_factor):
    '''
    Examines all of the trees output by each chain and reports which chains should
    be merged. Chains will meet the criteria if the log(sum(all_tree_likelihoods))
    is within some factor of the maximum of that value across chains.
    '''
    logSumLHs = []
    for chain_dir in chain_dirs:
        logLHs = []
        tree_zip_file = zipfile.ZipFile(os.path.join(chain_dir,'trees.zip'), mode = 'r')
        for tree_name in tree_zip_file.namelist():
            if tree_name.startswith("tree"):
                #the logged likelihood is in the names of the trees, just use that.
                logLHs.append(float(tree_name.split('_')[-1]))
        logSumLHs.append(scipy.special.logsumexp(logLHs))

    # Check below assumes that LLH < 0, which it should always be. We need this
    # assumption for the idea that a "slightly worse" chain has a "slightly
    # more negative" LH to work.
    logSumLHs = np.array(logSumLHs)
    assert np.all(logSumLHs < 0)
    bestLogSumLH = np.max(logSumLHs)

    included_chains = []
    excluded_chains = []
    for cidx, logsumlh in enumerate(logSumLHs):
        if logsumlh >= chain_inclusion_factor * bestLogSumLH:
            included_chains.append((cidx, logsumlh))
        else:
            excluded_chains.append((cidx, logsumlh))

    assert len(included_chains) >= 1
    return (included_chains, excluded_chains)
//  [python]
def bayesFactors(model1, model2):
    """
    Computes the Bayes factor for two competing models.

    The computation is based on Newton & Raftery 1994 (Eq. 13).

    Parameters
    ----------
    model1 : PyAstronomy.funcFit.TraceAnalysis instance
        TraceAnalysis for the first model.
    model2 : PyAstronomy.funcFit.TraceAnalysis instance
        TraceAnalysis for the second model.

    Returns
    -------
    BF : float
        The Bayes factor BF (neither log10(BF) nor ln(BF)).
        Note that a small value means that the first model
        is favored, i.e., BF=p(M2)/p(M1).

    """
    logp1 = model1["deviance"] / (-2.)
    logp2 = model2["deviance"] / (-2.)

    # Given a number of numbers x1, x2, ... whose logarithms are given by l_i=ln(x_i) etc.,
    # logsum calculates: ln(sum_i exp(l_i)) = ln(sum_i x_i)
    bf = numpy.exp(sm.logsumexp(-logp1) - numpy.log(len(logp1))
                   - (sm.logsumexp(-logp2) - numpy.log(len(logp2))))
    return bf
//  [python]
def bayesFactors(model1, model2):
    """
    Computes the Bayes factor for two competing models.

    The computation is based on Newton & Raftery 1994 (Eq. 13).

    Parameters
    ----------
    model1 : PyAstronomy.funcFit.TraceAnalysis instance
        TraceAnalysis for the first model.
    model2 : PyAstronomy.funcFit.TraceAnalysis instance
        TraceAnalysis for the second model.

    Returns
    -------
    BF : float
        The Bayes factor BF (neither log10(BF) nor ln(BF)).
        Note that a small value means that the first model
        is favored, i.e., BF=p(M2)/p(M1).

    """
    logp1 = model1["deviance"] / (-2.)
    logp2 = model2["deviance"] / (-2.)

    # Given a number of numbers x1, x2, ... whose logarithms are given by l_i=ln(x_i) etc.,
    # logsum calculates: ln(sum_i exp(l_i)) = ln(sum_i x_i)
    bf = numpy.exp(scipy.special.logsumexp(-logp1) - numpy.log(len(logp1))
                   - (scipy.special.logsumexp(-logp2) - numpy.log(len(logp2))))
    return bf
//  [python]
def eval(self, x, ii=None, log=True):
        """
        Evaluates the mog pdf.
        :param x: rows are inputs to evaluate at
        :param ii: a list of indices specifying which marginal to evaluate. if None, the joint pdf is evaluated
        :param log: if True, the log pdf is evaluated
        :return: pdf or log pdf
        """

        ps = np.array([self.xs[ix].eval(x, ii, log) for ix in range(len(self.a))]).T
        res = scipy.misc.logsumexp(ps + np.log(self.a), axis=1) if log else np.dot(ps, self.a)

        return res
//  [python]
def eval(self, x, ii=None, log=True):
        """
        Evaluates the mog pdf.
        :param x: rows are inputs to evaluate at
        :param ii: a list of indices specifying which marginal to evaluate. if None, the joint pdf is evaluated
        :param log: if True, the log pdf is evaluated
        :return: pdf or log pdf
        """

        ps = np.array([self.xs[ix].eval(x, ii, log) for ix in range(len(self.a))]).T
        res = scipy.special.logsumexp(ps + np.log(self.a), axis=1) if log else np.dot(ps, self.a)

        return res
//  [python]
def __mul__(self, other):
        """Multiply with a single gaussian."""

        assert isinstance(other, Gaussian)

        ys = [x * other for x in self.xs]

        lcs = np.empty_like(self.a)

        for i, (x, y) in enumerate(zip(self.xs, ys)):

            lcs[i] = x.logdetP + other.logdetP - y.logdetP
            lcs[i] -= np.dot(x.m, np.dot(x.P, x.m)) + np.dot(other.m, np.dot(other.P, other.m)) - np.dot(y.m, np.dot(y.P, y.m))
            lcs[i] *= 0.5

        la = np.log(self.a) + lcs
        la -= scipy.misc.logsumexp(la)
        a = np.exp(la)

        return MoG(a=a, xs=ys)
//  [python]
def __mul__(self, other):
        """Multiply with a single gaussian."""

        assert isinstance(other, Gaussian)

        ys = [x * other for x in self.xs]

        lcs = np.empty_like(self.a)

        for i, (x, y) in enumerate(zip(self.xs, ys)):

            lcs[i] = x.logdetP + other.logdetP - y.logdetP
            lcs[i] -= np.dot(x.m, np.dot(x.P, x.m)) + np.dot(other.m, np.dot(other.P, other.m)) - np.dot(y.m, np.dot(y.P, y.m))
            lcs[i] *= 0.5

        la = np.log(self.a) + lcs
        la -= scipy.special.logsumexp(la)
        a = np.exp(la)

        return MoG(a=a, xs=ys)
//  [python]
def geometric_deviation_pmf(base, geom_p, dev_max):
    """ PMF of geometrically distributed deviation from given base counts.
    
    Args:
        base (numpy.array): base counts to deviate from
        geom_p (float): probability for geometric distribution
        dev_max (int): maximum deviation
    
    Returns:
        tuple of numpy.array: counts, log_probs
    """

    base = base.flatten()
    
    num_dev = 2 * dev_max + 1

    dev = np.ones((num_dev,))
    dev = np.cumsum(dev) - dev_max - 1.

    log_probs = np.abs(dev) * np.log(1-geom_p) + np.log(geom_p)

    counts = np.tile(base, num_dev).reshape((num_dev, len(base))).T
    log_probs = np.tile(log_probs, len(base)).reshape((len(base), num_dev))

    counts = counts + dev

    # Exclude nonzero counts
    log_probs[counts < 0] = -np.inf

    # Normalize
    log_norm = scipy.misc.logsumexp(log_probs, axis=1).T
    log_probs = (log_probs.T - log_norm.T).T
    
    return counts, log_probs
//  [python]
def geometric_deviation_pmf(base, geom_p, dev_max):
    """ PMF of geometrically distributed deviation from given base counts.
    
    Args:
        base (numpy.array): base counts to deviate from
        geom_p (float): probability for geometric distribution
        dev_max (int): maximum deviation
    
    Returns:
        tuple of numpy.array: counts, log_probs
    """

    base = base.flatten()
    
    num_dev = 2 * dev_max + 1

    dev = np.ones((num_dev,))
    dev = np.cumsum(dev) - dev_max - 1.

    log_probs = np.abs(dev) * np.log(1-geom_p) + np.log(geom_p)

    counts = np.tile(base, num_dev).reshape((num_dev, len(base))).T
    log_probs = np.tile(log_probs, len(base)).reshape((len(base), num_dev))

    counts = counts + dev

    # Exclude nonzero counts
    log_probs[counts < 0] = -np.inf

    # Normalize
    log_norm = scipy.special.logsumexp(log_probs, axis=1).T
    log_probs = (log_probs.T - log_norm.T).T
    
    return counts, log_probs
//  [python]
def __div__(self, other):
        """Divide by a single gaussian."""

        assert isinstance(other, Gaussian)

        ys = [x / other for x in self.xs]

        lcs = np.empty_like(self.a)

        for i, (x, y) in enumerate(zip(self.xs, ys)):

            lcs[i] = x.logdetP - other.logdetP - y.logdetP
            lcs[i] -= np.dot(x.m, np.dot(x.P, x.m)) - np.dot(other.m, np.dot(other.P, other.m)) - np.dot(y.m, np.dot(y.P, y.m))
            lcs[i] *= 0.5

        la = np.log(self.a) + lcs
        la -= scipy.misc.logsumexp(la)
        a = np.exp(la)

        return MoG(a=a, xs=ys)
//  [python]
def __div__(self, other):
        """Divide by a single gaussian."""

        assert isinstance(other, Gaussian)

        ys = [x / other for x in self.xs]

        lcs = np.empty_like(self.a)

        for i, (x, y) in enumerate(zip(self.xs, ys)):

            lcs[i] = x.logdetP - other.logdetP - y.logdetP
            lcs[i] -= np.dot(x.m, np.dot(x.P, x.m)) - np.dot(other.m, np.dot(other.P, other.m)) - np.dot(y.m, np.dot(y.P, y.m))
            lcs[i] *= 0.5

        la = np.log(self.a) + lcs
        la -= scipy.special.logsumexp(la)
        a = np.exp(la)

        return MoG(a=a, xs=ys)
//  [python]
def logpdf(self, pts, pool=None):
        """Evaluate the logpdf of the KDE at `pts`."""
        logpdfs = [logweight + kde(pts, pool=pool)
                   for logweight, kde in zip(self._logweights, self._kdes)]
        if len(pts.shape) == 1:
            return logsumexp(logpdfs)
        else:
            return logsumexp(logpdfs, axis=0)
//  [python]
def logpdf(self, pts, pool=None):
        """Evaluate the logpdf of the KDE at `pts`."""
        logpdfs = [logweight + kde(pts, pool=pool)
                   for logweight, kde in zip(self._logweights, self._kdes)]
        if len(pts.shape) == 1:
            return scipy.special.logsumexp(logpdfs)
        else:
            return scipy.special.logsumexp(logpdfs, axis=0)
//  [python]
def _predict(self, X):
		check_is_fitted(self, ['X_', 'y_'])
		X = check_array(X, force_all_finite='allow-nan')
		
		Ncovered = len(self.classes_)
		logprior = -log(Ncovered)
		
		XT = numpy.where(self.islogvar, log10(X), X)
		Xlogprobs = numpy.empty((len(X), len(self.class_properties_)))
		for i, (l1, feature_distributions) in enumerate(self.class_properties_):
			Xlogprobs[:,i] = self._evaluate_feature_dist(feature_distributions, XT, logprior)

		logprobany = scipy.misc.logsumexp(Xlogprobs, axis=1)
		nearest = Xlogprobs.argmax(axis=1)
		Xlogprobs -= logprobany.reshape((-1,1))
		self.nearest_ = nearest
		self.logproba_ = Xlogprobs
		self.surprise_ = logprobany
//  [python]
def _predict(self, X):
		check_is_fitted(self, ['X_', 'y_'])
		X = check_array(X, force_all_finite='allow-nan')
		
		Ncovered = len(self.classes_)
		logprior = -log(Ncovered)
		
		XT = numpy.where(self.islogvar, log10(X), X)
		Xlogprobs = numpy.empty((len(X), len(self.class_properties_)))
		for i, (l1, feature_distributions) in enumerate(self.class_properties_):
			Xlogprobs[:,i] = self._evaluate_feature_dist(feature_distributions, XT, logprior)

		logprobany = scipy.special.logsumexp(Xlogprobs, axis=1)
		nearest = Xlogprobs.argmax(axis=1)
		Xlogprobs -= logprobany.reshape((-1,1))
		self.nearest_ = nearest
		self.logproba_ = Xlogprobs
		self.surprise_ = logprobany
//  [python]
def test_sum_product(self):
        cn_states = create_cn_states()
        brk_states = create_brk_states()

        num_cn_states = cn_states.shape[0]

        num_segments = 3

        for i in range(num_replicates):

            model = remixt.vhmm.RemixtModel(
                3, num_segments, 0,
                cn_states,
                brk_states,
                np.array([0, 0, 1], dtype=np.uint8),
                np.array([-1] * 3),
                np.array([-1] * 3, dtype=np.int8),
                cn_max,
                1.0,
            )

            framelogprob = np.random.rand(num_segments, num_cn_states)

            alphas = np.zeros((num_segments, num_cn_states))
            betas = np.zeros((num_segments, num_cn_states))
            remixt.vhmm.sum_product(model, framelogprob, alphas, betas)

            for seg_idx in range(num_segments):
                posterior_marginal_1 = naive_posterior_marginal(cn_states, framelogprob, model, seg_idx)

                gamma = alphas[seg_idx] + betas[seg_idx]
                posterior_marginal_2 = np.exp(gamma - scipy.misc.logsumexp(gamma))
                posterior_marginal_2 /= posterior_marginal_2.sum()

                error = np.sum(np.square(posterior_marginal_1 - posterior_marginal_2))
                self.assertAlmostEqual(error, 0.0, places=3)
//  [python]
def test_sum_product(self):
        cn_states = create_cn_states()
        brk_states = create_brk_states()

        num_cn_states = cn_states.shape[0]

        num_segments = 3

        for i in range(num_replicates):

            model = remixt.vhmm.RemixtModel(
                3, num_segments, 0,
                cn_states,
                brk_states,
                np.array([0, 0, 1], dtype=np.uint8),
                np.array([-1] * 3),
                np.array([-1] * 3, dtype=np.int8),
                cn_max,
                1.0,
            )

            framelogprob = np.random.rand(num_segments, num_cn_states)

            alphas = np.zeros((num_segments, num_cn_states))
            betas = np.zeros((num_segments, num_cn_states))
            remixt.vhmm.sum_product(model, framelogprob, alphas, betas)

            for seg_idx in range(num_segments):
                posterior_marginal_1 = naive_posterior_marginal(cn_states, framelogprob, model, seg_idx)

                gamma = alphas[seg_idx] + betas[seg_idx]
                posterior_marginal_2 = np.exp(gamma - scipy.special.logsumexp(gamma))
                posterior_marginal_2 /= posterior_marginal_2.sum()

                error = np.sum(np.square(posterior_marginal_1 - posterior_marginal_2))
                self.assertAlmostEqual(error, 0.0, places=3)
//  [python]
def logpdf(self, pts, pool=None):
        """Evaluate the log-transdimensional-pdf at `pts` as estimated by the KDE."""
        logpdfs = []
        for logweight, space, kde in zip(self._logweights,
                                         self.spaces,
                                         self.kdes):
            # Calculate the probability for each parameter space individually
            if np.all(space == ~pts.mask) and np.isfinite(logweight):
                logpdfs.append(logweight + kde(pts[space], pool=pool))

        return logsumexp(logpdfs, axis=0)
//  [python]
def logpdf(self, pts, pool=None):
        """Evaluate the log-transdimensional-pdf at `pts` as estimated by the KDE."""
        logpdfs = []
        for logweight, space, kde in zip(self._logweights,
                                         self.spaces,
                                         self.kdes):
            # Calculate the probability for each parameter space individually
            if np.all(space == ~pts.mask) and np.isfinite(logweight):
                logpdfs.append(logweight + kde(pts[space], pool=pool))

        return scipy.special.logsumexp(logpdfs, axis=0)
//  [python]
def _evaluate_feature_dist(self, feature_distributions, x, logprior):
		assert len(feature_distributions) == x.shape[1], (len(feature_distributions), x.shape)
		logprob = numpy.zeros((len(x), len(feature_distributions)))
		for i, mix in enumerate(feature_distributions):
			if mix is None:
				pass
			else:
				means = numpy.array([g.mu for g in mix.components])
				variances = numpy.array([g.sigma[0] for g in mix.components])
				weights = mix.weights.reshape(means.shape)
				
				logprob_col = - 0.5 * log(variances * 2 * pi) \
					- 0.5 * ((x[:,i].reshape((1,-1)) - means)**2 / variances) \
					+ log(weights)
				
				# sum across gaussian components:
				logprob_col = scipy.misc.logsumexp(logprob_col, axis=0)
				assert len(logprob_col) == len(x), (logprob_col.shape, x.shape)
				logprob[:,i] = logprob_col

		# handle missing values:
		logprob[~numpy.isfinite(logprob)] = 0
		# avoid overly small values:
		# In the tails of the distribution, we know we will not be accurate
		logprob[logprob < log(self.minprob)] = log(self.minprob)
		# multiply across columns:
		logprob = logprob.sum(axis=1)
		# combine with prior
		return logprob + logprior
//  [python]
def _evaluate_feature_dist(self, feature_distributions, x, logprior):
		assert len(feature_distributions) == x.shape[1], (len(feature_distributions), x.shape)
		logprob = numpy.zeros((len(x), len(feature_distributions)))
		for i, mix in enumerate(feature_distributions):
			if mix is None:
				pass
			else:
				means = numpy.array([g.mu for g in mix.components])
				variances = numpy.array([g.sigma[0] for g in mix.components])
				weights = mix.weights.reshape(means.shape)
				
				logprob_col = - 0.5 * log(variances * 2 * pi) \
					- 0.5 * ((x[:,i].reshape((1,-1)) - means)**2 / variances) \
					+ log(weights)
				
				# sum across gaussian components:
				logprob_col = scipy.special.logsumexp(logprob_col, axis=0)
				assert len(logprob_col) == len(x), (logprob_col.shape, x.shape)
				logprob[:,i] = logprob_col

		# handle missing values:
		logprob[~numpy.isfinite(logprob)] = 0
		# avoid overly small values:
		# In the tails of the distribution, we know we will not be accurate
		logprob[logprob < log(self.minprob)] = log(self.minprob)
		# multiply across columns:
		logprob = logprob.sum(axis=1)
		# combine with prior
		return logprob + logprior
//  [python]
def _evaluate_point_logpdf(args):
    """
    Evaluate the Gaussian KDE at a given point `p`.  This lives outside the KDE method to allow for
    parallelization using :mod:`multipocessing`. Since :func:`map` only allows single-argument
    functions, the following arguments to be packed into a single tuple.

    :param p:
        The point to evaluate the KDE at.

    :param data:
        The `(N, ndim)`-shaped array of data used to construct the KDE.

    :param cho_factor:
        A Cholesky decomposition of the kernel covariance matrix.
    """
    point, data, cho_factor = args

    # Use Cholesky decomposition to avoid direct inversion of covariance matrix
    diff = data - point
    tdiff = la.cho_solve(cho_factor, diff.T, check_finite=False).T
    diff *= tdiff

    # Work in the log to avoid large numbers
    return logsumexp(-np.sum(diff, axis=1)/2)
//  [python]
def fit_mog(x, n_components, w=None, tol=1.0e-9, maxiter=float('inf'), verbose=False):
    """Fit and return a mixture of gaussians to (possibly weighted) data using expectation maximization."""

    x = x[:, np.newaxis] if x.ndim == 1 else x
    n_data, n_dim = x.shape

    # initialize
    a = np.ones(n_components) / n_components
    ms = rng.randn(n_components, n_dim)
    Ss = [np.eye(n_dim) for _ in range(n_components)]
    iter = 0

    # calculate log p(x,z), log p(x) and total log likelihood
    logPxz = np.array([scipy.stats.multivariate_normal.logpdf(x, ms[k], Ss[k]) for k in range(n_components)])
    logPxz += np.log(a)[:, np.newaxis]
    logPx = scipy.misc.logsumexp(logPxz, axis=0)
    loglik_prev = np.mean(logPx) if w is None else np.dot(w, logPx)

    while True:

        # e step
        z = np.exp(logPxz - logPx)

        # m step
        if w is None:
            Nk = np.sum(z, axis=1)
            a = Nk / n_data
            ms = np.dot(z, x) / Nk[:, np.newaxis]
            for k in range(n_components):
                xm = x - ms[k]
                Ss[k] = np.dot(xm.T * z[k], xm) / Nk[k]
        else:
            zw = z * w
            a = np.sum(zw, axis=1)
            ms = np.dot(zw, x) / a[:, np.newaxis]
            for k in range(n_components):
                xm = x - ms[k]
                Ss[k] = np.dot(xm.T * zw[k], xm) / a[k]

        # calculate log p(x,z), log p(x) and total log likelihood
        logPxz = np.array([scipy.stats.multivariate_normal.logpdf(x, ms[k], Ss[k])
                           for k in range(n_components)])
        logPxz += np.log(a)[:, np.newaxis]
        logPx = scipy.misc.logsumexp(logPxz, axis=0)
        loglik = np.mean(logPx) if w is None else np.dot(w, logPx)

        # check progress
        iter += 1
        diff = loglik - loglik_prev
        assert diff >= 0.0, 'Log likelihood decreased! There is a bug somewhere!'
        if verbose: print('Iteration = {0}, log likelihood = {1}, diff = {2}'.format(iter, loglik, diff))
        if diff < tol or iter > maxiter: break
        loglik_prev = loglik

    return MoG(a=a, ms=ms, Ss=Ss)
//  [python]
def fit_mog(x, n_components, w=None, tol=1.0e-9, maxiter=float('inf'), verbose=False):
    """Fit and return a mixture of gaussians to (possibly weighted) data using expectation maximization."""

    x = x[:, np.newaxis] if x.ndim == 1 else x
    n_data, n_dim = x.shape

    # initialize
    a = np.ones(n_components) / n_components
    ms = rng.randn(n_components, n_dim)
    Ss = [np.eye(n_dim) for _ in range(n_components)]
    iter = 0

    # calculate log p(x,z), log p(x) and total log likelihood
    logPxz = np.array([scipy.stats.multivariate_normal.logpdf(x, ms[k], Ss[k]) for k in range(n_components)])
    logPxz += np.log(a)[:, np.newaxis]
    logPx = scipy.special.logsumexp(logPxz, axis=0)
    loglik_prev = np.mean(logPx) if w is None else np.dot(w, logPx)

    while True:

        # e step
        z = np.exp(logPxz - logPx)

        # m step
        if w is None:
            Nk = np.sum(z, axis=1)
            a = Nk / n_data
            ms = np.dot(z, x) / Nk[:, np.newaxis]
            for k in range(n_components):
                xm = x - ms[k]
                Ss[k] = np.dot(xm.T * z[k], xm) / Nk[k]
        else:
            zw = z * w
            a = np.sum(zw, axis=1)
            ms = np.dot(zw, x) / a[:, np.newaxis]
            for k in range(n_components):
                xm = x - ms[k]
                Ss[k] = np.dot(xm.T * zw[k], xm) / a[k]

        # calculate log p(x,z), log p(x) and total log likelihood
        logPxz = np.array([scipy.stats.multivariate_normal.logpdf(x, ms[k], Ss[k])
                           for k in range(n_components)])
        logPxz += np.log(a)[:, np.newaxis]
        logPx = scipy.special.logsumexp(logPxz, axis=0)
        loglik = np.mean(logPx) if w is None else np.dot(w, logPx)

        # check progress
        iter += 1
        diff = loglik - loglik_prev
        assert diff >= 0.0, 'Log likelihood decreased! There is a bug somewhere!'
        if verbose: print('Iteration = {0}, log likelihood = {1}, diff = {2}'.format(iter, loglik, diff))
        if diff < tol or iter > maxiter: break
        loglik_prev = loglik

    return MoG(a=a, ms=ms, Ss=Ss)
//  [python]
def trimmer(self, kept_run_lengths):
        #print(kept_run_lengths)
        """Trim the relevant quantities for the MDGP model"""
        self.joint_log_probabilities = (self.joint_log_probabilities[kept_run_lengths,:,:])
        #print('kept_run_lengths',kept_run_lengths)
        #print('self.sums.shape',self.sums.shape)
        self.sums = self.sums[kept_run_lengths,:,:]
        self.retained_run_lengths = (self.retained_run_lengths[kept_run_lengths])
        self.model_log_evidence = scipy.misc.logsumexp(self.joint_log_probabilities)
//  [python]
def trimmer(self, kept_run_lengths):
        #print(kept_run_lengths)
        """Trim the relevant quantities for the MDGP model"""
        self.joint_log_probabilities = (self.joint_log_probabilities[kept_run_lengths,:,:])
        #print('kept_run_lengths',kept_run_lengths)
        #print('self.sums.shape',self.sums.shape)
        self.sums = self.sums[kept_run_lengths,:,:]
        self.retained_run_lengths = (self.retained_run_lengths[kept_run_lengths])
        self.model_log_evidence = scipy.special.logsumexp(self.joint_log_probabilities)
//  [python]
def resample_probs(self, genomes, fitness_callback=None):
        """ Calculate resampling probabilities.

        Args:
            genomes (list of RearrangedGenome): list of rearranged genomes to calculate prob from fitnesses

        Kwargs:
            fitness_callback (callable): modify fitness callback 

        """

        fitnesses = list()
        for genome in genomes:
            fitnesses.append(self.genome_fitness(genome, fitness_callback))

        fitnesses = np.array(fitnesses)

        prob = np.exp(fitnesses - scipy.misc.logsumexp(fitnesses))

        return prob
//  [python]
def resample_probs(self, genomes, fitness_callback=None):
        """ Calculate resampling probabilities.

        Args:
            genomes (list of RearrangedGenome): list of rearranged genomes to calculate prob from fitnesses

        Kwargs:
            fitness_callback (callable): modify fitness callback 

        """

        fitnesses = list()
        for genome in genomes:
            fitnesses.append(self.genome_fitness(genome, fitness_callback))

        fitnesses = np.array(fitnesses)

        prob = np.exp(fitnesses - scipy.special.logsumexp(fitnesses))

        return prob
//  [python]
def eval(self, x, ii=None, log=True):
        """
        Evaluates the mog pdf.
        :param x: rows are inputs to evaluate at
        :param ii: a list of indices specifying which marginal to evaluate. if None, the joint pdf is evaluated
        :param log: if True, the log pdf is evaluated
        :return: pdf or log pdf
        """

        ps = np.array([c.eval(x, ii, log) for c in self.xs]).T
        res = scipy.misc.logsumexp(ps + np.log(self.a), axis=1) if log else np.dot(ps, self.a)

        return res
//  [python]
def eval(self, x, ii=None, log=True):
        """
        Evaluates the mog pdf.
        :param x: rows are inputs to evaluate at
        :param ii: a list of indices specifying which marginal to evaluate. if None, the joint pdf is evaluated
        :param log: if True, the log pdf is evaluated
        :return: pdf or log pdf
        """

        ps = np.array([c.eval(x, ii, log) for c in self.xs]).T
        res = scipy.special.logsumexp(ps + np.log(self.a), axis=1) if log else np.dot(ps, self.a)

        return res
//  [python]
def turner_hyperparameter_optimization(self, step_size):
        """Called by HyperparameterOpimizer at each iteration of the
        optimization routine.

        NOTE: Should be checked w.r.t. step-size computation, probably one can
        do better here!"""

        """STEP 1: Get the new gradient value"""
        sign, gradient = scipy.misc.logsumexp(
                a=(self.model_specific_joint_log_probabilities_derivative),
                b=self.model_specific_joint_log_probabilities_derivative_sign,
                return_sign=True, axis=1)
        gradient = np.exp(gradient)*sign

        """STEP 2: Get the difference a_{t-1} - a_{t-2}, b_{t-1} - b_{t-2}"""
        #Debug: Initialize self.a_old as self.a!, gradient_old = 0
        dif_old_a = self.a - self.a_old
        dif_old_b = self.b - self.b_old
        dif_old_val = np.array([dif_old_a, dif_old_b])
        dif_old_grad = gradient - self.gradient_old

        """STEP 3: Compute step size via BB method"""
        if True:
            D1_sign = np.dot(np.sign(dif_old_val), np.sign(dif_old_grad))
            D1 = min(max(np.abs(np.dot(dif_old_val, dif_old_grad)), pow(10,5)),
                     pow(10, -5))*D1_sign
            D2 = min(max(np.dot(dif_old_grad, dif_old_grad), pow(10,5)),
                     pow(10,-5))
            #D3 = min(max(np.dot(dif_old_val, dif_old_val), pow(10,5)),
            #         pow(10,-5))
            alpha_1 = D1/D2
            #alpha_2 = D3/D1
            step_size = alpha_1

        """STEP 2: Calculate the new a-value, and return the difference to
        old a-value, (a_{it+1} - a_it)"""
        self.a_old, self.b_old = self.a, self.b
        self.gradient_old = gradient
        self.a = min(max(self.a + gradient[0]*step_size, pow(10,-20)), pow(10,15))
        self.b = min(max(self.b + gradient[1]*step_size, pow(10,-20)), pow(10,15))
        a_dif, b_dif = self.a - self.a_old, self.b - self.b_old
        return [a_dif, b_dif]
//  [python]
def turner_hyperparameter_optimization(self, step_size):
        """Called by HyperparameterOpimizer at each iteration of the
        optimization routine.

        NOTE: Should be checked w.r.t. step-size computation, probably one can
        do better here!"""

        """STEP 1: Get the new gradient value"""
        sign, gradient = scipy.special.logsumexp(
                a=(self.model_specific_joint_log_probabilities_derivative),
                b=self.model_specific_joint_log_probabilities_derivative_sign,
                return_sign=True, axis=1)
        gradient = np.exp(gradient)*sign

        """STEP 2: Get the difference a_{t-1} - a_{t-2}, b_{t-1} - b_{t-2}"""
        #Debug: Initialize self.a_old as self.a!, gradient_old = 0
        dif_old_a = self.a - self.a_old
        dif_old_b = self.b - self.b_old
        dif_old_val = np.array([dif_old_a, dif_old_b])
        dif_old_grad = gradient - self.gradient_old

        """STEP 3: Compute step size via BB method"""
        if True:
            D1_sign = np.dot(np.sign(dif_old_val), np.sign(dif_old_grad))
            D1 = min(max(np.abs(np.dot(dif_old_val, dif_old_grad)), pow(10,5)),
                     pow(10, -5))*D1_sign
            D2 = min(max(np.dot(dif_old_grad, dif_old_grad), pow(10,5)),
                     pow(10,-5))
            #D3 = min(max(np.dot(dif_old_val, dif_old_val), pow(10,5)),
            #         pow(10,-5))
            alpha_1 = D1/D2
            #alpha_2 = D3/D1
            step_size = alpha_1

        """STEP 2: Calculate the new a-value, and return the difference to
        old a-value, (a_{it+1} - a_it)"""
        self.a_old, self.b_old = self.a, self.b
        self.gradient_old = gradient
        self.a = min(max(self.a + gradient[0]*step_size, pow(10,-20)), pow(10,15))
        self.b = min(max(self.b + gradient[1]*step_size, pow(10,-20)), pow(10,15))
        a_dif, b_dif = self.a - self.a_old, self.b - self.b_old
        return [a_dif, b_dif]
//  [python]
def pref_uncertainty_query_loss(self, trajs, act_seqs):
    if len(trajs) < 2:
      raise ValueError

    rew_preds = []
    for traj, act_seq in zip(trajs, act_seqs):
      rew_preds.append(
          self.reward_model.compute_rew_ens_preds_of_transes(
              traj[:-1, :], act_seq, traj[1:, :]).mean(axis=0))
    rew_preds = np.array(
        rew_preds)  # rew_preds[traj_idx][member_idx] = predicted reward
    pref_log_prob_distrns = rew_preds - scipy.misc.logsumexp(
        rew_preds, axis=0
    )  # ... = member_idx's prob of traj_idx being preferred over all other trajs
    pref_prob_distrns = np.exp(pref_log_prob_distrns)
    pref_prob_distrns = np.swapaxes(pref_prob_distrns, 0, 1)
    pref_prob_distrns = pref_prob_distrns[np.newaxis, :, :]
    return -np.mean(utils.np_ens_disag(pref_prob_distrns))
//  [python]
def pref_uncertainty_query_loss(self, trajs, act_seqs):
    if len(trajs) < 2:
      raise ValueError

    rew_preds = []
    for traj, act_seq in zip(trajs, act_seqs):
      rew_preds.append(
          self.reward_model.compute_rew_ens_preds_of_transes(
              traj[:-1, :], act_seq, traj[1:, :]).mean(axis=0))
    rew_preds = np.array(
        rew_preds)  # rew_preds[traj_idx][member_idx] = predicted reward
    pref_log_prob_distrns = rew_preds - scipy.special.logsumexp(
        rew_preds, axis=0
    )  # ... = member_idx's prob of traj_idx being preferred over all other trajs
    pref_prob_distrns = np.exp(pref_log_prob_distrns)
    pref_prob_distrns = np.swapaxes(pref_prob_distrns, 0, 1)
    pref_prob_distrns = pref_prob_distrns[np.newaxis, :, :]
    return -np.mean(utils.np_ens_disag(pref_prob_distrns))
//  [python]
def pairwise_prob(beta):
    """
    Computes the pairwise marginal probabilities.
    Returns a numpy array of size (w-1) x k x k,
    where w is the word length and k is the size of the alphabet.
    
    Parameter:
    - beta, a (w-1) x k x k numpy array of log belief tables.
    """

    return np.exp(beta - scipy.misc.logsumexp(beta, axis=(1,2))[:, np.newaxis, np.newaxis])
//  [python]
def pairwise_prob(beta):
    """
    Computes the pairwise marginal probabilities.
    Returns a numpy array of size (w-1) x k x k,
    where w is the word length and k is the size of the alphabet.
    
    Parameter:
    - beta, a (w-1) x k x k numpy array of log belief tables.
    """

    return np.exp(beta - scipy.special.logsumexp(beta, axis=(1,2))[:, np.newaxis, np.newaxis])
//  [python]
def sum_product_messages(psi):
    """
    Returns the (backward messages, forward messages) tuple.
    Each messages is a (w-2) x k numpy array,
    where w is the word length and k is the size of the alphabet.

    Parameter:
    - psi, a (w-1) x k x k numpy array of clique tree potentials.
    """
    
    # Backward messages
    bwd = []
    prev_msgs = np.zeros(psi.shape[1])
    for clique in psi[:0:-1]:
        msg = scipy.misc.logsumexp(clique + prev_msgs, axis=1)
        bwd.append(msg)
        prev_msgs += msg

    # Forward messages
    fwd = []
    prev_msgs = np.zeros(psi.shape[1])
    for clique in psi[:-1]:
        msg = scipy.misc.logsumexp(clique + prev_msgs[:, np.newaxis], axis=0)
        fwd.append(msg)
        prev_msgs += msg

    return (np.array(bwd), np.array(fwd))
//  [python]
def sum_product_messages(psi):
    """
    Returns the (backward messages, forward messages) tuple.
    Each messages is a (w-2) x k numpy array,
    where w is the word length and k is the size of the alphabet.

    Parameter:
    - psi, a (w-1) x k x k numpy array of clique tree potentials.
    """
    
    # Backward messages
    bwd = []
    prev_msgs = np.zeros(psi.shape[1])
    for clique in psi[:0:-1]:
        msg = scipy.special.logsumexp(clique + prev_msgs, axis=1)
        bwd.append(msg)
        prev_msgs += msg

    # Forward messages
    fwd = []
    prev_msgs = np.zeros(psi.shape[1])
    for clique in psi[:-1]:
        msg = scipy.special.logsumexp(clique + prev_msgs[:, np.newaxis], axis=0)
        fwd.append(msg)
        prev_msgs += msg

    return (np.array(bwd), np.array(fwd))
//  [python]
def normalize_latent(self, log_pygx_unnorm):
        """Normalize the latent variable distribution

        For each sample in the training set, we estimate a probability distribution
        over y_j, each hidden factor. Here we normalize it. (Eq. 7 in paper.)
        This normalization factor is used for estimating TC.

        Parameters
        ----------
        Unnormalized distribution of hidden factors for each training sample.

        Returns
        -------
        p_y_given_x : 3D array, shape (n_hidden, n_samples)
            p(y_j|x^l), the probability distribution over all hidden factors,
            for data samples l = 1...n_samples
        log_z : 2D array, shape (n_hidden, n_samples)
            Point-wise estimate of total correlation explained by each Y_j for each sample,
            used to estimate overall total correlation.

        """
        with np.errstate(under='ignore'):
            log_z = logsumexp(log_pygx_unnorm, axis=0)  # Essential to maintain precision.
            log_pygx = log_pygx_unnorm[1] - log_z
            p_norm = np.exp(log_pygx)
        return p_norm.clip(1e-6, 1 - 1e-6), log_pygx, log_z