jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def probDiffs(readsf, pos_ascf, pos_allf, chrm, fout_prob, fout_idDiff, fout_pshap, fout_idhap):

    reads=pd.read_csv(readsf, sep=',',index_col=0,header=0)
    pos_asc=np.loadtxt(pos_ascf,dtype='float', delimiter = ",")
    pos_all=np.loadtxt(pos_allf,dtype='float', delimiter = ",")

    l=int(np.shape(reads)[1])

    reads['chrm']=chrm
    reads['pos']=pos_all

    xx=reads['pos'].isin(pos_asc)

    reads=reads.loc[xx,:]
    reads.reset_index(drop=True, inplace=True)


    cols=list(map(str, list(range(l))))
    r1=reads[cols].to_numpy()
    r3=np.array(np.hsplit(r1,int(np.shape(r1)[1]/4)))


    p_ae=r3[:,:,0]
    p_de=r3[:,:,1]
    p_ac=r3[:,:,2]
    p_dc=r3[:,:,3]


    der= p_de + p_dc
    anc= p_ae + p_ac

    total=der+anc

    prob_ar= der / total


    idDiff=scp.comb(der,1) * scp.comb(anc,1)/ scp.comb(total, 2)



    pshap=np.random.binomial(1,prob_ar)
    pshap[pshap<0]=-9

    idhap=np.random.binomial(1, idDiff)
    idhap[total<2]=-9

    prob_ar[total==0]=-9
    idDiff[total<2]=-9

    np.savetxt(fname=fout_prob, X=prob_ar.T, delimiter=",")
    np.savetxt(fname=fout_idDiff, X=idDiff.T, delimiter=",")
    np.savetxt(fname=fout_pshap, X=pshap.T, delimiter=",")
    np.savetxt(fname=fout_idhap, X=idhap.T, delimiter=",")
//  [python]
def get_sparse_sol_via_brute_force(self, k):
        """Get sparse solution through brute force

        Args:
            k (int): cardinality of the final sparse solution
        """

        # enumerate all possible support sets
        best_loss = self.loss
        best_supp_str = self.fixed_supp_mask.tobytes()

        total_num_combinations = s.comb(sum(self.allowed_supp_mask) - sum(self.fixed_supp_mask), k - sum(self.fixed_supp_mask), exact=True)
        total_supp_mask = np.zeros((total_num_combinations, self.p), dtype=bool)
        total_supp_mask[:, self.fixed_supp_mask] = True

        additional_fixed_supp_masks = combinations(list(np.where(np.logical_xor(self.allowed_supp_mask, self.fixed_supp_mask))[0]), k - sum(self.fixed_supp_mask))

        for i, additional_fixed_supp_mask in enumerate(additional_fixed_supp_masks):
            total_supp_mask[i, list(additional_fixed_supp_mask)] = True
            tmp_support_str = total_supp_mask[i].tobytes()
            if tmp_support_str in self.saved_solution:
                betas_on_supp_tmp, r_on_supp_tmp, loss_tmp = self.saved_solution[tmp_support_str]
            else:
                betas_on_supp_tmp, r_on_supp_tmp, loss_tmp = self.finetune_on_current_support(total_supp_mask[i])
                if len(self.saved_solution) < self.max_saved_solutions:
                    self.saved_solution[tmp_support_str] = (betas_on_supp_tmp, r_on_supp_tmp, loss_tmp)

            if loss_tmp < best_loss:
                best_loss = loss_tmp
                best_supp_str = tmp_support_str
        
        self.betas.fill(0)
        best_betas_on_supp, self.r, self.loss = self.finetune_on_current_support(np.frombuffer(best_supp_str, dtype=bool))
        self.betas[np.frombuffer(best_supp_str, dtype=bool)] = best_betas_on_supp
//  [python]
def get_sparse_sol_via_brute_force(self, k):
        """Get sparse solution through brute force

        Args:
            k (int): cardinality of the final sparse solution
        """

        # enumerate all possible support sets
        best_loss = self.loss
        best_supp_str = self.fixed_supp_mask.tobytes()

        total_num_combinations = scipy.special.comb(sum(self.allowed_supp_mask) - sum(self.fixed_supp_mask), k - sum(self.fixed_supp_mask), exact=True)
        total_supp_mask = np.zeros((total_num_combinations, self.p), dtype=bool)
        total_supp_mask[:, self.fixed_supp_mask] = True

        additional_fixed_supp_masks = combinations(list(np.where(np.logical_xor(self.allowed_supp_mask, self.fixed_supp_mask))[0]), k - sum(self.fixed_supp_mask))

        for i, additional_fixed_supp_mask in enumerate(additional_fixed_supp_masks):
            total_supp_mask[i, list(additional_fixed_supp_mask)] = True
            tmp_support_str = total_supp_mask[i].tobytes()
            if tmp_support_str in self.saved_solution:
                betas_on_supp_tmp, r_on_supp_tmp, loss_tmp = self.saved_solution[tmp_support_str]
            else:
                betas_on_supp_tmp, r_on_supp_tmp, loss_tmp = self.finetune_on_current_support(total_supp_mask[i])
                if len(self.saved_solution) < self.max_saved_solutions:
                    self.saved_solution[tmp_support_str] = (betas_on_supp_tmp, r_on_supp_tmp, loss_tmp)

            if loss_tmp < best_loss:
                best_loss = loss_tmp
                best_supp_str = tmp_support_str
        
        self.betas.fill(0)
        best_betas_on_supp, self.r, self.loss = self.finetune_on_current_support(np.frombuffer(best_supp_str, dtype=bool))
        self.betas[np.frombuffer(best_supp_str, dtype=bool)] = best_betas_on_supp
//  [python]
def get_combinatorial(n):
    natoms = n * n * 2
    return s.comb(natoms, natoms / 2)
//  [python]
def get_combinatorial(n):
    natoms = n * n * 2
    return scs.comb(natoms, natoms / 2)
//  [python]
def shapley_coefficients(n):
    out = np.zeros(n)
    for i in range(n):
        out[i] = 1 / (n * s.comb(n-1,i))
    return out
//  [python]
def shapley_coefficients(n):
    out = np.zeros(n)
    for i in range(n):
        out[i] = 1 / (n * scipy.special.comb(n-1,i))
    return out
