code: def fit(self, X, ranks, replicates=1, verbose=True):
          if not isinstance(ranks, collections.Iterable):
              ranks = (ranks,)
          for r in ranks:
              if r not in self.results:
                  self.results[r] = []
              if verbose:
                  itr = trange(replicates,
                               desc='Fitting rank-{} models'.format(r),
                               leave=False)
              else:
                  itr = range(replicates)
              for i in itr:
                  model_fit = self._fit_method(X, r, **self._fit_options)
                  self.results[r].append(model_fit)
              if verbose:
                  min_obj = min([res.obj for res in self.results[r]])
                  max_obj = max([res.obj for res in self.results[r]])
                  elapsed = sum([res.total_time for res in self.results[r]])
                  print('Rank-{} models:  min obj, {:.2f};  '
                        'max obj, {:.2f};  time to fit, '
                        '{:.1f}s'.format(r, min_obj, max_obj, elapsed))
          for r in ranks:
              idx = np.argsort([result.obj for result in self.results[r]])
              self.results[r] = [self.results[r][i] for i in idx]
          for i in reversed(range(1, len(ranks))):
              r0, r1 = ranks[i-1], ranks[i]
              U = self.results[r0][0].factors
              V = self.results[r1][0].factors
              kruskal_align(U, V, permute_U=True)
          for r in ranks:
              U = self.results[r][0].factors
              self.results[r][0].similarity = 1.0
              for res in self.results[r][1:]:
                  res.similarity = kruskal_align(U, res.factors, permute_V=True)