code: def kruskal_align(U, V, permute_U=False, permute_V=False):
      unrm = [f / np.linalg.norm(f, axis=0) for f in U.factors]
      vnrm = [f / np.linalg.norm(f, axis=0) for f in V.factors]
      sim_matrices = [np.dot(u.T, v) for u, v in zip(unrm, vnrm)]
      cost = 1 - np.mean(np.abs(sim_matrices), axis=0)
      indices = Munkres().compute(cost.copy())
      prmU, prmV = zip(*indices)
      similarity = np.mean(1 - cost[prmU, prmV])
      unmatched_U = list(set(range(U.rank)) - set(prmU))
      unmatched_V = list(set(range(V.rank)) - set(prmV))
      if permute_U and permute_V:
          idx = np.argsort(cost[prmU, prmV])
      elif permute_V:
          idx = np.argsort(prmU)
      elif permute_U:
          idx = np.argsort(prmV)
      else:
          return similarity
      prmU = [prmU[i] for i in idx]
      prmV = [prmV[i] for i in idx]
      if permute_U:
          U.permute(prmU)
      if permute_V:
          V.permute(prmV)
      flips = np.sign([F[prmU, prmV] for F in sim_matrices])
      flips[0] *= np.prod(flips, axis=0)
      if permute_U:
          for i, f in enumerate(flips):
              U.factors[i] *= f
      elif permute_V:
          for i, f in enumerate(flips):
              V.factors[i] *= f
      return similarity