def compute_mi_by_probs(probs, j_probs, log_j_probs):
    prod_probs = np.zeros((probs.shape[0], probs.shape[0], 2, 2))
    prod_probs[:, :, 0, 0] = np.outer(probs[:, 0], probs[:, 0])
    prod_probs[:, :, 0, 1] = np.outer(probs[:, 0], probs[:, 1])
    prod_probs[:, :, 1, 0] = np.outer(probs[:, 1], probs[:, 0])
    prod_probs[:, :, 1, 1] = np.outer(probs[:, 1], probs[:, 1])
    mut_info = np.sum((j_probs * (log_j_probs - np.log(prod_probs))), axis=(2, 3))
    np.fill_diagonal(mut_info, 0)
    return mut_info
def compute_mi_by_probs(probs, j_probs, log_j_probs):
    prod_probs = np.zeros((probs.shape[0], probs.shape[0], 2, 2))
    prod_probs[:, :, 0, 0] = np.outer(probs[:, 0], probs[:, 0])
    prod_probs[:, :, 0, 1] = np.outer(probs[:, 0], probs[:, 1])
    prod_probs[:, :, 1, 0] = np.outer(probs[:, 1], probs[:, 0])
    prod_probs[:, :, 1, 1] = np.outer(probs[:, 1], probs[:, 1])
    mut_info = np.sum((j_probs * (log_j_probs - np.log(prod_probs))), axis=(2, 3))
    np.fill_diagonal(mut_info, 0)
    return mut_info