Repository: phenology/cgc

Issue:
Remove non-Numba-accelerated low-memory version
It does not work with thread parallelism


Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 7dad99c..7082963 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,6 +8,10 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
 [Unreleased]
 ************
 
+Changed
+-------
+* Numerical parameter epsilon is removed, which should lead to some improvement in the algorithm when empty clusters are present
+
 [0.5.0] - 2021-09-23
 ********************
 
diff --git a/cgc/coclustering.py b/cgc/coclustering.py
index cf87462..44cc191 100644
--- a/cgc/coclustering.py
+++ b/cgc/coclustering.py
@@ -51,9 +51,6 @@ class Coclustering(object):
     :type max_iterations: int, optional
     :param nruns: Number of differently-initialized runs.
     :type nruns: int, optional
-    :param epsilon: Numerical parameter, avoids zero arguments in the
-        logarithm that appears in the expression of the objective function.
-    :type epsilon: float, optional
     :param output_filename: Name of the JSON file where to write the results.
     :type output_filename: string, optional
     :param row_clusters_init: Initial row cluster assignment.
@@ -68,7 +65,6 @@ class Coclustering(object):
                  conv_threshold=1.e-5,
                  max_iterations=1,
                  nruns=1,
-                 epsilon=1.e-8,
                  output_filename='',
                  row_clusters_init=None,
                  col_clusters_init=None):
@@ -78,7 +74,6 @@ class Coclustering(object):
         self.conv_threshold = conv_threshold
         self.max_iterations = max_iterations
         self.nruns = nruns
-        self.epsilon = epsilon
         self.output_filename = output_filename
         self.row_clusters_init = row_clusters_init
         self.col_clusters_init = col_clusters_init
@@ -119,10 +114,7 @@ class Coclustering(object):
         self.results.write(filename=self.output_filename)
         return self.results
 
-    def run_with_threads(self,
-                         nthreads=1,
-                         low_memory=False,
-                         numba_jit=False):
+    def run_with_threads(self, nthreads=1, low_memory=False):
         """
         Run the co-clustering using an algorithm based on Numpy plus threading
         (only suitable for local runs).
@@ -130,11 +122,9 @@ class Coclustering(object):
         :param nthreads: Number of threads employed to simultaneously run
             differently-initialized co-clustering analysis.
         :type nthreads: int, optional
-        :param low_memory: If True, use a memory-conservative algorithm.
+        :param low_memory: Make use of a low-memory version of the algorithm
+            with Numba JIT acceleration
         :type low_memory: bool, optional
-        :param numba_jit: If True, and low_memory is True, then use Numba
-                          just-in-time compilation to improve the performance.
-        :type numba_jit: bool, optional
         :return: Co-clustering results.
         :type: cgc.coclustering.CoclusteringResults
         """
@@ -146,9 +136,7 @@ class Coclustering(object):
                                 self.nclusters_col,
                                 self.conv_threshold,
                                 self.max_iterations,
-                                self.epsilon,
                                 low_memory,
-                                numba_jit,
                                 row_clusters_init=self.row_clusters_init,
                                 col_clusters_init=self.col_clusters_init)
                 for _ in range(self.nruns)
@@ -180,10 +168,8 @@ class Coclustering(object):
                 self.nclusters_col,
                 self.conv_threshold,
                 self.max_iterations,
-                self.epsilon,
                 row_clusters_init=self.row_clusters_init,
-                col_clusters_init=self.col_clusters_init
-            )
+                col_clusters_init=self.col_clusters_init)
             logger.info(f'Error = {e}')
             if converged:
                 logger.info(f'Run converged in {niters} iterations')
@@ -202,19 +188,18 @@ class Coclustering(object):
         simultaneously submitted to the scheduler (experimental, discouraged).
         """
         Z = self.client.scatter(self.Z)
-        futures = [self.client.submit(
-                       coclustering_dask.coclustering,
-                       Z,
-                       self.nclusters_row,
-                       self.nclusters_col,
-                       self.conv_threshold,
-                       self.max_iterations,
-                       self.epsilon,
-                       row_clusters_init=self.row_clusters_init,
-                       col_clusters_init=self.col_clusters_init,
-                       run_on_worker=True,
-                       pure=False)
-                   for _ in range(self.nruns)]
+        futures = [
+            self.client.submit(coclustering_dask.coclustering,
+                               Z,
+                               self.nclusters_row,
+                               self.nclusters_col,
+                               self.conv_threshold,
+                               self.max_iterations,
+                               row_clusters_init=self.row_clusters_init,
+                               col_clusters_init=self.col_clusters_init,
+                               run_on_worker=True,
+                               pure=False) for _ in range(self.nruns)
+        ]
         for future, result in dask.distributed.as_completed(futures,
                                                             with_results=True):
             logger.info(f'Retrieving run {self.results.nruns_completed} ..')
diff --git a/cgc/coclustering_dask.py b/cgc/coclustering_dask.py
index 2b5180b..2c32995 100644
--- a/cgc/coclustering_dask.py
+++ b/cgc/coclustering_dask.py
@@ -1,16 +1,13 @@
 import logging
 
-import numpy as np
-
 import dask.array as da
 from dask.distributed import get_client, rejoin, secede
 
 logger = logging.getLogger(__name__)
 
 
-def _distance(Z, Y, epsilon):
+def _distance(Z, Y):
     """ Distance function """
-    Y = Y + epsilon
     # The first term below is equal to one row of: da.dot(da.ones(m, n), Y)
     # with Z.shape = (m, n) and Y.shape = (n, k)
     return Y.sum(axis=0, keepdims=True) - da.matmul(Z, da.log(Y))
@@ -22,12 +19,12 @@ def _initialize_clusters(n_el, n_clusters, chunks=None):
     return da.random.permutation(cluster_idx)
 
 
-def _setup_cluster_matrix(n_clusters, cluster_idx):
+def _setup_cluster_matrix(cluster_labels, cluster_idx):
     """ Set cluster occupation matrix """
-    return da.eye(n_clusters, dtype=np.bool, chunks=n_clusters)[cluster_idx]
+    return da.equal.outer(cluster_idx, cluster_labels)
 
 
-def coclustering(Z, nclusters_row, nclusters_col, errobj, niters, epsilon,
+def coclustering(Z, nclusters_row, nclusters_col, errobj, niters,
                  col_clusters_init=None, row_clusters_init=None,
                  run_on_worker=False):
     """