//  [python]
def generalized_sekiguchi(fb, order_low_pass, order_high_pass):
    """Generalized Sekiguchi Filter

    Parameters
    ----------
    fb : float
        Blending frequency in Hz.
    order_low_pass : int
        Order of roll-off of the low-pass filter
    order_high_pass : int
        Order of roll-off of the high-pass filter

    Returns
    -------
    lpf : kontrol.TransferFunction
        The low-pass filter
    hpf : kontrol.TransferFunction
        The high-pass filter
    """
    nl = order_low_pass
    nh = order_high_pass
    den = (scipy.misc.comb(nl+nh-1, np.arange(0, nl+nh))
           * (2*np.pi*fb)**np.arange(0, nl+nh))
    lpf_num = den[nl:]
    lpf = kontrol.TransferFunction(lpf_num, den)
    return lpf, 1-lpf
//  [python]
def generalized_sekiguchi(fb, order_low_pass, order_high_pass):
    """Generalized Sekiguchi Filter

    Parameters
    ----------
    fb : float
        Blending frequency in Hz.
    order_low_pass : int
        Order of roll-off of the low-pass filter
    order_high_pass : int
        Order of roll-off of the high-pass filter

    Returns
    -------
    lpf : kontrol.TransferFunction
        The low-pass filter
    hpf : kontrol.TransferFunction
        The high-pass filter
    """
    nl = order_low_pass
    nh = order_high_pass
    den = (scipy.special.comb(nl+nh-1, np.arange(0, nl+nh))
           * (2*np.pi*fb)**np.arange(0, nl+nh))
    lpf_num = den[nl:]
    lpf = kontrol.TransferFunction(lpf_num, den)
    return lpf, 1-lpf
//  [python]
def unbias_miracle(k, epsilon, number_candidates, z, n, normalization=1):
  """Get the unbiased estimate for miracle.

  Args:
    k: The dimension of the one-hot encoded x.
    epsilon: The privacy parameter epsilon.
    number_candidates: The number of candidates to be sampled.
    z: The privatized data.
    n: The number of users.
    normalization: Indicator whether to clip and normalize or project on the
      simplex

  Returns:
    p_estimate: The unbiased estimate for miracle
  """
  d = int(np.ceil(k / (np.exp(epsilon) + 1)))
  c1 = np.exp(epsilon) / (
      s.comb(k - 1, d - 1) * np.exp(epsilon) + s.comb(k - 1, d))
  c2 = 1 / (s.comb(k - 1, d - 1) * np.exp(epsilon) + s.comb(k - 1, d))
  beta = np.array(range(number_candidates + 1)) / number_candidates
  # The probability with which you choose a candidate inside the cap.
  pi_in = 1 / number_candidates * (c1 / (beta * c1 + (1 - beta) * c2))
  expectation = np.sum(
      stats.binom.pmf(range(number_candidates + 1), number_candidates, d / k) *
      range(number_candidates + 1) * pi_in)
  b_hat = (d - expectation) / (k - 1)
  m_hat = k * expectation / (k - 1) - d / (k - 1)

  p_estimate = (1.0 * np.sum(z, axis=0) / (n * m_hat)) - b_hat / m_hat

  if normalization == 0:
    # Clip and normalize.
    p_estimate = ss.probability_normalize(p_estimate)
  elif normalization == 1:
    # Project on the simplex.
    p_estimate = ss.probability_project_simplex(p_estimate)
  return p_estimate
//  [python]
def unbias_miracle(k, epsilon, number_candidates, z, n, normalization=1):
  """Get the unbiased estimate for miracle.

  Args:
    k: The dimension of the one-hot encoded x.
    epsilon: The privacy parameter epsilon.
    number_candidates: The number of candidates to be sampled.
    z: The privatized data.
    n: The number of users.
    normalization: Indicator whether to clip and normalize or project on the
      simplex

  Returns:
    p_estimate: The unbiased estimate for miracle
  """
  d = int(np.ceil(k / (np.exp(epsilon) + 1)))
  c1 = np.exp(epsilon) / (
      sc.comb(k - 1, d - 1) * np.exp(epsilon) + sc.comb(k - 1, d))
  c2 = 1 / (sc.comb(k - 1, d - 1) * np.exp(epsilon) + sc.comb(k - 1, d))
  beta = np.array(range(number_candidates + 1)) / number_candidates
  # The probability with which you choose a candidate inside the cap.
  pi_in = 1 / number_candidates * (c1 / (beta * c1 + (1 - beta) * c2))
  expectation = np.sum(
      stats.binom.pmf(range(number_candidates + 1), number_candidates, d / k) *
      range(number_candidates + 1) * pi_in)
  b_hat = (d - expectation) / (k - 1)
  m_hat = k * expectation / (k - 1) - d / (k - 1)

  p_estimate = (1.0 * np.sum(z, axis=0) / (n * m_hat)) - b_hat / m_hat

  if normalization == 0:
    # Clip and normalize.
    p_estimate = ss.probability_normalize(p_estimate)
  elif normalization == 1:
    # Project on the simplex.
    p_estimate = ss.probability_project_simplex(p_estimate)
  return p_estimate
//  [python]
def multiple_comparisons_correction(n_algorithms,
                                    alpha,
                                    method='benjamini-yekutieli'):
  """Compute corrected p-value thresholds to account for multiple comparisons.

  Types of correction methods:
  - Benjamini-Yekutieli correction:
    https://en.wikipedia.org/wiki/False_discovery_rate#Benjamini%E2%80%93Yekutieli_procedure
  - Bonferroni correction:
    https://en.wikipedia.org/wiki/Bonferroni_correction

  Args:
    n_algorithms: Number of algorithms.
    alpha: Uncorrected p-value threshold.
    method: 'benjamini-yekutieli' or 'bonferroni'

  Returns:
    If method == 'benjamini-yekutieli':
      A list of float values with length (number of possible pairs of
      algorithms), i.e. length (number of possible comparisons).
      The i'th float is the corrected p-value threshold for the pair of
      algorithms with the i'th largest uncorrected p-value.
    If method == 'bonferroni':
      A float value for the corrected p-value threshold.
  """
  n_algo_pairs = int(s.comb(n_algorithms, 2))

  if method == 'benjamini-yekutieli':
    c = 1
    pthresh_corrected = [1 / n_algo_pairs * alpha]
    for i in range(2, n_algo_pairs + 1):
      c += 1 / i
      pthresh_corrected.append(i / (n_algo_pairs * c) * alpha)

  elif method == 'bonferroni':
    pthresh_corrected = alpha / n_algo_pairs

  else:
    raise ValueError('Invalid multiple comparisons correction method: %s' %
                     method)

  logging.info('Corrected p-value thresholds: %s', pthresh_corrected)
  return pthresh_corrected
