code: def rank_alternation(edm_missing, rank, niter=50, print_out=False, edm_true=None):
      
      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
docs: 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.