docs: def rank_alternation(edm_missing, rank, niter=50, print_out=False, edm_true=None):
      """Complete and denoise EDM using rank alternation.
  
      Iteratively impose rank and strucutre to complete marix entries
  
      :param edm_missing: EDM with 0 where no measurement was taken.
      :param rank: expected rank of complete EDM.
      :param niter: maximum number of iterations.
      :param edm: if given, the relative EDM error is tracked. 
  
      :return: Completed matrix and array of errors (empty if no true edm is given).
      The matrix is of the correct structure, but might not have the right measured entries.
  
      """
      from pylocus.basics import low_rank_approximation
      errs = []
      N = edm_missing.shape[0]
      edm_complete = edm_missing.copy()
      edm_complete[edm_complete == 0] = np.mean(edm_complete[edm_complete > 0])
      for i in range(niter):
          # impose matrix rank
          edm_complete = low_rank_approximation(edm_complete, rank)
  
          # impose known entries
          edm_complete[edm_missing > 0] = edm_missing[edm_missing > 0]
  
          # impose matrix structure
          edm_complete[range(N), range(N)] = 0.0
          edm_complete[edm_complete < 0] = 0.0
          edm_complete = 0.5 * (edm_complete + edm_complete.T)
  
          if edm_true is not None:
              err = np.linalg.norm(edm_complete - edm_true)
              errs.append(err)
      return edm_complete, errs