//  [python]
def multiple_comparisons_correction(n_algorithms,
                                    alpha,
                                    method='benjamini-yekutieli'):
  """Compute corrected p-value thresholds to account for multiple comparisons.

  Types of correction methods:
  - Benjamini-Yekutieli correction:
    https://en.wikipedia.org/wiki/False_discovery_rate#Benjamini%E2%80%93Yekutieli_procedure
  - Bonferroni correction:
    https://en.wikipedia.org/wiki/Bonferroni_correction

  Args:
    n_algorithms: Number of algorithms.
    alpha: Uncorrected p-value threshold.
    method: 'benjamini-yekutieli' or 'bonferroni'

  Returns:
    If method == 'benjamini-yekutieli':
      A list of float values with length (number of possible pairs of
      algorithms), i.e. length (number of possible comparisons).
      The i'th float is the corrected p-value threshold for the pair of
      algorithms with the i'th largest uncorrected p-value.
    If method == 'bonferroni':
      A float value for the corrected p-value threshold.
  """
  n_algo_pairs = int(scipy.special.comb(n_algorithms, 2))

  if method == 'benjamini-yekutieli':
    c = 1
    pthresh_corrected = [1 / n_algo_pairs * alpha]
    for i in range(2, n_algo_pairs + 1):
      c += 1 / i
      pthresh_corrected.append(i / (n_algo_pairs * c) * alpha)

  elif method == 'bonferroni':
    pthresh_corrected = alpha / n_algo_pairs

  else:
    raise ValueError('Invalid multiple comparisons correction method: %s' %
                     method)

  logging.info('Corrected p-value thresholds: %s', pthresh_corrected)
  return pthresh_corrected
//  [python]
def bernstein_poly(i, n, t):
# The Bernstein polynomial of n, i as a function of t
    return s.comb(n, i) * ( t**(n-i) ) * (1 - t)**i
//  [python]
def bernstein_poly(i, n, t):
# The Bernstein polynomial of n, i as a function of t
    return comb(n, i) * ( t**(n-i) ) * (1 - t)**i
//  [python]
def admixture_operator(n_node, p):
    # axis0=n_from_parent, axis1=der_from_parent, axis2=der_in_parent
    der_in_parent = np.tile(np.arange(n_node + 1), (n_node + 1, n_node + 1, 1))
    n_from_parent = np.transpose(der_in_parent, [2, 0, 1])
    der_from_parent = np.transpose(der_in_parent, [0, 2, 1])

    anc_in_parent = n_node - der_in_parent
    anc_from_parent = n_from_parent - der_from_parent

    x = s.comb(der_in_parent, der_from_parent) * s.comb(
        anc_in_parent, anc_from_parent) / s.comb(n_node, n_from_parent)
    # rearrange so axis0=1, axis1=der_in_parent, axis2=der_from_parent, axis3=n_from_parent
    x = np.transpose(x)
    x = np.reshape(x, [1] + list(x.shape))

    n = np.arange(n_node+1)
    B = s.comb(n_node, n)

    # the two arrays to convolve_sum_axes
    x1 = (x * B * ((1-p)**n) * (p**(n[::-1])))
    x2 = x[:, :, :, ::-1]

    # reduce array size; approximate low probability events with 0
    mu = n_node * (1-p)
    sigma = np.sqrt(n_node * p * (1-p))
    n_sd = 4
    lower = np.max((0, np.floor(mu - n_sd*sigma)))
    upper = np.min((n_node, np.ceil(mu + n_sd*sigma))) + 1
    lower, upper = int(lower), int(upper)

    ##x1 = x1[:,:,:upper,lower:upper]
    ##x2 = x2[:,:,:(n_node-lower+1),lower:upper]

    ret = convolve_sum_axes(x1, x2)
    # axis0=der_in_parent1, axis1=der_in_parent2, axis2=der_in_child
    ret = np.reshape(ret, ret.shape[1:])
    if ret.shape[2] < (n_node+1):
        ret = np.pad(ret, [(0,0),(0,0),(0,n_node+1-ret.shape[2])], "constant")
    return ret[:, :, :(n_node+1)]
//  [python]
def admixture_operator(n_node, p):
    # axis0=n_from_parent, axis1=der_from_parent, axis2=der_in_parent
    der_in_parent = np.tile(np.arange(n_node + 1), (n_node + 1, n_node + 1, 1))
    n_from_parent = np.transpose(der_in_parent, [2, 0, 1])
    der_from_parent = np.transpose(der_in_parent, [0, 2, 1])

    anc_in_parent = n_node - der_in_parent
    anc_from_parent = n_from_parent - der_from_parent

    x = comb(der_in_parent, der_from_parent) * comb(
        anc_in_parent, anc_from_parent) / comb(n_node, n_from_parent)
    # rearrange so axis0=1, axis1=der_in_parent, axis2=der_from_parent, axis3=n_from_parent
    x = np.transpose(x)
    x = np.reshape(x, [1] + list(x.shape))

    n = np.arange(n_node+1)
    B = comb(n_node, n)

    # the two arrays to convolve_sum_axes
    x1 = (x * B * ((1-p)**n) * (p**(n[::-1])))
    x2 = x[:, :, :, ::-1]

    # reduce array size; approximate low probability events with 0
    mu = n_node * (1-p)
    sigma = np.sqrt(n_node * p * (1-p))
    n_sd = 4
    lower = np.max((0, np.floor(mu - n_sd*sigma)))
    upper = np.min((n_node, np.ceil(mu + n_sd*sigma))) + 1
    lower, upper = int(lower), int(upper)

    ##x1 = x1[:,:,:upper,lower:upper]
    ##x2 = x2[:,:,:(n_node-lower+1),lower:upper]

    ret = convolve_sum_axes(x1, x2)
    # axis0=der_in_parent1, axis1=der_in_parent2, axis2=der_in_child
    ret = np.reshape(ret, ret.shape[1:])
    if ret.shape[2] < (n_node+1):
        ret = np.pad(ret, [(0,0),(0,0),(0,n_node+1-ret.shape[2])], "constant")
    return ret[:, :, :(n_node+1)]