@@ -43,9 +40,6 @@ def coclustering(Z, nclusters_row, nclusters_col, errobj, niters, epsilon,
     :type errobj: float, optional
     :param niters: Maximum number of iterations.
     :type niters: int, optional
-    :param epsilon: Numerical parameter, avoids zero arguments in the
-        logarithm that appears in the expression of the objective function.
-    :type epsilon: float, optional
     :param row_clusters_init: Initial row cluster assignment.
     :type row_clusters_init: numpy.ndarray or array_like, optional
     :param col_clusters_init: Initial column cluster assignment.
@@ -74,8 +68,6 @@ def coclustering(Z, nclusters_row, nclusters_col, errobj, niters, epsilon,
     s = 0
     converged = False
 
-    Gavg = Z.mean()
-
     while (not converged) & (s < niters):
         logger.debug(f'Iteration # {s} ..')
         # Calculate cluster based averages
@@ -83,22 +75,24 @@ def coclustering(Z, nclusters_row, nclusters_col, errobj, niters, epsilon,
         # originally computed as:  da.dot(da.dot(R.T, da.ones((m, n))), C)
         nel_row_clusters = da.bincount(row_clusters, minlength=nclusters_row)
         nel_col_clusters = da.bincount(col_clusters, minlength=nclusters_col)
+        row_cluster_labels = nel_row_clusters.nonzero()[0].compute()
+        col_cluster_labels = nel_col_clusters.nonzero()[0].compute()
         logger.debug('num of populated clusters: row {}, col {}'.format(
-                        da.sum(nel_row_clusters > 0).compute(),
-                        da.sum(nel_col_clusters > 0).compute()))
-        R = _setup_cluster_matrix(nclusters_row, row_clusters)
-        C = _setup_cluster_matrix(nclusters_col, col_clusters)
-        nel_clusters = da.outer(nel_row_clusters, nel_col_clusters)
-        CoCavg = (da.matmul(da.matmul(R.T, Z), C) + Gavg * epsilon) / \
-                 (nel_clusters + epsilon)
+                        len(row_cluster_labels),
+                        len(col_cluster_labels)))
+        R = _setup_cluster_matrix(row_cluster_labels, row_clusters)
+        C = _setup_cluster_matrix(col_cluster_labels, col_clusters)
+        nel_clusters = da.outer(nel_row_clusters[row_cluster_labels],
+                                nel_col_clusters[col_cluster_labels])
+        CoCavg = da.matmul(da.matmul(R.T, Z), C) / nel_clusters
 
         # Calculate distance based on row approximation
-        d_row = _distance(Z, da.matmul(C, CoCavg.T), epsilon)
+        d_row = _distance(Z, da.matmul(C, CoCavg.T))
         # Assign to best row cluster
         row_clusters = da.argmin(d_row, axis=1)
 
         # Calculate distance based on column approximation
-        d_col = _distance(Z.T, da.matmul(R, CoCavg), epsilon)
+        d_col = _distance(Z.T, da.matmul(R, CoCavg))
         # Assign to best column cluster
         col_clusters = da.argmin(d_col, axis=1)
 
@@ -107,9 +101,11 @@ def coclustering(Z, nclusters_row, nclusters_col, errobj, niters, epsilon,
         minvals = da.min(d_col, axis=1)
         # power 1 divergence, power 2 euclidean
         e = da.sum(da.power(minvals, 1))
-        row_clusters, col_clusters, e = client.persist([row_clusters,
-                                                        col_clusters,
-                                                        e])
+        row_clusters, col_clusters, e = client.persist([
+            da.take(row_cluster_labels, row_clusters),
+            da.take(col_cluster_labels, col_clusters),
+            e
+        ])
         if run_on_worker:
             # this is workaround for e.compute() for a function that runs
             # on a worker with multiple threads
diff --git a/cgc/coclustering_numpy.py b/cgc/coclustering_numpy.py
index 8059058..31a2e4a 100644
--- a/cgc/coclustering_numpy.py
+++ b/cgc/coclustering_numpy.py
@@ -6,43 +6,30 @@ import numba
 logger = logging.getLogger(__name__)
 
 
-def _distance(Z, Y, epsilon):
+def _distance(Z, Y):
     """ Distance function """
-    Y = Y + epsilon
     # The first term below is equal to one row of: da.dot(da.ones(m, n), Y)
     # with Z.shape = (m, n) and Y.shape = (n, k)
     return Y.sum(axis=0, keepdims=True) - np.dot(Z, np.log(Y))
 
 
-def _min_dist(Z, M, CoCavg, epsilon):
+def _min_dist(Z, M, CoCavg):
     Y = np.dot(M, CoCavg)
-    d = _distance(Z, Y, epsilon)
+    d = _distance(Z, Y)
     return np.argmin(d, axis=1), np.min(d, axis=1)
 
 
-def _min_dist_lowmem(Z, clusters, CoCavg, epsilon):
-    m, n = Z.shape
-    l, k = CoCavg.shape
-    Y = CoCavg[clusters] + epsilon
-    sum = Y.sum(axis=0)
-    min_d = np.full(m, np.nan_to_num(np.inf))  # Initialize with largest float
-    clusters_new = np.zeros(m, dtype=np.int)
-    for ir in range(k):
-        # Calculate distance for cluster ir
-        d = sum[ir] - np.dot(Z, np.log(Y[:, ir]))
-        # If distance is smaller then previous assignment, reassign
-        smaller = d < min_d
-        min_d = smaller * d + ~smaller * min_d
-        clusters_new = smaller * ir + ~smaller * clusters_new
-    return clusters_new, min_d
-
-
 @numba.jit(nopython=True, nogil=True, cache=True)
-def _min_dist_numba(Z, clusters, CoCavg, epsilon, max=np.nan_to_num(np.inf)):
+def _min_dist_numba(Z, clusters, labels, CoCavg, max=np.nan_to_num(np.inf)):
     m, n = Z.shape
     l, k = CoCavg.shape
-    Y = CoCavg[clusters] + epsilon
+
+    # Extend the matrix of cluster averages to include empty clusters
+    CoCavg_ext = np.full((np.max(labels) + 1, k), np.nan)
+    CoCavg_ext[labels, :] = CoCavg
+    Y = CoCavg_ext[clusters]
     sum = Y.sum(axis=0)
+
     min_d = np.full(m, max)  # Initialize with largest float
     clusters_new = np.zeros(m, dtype=numba.types.int64)
     for ir in range(k):
@@ -61,43 +48,29 @@ def _initialize_clusters(n_el, n_clusters):
     return np.random.permutation(cluster_idx)
 
 
-def _setup_cluster_matrix(n_clusters, cluster_idx):
+def _setup_cluster_matrix(cluster_labels, cluster_idx):
     """ Set cluster occupation matrix """
-    return np.eye(n_clusters, dtype=np.bool)[cluster_idx]
-
-
-def _cluster_dot(Z, row_clusters, col_clusters, nclusters_row, nclusters_col):
-    """
-    To replace np.dot(np.dot(R.T, Z), C), where R and C are full matrix
-    """
-    product = np.zeros((nclusters_row, nclusters_col))
-    for r in range(nclusters_row):
-        idx_r = np.where(row_clusters == r)[0]
-        for c in range(nclusters_col):
-            idx_c = np.where(col_clusters == c)[0]
-            ir, ic = np.meshgrid(idx_r, idx_c)
-            product[r, c] = Z[ir, ic].sum()
-    return product
+    return np.equal.outer(cluster_idx, cluster_labels)
 
 
 @numba.jit(nopython=True, nogil=True, parallel=True, cache=True)
-def _cluster_dot_numba(Z, row_clusters, col_clusters, nclusters_row,
-                       nclusters_col):
+def _cluster_dot_numba(Z, row_clusters, col_clusters, row_cluster_labels,
+                       col_cluster_labels):
     """
-    To replace np.dot(np.dot(R.T, Z), C), where R and C are full matrix
+    To replace np.dot(np.dot(R.T, Z), C), where R and C are occupation matrices
     """
-    product = np.zeros((nclusters_row, nclusters_col))
-    for r in range(nclusters_row):
-        idx_r = np.where(row_clusters == r)[0]
-        for c in range(nclusters_col):
-            idx_c = np.where(col_clusters == c)[0]
+    product = np.zeros((len(row_cluster_labels), len(col_cluster_labels)))
+    for ircl in range(len(row_cluster_labels)):
+        idx_rcl, = np.where(row_clusters == row_cluster_labels[ircl])
+        for iccl in range(len(col_cluster_labels)):
+            idx_ccl, = np.where(col_clusters == col_cluster_labels[iccl])
 
             prod_rc = 0
-            for idr in idx_r:
-                for idc in idx_c:
+            for idr in idx_rcl:
+                for idc in idx_ccl:
                     prod_rc += Z[idr, idc]
 
-            product[r, c] = prod_rc
+            product[ircl, iccl] = prod_rc
 
     return product
 
@@ -107,9 +80,7 @@ def coclustering(Z,
                  nclusters_col,
                  errobj,
                  niters,
-                 epsilon,
                  low_memory=False,
-                 numba_jit=False,
                  row_clusters_init=None,
                  col_clusters_init=None):
     """
@@ -125,14 +96,9 @@ def coclustering(Z,
     :type errobj: float, optional
     :param niters: Maximum number of iterations.
     :type niters: int, optional
-    :param epsilon: Numerical parameter, avoids zero arguments in the
-        logarithm that appears in the expression of the objective function.
-    :type epsilon: float, optional
-    :param low_memory: Make use of a low-memory version of the algorithm.
+    :param low_memory: Make use of a low-memory version of the algorithm with
+        Numba JIT acceleration
     :type low_memory: bool, optional
-    :param numba_jit: Make use of Numba JIT acceleration (only if low_memory
-        is True).
-    :type numba_jit: bool, optional
     :param row_clusters_init: Initial row cluster assignment.
     :type row_clusters_init: numpy.ndarray or array_like, optional
     :param col_clusters_init: Initial column cluster assignment.
@@ -157,47 +123,39 @@ def coclustering(Z,
     s = 0
     converged = False
 
-    Gavg = Z.mean()
-
     while (not converged) & (s < niters):
         logger.debug(f'Iteration # {s} ..')
         # Calculate cluster based averages
         nel_row_clusters = np.bincount(row_clusters, minlength=nclusters_row)
         nel_col_clusters = np.bincount(col_clusters, minlength=nclusters_col)
+        row_cluster_labels, = nel_row_clusters.nonzero()
+        col_cluster_labels, = nel_col_clusters.nonzero()
         logger.debug('num of populated clusters: row {}, col {}'.format(
-            np.sum(nel_row_clusters > 0), np.sum(nel_col_clusters > 0)))
-        nel_clusters = np.outer(nel_row_clusters, nel_col_clusters)
+            len(row_cluster_labels), len(col_cluster_labels)))
+        nel_clusters = np.outer(nel_row_clusters[row_cluster_labels],
+                                nel_col_clusters[col_cluster_labels])
         if low_memory:
-            if numba_jit:
-                CoCavg = _cluster_dot_numba(Z, row_clusters, col_clusters,
-                                            nclusters_row, nclusters_col)
-            else:
-                CoCavg = _cluster_dot(Z, row_clusters, col_clusters,
-                                      nclusters_row, nclusters_col)
+            CoCavg = _cluster_dot_numba(Z, row_clusters, col_clusters,
+                                        row_cluster_labels, col_cluster_labels)
         else:
-            R = _setup_cluster_matrix(nclusters_row, row_clusters)
-            C = _setup_cluster_matrix(nclusters_col, col_clusters)
+            R = _setup_cluster_matrix(row_cluster_labels, row_clusters)
+            C = _setup_cluster_matrix(col_cluster_labels, col_clusters)
             CoCavg = np.dot(np.dot(R.T, Z), C)
-        CoCavg += Gavg * epsilon
-        CoCavg /= nel_clusters + epsilon
+        CoCavg = CoCavg / nel_clusters
 
         # Calculate distances based on approximation and assign best clusters
         if low_memory:
-            if numba_jit:
-                _row_clusters, _ = _min_dist_numba(Z, col_clusters, CoCavg.T,
-                                                   epsilon)
-                col_clusters, dist = _min_dist_numba(Z.T, row_clusters, CoCavg,
-                                                     epsilon)
-                row_clusters = _row_clusters
-            else:
-                _row_clusters, _ = _min_dist_lowmem(Z, col_clusters, CoCavg.T,
-                                                    epsilon)
-                col_clusters, dist = _min_dist_lowmem(Z.T, row_clusters,
-                                                      CoCavg, epsilon)
-                row_clusters = _row_clusters
+            _row_clusters, _ = _min_dist_numba(Z, col_clusters,
+                                               col_cluster_labels, CoCavg.T)
+            col_clusters, dist = _min_dist_numba(Z.T, row_clusters,
+                                                 row_cluster_labels, CoCavg)
+            row_clusters = _row_clusters
         else:
-            row_clusters, _ = _min_dist(Z, C, CoCavg.T, epsilon)
-            col_clusters, dist = _min_dist(Z.T, R, CoCavg, epsilon)
+            row_clusters, _ = _min_dist(Z, C, CoCavg.T)
+            col_clusters, dist = _min_dist(Z.T, R, CoCavg)
+
+        row_clusters = np.take(row_cluster_labels, row_clusters)
+        col_clusters = np.take(col_cluster_labels, col_clusters)
 
         # Error value (actually just the column components really)
         old_e = e
diff --git a/cgc/kmeans.py b/cgc/kmeans.py
index 958e000..1f7203b 100644
--- a/cgc/kmeans.py
+++ b/cgc/kmeans.py
@@ -146,9 +146,9 @@ class Kmeans(object):
                     -1).tolist()
             logger.warning(
                 "Multiple k values with the same silhouette score: {},"
-                "picking the smallest one: {}".
-                format([self.k_range[i] for i in idx_k_list],
-                       self.k_range[idx_k]))
+                "picking the smallest one: {}".format(
+                    [self.k_range[i] for i in idx_k_list],
+                    self.k_range[idx_k]))
         self.results.measure_list = silhouette_avg_list
         self.results.k_value = self.k_range[idx_k]
         self.kmean_cluster = kmean_cluster_list[idx_k]
@@ -166,10 +166,8 @@ class Kmeans(object):
         # Reshape the centroids of means to the shape of cluster matrix,
         # taking into account non-constructive clusters
         self.results.cl_mean_centroids = np.full(self.nclusters, np.nan)
-        indices = np.meshgrid(
-            *[np.unique(cl) for cl in self.clusters],
-            indexing='ij'
-        )
+        indices = np.meshgrid(*[np.unique(cl) for cl in self.clusters],
+                              indexing='ij')
         mask = np.zeros_like(self.results.cl_mean_centroids, dtype=bool)
         mask[tuple(indices)] = True
         self.results.cl_mean_centroids[mask] = cl_mean_centroids
@@ -213,8 +211,7 @@ class Kmeans(object):
         # Normalize all statistics to [0, 1]
         minimum = self.stat_measures.min(axis=0)
         maximum = self.stat_measures.max(axis=0)
-        self.stat_measures_norm = np.divide((self.stat_measures - minimum),
-                                            (maximum - minimum))
-
-        # Set statistics to zero if all its values are identical (max == min)
-        self.stat_measures_norm[np.isnan(self.stat_measures_norm)] = 0.
+        self.stat_measures_norm = np.divide(
+            (self.stat_measures - minimum), (maximum - minimum),
+            out=np.zeros_like((self.stat_measures)),
+            where=(maximum - minimum) != 0)
diff --git a/cgc/triclustering.py b/cgc/triclustering.py
index 0dceca0..d4623a7 100644
--- a/cgc/triclustering.py
+++ b/cgc/triclustering.py
@@ -56,9 +56,6 @@ class Triclustering(object):
     :type max_iterations: int, optional
     :param nruns: Number of differently-initialized runs.
     :type nruns: int, optional
-    :param epsilon: Numerical parameter, avoids zero arguments in the
-        logarithm that appears in the expression of the objective function.
-    :type epsilon: float, optional
     :param output_filename: Name of the JSON file where to write the results.
     :type output_filename: string, optional
     :param row_clusters_init: Initial row cluster assignment.
@@ -70,7 +67,7 @@ class Triclustering(object):
     """
     def __init__(self, Z, nclusters_row, nclusters_col, nclusters_bnd,
                  conv_threshold=1.e-5, max_iterations=1, nruns=1,
-                 epsilon=1.e-8, output_filename='', row_clusters_init=None,
+                 output_filename='', row_clusters_init=None,
                  col_clusters_init=None, bnd_clusters_init=None):
         # Input parameters -----------------
         self.nclusters_row = nclusters_row
@@ -79,7 +76,6 @@ class Triclustering(object):
         self.conv_threshold = conv_threshold
         self.max_iterations = max_iterations
         self.nruns = nruns
-        self.epsilon = epsilon
         self.output_filename = output_filename
         self.row_clusters_init = row_clusters_init
         self.col_clusters_init = col_clusters_init
@@ -122,7 +118,6 @@ class Triclustering(object):
                                 self.nclusters_bnd,
                                 self.conv_threshold,
                                 self.max_iterations,
-                                self.epsilon,
                                 row_clusters_init=self.row_clusters_init,
                                 col_clusters_init=self.col_clusters_init,
                                 bnd_clusters_init=self.bnd_clusters_init
@@ -173,7 +168,6 @@ class Triclustering(object):
                     self.nclusters_bnd,
                     self.conv_threshold,
                     self.max_iterations,
-                    self.epsilon,
                     row_clusters_init=self.row_clusters_init,
                     col_clusters_init=self.col_clusters_init,
                     bnd_clusters_init=self.bnd_clusters_init
diff --git a/cgc/triclustering_dask.py b/cgc/triclustering_dask.py
index 4fe301f..51ac9f7 100644
--- a/cgc/triclustering_dask.py
+++ b/cgc/triclustering_dask.py
@@ -9,9 +9,8 @@ from dask.distributed import get_client
 logger = logging.getLogger(__name__)
 
 
-def _distance(Z, Y, epsilon):
+def _distance(Z, Y):
     """ Distance function """
-    Y = Y + epsilon
     return Y.sum(axis=(1, 2)) - da.einsum('ijk,ljk->il', Z, da.log(Y))
 
 
@@ -21,14 +20,14 @@ def _initialize_clusters(n_el, n_clusters, chunks=None):
     return da.random.permutation(cluster_idx)
 
 
-def _setup_cluster_matrix(n_clusters, cluster_idx):
+def _setup_cluster_matrix(cluster_labels, cluster_idx):
     """ Set cluster occupation matrix """
-    return da.eye(n_clusters, dtype=np.bool, chunks=n_clusters)[cluster_idx]
+    return da.equal.outer(cluster_idx, cluster_labels)
 
 
 def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
-                  niters, epsilon, row_clusters_init=None,
-                  col_clusters_init=None, bnd_clusters_init=None):
+                  niters, row_clusters_init=None, col_clusters_init=None,
+                  bnd_clusters_init=None):
     """
     Run the tri-clustering analysis, Dask implementation.
 
@@ -45,9 +44,6 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
     :type errobj: float, optional
     :param niters: Maximum number of iterations.
     :type niters: int, optional
-    :param epsilon: Numerical parameter, avoids zero arguments in the
-        logarithm that appears in the expression of the objective function.
-    :type epsilon: float, optional
     :param row_clusters_init: Initial row cluster assignment.
     :type row_clusters_init: numpy.ndarray or array_like, optional
     :param col_clusters_init: Initial column cluster assignment.
@@ -79,42 +75,51 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
     s = 0
     converged = False
 
-    Gavg = Z.mean()
-
     while (not converged) & (s < niters):
         logger.debug(f'Iteration # {s} ..')
         # Calculate number of elements in each tri-cluster
         nel_row_clusters = da.bincount(row_clusters, minlength=nclusters_row)
         nel_col_clusters = da.bincount(col_clusters, minlength=nclusters_col)
         nel_bnd_clusters = da.bincount(bnd_clusters, minlength=nclusters_bnd)
+        row_cluster_labels = nel_row_clusters.nonzero()[0].compute()
+        col_cluster_labels = nel_col_clusters.nonzero()[0].compute()
+        bnd_cluster_labels = nel_bnd_clusters.nonzero()[0].compute()
         logger.debug(
             'num of populated clusters: row {}, col {}, bnd {}'.format(
-                da.sum(nel_row_clusters > 0).compute(),
-                da.sum(nel_col_clusters > 0).compute(),
-                da.sum(nel_bnd_clusters > 0).compute()
+                len(row_cluster_labels),
+                len(col_cluster_labels),
+                len(bnd_cluster_labels)
             )
         )
-        nel_clusters = da.einsum('i,j->ij', nel_row_clusters, nel_col_clusters)
-        nel_clusters = da.einsum('i,jk->ijk', nel_bnd_clusters, nel_clusters)
+        nel_clusters = da.einsum(
+            'i,j->ij',
+            nel_row_clusters[row_cluster_labels],
+            nel_col_clusters[col_cluster_labels]
+        )
+        nel_clusters = da.einsum(
+            'i,jk->ijk',
+            nel_bnd_clusters[bnd_cluster_labels],
+            nel_clusters
+        )
 
-        R = _setup_cluster_matrix(nclusters_row, row_clusters)
-        C = _setup_cluster_matrix(nclusters_col, col_clusters)
-        B = _setup_cluster_matrix(nclusters_bnd, bnd_clusters)
+        R = _setup_cluster_matrix(row_cluster_labels, row_clusters)
+        C = _setup_cluster_matrix(col_cluster_labels, col_clusters)
+        B = _setup_cluster_matrix(bnd_cluster_labels, bnd_clusters)
 
-        # calculate tri-cluster averages (epsilon takes care of empty clusters)
+        # calculate tri-cluster averages
         # first sum values in each tri-cluster ..
         TriCavg = da.einsum('ij,ilm->jlm', B, Z)  # .. along band axis
         TriCavg = da.einsum('ij,kim->kjm', R, TriCavg)  # .. along row axis
         TriCavg = da.einsum('ij,kli->klj', C, TriCavg)  # .. along col axis
         # finally divide by number of elements in each tri-cluster
-        TriCavg = (TriCavg + Gavg * epsilon) / (nel_clusters + epsilon)
+        TriCavg = TriCavg / nel_clusters
 
         # unpack tri-cluster averages ..
         avg_unpck = da.einsum('ij,jkl->ikl', B, TriCavg)  # .. along band axis
         avg_unpck = da.einsum('ij,klj->kli', C, avg_unpck)  # .. along col axis
         # use these for the row cluster assignment
         idx = (1, 0, 2)
-        d_row = _distance(Z.transpose(idx), avg_unpck.transpose(idx), epsilon)
+        d_row = _distance(Z.transpose(idx), avg_unpck.transpose(idx))
         row_clusters = da.argmin(d_row, axis=1)
 
         # unpack tri-cluster averages ..
@@ -122,14 +127,14 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
         avg_unpck = da.einsum('ij,kjl->kil', R, avg_unpck)  # .. along row axis
         # use these for the col cluster assignment
         idx = (2, 0, 1)
-        d_col = _distance(Z.transpose(idx), avg_unpck.transpose(idx), epsilon)
+        d_col = _distance(Z.transpose(idx), avg_unpck.transpose(idx))
         col_clusters = da.argmin(d_col, axis=1)
 
         # unpack tri-cluster averages ..
         avg_unpck = da.einsum('ij,kjl->kil', R, TriCavg)  # .. along row axis
         avg_unpck = da.einsum('ij,klj->kli', C, avg_unpck)  # .. along col axis
         # use these for the band cluster assignment
-        d_bnd = _distance(Z, avg_unpck, epsilon)
+        d_bnd = _distance(Z, avg_unpck)
         bnd_clusters = da.argmin(d_bnd, axis=1)
 
         # Error value (actually just the band component really)
@@ -137,9 +142,12 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
         minvals = da.min(d_bnd, axis=1)
         # power 1 divergence, power 2 euclidean
         e = da.sum(da.power(minvals, 1))
-        row_clusters, col_clusters, bnd_clusters, e = client.persist(
-            [row_clusters, col_clusters, bnd_clusters, e]
-        )
+        row_clusters, col_clusters, bnd_clusters, e = client.persist([
+            da.take(row_cluster_labels, row_clusters),
+            da.take(col_cluster_labels, col_clusters),
+            da.take(bnd_cluster_labels, bnd_clusters),
+            e
+        ])
         e = e.compute()
         logger.debug(f'Error = {e:+.15e}, dE = {e - old_e:+.15e}')
         converged = abs(e - old_e) < errobj
diff --git a/cgc/triclustering_numpy.py b/cgc/triclustering_numpy.py
index dba626a..6195079 100644
--- a/cgc/triclustering_numpy.py
+++ b/cgc/triclustering_numpy.py
@@ -5,9 +5,8 @@ import numpy as np
 logger = logging.getLogger(__name__)
 
 
-def _distance(Z, Y, epsilon):
+def _distance(Z, Y):
     """ Distance function """
-    Y = Y + epsilon
     return Y.sum(axis=(1, 2)) - np.einsum('ijk,ljk->il', Z, np.log(Y))
 
 
@@ -17,14 +16,14 @@ def _initialize_clusters(n_el, n_clusters):
     return np.random.permutation(cluster_idx)
 
 
-def _setup_cluster_matrix(n_clusters, cluster_idx):
+def _setup_cluster_matrix(cluster_labels, cluster_idx):
     """ Set cluster occupation matrix """
-    return np.eye(n_clusters, dtype=np.bool)[cluster_idx]
+    return np.equal.outer(cluster_idx, cluster_labels)
 
 
 def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
-                  niters, epsilon, row_clusters_init=None,
-                  col_clusters_init=None, bnd_clusters_init=None):
+                  niters, row_clusters_init=None, col_clusters_init=None,
+                  bnd_clusters_init=None):
     """
     Run the tri-clustering analysis, Numpy-based implementation.
 
