def compute_local_sensitivity_bounds_gnmax()

in research/pate_2018/smooth_sensitivity.py [0:0]


def compute_local_sensitivity_bounds_gnmax(votes, num_teachers, sigma, order):
  """Computes a list of max-LS-at-distance-d for the GNMax mechanism.

  A more efficient implementation of Algorithms 4 and 5 working in time
  O(teachers*classes). A naive implementation is O(teachers^2*classes) or worse.

  Args:
    votes: A numpy array of votes.
    num_teachers: Total number of voting teachers.
    sigma: Standard deviation of the Guassian noise.
    order: The Renyi order.

  Returns:
    A numpy array of local sensitivities at distances d, 0 <= d <= num_teachers.
  """

  num_classes = len(votes)  # Called m in the paper.

  logq0 = _compute_logq0(sigma, order)
  logq1 = _compute_logq1(sigma, order, num_classes)
  logq = pate.compute_logq_gaussian(votes, sigma)
  plateau = _compute_local_sens_gnmax(logq1, sigma, num_classes, order)

  res = np.full(num_teachers, plateau)

  if logq1 <= logq <= logq0:
    return res

  # Invariant: votes is sorted in the non-increasing order.
  votes = sorted(votes, reverse=True)

  res[0] = _compute_local_sens_gnmax(logq, sigma, num_classes, order)
  curr_d = 0

  go_left = logq > logq0  # Otherwise logq < logq1 and we go right.

  # Iterate while the following is true:
  #    1. If we are going left, logq is still larger than logq0 and we may still
  #       increase the gap between votes[0] and votes[1].
  #    2. If we are going right, logq is still smaller than logq1.
  while ((go_left and logq > logq0 and votes[1] > 0) or
         (not go_left and logq < logq1)):
    curr_d += 1
    if go_left:  # Try decreasing logq.
      votes[0] += 1
      votes[1] -= 1
      idx = 1
      # Restore the invariant. (Can be implemented more efficiently by keeping
      # track of the range of indices equal to votes[1]. Does not seem to matter
      # for the overall running time.)
      while idx < len(votes) - 1 and votes[idx] < votes[idx + 1]:
        votes[idx], votes[idx + 1] = votes[idx + 1], votes[idx]
        idx += 1
    else:  # Go right, i.e., try increasing logq.
      votes[0] -= 1
      votes[1] += 1  # The invariant holds since otherwise logq >= logq1.

    logq = pate.compute_logq_gaussian(votes, sigma)
    res[curr_d] = _compute_local_sens_gnmax(logq, sigma, num_classes, order)

  return res