//  [python]
def get_accuracy(series_groundtruth, series_parsedlog, debug=False):
    series_groundtruth_valuecounts = series_groundtruth.value_counts()
    real_pairs = 0
    for count in series_groundtruth_valuecounts:
        if count > 1:
            real_pairs += s.comb(count, 2)

    series_parsedlog_valuecounts = series_parsedlog.value_counts()
    parsed_pairs = 0
    for count in series_parsedlog_valuecounts:
        if count > 1:
            parsed_pairs += s.comb(count, 2)

    accurate_pairs = 0
    accurate_events = 0  # determine how many lines are correctly parsed
    for parsed_eventId in series_parsedlog_valuecounts.index:
        logIds = series_parsedlog[series_parsedlog == parsed_eventId].index
        series_groundtruth_logId_valuecounts = series_groundtruth[logIds].value_counts()
        error_eventIds = (parsed_eventId, series_groundtruth_logId_valuecounts.index.tolist())
        error = True
        if series_groundtruth_logId_valuecounts.size == 1:
            groundtruth_eventId = series_groundtruth_logId_valuecounts.index[0]
            if logIds.size == series_groundtruth[series_groundtruth == groundtruth_eventId].size:
                accurate_events += logIds.size
                error = False
        if error and debug:
            print('(parsed_eventId, groundtruth_eventId) =', error_eventIds, 'failed', logIds.size, 'messages')
        for count in series_groundtruth_logId_valuecounts:
            if count > 1:
                accurate_pairs += s.comb(count, 2)

    precision = float(accurate_pairs) / parsed_pairs
    recall = float(accurate_pairs) / real_pairs
    f_measure = 2 * precision * recall / (precision + recall)
    accuracy = float(accurate_events) / series_groundtruth.size
    return precision, recall, f_measure, accuracy
//  [python]
def get_accuracy(series_groundtruth, series_parsedlog, debug=False):
    series_groundtruth_valuecounts = series_groundtruth.value_counts()
    real_pairs = 0
    for count in series_groundtruth_valuecounts:
        if count > 1:
            real_pairs += scipy.special.comb(count, 2)

    series_parsedlog_valuecounts = series_parsedlog.value_counts()
    parsed_pairs = 0
    for count in series_parsedlog_valuecounts:
        if count > 1:
            parsed_pairs += scipy.special.comb(count, 2)

    accurate_pairs = 0
    accurate_events = 0  # determine how many lines are correctly parsed
    for parsed_eventId in series_parsedlog_valuecounts.index:
        logIds = series_parsedlog[series_parsedlog == parsed_eventId].index
        series_groundtruth_logId_valuecounts = series_groundtruth[logIds].value_counts()
        error_eventIds = (parsed_eventId, series_groundtruth_logId_valuecounts.index.tolist())
        error = True
        if series_groundtruth_logId_valuecounts.size == 1:
            groundtruth_eventId = series_groundtruth_logId_valuecounts.index[0]
            if logIds.size == series_groundtruth[series_groundtruth == groundtruth_eventId].size:
                accurate_events += logIds.size
                error = False
        if error and debug:
            print('(parsed_eventId, groundtruth_eventId) =', error_eventIds, 'failed', logIds.size, 'messages')
        for count in series_groundtruth_logId_valuecounts:
            if count > 1:
                accurate_pairs += scipy.special.comb(count, 2)

    precision = float(accurate_pairs) / parsed_pairs
    recall = float(accurate_pairs) / real_pairs
    f_measure = 2 * precision * recall / (precision + recall)
    accuracy = float(accurate_events) / series_groundtruth.size
    return precision, recall, f_measure, accuracy
//  [python]
def unbias_modified_miracle(k,
                            epsilon,
                            number_candidates,
                            z,
                            n,
                            normalization=1):
  """Get the unbiased estimate for modified miracle.

  Args:
    k: The dimension of the one-hot encoded x.
    epsilon: The privacy parameter epsilon.
    number_candidates: The number of candidates to be sampled.
    z: The privatized data.
    n: The number of users.
    normalization: Indicator whether to clip and normalize or project on the
      simplex

  Returns:
    p_estimate: The unbiased estimate for modified miracle
  """
  expected_beta = np.ceil(k / (np.exp(epsilon) + 1)) / k
  d = int(np.ceil(k / (np.exp(epsilon) + 1)))
  c1 = np.exp(epsilon) / (
      s.comb(k - 1, d - 1) * np.exp(epsilon) + s.comb(k - 1, d))
  c2 = 1 / (s.comb(k - 1, d - 1) * np.exp(epsilon) + s.comb(k - 1, d))
  # The fraction of candidates that lie inside the cap.
  beta = np.array(range(number_candidates + 1)) / number_candidates

  pi_in = 1 / number_candidates * (c1 / (beta * c1 + (1 - beta) * c2))
  tilde_pi_in_1 = (
      pi_in * (1 + beta * (np.exp(epsilon) - 1)) / (1 + expected_beta *
                                                    (np.exp(epsilon) - 1)))
  tilde_pi_in_2 = (
      tilde_pi_in_1 * (beta + expected_beta * (np.exp(epsilon) - 1)) /
      (beta * np.exp(epsilon)))
  tilde_pi_in_2[0,] = 0
  indicator = beta <= expected_beta
  # The probability with which you choose a candidate inside the cap.
  tilde_pi_in = indicator * tilde_pi_in_1 + (1 - indicator) * tilde_pi_in_2
  expectation = np.sum(
      stats.binom.pmf(range(number_candidates + 1), number_candidates, d / k) *
      range(number_candidates + 1) * tilde_pi_in)
  b_tilde = (d - expectation) / (k - 1)
  m_tilde = k * expectation / (k - 1) - d / (k - 1)

  p_estimate = (1.0 * np.sum(z, axis=0) / (n * m_tilde)) - b_tilde / m_tilde

  if normalization == 0:
    # Clip and normalize.
    p_estimate = ss.probability_normalize(p_estimate)
  elif normalization == 1:
    # Project on the simplex.
    p_estimate = ss.probability_project_simplex(p_estimate)
  return p_estimate
