r"""Perform the clustering on the input components updating the initial
        guess. The result is available in the member ``self.g``.

        Return the number of iterations at convergence, or None.

        :param eps:

            If relative change of distance between current and last step falls below ``eps``,
            declare convergence:

            .. math::
                0 < \frac{d^t - d^{t-1}}{d^t} < \varepsilon

        :param kill:

             If a component is assigned zero weight (no input components), it is removed.

        :param max_steps:

             Perform a maximum number of update steps.

        :param verbose:

             Output information on progress of algorithm.

def run(self, eps=1e-4, kill=True, max_steps=50, verbose=False):
        r"""Perform the clustering on the input components updating the initial
        guess. The result is available in the member ``self.g``.

        Return the number of iterations at convergence, or None.

        :param eps:

            If relative change of distance between current and last step falls below ``eps``,
            declare convergence:

            .. math::
                0 < \frac{d^t - d^{t-1}}{d^t} < \varepsilon

        :param kill:

             If a component is assigned zero weight (no input components), it is removed.

        :param max_steps:

             Perform a maximum number of update steps.

        :param verbose:

             Output information on progress of algorithm.

        """
        old_distance = np.finfo(np.float64).max
        new_distance = np.finfo(np.float64).max

        if verbose:
            print('Starting hierarchical clustering with %d components.' % len(self.g.components))
        converged = False
        for step in range(1, max_steps + 1):
            self._cleanup(kill, verbose)
            self._regroup()
            self._refit()

            new_distance = self._distance()
            assert new_distance >= 0, 'Found non-positive distance %d' % new_distance

            if verbose:
                print('Distance in step %d: %g' % (step, new_distance))
            if new_distance == old_distance:
                converged = True
                if verbose:
                    print('Exact minimum found after %d steps' % step)
                break

            rel_change = (old_distance - new_distance) / old_distance
            assert not (rel_change < -1e-13), 'distance increased'

            if rel_change < eps and not converged and step > 0:
                converged = True
                if verbose and new_distance != old_distance:
                    print('Close enough to local minimum after %d steps' % step)
                break

            # save distance for comparison in next step
            old_distance = new_distance

        self._cleanup(kill, verbose)
        if verbose:
            print('%d components remain.' % len(self.g.components))

        if converged:
            return step