@@ -41,9 +40,6 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
     :type errobj: float, optional
     :param niters: Maximum number of iterations.
     :type niters: int, optional
-    :param epsilon: Numerical parameter, avoids zero arguments in the
-        logarithm that appears in the expression of the objective function.
-    :type epsilon: float, optional
     :param row_clusters_init: Initial row cluster assignment.
     :type row_clusters_init: numpy.ndarray or array_like, optional
     :param col_clusters_init: Initial column cluster assignment.
@@ -56,9 +52,6 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
     """
     [d, m, n] = Z.shape
 
-    # Calculate average
-    Gavg = Z.mean()
-
     # Initialize cluster assignments
     row_clusters = row_clusters_init if row_clusters_init is not None \
         else _initialize_clusters(m, nclusters_row)
@@ -77,34 +70,45 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
         nel_row_clusters = np.bincount(row_clusters, minlength=nclusters_row)
         nel_col_clusters = np.bincount(col_clusters, minlength=nclusters_col)
         nel_bnd_clusters = np.bincount(bnd_clusters, minlength=nclusters_bnd)
+        row_cluster_labels, = nel_row_clusters.nonzero()
+        col_cluster_labels, = nel_col_clusters.nonzero()
+        bnd_cluster_labels, = nel_bnd_clusters.nonzero()
         logger.debug(
             'num of populated clusters: row {}, col {}, bnd {}'.format(
-                np.sum(nel_row_clusters > 0),
-                np.sum(nel_col_clusters > 0),
-                np.sum(nel_bnd_clusters > 0)
+                len(row_cluster_labels),
+                len(col_cluster_labels),
+                len(bnd_cluster_labels),
             )
         )
-        nel_clusters = np.einsum('i,j->ij', nel_row_clusters, nel_col_clusters)
-        nel_clusters = np.einsum('i,jk->ijk', nel_bnd_clusters, nel_clusters)
+        nel_clusters = np.einsum(
+            'i,j->ij',
+            nel_row_clusters[row_cluster_labels],
+            nel_col_clusters[col_cluster_labels]
+        )
+        nel_clusters = np.einsum(
+            'i,jk->ijk',
+            nel_bnd_clusters[bnd_cluster_labels],
+            nel_clusters
+        )
 
-        R = _setup_cluster_matrix(nclusters_row, row_clusters)
-        C = _setup_cluster_matrix(nclusters_col, col_clusters)
-        B = _setup_cluster_matrix(nclusters_bnd, bnd_clusters)
+        R = _setup_cluster_matrix(row_cluster_labels, row_clusters)
+        C = _setup_cluster_matrix(col_cluster_labels, col_clusters)
+        B = _setup_cluster_matrix(bnd_cluster_labels, bnd_clusters)
 
-        # calculate tri-cluster averages (epsilon takes care of empty clusters)
+        # calculate tri-cluster averages
         # first sum values in each tri-cluster ..
         TriCavg = np.einsum('ij,ilm->jlm', B, Z)  # .. along band axis
         TriCavg = np.einsum('ij,kim->kjm', R, TriCavg)  # .. along row axis
         TriCavg = np.einsum('ij,kli->klj', C, TriCavg)  # .. along col axis
         # finally divide by number of elements in each tri-cluster
-        TriCavg = (TriCavg + Gavg * epsilon) / (nel_clusters + epsilon)
+        TriCavg = TriCavg / nel_clusters
 
         # unpack tri-cluster averages ..
         avg_unpck = np.einsum('ij,jkl->ikl', B, TriCavg)  # .. along band axis
         avg_unpck = np.einsum('ij,klj->kli', C, avg_unpck)  # .. along col axis
         # use these for the row cluster assignment
         idx = (1, 0, 2)
-        d_row = _distance(Z.transpose(idx), avg_unpck.transpose(idx), epsilon)
+        d_row = _distance(Z.transpose(idx), avg_unpck.transpose(idx))
         row_clusters = np.argmin(d_row, axis=1)
 
         # unpack tri-cluster averages ..
@@ -112,16 +116,20 @@ def triclustering(Z, nclusters_row, nclusters_col, nclusters_bnd, errobj,
         avg_unpck = np.einsum('ij,kjl->kil', R, avg_unpck)  # .. along row axis
         # use these for the col cluster assignment
         idx = (2, 0, 1)
-        d_col = _distance(Z.transpose(idx), avg_unpck.transpose(idx), epsilon)
+        d_col = _distance(Z.transpose(idx), avg_unpck.transpose(idx))
         col_clusters = np.argmin(d_col, axis=1)
 
         # unpack tri-cluster averages ..
         avg_unpck = np.einsum('ij,kjl->kil', R, TriCavg)  # .. along row axis
         avg_unpck = np.einsum('ij,klj->kli', C, avg_unpck)  # .. along col axis
         # use these for the band cluster assignment
-        d_bnd = _distance(Z, avg_unpck, epsilon)
+        d_bnd = _distance(Z, avg_unpck)
         bnd_clusters = np.argmin(d_bnd, axis=1)
 
+        row_clusters = np.take(row_cluster_labels, row_clusters)
+        col_clusters = np.take(col_cluster_labels, col_clusters)
+        bnd_clusters = np.take(bnd_cluster_labels, bnd_clusters)
+
         # Error value (actually just the band component really)
         old_e = e
         minvals = np.min(d_bnd, axis=1)
diff --git a/docs/coclustering.rst b/docs/coclustering.rst
index 5d08be8..3690fbf 100644
--- a/docs/coclustering.rst
+++ b/docs/coclustering.rst
@@ -41,7 +41,6 @@ the co-clustering analysis is setup by initializing a ``Coclustering`` object:
         max_iterations=100,  # maximum number of iterations
         conv_threshold=1.e-5,  # error convergence threshold 
         nruns=10,  # number of differently-initialized runs
-        epsilon=1.e-8,  # numerical parameter
         output_filename='results.json'  # JSON file where to write output
     )
 
@@ -85,13 +84,8 @@ with the optional flag ``low_memory``:
 
     results = cc.run_with_threads(nthreads=4, low_memory=True)
 
-The reduced memory requirement comes at the cost of performance. However, the performance loss of the low-memory
-algorithm can be significantly reduced by using `Numba`_'s just-in-time compilation feature, which can be activated with
-a second optional flag, ``numba_jit``:
-
-.. code-block:: python
-
-    results = cc.run_with_threads(nthreads=4, low_memory=True, numba_jit=True)
+The reduced memory requirement comes at a certain cost of performance. Fortunately, we also applied Numba`_'s just-in-time 
+compilation feature in the ``low_memory`` option. Thanks to this feature, the performance cost is significantly reduced.
 
 .. _Numba: https://numba.pydata.org
 
diff --git a/docs/triclustering.rst b/docs/triclustering.rst
index 7cfba76..42a7b22 100644
--- a/docs/triclustering.rst
+++ b/docs/triclustering.rst
@@ -38,7 +38,6 @@ is setup by creating an instance of ``Triclustering``:
         max_iterations=100,  # maximum number of iterations
         conv_threshold=1.e-5,  # error convergence threshold 
         nruns=10,  # number of differently-initialized runs
-        epsilon=1.e-8  # numerical parameter
         output_filename='results.json'  # JSON file where to write output
     )
 
diff --git a/notebooks/memory_usage/memory_estimate_coclustering.ipynb b/notebooks/memory_usage/memory_estimate_coclustering.ipynb
index dbb81c9..b3856ce 100644
--- a/notebooks/memory_usage/memory_estimate_coclustering.ipynb
+++ b/notebooks/memory_usage/memory_estimate_coclustering.ipynb
@@ -1,19 +1,19 @@
 {
  "cells": [
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "## Estimate memory usage of co-clustering\n",
     "\n",
-    "This Notebook demonstates how to estimate the memory usage of co-clustering. And compare it with the actual memory usage."
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+    "This notebook demonstates how to estimate the memory usage of co-clustering. And compare it with the actual memory usage."
+   ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "NOTE: This notebook makes use of memory_profiler, run the following block to install it using pip:"
+    "NOTE: This notebook makes use of [memory_profiler](https://github.com/pythonprofilers/memory_profiler), run the following block to install it using `pip`:"
    ]
   },
   {
@@ -43,11 +43,11 @@
    ]
   },
   {
-   "source": [
-    "First we define a function which compare the actual vs estimated memory usage for a certain set of (m, n, k, l), i.e. matrix sizes and row/col cluster numbers."
-   ],
    "cell_type": "markdown",
-   "metadata": {}
+   "metadata": {},
+   "source": [
+    "First we define a function which compare the actual vs estimated memory usage for a certain set of matrix sizes (m, n) and number of row/column clusters (k, l):"
+   ]
   },
   {
    "cell_type": "code",
@@ -55,7 +55,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "def memory_usage_comapre(input, m, n, k, l):\n",
+    "def memory_usage_compare(input, m, n, k, l):\n",
     "    # Compare real vs memomry estimated usage of coclustering_numpy \n",
     "\n",
     "    # Generate data\n",
@@ -83,12 +83,11 @@
    ]
   },
   {
-   "source": [
-    "Then we perform the comparison among a certain set of (m,n) and (k,l).\n",
-    "Due to the initialisations within `memory_profiler`, the comparison for the first set may show artificial extra memory usage. Please execute the cell below twice to avoid this effect."
-   ],
    "cell_type": "markdown",
-   "metadata": {}
+   "metadata": {},
+   "source": [
+    "Then we perform the comparison among a certain set of (m,n) and (k,l):"
+   ]
   },
   {
    "cell_type": "code",
@@ -98,15 +97,14 @@
    },
    "outputs": [
     {
-     "output_type": "stream",
      "name": "stdout",
+     "output_type": "stream",
      "text": [
-      "INFO:cgc.utils:Estimated memory usage: 686.69MB, peak number: 1\n",
-      "INFO:cgc.utils:Estimated memory usage: 686.69MB, peak number: 1\n",
-      "INFO:cgc.utils:Estimated memory usage: 2575.02MB, peak number: 1\n",
-      "INFO:cgc.utils:Estimated memory usage: 2575.02MB, peak number: 2\n",
-      "INFO:cgc.utils:Estimated memory usage: 801.17MB, peak number: 2\n",
-      "INFO:cgc.utils:Estimated memory usage: 193.79MB, peak number: 1\n"
+      "INFO:cgc.utils:Estimated memory usage: 600.85MB, peak number: 1\n",
+      "INFO:cgc.utils:Estimated memory usage: 2217.39MB, peak number: 1\n",
+      "INFO:cgc.utils:Estimated memory usage: 2217.39MB, peak number: 2\n",
+      "INFO:cgc.utils:Estimated memory usage: 715.33MB, peak number: 2\n",
+      "INFO:cgc.utils:Estimated memory usage: 193.21MB, peak number: 1\n"
      ]
     }
    ],
@@ -114,11 +112,11 @@
     "mn_list = [(300000, 50), (500000, 50), (50, 500000), (100, 300000), (5000,5000)]\n",
     "kl_list = [(100, 20), (250, 20), (20,250), (10, 100), (20, 20)]\n",
     "\n",
-    "input = {'errobj':1.e-5, \n",
-    "         'niters':1, \n",
-    "         'epsilon':1.e-8}\n",
-    "input['low_memory'] = False\n",
-    "input['numba_jit'] = False\n",
+    "input = {\n",
+    "    'errobj': 1.e-5, \n",
+    "    'niters': 1,\n",
+    "    'low_memory': False\n",
+    "}\n",
     "\n",
     "comp_res = np.empty((0,3)) # [row: mem_peak_real, mem_peak_est, mem_est_diff]\n",
     "for mn, kl in zip(mn_list, kl_list): \n",
@@ -126,15 +124,15 @@
     "    n = mn[1]\n",
     "    k = kl[0]\n",
     "    l = kl[1]\n",
-    "    comp_res = np.append(comp_res, memory_usage_comapre(input, m, n, k, l), axis=0)\n"
+    "    comp_res = np.append(comp_res, memory_usage_compare(input, m, n, k, l), axis=0)\n"
    ]
   },
   {
-   "source": [
-    "The comparision output: real memory peak, estiamted memorypeak, and real minus estimated"
-   ],
    "cell_type": "markdown",
-   "metadata": {}
+   "metadata": {},
+   "source": [
+    "The comparision output: measured memory peak, estimated memory peak, and the difference between the two."
+   ]
   },
   {
    "cell_type": "code",
@@ -144,19 +142,18 @@
    },
    "outputs": [
     {
-     "output_type": "execute_result",
      "data": {
       "text/plain": [
-       "array([[ 758.35107422,  686.68746948,   71.66360474],\n",
-       "       [ 670.96435547,  686.68746948,  -15.72311401],\n",
-       "       [2589.81689453, 2575.01983643,   14.79705811],\n",
-       "       [2552.00830078, 2575.01983643,  -23.01153564],\n",
-       "       [ 813.09667969,  801.16653442,   11.93014526],\n",
-       "       [ 190.75830078,  193.78662109,   -3.02832031]])"
+       "array([[ 616.31982422,  600.85391998,   15.46590424],\n",
+       "       [2219.65283203, 2217.38910675,    2.26372528],\n",
+       "       [2223.54736328, 2217.38910675,    6.15825653],\n",
+       "       [ 742.28808594,  715.33298492,   26.95510101],\n",
+       "       [ 190.73486328,  193.2144165 ,   -2.47955322]])"
       ]
      },
+     "execution_count": 5,
      "metadata": {},
-     "execution_count": 5
+     "output_type": "execute_result"
     }
    ],
    "source": [
@@ -165,11 +162,11 @@
    ]
   },
   {
-   "source": [
-    "If we calculate the fraction of the difference/actual usage, we can see the fraction is quite small. Therefore we can conclude that the estiamte is quite accurrate. "
-   ],
    "cell_type": "markdown",
-   "metadata": {}
+   "metadata": {},
+   "source": [
+    "If we calculate the fraction of the difference/actual usage, we can see the fraction is quite small: "
+   ]
   },
   {
    "cell_type": "code",
@@ -177,15 +174,14 @@
    "metadata": {},
    "outputs": [
     {
-     "output_type": "execute_result",
      "data": {
       "text/plain": [
-       "array([ 0.09449925, -0.02343361,  0.00571355, -0.00901703,  0.01467248,\n",
-       "       -0.01587517])"
+       "array([ 0.02509396,  0.00101986,  0.00276956,  0.03631353, -0.013     ])"
       ]
      },
+     "execution_count": 6,
      "metadata": {},
-     "execution_count": 6
+     "output_type": "execute_result"
     }
    ],
    "source": [
@@ -195,9 +191,13 @@
   }
  ],
  "metadata": {
+  "interpreter": {
+   "hash": "e82b70dc0b932b5d8a4418de9b7c8931d4ec0c2e87ab55b59e13124e7cc57761"
+  },
   "kernelspec": {
-   "name": "python3",
-   "display_name": "Python 3.9.4 64-bit ('cgc': conda)"
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
@@ -209,12 +209,9 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.4"
-  },
-  "interpreter": {
-   "hash": "e82b70dc0b932b5d8a4418de9b7c8931d4ec0c2e87ab55b59e13124e7cc57761"
+   "version": "3.9.6"
   }
  },
  "nbformat": 4,
  "nbformat_minor": 4
-}
\ No newline at end of file
+}
diff --git a/notebooks/memory_usage/memory_profile_coclustering.ipynb b/notebooks/memory_usage/memory_profile_coclustering.ipynb
index 4fc87f9..7a731a7 100644
--- a/notebooks/memory_usage/memory_profile_coclustering.ipynb
+++ b/notebooks/memory_usage/memory_profile_coclustering.ipynb
@@ -18,21 +18,20 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
-    "!pip install memory-profiler"
+    "# !pip install memory-profiler"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [],
    "source": [
     "import matplotlib.pyplot as plt\n",
-    "import numba\n",
     "import numpy as np \n",
     "import logging \n",
     "import sys \n",
@@ -47,7 +46,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -73,7 +72,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 4,
    "metadata": {
     "tags": []
    },
@@ -94,7 +93,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -105,7 +104,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -113,7 +112,6 @@
     "         'nclusters_col':l, \n",
     "         'errobj':1.e-5, \n",
     "         'niters':5, \n",
-    "         'epsilon':1.e-8,\n",
     "         'row_clusters_init': row_clusters,\n",
     "         'col_clusters_init': col_clusters,}"
    ]
@@ -128,7 +126,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "metadata": {
     "tags": []
    },
@@ -137,12 +135,12 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "15.8 s ± 116 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+      "11.2 s ± 810 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
      ]
     },
     {
      "data": {
-      "image/png": "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\n",
+      "image/png": "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",
       "text/plain": [
        "<Figure size 432x288 with 1 Axes>"
       ]
@@ -156,14 +154,13 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[ 35 136  12 ... 224 224 157] [1 4 0 0 9 8 5 5 7 5 2 5 7 6 2 8 3 2 4 0 3 9 4 7 4 1 2 0 8 4 7 9 6 1 9 5 9\n",
-      " 1 0 3 6 6 3 8 8 2 7 3 6 1] -3616922779.075045\n"
+      "[ 77 222  76 ... 127  88 196] [9 4 3 4 6 8 1 5 4 0 2 4 5 2 3 2 7 9 1 5 7 6 7 4 6 9 0 1 6 2 5 0 1 5 9 1 8\n",
+      " 6 9 3 3 2 7 3 8 7 0 0 8 8] -3616207595.3329263\n"
      ]
     }
    ],
    "source": [
     "input['low_memory'] = False\n",
-    "input['numba_jit'] = False\n",
     "profile(coclustering_numpy.coclustering, Z, input)"
    ]
   },
@@ -173,75 +170,33 @@
    "source": [
     "### Mode 2: low-memory run\n",
     "In this mode the memory usage is significantly reduced by replacing large matrix multiplications with for loops.\n",
-    "This comes at the cost of the performance."
+    "This comes at the cost of the performance. [Numba](https://numba.pydata.org) is used to accelerate the for loops."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 8,
    "metadata": {
     "tags": []
    },
    "outputs": [
     {
-     "name": "stdout",
+     "name": "stderr",
      "output_type": "stream",
      "text": [
-      "27.1 s ± 587 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+      "OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.\n"
      ]
     },
-    {
-     "data": {
-      "image/png": "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\n",
-      "text/plain": [
-       "<Figure size 432x288 with 1 Axes>"
-      ]
-     },
-     "metadata": {
-      "needs_background": "light"
-     },
-     "output_type": "display_data"
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[ 35 136  12 ... 224 224 157] [1 4 0 0 9 8 5 5 7 5 2 5 7 6 2 8 3 2 4 0 3 9 4 7 4 1 2 0 8 4 7 9 6 1 9 5 9\n",
-      " 1 0 3 6 6 3 8 8 2 7 3 6 1] -3616922779.0750217\n"
-     ]
-    }
-   ],
-   "source": [
-    "input['low_memory'] = True\n",
-    "input['numba_jit'] = False\n",
-    "profile(coclustering_numpy.coclustering, Z, input)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Mode 3: low-memory run with numba acceleration\n",
-    "In this mode we still use the low memery configuration, but use [Numba](https://numba.pydata.org) to accelerate the for loops. The memory footprint is still low, and the performance is intermediate between the two previous cases."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "23.2 s ± 495 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
+      "40.8 s ± 4.45 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
      ]
     },
     {
      "data": {
-      "image/png": "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\n",
+      "image/png": "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",
       "text/plain": [
        "<Figure size 432x288 with 1 Axes>"
       ]
@@ -255,21 +210,20 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[ 35 136  12 ... 224 224 157] [1 4 0 0 9 8 5 5 7 5 2 5 7 6 2 8 3 2 4 0 3 9 4 7 4 1 2 0 8 4 7 9 6 1 9 5 9\n",
-      " 1 0 3 6 6 3 8 8 2 7 3 6 1] -3616922779.0750356\n"
+      "[ 77 222  76 ... 127  88 196] [9 4 3 4 6 8 1 5 4 0 2 4 5 2 3 2 7 9 1 5 7 6 7 4 6 9 0 1 6 2 5 0 1 5 9 1 8\n",
+      " 6 9 3 3 2 7 3 8 7 0 0 8 8] -3616207595.3329268\n"
      ]
     }
    ],
    "source": [
     "input['low_memory'] = True\n",
-    "input['numba_jit'] = True\n",
     "profile(coclustering_numpy.coclustering, Z, input)"
    ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -283,7 +237,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.7"
+   "version": "3.9.6"
   }
  },
  "nbformat": 4,


Test patch:
diff --git a/tests/test_coclustering.py b/tests/test_coclustering.py
index 4c42186..44a03ad 100644
--- a/tests/test_coclustering.py
+++ b/tests/test_coclustering.py
@@ -1,7 +1,6 @@
 import pytest
 
 import numpy as np
-import dask.array as da
 
 from cgc.coclustering import Coclustering
 
@@ -12,12 +11,14 @@ def coclustering():
     m, n = 10, 8
     ncl_row, ncl_col = 5, 2
     Z = np.random.randint(100, size=(m, n)).astype('float64')
-    return Coclustering(Z, nclusters_row=ncl_row, nclusters_col=ncl_col,
-                        conv_threshold=1.e-5, max_iterations=100, nruns=1,
-                        epsilon=1.e-8,
+    return Coclustering(Z,
+                        nclusters_row=ncl_row,
+                        nclusters_col=ncl_col,
+                        conv_threshold=1.e-5,
+                        max_iterations=100,
+                        nruns=1,
                         row_clusters_init=[0, 1, 2, 3, 4, 0, 1, 2, 3, 4],
-                        col_clusters_init=[0, 1, 0, 1, 0, 1, 0, 1]
-                        )
+                        col_clusters_init=[0, 1, 0, 1, 0, 1, 0, 1])
 
 
 class TestCoclustering:
@@ -41,15 +42,6 @@ class TestCoclustering:
                                 [0, 1, 0, 0, 0, 1, 0, 1])
         assert np.isclose(coclustering.results.error, -11503.89447245418)
 
-    def test_run_with_threads_numba(self, coclustering):
-        coclustering.run_with_threads(nthreads=2, low_memory=True,
-                                      numba_jit=True)
-        np.testing.assert_equal(coclustering.results.row_clusters,
-                                [0, 0, 2, 4, 1, 0, 1, 2, 3, 4])
-        np.testing.assert_equal(coclustering.results.col_clusters,
-                                [0, 1, 0, 0, 0, 1, 0, 1])
-        assert np.isclose(coclustering.results.error, -11503.89447245418)
-
     def test_dask_runs_memory(self, client, coclustering):
         coclustering.run_with_dask(client=client, low_memory=True)
         np.testing.assert_equal(coclustering.results.row_clusters,
@@ -66,14 +58,6 @@ class TestCoclustering:
                                 [0, 1, 0, 0, 0, 1, 0, 1])
         assert np.isclose(coclustering.results.error, -11503.89447245418)
 
-    def test_dask_performance_raises_error(self, client):
-        m, n = 10, 8
-        Z = np.random.randint(100, size=(m, n)).astype('float64')
-        cc = Coclustering(Z, nclusters_row=5, nclusters_col=2,
-                          epsilon="epsilon")  # wrong data type for epsilon
-        with pytest.raises(TypeError):
-            cc.run_with_dask()
-
     def test_nruns_completed_threads(self, coclustering):
         coclustering.run_with_threads(nthreads=1)
         assert coclustering.results.nruns_completed == 1
diff --git a/tests/test_coclustering_dask.py b/tests/test_coclustering_dask.py
index 11b22de..21d0177 100644
--- a/tests/test_coclustering_dask.py
+++ b/tests/test_coclustering_dask.py
@@ -10,14 +10,14 @@ class TestDistance:
         m = 4
         n = 5
         k = 2
-        da.random.seed(1234)
-        Z = da.random.randint(100, size=(m, n)).astype('float64')
-        Y = da.random.randint(2, size=(n, k)).astype('float64')
-        epsilon = 1.e-8
-        d = coclustering_dask._distance(Z, Y, epsilon)
+        # original matrix can contain zeros
+        Z = da.random.randint(0, 2, size=(m, n)).astype('float64')
+        # not the (expanded) cluster averages
+        Y = da.random.randint(1, 10, size=(n, k)).astype('float64')
+        d = coclustering_dask._distance(Z, Y)
         assert isinstance(d, da.core.Array)
         assert d.shape == (m, k)
-        assert ~np.any(np.isinf(d))
+        assert ~np.any(np.isinf(d.compute()))
 
 
 class TestInitializeClusters:
@@ -57,9 +57,8 @@ class TestCoclustering:
         Z = da.random.permutation(da.arange(4)).reshape(2, 2)
         ncl_row = 2
         ncl_col = 2
-        da.random.seed(1234)
         _, _, row_cl, col_cl, _ = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 1, 1.e-8
+            Z, ncl_row, ncl_col, 1.e-5, 1
         )
         assert isinstance(row_cl, da.core.Array)
         assert isinstance(col_cl, da.core.Array)
@@ -67,27 +66,24 @@ class TestCoclustering:
     def test_small_matrix(self, client):
         da.random.seed(1234)
         Z = da.random.permutation(da.arange(12)).reshape(3, 4)
+        Z = Z.astype('float64')
         ncl_row = 2
         ncl_col = 3
         conv, niterations, row_cl, col_cl, error = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, 1.e-8
+            Z, ncl_row, ncl_col, 1.e-5, 100
         )
         assert conv
         assert niterations == 3
-        np.testing.assert_array_equal(row_cl.compute(),
-                                      np.array([1, 0, 0]))
-        np.testing.assert_array_equal(col_cl.compute(),
-                                      np.array([1, 1, 2, 0]))
+        np.testing.assert_array_equal(row_cl.compute(), np.array([1, 0, 0]))
+        np.testing.assert_array_equal(col_cl.compute(), np.array([1, 1, 2, 0]))
         np.testing.assert_almost_equal(error, -56.457907947376775)
 
     def test_bigger_matrix(self, client):
+        da.random.seed(1234)
         Z = da.random.randint(100, size=(20, 15)).astype('float64')
         ncl_row = 5
         ncl_col = 6
-        da.random.seed(1234)
-        _, _, row_cl, col_cl, _ = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, 1.e-8
-        )
+        _, _, row_cl, col_cl, _ = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
         np.testing.assert_array_equal(np.sort(np.unique(row_cl.compute())),
                                       np.arange(ncl_row))
         np.testing.assert_array_equal(np.sort(np.unique(col_cl.compute())),
@@ -97,50 +93,44 @@ class TestCoclustering:
         # it should immediately converge (2 iterations)
         ncl_row = 8
         ncl_col = 7
-        da.random.seed(1234)
-        Z = da.random.randint(100, size=(ncl_row, ncl_col)).astype('float64')
-        conv, niterations, _, _, _ = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, 1.e-8
+        Z = da.arange(1, ncl_row*ncl_col+1).reshape(ncl_row, ncl_col)
+        Z = Z.astype(float)
+        conv, niterations, _, _, e = coclustering(
+            Z, ncl_row, ncl_col, 1.e-5, 100
         )
         assert conv
         assert niterations == 2
+        assert np.isfinite(e)
 
     def test_constant_col_matrix(self, client):
         # should give one cluster in rows
-        Z = np.tile(np.arange(7), (8, 1))
+        Z = np.tile(np.arange(1, 8), (8, 1)).astype(float)
         Z = da.from_array(Z)
         ncl_row = 3
-        ncl_col = 4
-        da.random.seed(1234)
-        _, _, row_cl, col_cl, _ = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, 1.e-8
-        )
-        print(col_cl.compute())
+        ncl_col = 7
+        _, _, row_cl, col_cl, e = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
         assert np.unique(row_cl.compute()).size == 1
         assert np.unique(col_cl.compute()).size == ncl_col
+        assert np.isfinite(e)
 
     def test_constant_row_matrix(self, client):
         # should give one cluster in columns
-        Z = np.repeat(np.arange(8), 7).reshape(8, 7)
+        Z = np.repeat(np.arange(1, 9), 7).reshape(8, 7).astype(float)
         Z = da.from_array(Z)
-        ncl_row = 3
+        ncl_row = 8
         ncl_col = 4
-        da.random.seed(1234)
-        _, _, row_cl, col_cl, _ = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, 1.e-8
-        )
+        _, _, row_cl, col_cl, e = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
         assert np.unique(row_cl.compute()).size == ncl_row
         assert np.unique(col_cl.compute()).size == 1
+        assert np.isfinite(e)
 
-    def test_zero_matrix(self, client):
-        # special case for the error - and no nan/inf
-        Z = np.zeros((8, 7))
+    def test_constant_matrix(self, client):
+        # should give one cluster in column and rows
+        Z = np.ones((8, 7)) * 5
         Z = da.from_array(Z)
         ncl_row = 3
         ncl_col = 4
-        epsilon = 1.e-6
-        da.random.seed(1234)
-        _, _, _, _, error = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, epsilon
-        )
-        assert np.isclose(error, Z.size*epsilon)
+        _, _, row_cl, col_cl, e = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
+        assert np.unique(row_cl.compute()).size == 1
+        assert np.unique(col_cl.compute()).size == 1
+        assert np.isfinite(e)
diff --git a/tests/test_coclustering_numpy.py b/tests/test_coclustering_numpy.py
index 2e1a126..f44ec76 100644
--- a/tests/test_coclustering_numpy.py
+++ b/tests/test_coclustering_numpy.py
@@ -6,24 +6,20 @@ from cgc.coclustering_numpy import coclustering
 
 class TestDistance:
     def test_distance_basic(self):
+        # original matrix can contain zeros..
         Z = np.arange(0, 40).reshape(8, 5).astype('float64')
-        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=np.int32)
-        col_clusters = np.array([0, 0, 0, 1, 1], dtype=np.int32)
+        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=int)
+        col_clusters = np.array([0, 0, 0, 1, 1], dtype=int)
+        # ..not the cluster average
         cc = np.arange(1, 9).reshape(4, 2)
-        np.random.seed(1234)
-        R = np.eye(4, dtype=np.int32)[row_clusters]
-        C = np.eye(2, dtype=np.int32)[col_clusters]
-        R[1, :] = 0
-        C[2, :] = 0
-        CoCavg = coclustering_numpy._cluster_dot(Z, row_clusters, col_clusters,
-                                                 4, 2)
-        d_row = coclustering_numpy._min_dist(Z, C, CoCavg.T, 1.e-6)
-        d_col = coclustering_numpy._min_dist(Z.T, R, CoCavg, 1.e-6)
-
-        assert d_row[1].size == 8
-        assert d_col[1].size == 5
-        assert ~np.any(np.isinf(d_row[1]))
-        assert ~np.any(np.isinf(d_col[1]))
+        R = np.eye(4, dtype=bool)[row_clusters]
+        C = np.eye(2, dtype=bool)[col_clusters]
+        _, d_row = coclustering_numpy._min_dist(Z, C, cc.T)
+        _, d_col = coclustering_numpy._min_dist(Z.T, R, cc)
+        assert d_row.size == 8
+        assert d_col.size == 5
+        assert np.isfinite(d_row).all()
+        assert np.isfinite(d_col).all()
 
 
 class TestInitializeClusters:
@@ -51,12 +47,12 @@ class TestInitializeClusters:
 class TestCoclustering:
     def test_small_matrix(self):
         np.random.seed(1234)
-        Z = np.random.permutation(np.arange(12)).reshape(3,
-                                                         4).astype('float64')
+        Z = np.random.permutation(np.arange(12)).reshape(3, 4)
+        Z = Z.astype('float64')
         ncl_row = 2
         ncl_col = 3
         conv, niterations, row_cl, col_cl, error = coclustering(
-            Z, ncl_row, ncl_col, 1.e-5, 100, 1.e-8)
+            Z, ncl_row, ncl_col, 1.e-5, 100)
         assert conv
         assert niterations == 3
         np.testing.assert_array_equal(row_cl, np.array([1, 0, 0]))
@@ -64,12 +60,11 @@ class TestCoclustering:
         np.testing.assert_almost_equal(error, -56.457907947376775)
 
     def test_bigger_matrix(self):
+        np.random.seed(1234)
         Z = np.random.randint(100, size=(20, 15)).astype('float64')
         ncl_row = 5
         ncl_col = 6
-        np.random.seed(1234)
-        _, _, row_cl, col_cl, _ = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100,
-                                               1.e-8)
+        _, _, row_cl, col_cl, _ = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
         np.testing.assert_array_equal(np.sort(np.unique(row_cl)),
                                       np.arange(ncl_row))
         np.testing.assert_array_equal(np.sort(np.unique(col_cl)),
@@ -79,118 +74,75 @@ class TestCoclustering:
         # it should immediately converge (2 iterations)
         ncl_row = 8
         ncl_col = 7
-        np.random.seed(1234)
-        Z = np.random.randint(100, size=(ncl_row, ncl_col)).astype('float64')
-        conv, niterations, _, _, _ = coclustering(Z, ncl_row, ncl_col, 1.e-5,
-                                                  100, 1.e-8)
+        Z = np.arange(1, ncl_row * ncl_col + 1).reshape(ncl_row, ncl_col)
+        Z = Z.astype(float)
+        conv, niterations, _, _, e = coclustering(Z, ncl_row, ncl_col, 1.e-5,
+                                                  100)
         assert conv
         assert niterations == 2
+        assert np.isfinite(e)
 
     def test_constant_col_matrix(self):
         # should give one cluster in rows
-        Z = np.tile(np.arange(7), (8, 1)).astype('float64')
+        Z = np.tile(np.arange(1, 8), (8, 1)).astype(float)
         ncl_row = 3
-        ncl_col = 4
-        np.random.seed(1234)
-        _, _, row_cl, col_cl, _ = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100,
-                                               1.e-8)
+        ncl_col = 7
+        _, _, row_cl, col_cl, e = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
         assert np.unique(row_cl).size == 1
         assert np.unique(col_cl).size == ncl_col
+        assert np.isfinite(e)
 
     def test_constant_row_matrix(self):
         # should give one cluster in columns
-        Z = np.repeat(np.arange(8), 7).reshape(8, 7).astype('float64')
-        ncl_row = 3
+        Z = np.repeat(np.arange(1, 9), 7).reshape(8, 7).astype(float)
+        ncl_row = 8
         ncl_col = 4
-        np.random.seed(1234)
-        _, _, row_cl, col_cl, _ = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100,
-                                               1.e-8)
+        _, _, row_cl, col_cl, e = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
         assert np.unique(row_cl).size == ncl_row
         assert np.unique(col_cl).size == 1
+        assert np.isfinite(e)
 
-    def test_zero_matrix(self):
-        # special case for the error - and no nan/inf
-        Z = np.zeros((8, 7))
+    def test_constant_matrix(self):
+        # should give one cluster in column and rows
+        Z = np.ones((8, 7)) * 5
         ncl_row = 3
         ncl_col = 4
-        epsilon = 1.e-6
-        np.random.seed(1234)
-        _, _, _, _, error = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100,
-                                         epsilon)
-        assert np.isclose(error, Z.size * epsilon)
-
-
-class TestLowMemory:
-    def test_distance_lowmem(self):
-        Z = np.arange(0, 40).reshape(8, 5).astype('float64')
-        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=np.int32)
-        col_clusters = np.array([0, 0, 0, 1, 1], dtype=np.int32)
-        cc = np.arange(1, 9).reshape(4, 2)
-        np.random.seed(1234)
-        R = np.eye(4, dtype=np.int32)[row_clusters]
-        C = np.eye(2, dtype=np.int32)[col_clusters]
-        CoCavg = coclustering_numpy._cluster_dot(Z, row_clusters, col_clusters,
-                                                 4, 2)
-
-        d_row = coclustering_numpy._min_dist(Z, C, CoCavg.T, 1.e-6)
-        d_row_lowmem_numba = coclustering_numpy._min_dist_lowmem(
-            Z, col_clusters, CoCavg.T, 1.e-6)
-
-        d_col = coclustering_numpy._min_dist(Z.T, R, CoCavg, 1.e-6)
-        d_col_lowmem_numba = coclustering_numpy._min_dist_lowmem(
-            Z.T, row_clusters, CoCavg, 1.e-6)
-        # Test almost equal because numpy sum lose precision
-        np.testing.assert_almost_equal(d_row, d_row_lowmem_numba, 10)
-        np.testing.assert_almost_equal(d_col, d_col_lowmem_numba, 10)
-
-    def test_dot_clustering_lowmem(self):
-        Z = np.arange(0, 40).reshape(8, 5).astype('float64')
-        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=np.int32)
-        col_clusters = np.array([0, 0, 0, 1, 1], dtype=np.int32)
-        R = np.eye(4, dtype=np.int32)[row_clusters]
-        C = np.eye(2, dtype=np.int32)[col_clusters]
-        CoCavg = (np.dot(np.dot(R.T, Z), C) + Z.mean() * 1.e-6) / (
-            np.dot(np.dot(R.T, np.ones(Z.shape)), C) + 1.e-6)
-        CoCavg_lowmem = (coclustering_numpy._cluster_dot(
-            Z, row_clusters, col_clusters, 4, 2) + Z.mean() * 1.e-6) / (
-                coclustering_numpy._cluster_dot(np.ones(Z.shape), row_clusters,
-                                                col_clusters, 4, 2) + 1.e-6)
-        np.testing.assert_equal(CoCavg, CoCavg_lowmem)
+        _, _, row_cl, col_cl, e = coclustering(Z, ncl_row, ncl_col, 1.e-5, 100)
+        assert np.unique(row_cl).size == 1
+        assert np.unique(col_cl).size == 1
+        assert np.isfinite(e)
 
 
 class TestLowMemoryNumba:
     def test_distance_lowmem_numba(self):
+        # original matrix can contain zeros..
         Z = np.arange(0, 40).reshape(8, 5).astype('float64')
-        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=np.int32)
-        col_clusters = np.array([0, 0, 0, 1, 1], dtype=np.int32)
+        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=int)
+        col_clusters = np.array([0, 0, 0, 1, 1], dtype=int)
+        # ..not the cluster average
         cc = np.arange(1, 9).reshape(4, 2)
-        np.random.seed(1234)
-        R = np.eye(4, dtype=np.int32)[row_clusters]
-        C = np.eye(2, dtype=np.int32)[col_clusters]
-        CoCavg = coclustering_numpy._cluster_dot(Z, row_clusters, col_clusters,
-                                                 4, 2)
-
-        d_row = coclustering_numpy._min_dist(Z, C, CoCavg.T, 1.e-6)
-        d_row_lowmem_numba = coclustering_numpy._min_dist_numba(
-            Z, col_clusters, CoCavg.T, 1.e-6)
-
-        d_col = coclustering_numpy._min_dist(Z.T, R, CoCavg, 1.e-6)
-        d_col_lowmem_numba = coclustering_numpy._min_dist_numba(
-            Z.T, row_clusters, CoCavg, 1.e-6)
-        # Test almost equal because numpy sum lose precision
-        np.testing.assert_almost_equal(d_row, d_row_lowmem_numba, 10)
-        np.testing.assert_almost_equal(d_col, d_col_lowmem_numba, 10)
+        R = np.eye(4, dtype=bool)[row_clusters]
+        C = np.eye(2, dtype=bool)[col_clusters]
+        row_cl, d_row = coclustering_numpy._min_dist(Z, C, cc.T)
+        col_cl, d_col = coclustering_numpy._min_dist(Z.T, R, cc)
+
+        row_cl_numba, d_row_numba = coclustering_numpy._min_dist_numba(
+            Z, col_clusters, np.arange(2), cc.T)
+        col_cl_numba, d_col_numba = coclustering_numpy._min_dist_numba(
+            Z.T, row_clusters, np.arange(4), cc)
+
+        np.testing.assert_array_equal(row_cl, row_cl_numba)
+        np.testing.assert_array_equal(col_cl, col_cl_numba)
+        np.testing.assert_almost_equal(d_row, d_row_numba, 10)
+        np.testing.assert_almost_equal(d_col, d_col_numba, 10)
 
     def test_dot_clustering_lowmem_numba(self):
         Z = np.arange(0, 40).reshape(8, 5).astype('float64')
-        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=np.int32)
-        col_clusters = np.array([0, 0, 0, 1, 1], dtype=np.int32)
-        R = np.eye(4, dtype=np.int32)[row_clusters]
-        C = np.eye(2, dtype=np.int32)[col_clusters]
-        CoCavg = (np.dot(np.dot(R.T, Z), C) + Z.mean() * 1.e-6) / (
-            np.dot(np.dot(R.T, np.ones(Z.shape)), C) + 1.e-6)
-        CoCavg_lowmem_numba = (coclustering_numpy._cluster_dot_numba(
-            Z, row_clusters, col_clusters, 4, 2) + Z.mean() * 1.e-6) / (
-                coclustering_numpy._cluster_dot_numba(np.ones(
-                    Z.shape), row_clusters, col_clusters, 4, 2) + 1.e-6)
-        np.testing.assert_equal(CoCavg, CoCavg_lowmem_numba)
+        row_clusters = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=int)
+        col_clusters = np.array([0, 0, 0, 1, 1], dtype=int)
+        R = np.eye(4, dtype=int)[row_clusters]
+        C = np.eye(2, dtype=int)[col_clusters]
+        CoCavg = np.dot(np.dot(R.T, Z), C)
+        CoCavg_numba = (coclustering_numpy._cluster_dot_numba(
+            Z, row_clusters, col_clusters, np.arange(4), np.arange(2)))
+        np.testing.assert_equal(CoCavg, CoCavg_numba)
diff --git a/tests/test_triclustering.py b/tests/test_triclustering.py
index e3622ed..651cfd0 100644
--- a/tests/test_triclustering.py
+++ b/tests/test_triclustering.py
@@ -15,7 +15,7 @@ def triclustering():
     Z = np.random.randint(100, size=(d, m, n)).astype('float64')
     return Triclustering(Z, nclusters_row=ncl_row, nclusters_col=ncl_col,
                          nclusters_bnd=ncl_bnd, conv_threshold=1.e-5,
-                         max_iterations=100, nruns=1, epsilon=1.e-8,
+                         max_iterations=100, nruns=1,
                          row_clusters_init=[0, 1, 2, 3, 4, 0, 1, 2, 3, 4],
                          col_clusters_init=[0, 1, 0, 1, 0, 1, 0, 1],
                          bnd_clusters_init=[0, 1, 2, 0, 1, 2])
diff --git a/tests/test_triclustering_dask.py b/tests/test_triclustering_dask.py
index 0d2ab67..225f11c 100644
--- a/tests/test_triclustering_dask.py
+++ b/tests/test_triclustering_dask.py
@@ -12,17 +12,15 @@ class TestDistance:
         n = 5
         k = 2  # number of band clusters
         np.random.seed(1234)
-        Z = np.random.randint(100, size=(d, m, n)).astype('float64')
-        Y = np.random.randint(100, size=(k, m, n)).astype('float64')
-        Y[0, 3, 1] = 0.  # setting value to zero should not give infinite
-        epsilon = 1.e-8
-        distance = triclustering_dask._distance(Z, Y, epsilon)
+        Z = np.random.randint(0, 100, size=(d, m, n)).astype('float64')
+        Y = np.random.randint(1, 100, size=(k, m, n)).astype('float64')
+        distance = triclustering_dask._distance(Z, Y)
         assert distance.shape == (d, k)
         assert ~np.any(np.isinf(distance))
         target = [
-            [-1136.1376816, -2094.30771188],
-            [-542.49375152, -2152.56590639],
-            [-2074.05605694, -1505.49646914]
+            [-2597.79658089, -2653.23155338],
+            [-2249.72483363, -2215.08215482],
+            [-3268.76823711, -3154.50383315],
         ]
         assert np.allclose(distance, target)
 
@@ -58,7 +56,7 @@ class TestTriclustering:
         ncl_col = 4
         ncl_bnd = 2
         conv, niterations, row_cl, col_cl, bnd_cl, error = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=[1, 2, 0, 1],
             col_clusters_init=[1, 0, 3, 2, 0],
             bnd_clusters_init=[1, 0, 0]
@@ -83,8 +81,8 @@ class TestTriclustering:
         ncl_row = 5
         ncl_col = 6
         ncl_bnd = 3
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(nel_row), ncl_row),
             col_clusters_init=np.mod(np.arange(nel_col), ncl_col),
             bnd_clusters_init=np.mod(np.arange(nel_bnd), ncl_bnd)
@@ -95,32 +93,34 @@ class TestTriclustering:
                                       np.arange(ncl_col))
         np.testing.assert_array_equal(np.sort(np.unique(bnd_cl.compute())),
                                       np.arange(ncl_bnd))
+        assert np.isfinite(e)
 
     def test_as_many_clusters_as_elements(self, client):
         # it should immediately converge (2 iterations)
         ncl_row = 8
         ncl_col = 7
         ncl_bnd = 6
-        Z = np.random.randint(100, size=(ncl_bnd, ncl_row, ncl_col))
-        Z = Z.astype('float64')
-        conv, niterations, _, _, _, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        Z = np.arange(1, ncl_row*ncl_col*ncl_bnd+1)
+        Z = Z.reshape((ncl_bnd, ncl_row, ncl_col)).astype('float64')
+        conv, niterations, _, _, _, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(ncl_row), ncl_row),
             col_clusters_init=np.mod(np.arange(ncl_col), ncl_col),
             bnd_clusters_init=np.mod(np.arange(ncl_bnd), ncl_bnd)
         )
         assert conv
         assert niterations == 2
+        assert np.isfinite(e)
 
     def test_constant_row_matrix(self, client):
         # should give one cluster in rows
-        Z = np.repeat(np.arange(135).reshape((9, 15)), 12, axis=0)
+        Z = np.repeat(np.arange(1, 136).reshape((9, 15)), 12, axis=0)
         Z = Z.reshape((9, 12, 15))
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -128,15 +128,16 @@ class TestTriclustering:
         assert np.unique(row_cl.compute()).size == 1
         assert np.unique(col_cl.compute()).size == ncl_col
         assert np.unique(bnd_cl.compute()).size == ncl_bnd
+        assert np.isfinite(e)
 
     def test_constant_col_matrix(self, client):
         # should give one cluster in columns
-        Z = np.repeat(np.arange(108), 15).reshape((9, 12, 15))
+        Z = np.repeat(np.arange(1, 109), 15).reshape((9, 12, 15))
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -144,15 +145,16 @@ class TestTriclustering:
         assert np.unique(row_cl.compute()).size == ncl_row
         assert np.unique(col_cl.compute()).size == 1
         assert np.unique(bnd_cl.compute()).size == ncl_bnd
+        assert np.isfinite(e)
 
     def test_constant_bnd_matrix(self, client):
         # should give one cluster in bands
-        Z = np.tile(np.arange(180).reshape((12, 15)), (9, 1, 1))
+        Z = np.tile(np.arange(1, 181).reshape((12, 15)), (9, 1, 1))
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -160,14 +162,15 @@ class TestTriclustering:
         assert np.unique(row_cl.compute()).size == ncl_row
         assert np.unique(col_cl.compute()).size == ncl_col
         assert np.unique(bnd_cl.compute()).size == 1
+        assert np.isfinite(e)
 
     def test_constant_rows_and_cols_matrix(self, client):
         Z = np.stack([np.full((12, 15), i) for i in range(1, 10)])
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -175,6 +178,7 @@ class TestTriclustering:
         assert np.unique(row_cl.compute()).size == 1
         assert np.unique(col_cl.compute()).size == 1
         assert np.unique(bnd_cl.compute()).size == ncl_bnd
+        assert np.isfinite(e)
 
     def test_constant_rows_and_bnds_matrix(self, client):
         Z = np.stack([np.full((9, 12), i) for i in range(1, 16)])
@@ -182,8 +186,8 @@ class TestTriclustering:
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -191,6 +195,7 @@ class TestTriclustering:
         assert np.unique(row_cl.compute()).size == 1
         assert np.unique(col_cl.compute()).size == ncl_col
         assert np.unique(bnd_cl.compute()).size == 1
+        assert np.isfinite(e)
 
     def test_constant_cols_and_bnds_matrix(self, client):
         Z = np.stack([np.full((9, 15), i) for i in range(1, 13)])
@@ -198,8 +203,8 @@ class TestTriclustering:
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-20, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-20, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -207,18 +212,20 @@ class TestTriclustering:
         assert np.unique(row_cl.compute()).size == ncl_row
         assert np.unique(col_cl.compute()).size == 1
         assert np.unique(bnd_cl.compute()).size == 1
+        assert np.isfinite(e)
 
-    def test_zero_matrix(self, client):
-        # special case for the error - and no nan/inf
-        Z = np.zeros((8, 9, 10))
-        ncl_row = 9
-        ncl_col = 10
-        ncl_bnd = 8
-        epsilon = 1.e-6
-        _, _, _, _, _, error = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, epsilon,
-            row_clusters_init=np.mod(np.arange(9), ncl_row),
-            col_clusters_init=np.mod(np.arange(10), ncl_col),
-            bnd_clusters_init=np.mod(np.arange(8), ncl_bnd)
+    def test_constant_matrix(self, client):
+        Z = np.ones((9, 12, 15)) * 5.
+        ncl_row = 12
+        ncl_col = 15
+        ncl_bnd = 9
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-20, 100,
+            row_clusters_init=np.mod(np.arange(12), ncl_row),
+            col_clusters_init=np.mod(np.arange(15), ncl_col),
+            bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
         )
-        assert np.isclose(error, Z.size*epsilon)
+        assert np.unique(row_cl.compute()).size == 1
+        assert np.unique(col_cl.compute()).size == 1
+        assert np.unique(bnd_cl.compute()).size == 1
+        assert np.isfinite(e)
diff --git a/tests/test_triclustering_numpy.py b/tests/test_triclustering_numpy.py
index b1d6f2e..466ff8b 100644
--- a/tests/test_triclustering_numpy.py
+++ b/tests/test_triclustering_numpy.py
@@ -11,17 +11,17 @@ class TestDistance:
         n = 5
         k = 2  # number of band clusters
         np.random.seed(1234)
-        Z = np.random.randint(100, size=(d, m, n)).astype('float64')
-        Y = np.random.randint(100, size=(k, m, n)).astype('float64')
-        Y[0, 3, 1] = 0.  # setting value to zero should not give infinite
-        epsilon = 1.e-8
-        distance = triclustering_numpy._distance(Z, Y, epsilon)
+        # original matrix can contain zeros..
+        Z = np.random.randint(0, 100, size=(d, m, n)).astype('float64')
+        Y = np.random.randint(1, 100, size=(k, m, n)).astype('float64')
+        distance = triclustering_numpy._distance(Z, Y)
+        print(distance)
         assert distance.shape == (d, k)
         assert ~np.any(np.isinf(distance))
         target = [
-            [-1136.1376816, -2094.30771188],
-            [-542.49375152, -2152.56590639],
-            [-2074.05605694, -1505.49646914]
+            [-2597.79658089, -2653.23155338],
+            [-2249.72483363, -2215.08215482],
+            [-3268.76823711, -3154.50383315],
         ]
         assert np.allclose(distance, target)
 
@@ -56,7 +56,7 @@ class TestTriclustering:
         ncl_col = 4
         ncl_bnd = 2
         conv, niterations, row_cl, col_cl, bnd_cl, error = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=[1, 2, 0, 1],
             col_clusters_init=[1, 0, 3, 2, 0],
             bnd_clusters_init=[1, 0, 0]
@@ -81,8 +81,8 @@ class TestTriclustering:
         ncl_row = 5
         ncl_col = 6
         ncl_bnd = 3
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(nel_row), ncl_row),
             col_clusters_init=np.mod(np.arange(nel_col), ncl_col),
             bnd_clusters_init=np.mod(np.arange(nel_bnd), ncl_bnd)
@@ -93,32 +93,34 @@ class TestTriclustering:
                                       np.arange(ncl_col))
         np.testing.assert_array_equal(np.sort(np.unique(bnd_cl)),
                                       np.arange(ncl_bnd))
+        assert np.isfinite(e)
 
     def test_as_many_clusters_as_elements(self):
         # it should immediately converge (2 iterations)
         ncl_row = 8
         ncl_col = 7
         ncl_bnd = 6
-        Z = np.random.randint(100, size=(ncl_bnd, ncl_row, ncl_col))
-        Z = Z.astype('float64')
-        conv, niterations, _, _, _, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        Z = np.arange(1, ncl_row*ncl_col*ncl_bnd+1)
+        Z = Z.reshape((ncl_bnd, ncl_row, ncl_col)).astype('float64')
+        conv, niterations, _, _, _, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(ncl_row), ncl_row),
             col_clusters_init=np.mod(np.arange(ncl_col), ncl_col),
             bnd_clusters_init=np.mod(np.arange(ncl_bnd), ncl_bnd)
         )
         assert conv
         assert niterations == 2
+        assert np.isfinite(e)
 
     def test_constant_row_matrix(self):
         # should give one cluster in rows
-        Z = np.repeat(np.arange(135).reshape((9, 15)), 12, axis=0)
+        Z = np.repeat(np.arange(1, 136).reshape((9, 15)), 12, axis=0)
         Z = Z.reshape((9, 12, 15))
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -126,15 +128,16 @@ class TestTriclustering:
         assert np.unique(row_cl).size == 1
         assert np.unique(col_cl).size == ncl_col
         assert np.unique(bnd_cl).size == ncl_bnd
+        assert np.isfinite(e)
 
     def test_constant_col_matrix(self):
         # should give one cluster in columns
-        Z = np.repeat(np.arange(108), 15).reshape((9, 12, 15))
+        Z = np.repeat(np.arange(1, 109), 15).reshape((9, 12, 15))
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -142,15 +145,16 @@ class TestTriclustering:
         assert np.unique(row_cl).size == ncl_row
         assert np.unique(col_cl).size == 1
         assert np.unique(bnd_cl).size == ncl_bnd
+        assert np.isfinite(e)
 
     def test_constant_bnd_matrix(self):
         # should give one cluster in bands
-        Z = np.tile(np.arange(180).reshape((12, 15)), (9, 1, 1))
+        Z = np.tile(np.arange(1, 181).reshape((12, 15)), (9, 1, 1))
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -158,14 +162,15 @@ class TestTriclustering:
         assert np.unique(row_cl).size == ncl_row
         assert np.unique(col_cl).size == ncl_col
         assert np.unique(bnd_cl).size == 1
+        assert np.isfinite(e)
 
     def test_constant_rows_and_cols_matrix(self):
         Z = np.stack([np.full((12, 15), i) for i in range(1, 10)])
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -173,6 +178,7 @@ class TestTriclustering:
         assert np.unique(row_cl).size == 1
         assert np.unique(col_cl).size == 1
         assert np.unique(bnd_cl).size == ncl_bnd
+        assert np.isfinite(e)
 
     def test_constant_rows_and_bnds_matrix(self):
         Z = np.stack([np.full((9, 12), i) for i in range(1, 16)])
@@ -180,8 +186,8 @@ class TestTriclustering:
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -189,6 +195,7 @@ class TestTriclustering:
         assert np.unique(row_cl).size == 1
         assert np.unique(col_cl).size == ncl_col
         assert np.unique(bnd_cl).size == 1
+        assert np.isfinite(e)
 
     def test_constant_cols_and_bnds_matrix(self):
         Z = np.stack([np.full((9, 15), i) for i in range(1, 13)])
@@ -196,8 +203,8 @@ class TestTriclustering:
         ncl_row = 12
         ncl_col = 15
         ncl_bnd = 9
-        _, _, row_cl, col_cl, bnd_cl, _ = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-20, 100, 1.e-8,
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-20, 100,
             row_clusters_init=np.mod(np.arange(12), ncl_row),
             col_clusters_init=np.mod(np.arange(15), ncl_col),
             bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
@@ -205,18 +212,20 @@ class TestTriclustering:
         assert np.unique(row_cl).size == ncl_row
         assert np.unique(col_cl).size == 1
         assert np.unique(bnd_cl).size == 1
+        assert np.isfinite(e)
 
-    def test_zero_matrix(self):
-        # special case for the error - and no nan/inf
-        Z = np.zeros((8, 9, 10))
-        ncl_row = 9
-        ncl_col = 10
-        ncl_bnd = 8
-        epsilon = 1.e-6
-        _, _, _, _, _, error = triclustering(
-            Z, ncl_row, ncl_col, ncl_bnd, 1.e-5, 100, epsilon,
-            row_clusters_init=np.mod(np.arange(9), ncl_row),
-            col_clusters_init=np.mod(np.arange(10), ncl_col),
-            bnd_clusters_init=np.mod(np.arange(8), ncl_bnd)
+    def test_constant_matrix(self):
+        Z = np.ones((9, 12, 15)) * 5.
+        ncl_row = 12
+        ncl_col = 15
+        ncl_bnd = 9
+        _, _, row_cl, col_cl, bnd_cl, e = triclustering(
+            Z, ncl_row, ncl_col, ncl_bnd, 1.e-20, 100,
+            row_clusters_init=np.mod(np.arange(12), ncl_row),
+            col_clusters_init=np.mod(np.arange(15), ncl_col),
+            bnd_clusters_init=np.mod(np.arange(9), ncl_bnd)
         )
-        assert np.isclose(error, Z.size*epsilon)
+        assert np.unique(row_cl).size == 1
+        assert np.unique(col_cl).size == 1
+        assert np.unique(bnd_cl).size == 1
+        assert np.isfinite(e)