//  [python]
def unbias_modified_miracle(k,
                            epsilon,
                            number_candidates,
                            z,
                            n,
                            normalization=1):
  """Get the unbiased estimate for modified miracle.

  Args:
    k: The dimension of the one-hot encoded x.
    epsilon: The privacy parameter epsilon.
    number_candidates: The number of candidates to be sampled.
    z: The privatized data.
    n: The number of users.
    normalization: Indicator whether to clip and normalize or project on the
      simplex

  Returns:
    p_estimate: The unbiased estimate for modified miracle
  """
  expected_beta = np.ceil(k / (np.exp(epsilon) + 1)) / k
  d = int(np.ceil(k / (np.exp(epsilon) + 1)))
  c1 = np.exp(epsilon) / (
      sc.comb(k - 1, d - 1) * np.exp(epsilon) + sc.comb(k - 1, d))
  c2 = 1 / (sc.comb(k - 1, d - 1) * np.exp(epsilon) + sc.comb(k - 1, d))
  # The fraction of candidates that lie inside the cap.
  beta = np.array(range(number_candidates + 1)) / number_candidates

  pi_in = 1 / number_candidates * (c1 / (beta * c1 + (1 - beta) * c2))
  tilde_pi_in_1 = (
      pi_in * (1 + beta * (np.exp(epsilon) - 1)) / (1 + expected_beta *
                                                    (np.exp(epsilon) - 1)))
  tilde_pi_in_2 = (
      tilde_pi_in_1 * (beta + expected_beta * (np.exp(epsilon) - 1)) /
      (beta * np.exp(epsilon)))
  tilde_pi_in_2[0,] = 0
  indicator = beta <= expected_beta
  # The probability with which you choose a candidate inside the cap.
  tilde_pi_in = indicator * tilde_pi_in_1 + (1 - indicator) * tilde_pi_in_2
  expectation = np.sum(
      stats.binom.pmf(range(number_candidates + 1), number_candidates, d / k) *
      range(number_candidates + 1) * tilde_pi_in)
  b_tilde = (d - expectation) / (k - 1)
  m_tilde = k * expectation / (k - 1) - d / (k - 1)

  p_estimate = (1.0 * np.sum(z, axis=0) / (n * m_tilde)) - b_tilde / m_tilde

  if normalization == 0:
    # Clip and normalize.
    p_estimate = ss.probability_normalize(p_estimate)
  elif normalization == 1:
    # Project on the simplex.
    p_estimate = ss.probability_project_simplex(p_estimate)
  return p_estimate
//  [python]
def fisher_exact_test(df, label, col_c, col_t):
    columns = [col_c, col_t]

    row_sums = df[columns].sum()
    sum_c = row_sums[col_c]
    sum_t = row_sums[col_t]
    xs = df[df[label] == 1]
    x_c = xs[col_c][0]
    x_t = xs[col_t][0]
    x = x_c + x_t

    factor1 = s.comb(sum_c, x_c)
    factor2 = s.comb(sum_t, x_t)
    denominator = s.comb(sum_c + sum_t, x)
    pvalue = factor1 * factor2 / denominator
    return pvalue
//  [python]
def fisher_exact_test(df, label, col_c, col_t):
    columns = [col_c, col_t]

    row_sums = df[columns].sum()
    sum_c = row_sums[col_c]
    sum_t = row_sums[col_t]
    xs = df[df[label] == 1]
    x_c = xs[col_c][0]
    x_t = xs[col_t][0]
    x = x_c + x_t

    factor1 = special.comb(sum_c, x_c)
    factor2 = special.comb(sum_t, x_t)
    denominator = special.comb(sum_c + sum_t, x)
    pvalue = factor1 * factor2 / denominator
    return pvalue
//  [python]
def nchoosek(self,*args,**dargs) : return scipy.special.comb(*args,**dargs)
//  [python]
def ComputeHammingCDF(len_target, temprature, vocab):
    max_edits = len_target + 1  # we allow between 0 and len_target subs
    a = np.zeros(max_edits)
    for n_subs in range(max_edits):
        count_n_subs = []
        tot_edits = s.comb(len_target, n_subs)
        a[n_subs] = np.log(tot_edits) + n_subs * np.log(
            len(vocab) - 1
        )  # number of sequences: tot_edits * (N-1) ^ n_subs
        a[n_subs] += -n_subs / float(temprature) * np.log(
            len(vocab) - 1
        ) - n_subs / float(
            temprature
        )  # tot_edits * (N-1) ^ n_subs * ((N-1)e) ^ (-n_subs / T)
    p_subs = a - np.max(a)
    p_subs = np.exp(p_subs)
    p_subs /= np.sum(p_subs)
    p_hamming_cdf = np.cumsum(p_subs)
    return p_hamming_cdf
//  [python]
def ComputeHammingCDF(len_target, temprature, vocab):
    max_edits = len_target + 1  # we allow between 0 and len_target subs
    a = np.zeros(max_edits)
    for n_subs in range(max_edits):
        count_n_subs = []
        tot_edits = misc.comb(len_target, n_subs)
        a[n_subs] = np.log(tot_edits) + n_subs * np.log(
            len(vocab) - 1
        )  # number of sequences: tot_edits * (N-1) ^ n_subs
        a[n_subs] += -n_subs / float(temprature) * np.log(
            len(vocab) - 1
        ) - n_subs / float(
            temprature
        )  # tot_edits * (N-1) ^ n_subs * ((N-1)e) ^ (-n_subs / T)
    p_subs = a - np.max(a)
    p_subs = np.exp(p_subs)
    p_subs /= np.sum(p_subs)
    p_hamming_cdf = np.cumsum(p_subs)
    return p_hamming_cdf
//  [python]
def barnard_test_pdf(df, label, col_c, col_t, pi):
    columns = [col_c, col_t]

    row_sums = df[columns].sum()
    sum_c = row_sums[col_c]
    sum_t = row_sums[col_t]
    pis = (df[df[label] == 1] / row_sums).loc[:, columns]
    pi_c = pis[col_c][0]
    pi_t = pis[col_t][0]
    x_c = pi_c * sum_c
    x_t = pi_t * sum_t

    factor_c = s.comb(sum_c, x_c)
    factor_t = s.comb(sum_t, x_t)
    factor1 = pi ** (x_c + x_t)
    factor2 = (1.0 - pi) ** (sum_c + sum_t - x_c - x_t)
    pdf = factor_c * factor_t * factor1 * factor2
    return pdf
//  [python]
def barnard_test_pdf(df, label, col_c, col_t, pi):
    columns = [col_c, col_t]

    row_sums = df[columns].sum()
    sum_c = row_sums[col_c]
    sum_t = row_sums[col_t]
    pis = (df[df[label] == 1] / row_sums).loc[:, columns]
    pi_c = pis[col_c][0]
    pi_t = pis[col_t][0]
    x_c = pi_c * sum_c
    x_t = pi_t * sum_t

    factor_c = special.comb(sum_c, x_c)
    factor_t = special.comb(sum_t, x_t)
    factor1 = pi ** (x_c + x_t)
    factor2 = (1.0 - pi) ** (sum_c + sum_t - x_c - x_t)
    pdf = factor_c * factor_t * factor1 * factor2
    return pdf
