From be809d7da4ffc3c5a0b708ff199dd61828aac942 Mon Sep 17 00:00:00 2001 From: annoviko Date: Wed, 9 Oct 2019 23:16:12 +0200 Subject: [PATCH] #548: Verify input arguments for algorithm in cluster module. --- pyclustering/cluster/center_initializer.py | 10 ++-- pyclustering/cluster/optics.py | 26 +++++++++- pyclustering/cluster/rock.py | 29 +++++++++-- pyclustering/cluster/silhouette.py | 14 ++++++ pyclustering/cluster/somsc.py | 19 +++++++ pyclustering/cluster/syncnet.py | 16 +++++- .../tests/unit/ut_center_initializer.py | 4 -- pyclustering/cluster/tests/unit/ut_optics.py | 13 ++++- pyclustering/cluster/tests/unit/ut_rock.py | 49 ++++++++++++------- .../cluster/tests/unit/ut_silhouette.py | 9 ++-- pyclustering/cluster/tests/unit/ut_somsc.py | 11 ++++- pyclustering/cluster/tests/unit/ut_syncnet.py | 31 ++++++------ pyclustering/cluster/tests/unit/ut_xmeans.py | 15 ++++-- pyclustering/cluster/xmeans.py | 22 +++++++++ 14 files changed, 206 insertions(+), 62 deletions(-) diff --git a/pyclustering/cluster/center_initializer.py b/pyclustering/cluster/center_initializer.py index e39a4c57..e9e50a11 100755 --- a/pyclustering/cluster/center_initializer.py +++ b/pyclustering/cluster/center_initializer.py @@ -206,16 +206,16 @@ def __check_parameters(self): """ if (self.__amount <= 0) or (self.__amount > len(self.__data)): - raise AttributeError("Amount of cluster centers '" + str(self.__amount) + "' should be at least 1 and " - "should be less or equal to amount of points in data.") + raise ValueError("Amount of cluster centers '" + str(self.__amount) + "' should be at least 1 and " + "should be less or equal to amount of points in data.") if self.__candidates != kmeans_plusplus_initializer.FARTHEST_CENTER_CANDIDATE: if (self.__candidates <= 0) or (self.__candidates > len(self.__data)): - raise AttributeError("Amount of center candidates '" + str(self.__candidates) + "' should be at least 1 " - "and should be less or equal to amount of points in data.") + raise ValueError("Amount of center candidates '" + str(self.__candidates) + "' should be at least 1 " + "and should be less or equal to amount of points in data.") if len(self.__data) == 0: - raise AttributeError("Data is empty.") + raise ValueError("Data is empty.") def __calculate_shortest_distances(self, data, centers): diff --git a/pyclustering/cluster/optics.py b/pyclustering/cluster/optics.py index 81c74505..0d41afe0 100755 --- a/pyclustering/cluster/optics.py +++ b/pyclustering/cluster/optics.py @@ -392,7 +392,7 @@ class optics: """ - def __init__(self, sample, eps, minpts, amount_clusters = None, ccore = True, **kwargs): + def __init__(self, sample, eps, minpts, amount_clusters=None, ccore=True, **kwargs): """! @brief Constructor of clustering algorithm OPTICS. @@ -430,6 +430,8 @@ def __init__(self, sample, eps, minpts, amount_clusters = None, ccore = True, ** if self.__ccore: self.__ccore = ccore_library.workable() + self.__verify_arguments() + def process(self): """! @@ -772,4 +774,24 @@ def __neighbor_indexes_distance_matrix(self, optic_object): """ distances = self.__sample_pointer[optic_object.index_object] return [[index_neighbor, distances[index_neighbor]] for index_neighbor in range(len(distances)) - if ((distances[index_neighbor] <= self.__eps) and (index_neighbor != optic_object.index_object))] \ No newline at end of file + if ((distances[index_neighbor] <= self.__eps) and (index_neighbor != optic_object.index_object))] + + + def __verify_arguments(self): + """! + @brief Verify input parameters for the algorithm and throw exception in case of incorrectness. + + """ + if len(self.__sample_pointer) == 0: + raise ValueError("Input data is empty (size: '%d')." % len(self.__sample_pointer)) + + if self.__eps < 0: + raise ValueError("Connectivity radius (current value: '%d') should be greater or equal to 0." % self.__eps) + + if self.__minpts < 0: + raise ValueError("Minimum number of neighbors (current value: '%d') should be greater than 0." % + self.__minpts) + + if (self.__amount_clusters is not None) and (self.__amount_clusters <= 0): + raise ValueError("Amount of clusters (current value: '%d') should be greater than 0." % + self.__amount_clusters) diff --git a/pyclustering/cluster/rock.py b/pyclustering/cluster/rock.py index 9bbcca27..4f92ba47 100755 --- a/pyclustering/cluster/rock.py +++ b/pyclustering/cluster/rock.py @@ -87,13 +87,15 @@ def __init__(self, data, eps, number_clusters, threshold=0.5, ccore=True): self.__ccore = ccore if self.__ccore: self.__ccore = ccore_library.workable() - + + self.__verify_arguments() + self.__degree_normalization = 1.0 + 2.0 * ((1.0 - threshold) / (1.0 + threshold)) - + self.__adjacency_matrix = None self.__create_adjacency_matrix() - - + + def process(self): """! @brief Performs cluster analysis in line with rules of ROCK algorithm. @@ -229,3 +231,22 @@ def __calculate_goodness(self, cluster1, cluster2): devider = (len(cluster1) + len(cluster2)) ** self.__degree_normalization - len(cluster1) ** self.__degree_normalization - len(cluster2) ** self.__degree_normalization return number_links / devider + + + def __verify_arguments(self): + """! + @brief Verify input parameters for the algorithm and throw exception in case of incorrectness. + + """ + if len(self.__pointer_data) == 0: + raise ValueError("Input data is empty (size: '%d')." % len(self.__pointer_data)) + + if self.__eps < 0: + raise ValueError("Connectivity radius (current value: '%d') should be greater or equal to 0." % self.__eps) + + if self.__threshold < 0 or self.__threshold > 1: + raise ValueError("Threshold (current value: '%d') should be in range (0, 1)." % self.__threshold) + + if (self.__number_clusters is not None) and (self.__number_clusters <= 0): + raise ValueError("Amount of clusters (current value: '%d') should be greater than 0." % + self.__number_clusters) diff --git a/pyclustering/cluster/silhouette.py b/pyclustering/cluster/silhouette.py index 670481e9..0e9502ed 100755 --- a/pyclustering/cluster/silhouette.py +++ b/pyclustering/cluster/silhouette.py @@ -117,6 +117,8 @@ def __init__(self, data, clusters, **kwargs): if self.__ccore is False: self.__data = numpy.array(data) + self.__verify_arguments() + def process(self): """! @@ -273,6 +275,18 @@ def __calculate_dataset_difference(self, index_point): return dataset_differences + def __verify_arguments(self): + """! + @brief Verify input parameters for the algorithm and throw exception in case of incorrectness. + + """ + if len(self.__data) == 0: + raise ValueError("Input data is empty (size: '%d')." % len(self.__data)) + + if len(self.__clusters) == 0: + raise ValueError("Input clusters are empty (size: '%d')." % len(self.__clusters)) + + class silhouette_ksearch_type(IntEnum): """! diff --git a/pyclustering/cluster/somsc.py b/pyclustering/cluster/somsc.py index 3b45d15a..b92caa44 100755 --- a/pyclustering/cluster/somsc.py +++ b/pyclustering/cluster/somsc.py @@ -83,6 +83,8 @@ def __init__(self, data, amount_clusters, epouch=100, ccore=True): self.__network = None + self.__verify_arguments() + def process(self): """! @@ -121,3 +123,20 @@ def get_cluster_encoding(self): """ return type_encoding.CLUSTER_INDEX_LIST_SEPARATION + + + def __verify_arguments(self): + """! + @brief Verify input parameters for the algorithm and throw exception in case of incorrectness. + + """ + if len(self.__data_pointer) == 0: + raise ValueError("Input data is empty (size: '%d')." % len(self.__data_pointer)) + + if self.__amount_clusters <= 0: + raise ValueError("Amount of clusters (current value: '%d') should be greater than 0." % + self.__amount_clusters) + + if self.__epouch < 0: + raise ValueError("Amount of epouch (current value: '%d') should be greater or equal to 0." % + self.__epouch) diff --git a/pyclustering/cluster/syncnet.py b/pyclustering/cluster/syncnet.py index 4cdbd73f..51144360 100755 --- a/pyclustering/cluster/syncnet.py +++ b/pyclustering/cluster/syncnet.py @@ -198,7 +198,8 @@ class syncnet(sync_network): """ - def __init__(self, sample, radius, conn_repr = conn_represent.MATRIX, initial_phases = initial_type.RANDOM_GAUSSIAN, enable_conn_weight = False, ccore = True): + def __init__(self, sample, radius, conn_repr=conn_represent.MATRIX, initial_phases=initial_type.RANDOM_GAUSSIAN, + enable_conn_weight=False, ccore=True): """! @brief Contructor of the oscillatory network SYNC for cluster analysis. @@ -215,7 +216,9 @@ def __init__(self, sample, radius, conn_repr = conn_represent.MATRIX, initial_ph self._ccore_network_pointer = None self._osc_loc = sample self._num_osc = len(sample) - + + self._verify_arguments() + if (ccore is True) and ccore_library.workable(): self._ccore_network_pointer = syncnet_create_network(sample, radius, initial_phases, enable_conn_weight) @@ -244,6 +247,15 @@ def __del__(self): self._ccore_network_pointer = None + def _verify_arguments(self): + """! + @brief Verify input parameters for the algorithm and throw exception in case of incorrectness. + + """ + if self._num_osc <= 0: + raise ValueError("Input data is empty (size: '%d')." % self._num_osc) + + def _create_connections(self, radius): """! @brief Create connections between oscillators in line with input radius of connectivity. diff --git a/pyclustering/cluster/tests/unit/ut_center_initializer.py b/pyclustering/cluster/tests/unit/ut_center_initializer.py index 6efb4188..2a2bef76 100755 --- a/pyclustering/cluster/tests/unit/ut_center_initializer.py +++ b/pyclustering/cluster/tests/unit/ut_center_initializer.py @@ -320,7 +320,3 @@ def templateKmeansPlusPlusVariousCentersSimple02(self): def templateKmeansPlusPlusVariousCentersSimple03(self): self.templateKmeansPlusPlusSeveralRuns(SIMPLE_SAMPLES.SAMPLE_SIMPLE2, 4, 1) self.templateKmeansPlusPlusSeveralRuns(SIMPLE_SAMPLES.SAMPLE_SIMPLE2, 8, 1) - - -if __name__ == "__main__": - unittest.main() diff --git a/pyclustering/cluster/tests/unit/ut_optics.py b/pyclustering/cluster/tests/unit/ut_optics.py index bb200670..a144d669 100755 --- a/pyclustering/cluster/tests/unit/ut_optics.py +++ b/pyclustering/cluster/tests/unit/ut_optics.py @@ -170,5 +170,14 @@ def testImpossibleClusterOrderingAllocationGeterogeneous(self): assert 0 == len(borders) -if __name__ == "__main__": - unittest.main() \ No newline at end of file + def test_incorrect_data(self): + self.assertRaises(ValueError, optics, [], 0.1, 1) + + def test_incorrect_eps(self): + self.assertRaises(ValueError, optics, [[0], [1], [2]], -1.0, 1) + + def test_incorrect_minpts(self): + self.assertRaises(ValueError, optics, [[0], [1], [2]], 0.5, -1) + + def test_incorrect_amount_clusters(self): + self.assertRaises(ValueError, optics, [[0], [1], [2]], 0.5, 1, amount_clusters=-1) diff --git a/pyclustering/cluster/tests/unit/ut_rock.py b/pyclustering/cluster/tests/unit/ut_rock.py index e1403458..0b74b42c 100755 --- a/pyclustering/cluster/tests/unit/ut_rock.py +++ b/pyclustering/cluster/tests/unit/ut_rock.py @@ -24,51 +24,62 @@ """ -import unittest; +import unittest # Generate images without having a window appear. -import matplotlib; -matplotlib.use('Agg'); +import matplotlib +matplotlib.use('Agg') -from pyclustering.cluster.tests.rock_templates import RockTestTemplates; +from pyclustering.cluster.rock import rock +from pyclustering.cluster.tests.rock_templates import RockTestTemplates -from pyclustering.samples.definitions import SIMPLE_SAMPLES; +from pyclustering.samples.definitions import SIMPLE_SAMPLES class RockUnitTest(unittest.TestCase): def testClusterAllocationSampleSimple1(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE1, 1, 2, 0.5, [5, 5], False); - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE1, 5, 1, 0.5, [10], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE1, 1, 2, 0.5, [5, 5], False) + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE1, 5, 1, 0.5, [10], False) def testClusterAllocationSampleSimple2(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE2, 1, 3, 0.5, [10, 5, 8], False); - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE2, 5, 1, 0.5, [23], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE2, 1, 3, 0.5, [10, 5, 8], False) + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE2, 5, 1, 0.5, [23], False) def testClusterAllocationSampleSimple3(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE3, 1, 4, 0.5, [10, 10, 10, 30], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE3, 1, 4, 0.5, [10, 10, 10, 30], False) def testClusterAllocationSampleSimple3WrongRadius(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE3, 1.7, 4, 0.5, [10, 10, 10, 30], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE3, 1.7, 4, 0.5, [10, 10, 10, 30], False) def testClusterAllocationSampleSimple4(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE4, 1, 5, 0.5, [15, 15, 15, 15, 15], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE4, 1, 5, 0.5, [15, 15, 15, 15, 15], False) def testClusterAllocationSampleSimple4WrongRadius(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE4, 1.5, 5, 0.5, [15, 15, 15, 15, 15], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE4, 1.5, 5, 0.5, [15, 15, 15, 15, 15], False) def testClusterAllocationSampleSimple5(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE5, 1, 4, 0.5, [15, 15, 15, 15], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE5, 1, 4, 0.5, [15, 15, 15, 15], False) def testClusterTheSameData1(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE9, 1, 2, 0.5, [10, 20], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE9, 1, 2, 0.5, [10, 20], False) def testClusterTheSameData2(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE12, 1, 2, 0.5, [5, 5, 5], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE12, 1, 2, 0.5, [5, 5, 5], False) def testClusterAllocationIncorrectNumberClusters(self): - RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE4, 1, 4, 0.5, [15, 15, 15, 15, 15], False); + RockTestTemplates.templateLengthProcessData(SIMPLE_SAMPLES.SAMPLE_SIMPLE4, 1, 4, 0.5, [15, 15, 15, 15, 15], False) -if __name__ == "__main__": - unittest.main(); \ No newline at end of file + def test_incorrect_data(self): + self.assertRaises(ValueError, rock, [], 0.1, 2) + + def test_incorrect_eps(self): + self.assertRaises(ValueError, rock, [[0], [1], [2]], -1.0, 2) + + def test_incorrect_minpts(self): + self.assertRaises(ValueError, rock, [[0], [1], [2]], 0.5, 0) + + def test_incorrect_amount_clusters(self): + self.assertRaises(ValueError, rock, [[0], [1], [2]], 0.5, 1, -0.1) + self.assertRaises(ValueError, rock, [[0], [1], [2]], 0.5, 1, 1.1) diff --git a/pyclustering/cluster/tests/unit/ut_silhouette.py b/pyclustering/cluster/tests/unit/ut_silhouette.py index 47b00db2..b1f26457 100755 --- a/pyclustering/cluster/tests/unit/ut_silhouette.py +++ b/pyclustering/cluster/tests/unit/ut_silhouette.py @@ -30,7 +30,7 @@ import matplotlib matplotlib.use('Agg') -from pyclustering.cluster.silhouette import silhouette_ksearch_type +from pyclustering.cluster.silhouette import silhouette, silhouette_ksearch_type from pyclustering.cluster.tests.silhouette_templates import silhouette_test_template from pyclustering.samples.definitions import SIMPLE_SAMPLES, SIMPLE_ANSWERS @@ -163,5 +163,8 @@ def test_distance_matrix_sample07(self): SIMPLE_ANSWERS.ANSWER_SIMPLE7, False) -if __name__ == "__main__": - unittest.main() + def test_incorrect_data(self): + self.assertRaises(ValueError, silhouette, [], [[1, 2], [3, 4]]) + + def test_incorrect_clusters(self): + self.assertRaises(ValueError, silhouette, [[1], [2], [3], [4]], []) diff --git a/pyclustering/cluster/tests/unit/ut_somsc.py b/pyclustering/cluster/tests/unit/ut_somsc.py index 8a02925d..2894d228 100755 --- a/pyclustering/cluster/tests/unit/ut_somsc.py +++ b/pyclustering/cluster/tests/unit/ut_somsc.py @@ -30,6 +30,7 @@ import matplotlib matplotlib.use('Agg') +from pyclustering.cluster.somsc import somsc from pyclustering.cluster.tests.somsc_templates import SyncnetTestTemplates from pyclustering.samples.definitions import SIMPLE_SAMPLES @@ -88,5 +89,11 @@ def testClusterAllocationOneDimensionData(self): SyncnetTestTemplates.templateClusterAllocationOneDimensionData(False) -if __name__ == "__main__": - unittest.main() \ No newline at end of file + def test_incorrect_data(self): + self.assertRaises(ValueError, somsc, [], 1, 1) + + def test_incorrect_epouch(self): + self.assertRaises(ValueError, somsc, [[0], [1], [2]], 1, -1) + + def test_incorrect_amount_clusters(self): + self.assertRaises(ValueError, somsc, [[0], [1], [2]], 0, 1) diff --git a/pyclustering/cluster/tests/unit/ut_syncnet.py b/pyclustering/cluster/tests/unit/ut_syncnet.py index f64b5eb3..c1b30c70 100755 --- a/pyclustering/cluster/tests/unit/ut_syncnet.py +++ b/pyclustering/cluster/tests/unit/ut_syncnet.py @@ -23,23 +23,23 @@ """ -import unittest; +import unittest # Generate images without having a window appear. -import matplotlib; -matplotlib.use('Agg'); +import matplotlib +matplotlib.use('Agg') -from pyclustering.nnet import initial_type, conn_represent, solve_type; +from pyclustering.nnet import initial_type, conn_represent, solve_type -from pyclustering.cluster.tests.syncnet_templates import SyncnetTestTemplates; -from pyclustering.cluster.syncnet import syncnet, syncnet_visualizer; +from pyclustering.cluster.tests.syncnet_templates import SyncnetTestTemplates +from pyclustering.cluster.syncnet import syncnet, syncnet_visualizer -from pyclustering.utils import read_sample; +from pyclustering.utils import read_sample -from numpy import pi; +from numpy import pi -from pyclustering.samples.definitions import SIMPLE_SAMPLES; +from pyclustering.samples.definitions import SIMPLE_SAMPLES class SyncnetUnitTest(unittest.TestCase): @@ -149,12 +149,11 @@ def testConnectionApi(self): def testVisualizerNoFailure(self): - sample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE1); - network = syncnet(sample, 1.0, ccore = False); + sample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE1) + network = syncnet(sample, 1.0, ccore=False) - analyser = network.simulate(25, 5, solve_type.FAST, True); - syncnet_visualizer.animate_cluster_allocation(sample, analyser); + analyser = network.simulate(25, 5, solve_type.FAST, True) + syncnet_visualizer.animate_cluster_allocation(sample, analyser) - -if __name__ == "__main__": - unittest.main(); \ No newline at end of file + def test_incorrect_data(self): + self.assertRaises(ValueError, syncnet, [], 0.5) diff --git a/pyclustering/cluster/tests/unit/ut_xmeans.py b/pyclustering/cluster/tests/unit/ut_xmeans.py index bd3ae4c9..e95c140a 100755 --- a/pyclustering/cluster/tests/unit/ut_xmeans.py +++ b/pyclustering/cluster/tests/unit/ut_xmeans.py @@ -31,7 +31,7 @@ matplotlib.use('Agg') from pyclustering.cluster.tests.xmeans_templates import XmeansTestTemplates -from pyclustering.cluster.xmeans import splitting_type +from pyclustering.cluster.xmeans import xmeans, splitting_type from pyclustering.samples.definitions import SIMPLE_SAMPLES, FCPS_SAMPLES @@ -215,5 +215,14 @@ def testPredictTwoPoints(self): XmeansTestTemplates.templatePredict(SIMPLE_SAMPLES.SAMPLE_SIMPLE3, centers, [[2.1, 4.1], [2.1, 1.9]], 4, [3, 2], False) -if __name__ == "__main__": - unittest.main() \ No newline at end of file + def test_incorrect_data(self): + self.assertRaises(ValueError, xmeans, []) + + def test_incorrect_centers(self): + self.assertRaises(ValueError, xmeans, [[0], [1], [2]], []) + + def test_incorrect_tolerance(self): + self.assertRaises(ValueError, xmeans, [[0], [1], [2]], [[1]], 20, -1) + + def test_incorrect_repeat(self): + self.assertRaises(ValueError, xmeans, [[0], [1], [2]], repeat=0) diff --git a/pyclustering/cluster/xmeans.py b/pyclustering/cluster/xmeans.py index a4d1ce4a..668abd58 100755 --- a/pyclustering/cluster/xmeans.py +++ b/pyclustering/cluster/xmeans.py @@ -163,6 +163,8 @@ def __init__(self, data, initial_centers=None, kmax=20, tolerance=0.025, criteri if self.__ccore: self.__ccore = ccore_library.workable() + self.__verify_arguments() + def process(self): """! @@ -583,3 +585,23 @@ def __bayesian_information_criterion(self, clusters, centers): scores[index_cluster] = L - p * 0.5 * log(N) return sum(scores) + + + def __verify_arguments(self): + """! + @brief Verify input parameters for the algorithm and throw exception in case of incorrectness. + + """ + if len(self.__pointer_data) == 0: + raise ValueError("Input data is empty (size: '%d')." % len(self.__pointer_data)) + + if len(self.__centers) == 0: + raise ValueError("Initial centers are empty (size: '%d')." % len(self.__pointer_data)) + + if self.__tolerance < 0: + raise ValueError("Tolerance (current value: '%d') should be greater or equal to 0." % + self.__tolerance) + + if self.__repeat <= 0: + raise ValueError("Repeat (current value: '%d') should be greater than 0." % + self.__repeat)