//  [python]
def get_bezier_parameters(X, Y, degree=4):
    """ Least square bezier fit using penrose pseudoinverse.

    Parameters:

    X: array of x data.
    Y: array of y data. Y[0] is the y point for X[0].
    degree: degree of the Bézier curve. 2 for quadratic, 3 for cubic.

    Based on https://stackoverflow.com/questions/12643079/b%C3%A9zier-curve-fitting-with-scipy
    and probably on the 1998 thesis by Tim Andrew Pastva, "Bézier Curve Fitting".
    """
    if degree < 1:
        raise ValueError('degree must be 1 or greater.')

    if len(X) != len(Y):
        raise ValueError('X and Y must be of the same length.')

    if len(X) < degree + 1:
        raise ValueError('Not enough points.')

    def bpoly(n, t, k):
        """ Bernstein polynomial when a = 0 and b = 1. """
        return t ** k * (1 - t) ** (n - k) * s.comb(n, k)
        #return comb(n, i) * ( t**(n-i) ) * (1 - t)**i

    def bmatrix(T):
        """ Bernstein matrix for Bézier curves. """
        return np.matrix([[bpoly(degree, t, k) for k in range(degree + 1)] for t in T])

    def least_square_fit(points, M):
        M_ = np.linalg.pinv(M)
        return M_ * points

    T = np.linspace(0, 1, len(X))
    M = bmatrix(T)
    points = np.array(list(zip(X, Y)))
    
    final = least_square_fit(points, M).tolist()
    final[0] = [X[0], Y[0]]
    final[len(final)-1] = [X[len(X)-1], Y[len(Y)-1]]

    return final
//  [python]
def get_bezier_parameters(X, Y, degree=4):
    """ Least square bezier fit using penrose pseudoinverse.

    Parameters:

    X: array of x data.
    Y: array of y data. Y[0] is the y point for X[0].
    degree: degree of the Bézier curve. 2 for quadratic, 3 for cubic.

    Based on https://stackoverflow.com/questions/12643079/b%C3%A9zier-curve-fitting-with-scipy
    and probably on the 1998 thesis by Tim Andrew Pastva, "Bézier Curve Fitting".
    """
    if degree < 1:
        raise ValueError('degree must be 1 or greater.')

    if len(X) != len(Y):
        raise ValueError('X and Y must be of the same length.')

    if len(X) < degree + 1:
        raise ValueError('Not enough points.')

    def bpoly(n, t, k):
        """ Bernstein polynomial when a = 0 and b = 1. """
        return t ** k * (1 - t) ** (n - k) * comb(n, k)
        #return comb(n, i) * ( t**(n-i) ) * (1 - t)**i

    def bmatrix(T):
        """ Bernstein matrix for Bézier curves. """
        return np.matrix([[bpoly(degree, t, k) for k in range(degree + 1)] for t in T])

    def least_square_fit(points, M):
        M_ = np.linalg.pinv(M)
        return M_ * points

    T = np.linspace(0, 1, len(X))
    M = bmatrix(T)
    points = np.array(list(zip(X, Y)))
    
    final = least_square_fit(points, M).tolist()
    final[0] = [X[0], Y[0]]
    final[len(final)-1] = [X[len(X)-1], Y[len(Y)-1]]

    return final
//  [python]
def get_molar_fractions(step_size, elements, total=1., return_number_of_molar_fractions=False):
    'Get all molar fractions with the given step size'

    n_elem = len(elements)

    interval = int(total / step_size)
    n_combs = s.comb(n_elem, interval, exact=True, repetition=True)

    if return_number_of_molar_fractions:
        return n_combs

    counts = np.zeros((n_combs, n_elem), dtype=int)

    for i, comb in enumerate(it.combinations_with_replacement(range(n_elem), interval)):
        for j in range(n_elem):
            counts[i, j] = np.count_nonzero(np.array(comb) == j)

    return counts * step_size
//  [python]
def get_molar_fractions(step_size, elements, total=1., return_number_of_molar_fractions=False):
    'Get all molar fractions with the given step size'

    n_elem = len(elements)

    interval = int(total / step_size)
    n_combs = scipy.special.comb(n_elem, interval, exact=True, repetition=True)

    if return_number_of_molar_fractions:
        return n_combs

    counts = np.zeros((n_combs, n_elem), dtype=int)

    for i, comb in enumerate(it.combinations_with_replacement(range(n_elem), interval)):
        for j in range(n_elem):
            counts[i, j] = np.count_nonzero(np.array(comb) == j)

    return counts * step_size
//  [python]
def generate_empty_affinity(n, voters):
    affinity = np.zeros(shape=(s.comb(n, 2, exact=True),))
    args = list(enumerate(itertools.combinations(range(n), 2)))
    args = [(v, k, p) for v in voters for k, p in args]
    return affinity, args
//  [python]
def generate_empty_affinity(n, voters):
    affinity = np.zeros(shape=(scipy.special.comb(n, 2, exact=True),))
    args = list(enumerate(itertools.combinations(range(n), 2)))
    args = [(v, k, p) for v in voters for k, p in args]
    return affinity, args
//  [python]
def shap_exact(j, example, model, y_i):
    def powerset(iterable):
        s = list(iterable)
        return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1))

    n_features = example.shape[0]
    mask = np.zeros(n_features, dtype=np.bool)
    res = 0
    for s in powerset(set(range(n_features)).difference([j])):
        weight = s.comb(n_features - 1, len(s)) * n_features
        mask[:] = 0
        mask[list(s)] = 1
        f_s = model((example * mask).reshape((1, -1)))[:, y_i]
        mask[j] = 1
        f_s_with_i = model((example * mask).reshape((1, -1)))[:, y_i]
        res += (f_s_with_i - f_s) / weight

    return res
//  [python]
def shap_exact(j, example, model, y_i):
    def powerset(iterable):
        s = list(iterable)
        return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1))

    n_features = example.shape[0]
    mask = np.zeros(n_features, dtype=np.bool)
    res = 0
    for s in powerset(set(range(n_features)).difference([j])):
        weight = scipy.special.comb(n_features - 1, len(s)) * n_features
        mask[:] = 0
        mask[list(s)] = 1
        f_s = model((example * mask).reshape((1, -1)))[:, y_i]
        mask[j] = 1
        f_s_with_i = model((example * mask).reshape((1, -1)))[:, y_i]
        res += (f_s_with_i - f_s) / weight

    return res
//  [python]
def p_value_sample(X, Y, A, B, wv, w2i, vocab):
    
    random.seed(10)
    np.random.seed(10)
    all_s_words = {}
    
    assert(len(X) == len(Y))
    length = len(X)
    
    s_orig = s_group(X, Y, A, B,  wv, w2i, vocab, all_s_words) 
    
    num_of_samples = min(1000000, int(scipy.misc.comb(length*2,length)*100))
    print('num of samples', num_of_samples)
    larger = 0
    for i in range(num_of_samples):
        permute = np.random.permutation(X+Y)
        Xi = permute[:length]
        Yi = permute[length:]
        if s_group(Xi, Yi, A, B, space, all_s_words) > s_orig:
            larger += 1
    
    return larger/float(num_of_samples)
//  [python]
def p_value_sample(X, Y, A, B, wv, w2i, vocab):
    
    random.seed(10)
    np.random.seed(10)
    all_s_words = {}
    
    assert(len(X) == len(Y))
    length = len(X)
    
    s_orig = s_group(X, Y, A, B,  wv, w2i, vocab, all_s_words) 
    
    num_of_samples = min(1000000, int(scipy.special.comb(length*2,length)*100))
    print('num of samples', num_of_samples)
    larger = 0
    for i in range(num_of_samples):
        permute = np.random.permutation(X+Y)
        Xi = permute[:length]
        Yi = permute[length:]
        if s_group(Xi, Yi, A, B, space, all_s_words) > s_orig:
            larger += 1
    
    return larger/float(num_of_samples)
//  [python]
def __init__(self, n_states, n_actions, base, raw_dim, observation_space=None, gamma=0.9, alpha=0.2,
                 all_subspace=False, tree_subspace=False, subspace_q_size=10):
        self.q_table = np.zeros((n_states, n_actions))
        self.gamma = gamma
        self.alpha = alpha
        self.n_actions = n_actions
        self.count = np.zeros((n_states, n_actions))
        nvec = observation_space.nvec
        self.n_vars = len(observation_space.nvec)
        self.nvec = nvec
        if all_subspace:
            self.counts = [[np.zeros((var_range, n_actions)) for var_range in nvec],  # 1 var
                           [np.zeros((nvec[0] * nvec[1], n_actions)) for _ in
                            range(s.comb(len(nvec), 2, exact=True))]  # 2 var
                           ]
        if tree_subspace:
            self.counts = [[np.zeros((var_range, n_actions)) for var_range in nvec]]  # 1 var
            for i in range(1, self.n_vars):
                self.counts.append([None for _ in range(s.comb(self.n_vars, i + 1, exact=True))])

            self.subspace_q = pq.PQ()
            norm_h, level = 0, 0
            for count_id in range(self.n_vars):
                self.subspace_q.push((norm_h, level, count_id))

            def create_counter_space_mapping():
                from itertools import combinations
                counter_space_mapping = {i: {} for i in range(self.n_vars)}
                space_counter_mapping = {}
                vars = list(range(self.n_vars))
                for level in range(self.n_vars):
                    combs = list(combinations(vars, level + 1))
                    for counter_id, comb in enumerate(combs):
                        counter_space_mapping[level][counter_id] = comb
                        space_counter_mapping[comb] = counter_id
                return counter_space_mapping, space_counter_mapping
            # self.counter_space_mapping[level][counter_id] --> list of variable indices
            self.counter_space_mapping, self.space_counter_mapping = create_counter_space_mapping()
            self.subspace_q_size = subspace_q_size
            self.subspaces = None
        else:
            self.counts = [
                [np.zeros((var_range, n_actions)) for var_range in nvec],  # level 0
                [np.zeros((nvec[0] * nvec[1], n_actions)), np.zeros((np.prod(nvec[4:]), n_actions))],  # level 1
                [np.zeros((np.prod(nvec[:4]), n_actions)), np.zeros((nvec[0] * nvec[1] * np.prod(nvec[4:]), n_actions))],  # level 2
                [np.zeros((n_states, n_actions))]  # level 3
            ]
        self.base, self.raw_dim = base, raw_dim
//  [python]
def __init__(self, n_states, n_actions, base, raw_dim, observation_space=None, gamma=0.9, alpha=0.2,
                 all_subspace=False, tree_subspace=False, subspace_q_size=10):
        self.q_table = np.zeros((n_states, n_actions))
        self.gamma = gamma
        self.alpha = alpha
        self.n_actions = n_actions
        self.count = np.zeros((n_states, n_actions))
        nvec = observation_space.nvec
        self.n_vars = len(observation_space.nvec)
        self.nvec = nvec
        if all_subspace:
            self.counts = [[np.zeros((var_range, n_actions)) for var_range in nvec],  # 1 var
                           [np.zeros((nvec[0] * nvec[1], n_actions)) for _ in
                            range(scipy.special.comb(len(nvec), 2, exact=True))]  # 2 var
                           ]
        if tree_subspace:
            self.counts = [[np.zeros((var_range, n_actions)) for var_range in nvec]]  # 1 var
            for i in range(1, self.n_vars):
                self.counts.append([None for _ in range(scipy.special.comb(self.n_vars, i + 1, exact=True))])

            self.subspace_q = pq.PQ()
            norm_h, level = 0, 0
            for count_id in range(self.n_vars):
                self.subspace_q.push((norm_h, level, count_id))

            def create_counter_space_mapping():
                from itertools import combinations
                counter_space_mapping = {i: {} for i in range(self.n_vars)}
                space_counter_mapping = {}
                vars = list(range(self.n_vars))
                for level in range(self.n_vars):
                    combs = list(combinations(vars, level + 1))
                    for counter_id, comb in enumerate(combs):
                        counter_space_mapping[level][counter_id] = comb
                        space_counter_mapping[comb] = counter_id
                return counter_space_mapping, space_counter_mapping
            # self.counter_space_mapping[level][counter_id] --> list of variable indices
            self.counter_space_mapping, self.space_counter_mapping = create_counter_space_mapping()
            self.subspace_q_size = subspace_q_size
            self.subspaces = None
        else:
            self.counts = [
                [np.zeros((var_range, n_actions)) for var_range in nvec],  # level 0
                [np.zeros((nvec[0] * nvec[1], n_actions)), np.zeros((np.prod(nvec[4:]), n_actions))],  # level 1
                [np.zeros((np.prod(nvec[:4]), n_actions)), np.zeros((nvec[0] * nvec[1] * np.prod(nvec[4:]), n_actions))],  # level 2
                [np.zeros((n_states, n_actions))]  # level 3
            ]
        self.base, self.raw_dim = base, raw_dim
//  [python]
def calculate_certify_K_approx(self, edge_index, edge_weight, y,n0, n, alpha, idx_train, idx_test, num_nodes, fn, k_range):
        # v_range = [0,21]
        k_range = list(range(0,21))
        Ks = []
        total_Deltas = []
        classes, probs, total_num_remain_edges = self.certify_Ber_approx(edge_index, edge_weight, y, n0=n0,n=n,alpha=alpha,idx_train=idx_train, idx_test = idx_test)
        # statistic the remaining edges of the clean nodes: total_num_remain_edges
        # stattistic the degree of the clean nodes
        degrees = pyg_utils.degree(edge_index[0]).cpu().numpy()
        # calculate the Delta for each node by using Monte Carlo Sampling
        
        # for i in range(n): 
        #     '''num_remain_edges'''
        #     num_remain_edges = total_num_remain_edges[i]
        #     for k in v_range:
        #         # calculate the degrees of perturbed version
        #         degrees_perturb = degrees + k
        #         Deltas = 1 - sp_special.comb(degrees,num_remain_edges)/(sp_special.comb(degrees_perturb,num_remain_edges) * (1-self.args.prob)**k)
        #         total_Deltas.append(Deltas)
        for k in k_range:
        # for k in range(v_range[0],v_range[1]):
            print("K={}".format(k))
            Deltas_list = []
            for i in range(n): 
                num_remain_edges = (total_num_remain_edges[i])
                degrees_perturb = (degrees + k)
                Deltas = 1 - s.comb(degrees[idx_test.cpu()],num_remain_edges)/(s.comb(degrees_perturb[idx_test.cpu()],num_remain_edges)) * (1-self.args.prob)**(k) 
                # Deltas = 1 - sp_special.comb(degrees[idx_test.cpu()],num_remain_edges)/(sp_special.comb(degrees_perturb[idx_test.cpu()],num_remain_edges)) * (1-self.args.prob)**(degrees[idx_test.cpu()] - num_remain_edges + k)  * (self.args.prob)**(num_remain_edges) 
                
                
                # Deltas = 1 - sp_special.comb(degrees[idx_test.cpu()],num_remain_edges)/(sp_special.comb(degrees_perturb[idx_test.cpu()],num_remain_edges)) * (self.args.prob**(2*num_remain_edges) * (1-self.args.prob)**(2*degrees[idx_test.cpu()] - 2*num_remain_edges+k))
                Deltas_list.append(Deltas)
            mean_Deltas = np.mean(Deltas_list,axis=0)
            # print(mean_Deltas)
            total_Deltas.append(mean_Deltas)
        return total_Deltas, probs, classes
//  [python]
def calculate_certify_K_approx(self, edge_index, edge_weight, y,n0, n, alpha, idx_train, idx_test, num_nodes, fn, k_range):
        # v_range = [0,21]
        k_range = list(range(0,21))
        Ks = []
        total_Deltas = []
        classes, probs, total_num_remain_edges = self.certify_Ber_approx(edge_index, edge_weight, y, n0=n0,n=n,alpha=alpha,idx_train=idx_train, idx_test = idx_test)
        # statistic the remaining edges of the clean nodes: total_num_remain_edges
        # stattistic the degree of the clean nodes
        degrees = pyg_utils.degree(edge_index[0]).cpu().numpy()
        # calculate the Delta for each node by using Monte Carlo Sampling
        
        # for i in range(n): 
        #     '''num_remain_edges'''
        #     num_remain_edges = total_num_remain_edges[i]
        #     for k in v_range:
        #         # calculate the degrees of perturbed version
        #         degrees_perturb = degrees + k
        #         Deltas = 1 - sp_special.comb(degrees,num_remain_edges)/(sp_special.comb(degrees_perturb,num_remain_edges) * (1-self.args.prob)**k)
        #         total_Deltas.append(Deltas)
        for k in k_range:
        # for k in range(v_range[0],v_range[1]):
            print("K={}".format(k))
            Deltas_list = []
            for i in range(n): 
                num_remain_edges = (total_num_remain_edges[i])
                degrees_perturb = (degrees + k)
                Deltas = 1 - sp_special.comb(degrees[idx_test.cpu()],num_remain_edges)/(sp_special.comb(degrees_perturb[idx_test.cpu()],num_remain_edges)) * (1-self.args.prob)**(k) 
                # Deltas = 1 - sp_special.comb(degrees[idx_test.cpu()],num_remain_edges)/(sp_special.comb(degrees_perturb[idx_test.cpu()],num_remain_edges)) * (1-self.args.prob)**(degrees[idx_test.cpu()] - num_remain_edges + k)  * (self.args.prob)**(num_remain_edges) 
                
                
                # Deltas = 1 - sp_special.comb(degrees[idx_test.cpu()],num_remain_edges)/(sp_special.comb(degrees_perturb[idx_test.cpu()],num_remain_edges)) * (self.args.prob**(2*num_remain_edges) * (1-self.args.prob)**(2*degrees[idx_test.cpu()] - 2*num_remain_edges+k))
                Deltas_list.append(Deltas)
            mean_Deltas = np.mean(Deltas_list,axis=0)
            # print(mean_Deltas)
            total_Deltas.append(mean_Deltas)
        return total_Deltas, probs, classes
//  [python]
def naiive_subspace_detector(point_list, grade,
                             point_cost_threshold=0.1,
                             flat_flag=True, n_tests=1000):
    """
    Given a point cloud extract objects of the given grade
    This only works for conformal algebras at the moment
    """
    layout = point_list[0].layout
    if not layout.isconformal:
        raise ValueError('The naiive_subspace_detector only operates on conformal algebras at the moment')
    einf = layout.einf

    def sample_objects(point_list, grade, n_tests, flat_flag=True):
        if flat_flag:
            n_sample_objects = grade - 1
        else:
            n_sample_objects = grade

        # How many combos are there
        nchoosek = s.comb(len(point_list), n_sample_objects)

        # Get a combo generator
        combos = itertools.combinations(point_list, n_sample_objects)

        # Sample from the generator
        n_samples = int(min([n_tests, nchoosek]))
        sampled_objects_list = random.sample(list(combos), n_samples)

        # Wedge the results
        output_list = []
        for i in range(n_samples):
            print(i, flush=True)
            sampled_objects = sampled_objects_list[i]
            wedged_points = functools.reduce(lambda x, y: x ^ y, sampled_objects)
            output_object = wedged_points
            if flat_flag:
                output_object = output_object ^ einf
            final_object = (output_object).normal()
            output_list.append(final_object)
        return output_list

    def associate_points_with_objects(point_list, object_list, point_cost_threshold=0.1):
        # Check how close all the points are to each object
        point_associations = []
        for obj in object_list:
            this_obj_association = []
            for i, p in enumerate(point_list):
                if abs(obj ^ p) < point_cost_threshold:
                    this_obj_association.append(i)
            point_associations.append(this_obj_association)
        return point_associations

    print('SAMPLING', flush=True)
    object_list = sample_objects(point_list, grade, n_tests, flat_flag=flat_flag)

    print('ASSOCIATING', flush=True)
    point_associations = associate_points_with_objects(point_list,
                                                       object_list,
                                                       point_cost_threshold=point_cost_threshold)
    return point_associations, object_list