{
    "file_diffs": [
        {
            "old_file_content": "from concurrent.futures import Future\nfrom typing import Optional, List, Dict\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom AnyQt.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QThread, \\\n    pyqtSlot as Slot\nfrom AnyQt.QtGui import QIntValidator\nfrom AnyQt.QtWidgets import QGridLayout, QTableView\nfrom sklearn.metrics import silhouette_samples, silhouette_score\n\nfrom Orange.clustering import KMeans\nfrom Orange.clustering.kmeans import KMeansModel\nfrom Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable\nfrom Orange.data.util import get_unique_names, array_equal\nfrom Orange.preprocess import Normalize\nfrom Orange.preprocess.impute import ReplaceUnknowns\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.annotated_data import \\\n    ANNOTATED_DATA_SIGNAL_NAME, add_columns\nfrom Orange.widgets.utils.concurrent import ThreadExecutor, FutureSetWatcher\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.widget import Input, Output\n\n\nRANDOM_STATE = 0\nSILHOUETTE_MAX_SAMPLES = 5000\n\n\nclass ClusterTableModel(QAbstractTableModel):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.scores = []\n        self.start_k = 0\n\n    def rowCount(self, index=QModelIndex()):\n        return 0 if index.isValid() else len(self.scores)\n\n    @staticmethod\n    def columnCount(_index=QModelIndex()):\n        return 1\n\n    def flags(self, index):\n        if isinstance(self.scores[index.row()], str):\n            return Qt.NoItemFlags\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def set_scores(self, scores, start_k):\n        self.modelAboutToBeReset.emit()\n        self.scores = scores\n        self.start_k = start_k\n        self.modelReset.emit()\n\n    def clear_scores(self):\n        self.modelAboutToBeReset.emit()\n        self.scores = []\n        self.start_k = 0\n        self.modelReset.emit()\n\n    def data(self, index, role=Qt.DisplayRole):\n        score = self.scores[index.row()]\n        valid = not isinstance(score, str)\n        if role == Qt.DisplayRole:\n            return \"{:.3f}\".format(score) if valid else \"NA\"\n        elif role == Qt.TextAlignmentRole:\n            return Qt.AlignVCenter | Qt.AlignLeft\n        elif role == Qt.ToolTipRole and not valid:\n            return score\n        elif role == gui.BarRatioRole and valid:\n            return score\n        return None\n\n    def headerData(self, row, _orientation, role=Qt.DisplayRole):\n        if role == Qt.DisplayRole:\n            return str(row + self.start_k)\n        return None\n\n\nclass Task:\n    futures = []    # type: List[Future]\n    watcher = ...   # type: FutureSetWatcher\n    cancelled = False\n\n    def __init__(self, futures, watcher):\n        self.futures = futures\n        self.watcher = watcher\n\n    def cancel(self):\n        self.cancelled = True\n        for f in self.futures:\n            f.cancel()\n\n\nclass NotEnoughData(ValueError):\n    pass\n\n\nclass OWKMeans(widget.OWWidget):\n    name = \"k-Means\"\n    description = \"k-Means clustering algorithm with silhouette-based \" \\\n                  \"quality estimation.\"\n    icon = \"icons/KMeans.svg\"\n    priority = 2100\n    keywords = [\"kmeans\", \"clustering\"]\n\n    class Inputs:\n        data = Input(\"Data\", Table)\n\n    class Outputs:\n        annotated_data = Output(\n            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,\n            replaces=[\"Annotated Data\"]\n        )\n        centroids = Output(\"Centroids\", Table)\n\n    class Error(widget.OWWidget.Error):\n        failed = widget.Msg(\"Clustering failed\\nError: {}\")\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_attributes = widget.Msg(\"Data is missing features.\")\n\n    class Warning(widget.OWWidget.Warning):\n        no_silhouettes = widget.Msg(\n            \"Silhouette scores are not computed for >{} samples\".format(\n                SILHOUETTE_MAX_SAMPLES)\n        )\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_sparse_normalization = widget.Msg(\"Sparse data cannot be normalized\")\n\n    INIT_METHODS = ((\"Initialize with KMeans++\", \"k-means++\"),\n                    (\"Random initialization\", \"random\"))\n\n    resizing_enabled = False\n    buttons_area_orientation = Qt.Vertical\n\n    k = Setting(3)\n    k_from = Setting(2)\n    k_to = Setting(8)\n    optimize_k = Setting(False)\n    max_iterations = Setting(300)\n    n_init = Setting(10)\n    smart_init = Setting(0)  # KMeans++\n    selection = Setting(None, schema_only=True)  # type: Optional[int]\n    auto_commit = Setting(True)\n    normalize = Setting(True)\n\n    settings_version = 2\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        # type: (Dict, int) -> None\n        if version < 2:\n            if 'auto_apply' in settings:\n                settings['auto_commit'] = settings.get('auto_apply', True)\n                settings.pop('auto_apply', None)\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None  # type: Optional[Table]\n        self.__pending_selection = self.selection  # type: Optional[int]\n        self.clusterings = {}\n\n        self.__executor = ThreadExecutor(parent=self)\n        self.__task = None  # type: Optional[Task]\n\n        self._set_input_summary()\n        self._set_output_summary(None)\n\n        layout = QGridLayout()\n        bg = gui.radioButtonsInBox(\n            self.controlArea, self, \"optimize_k\", orientation=layout,\n            box=\"Number of Clusters\", callback=self.update_method,\n        )\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"Fixed:\", addToLayout=False), 1, 1)\n        sb = gui.hBox(None, margin=0)\n        gui.spin(\n            sb, self, \"k\", minv=2, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)\n        gui.rubber(sb)\n        layout.addWidget(sb, 1, 2)\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"From\", addToLayout=False), 2, 1)\n        ftobox = gui.hBox(None)\n        ftobox.layout().setContentsMargins(0, 0, 0, 0)\n        layout.addWidget(ftobox, 2, 2)\n        gui.spin(\n            ftobox, self, \"k_from\", minv=2, maxv=29,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_from)\n        gui.widgetLabel(ftobox, \"to\")\n        gui.spin(\n            ftobox, self, \"k_to\", minv=3, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_to)\n        gui.rubber(ftobox)\n\n        box = gui.vBox(self.controlArea, \"Preprocessing\")\n        gui.checkBox(box, self, \"normalize\", \"Normalize columns\",\n                     callback=self.invalidate)\n\n        box = gui.vBox(self.controlArea, \"Initialization\")\n        gui.comboBox(\n            box, self, \"smart_init\", items=[m[0] for m in self.INIT_METHODS],\n            callback=self.invalidate)\n\n        layout = QGridLayout()\n        gui.widgetBox(box, orientation=layout)\n        layout.addWidget(gui.widgetLabel(None, \"Re-runs: \"), 0, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 0, 1)\n        gui.lineEdit(\n            sb, self, \"n_init\", controlWidth=60,\n            valueType=int, validator=QIntValidator(), callback=self.invalidate)\n        layout.addWidget(\n            gui.widgetLabel(None, \"Maximum iterations: \"), 1, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 1, 1)\n        gui.lineEdit(\n            sb, self, \"max_iterations\", controlWidth=60, valueType=int,\n            validator=QIntValidator(), callback=self.invalidate)\n\n        self.apply_button = gui.auto_apply(self.buttonsArea, self, \"auto_commit\", box=None,\n                                           commit=self.commit)\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.mainArea, box=\"Silhouette Scores\")\n        self.mainArea.setVisible(self.optimize_k)\n        self.table_model = ClusterTableModel(self)\n        table = self.table_view = QTableView(self.mainArea)\n        table.setModel(self.table_model)\n        table.setSelectionMode(QTableView.SingleSelection)\n        table.setSelectionBehavior(QTableView.SelectRows)\n        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))\n        table.selectionModel().selectionChanged.connect(self.select_row)\n        table.setMaximumWidth(200)\n        table.horizontalHeader().setStretchLastSection(True)\n        table.horizontalHeader().hide()\n        table.setShowGrid(False)\n        box.layout().addWidget(table)\n\n    def adjustSize(self):\n        self.ensurePolished()\n        s = self.sizeHint()\n        self.resize(s)\n\n    def update_method(self):\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_k(self):\n        self.optimize_k = False\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_from(self):\n        self.k_to = max(self.k_from + 1, self.k_to)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_to(self):\n        self.k_from = min(self.k_from, self.k_to - 1)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def enough_data_instances(self, k):\n        \"\"\"k cannot be larger than the number of data instances.\"\"\"\n        return len(self.data) >= k\n\n    @property\n    def has_attributes(self):\n        return len(self.data.domain.attributes)\n\n    @staticmethod\n    def _compute_clustering(data, k, init, n_init, max_iter, random_state):\n        # type: (Table, int, str, int, int, bool) -> KMeansModel\n        if k > len(data):\n            raise NotEnoughData()\n\n        model = KMeans(\n            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,\n            random_state=random_state, preprocessors=[]\n        ).get_model(data)\n\n        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:\n            model.silhouette_samples = silhouette_samples(data.X, model.labels)\n            model.silhouette = np.mean(model.silhouette_samples)\n        else:\n            model.silhouette_samples = None\n            model.silhouette = \\\n                silhouette_score(data.X, model.labels,\n                                 sample_size=SILHOUETTE_MAX_SAMPLES,\n                                 random_state=RANDOM_STATE)\n\n        return model\n\n    @Slot(int, int)\n    def __progress_changed(self, n, d):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        self.progressBarSet(100 * n / d)\n\n    @Slot(int, Exception)\n    def __on_exception(self, idx, ex):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        if isinstance(ex, NotEnoughData):\n            self.Error.not_enough_data(len(self.data), self.k_from + idx)\n\n        # Only show failed message if there is only 1 k to compute\n        elif not self.optimize_k:\n            self.Error.failed(str(ex))\n\n        self.clusterings[self.k_from + idx] = str(ex)\n\n    @Slot(int, object)\n    def __clustering_complete(self, _, result):\n        # type: (int, KMeansModel) -> None\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        self.clusterings[result.k] = result\n\n    @Slot()\n    def __commit_finished(self):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        assert self.data is not None\n\n        self.__task = None\n        self.setInvalidated(False)\n        self.progressBarFinished()\n\n        if self.optimize_k:\n            self.update_results()\n\n        if self.optimize_k and all(isinstance(self.clusterings[i], str)\n                                   for i in range(self.k_from, self.k_to + 1)):\n            # Show the error of the last clustering\n            self.Error.failed(self.clusterings[self.k_to])\n\n        self.send_data()\n\n    def __launch_tasks(self, ks):\n        # type: (List[int]) -> None\n        \"\"\"Execute clustering in separate threads for all given ks.\"\"\"\n        preprocessed_data = self.preproces(self.data)\n        futures = [self.__executor.submit(\n            self._compute_clustering,\n            data=preprocessed_data,\n            k=k,\n            init=self.INIT_METHODS[self.smart_init][1],\n            n_init=self.n_init,\n            max_iter=self.max_iterations,\n            random_state=RANDOM_STATE,\n        ) for k in ks]\n        watcher = FutureSetWatcher(futures)\n        watcher.resultReadyAt.connect(self.__clustering_complete)\n        watcher.progressChanged.connect(self.__progress_changed)\n        watcher.exceptionReadyAt.connect(self.__on_exception)\n        watcher.doneAll.connect(self.__commit_finished)\n\n        self.__task = Task(futures, watcher)\n        self.progressBarInit()\n        self.setInvalidated(True)\n\n    def cancel(self):\n        if self.__task is not None:\n            task, self.__task = self.__task, None\n            task.cancel()\n\n            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)\n            task.watcher.progressChanged.disconnect(self.__progress_changed)\n            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)\n            task.watcher.doneAll.disconnect(self.__commit_finished)\n\n            self.progressBarFinished()\n            self.setInvalidated(False)\n\n    def run_optimization(self):\n        if not self.enough_data_instances(self.k_from):\n            self.Error.not_enough_data(len(self.data), self.k_from)\n            return\n\n        if not self.enough_data_instances(self.k_to):\n            self.Warning.not_enough_data(len(self.data), self.k_to)\n            return\n\n        needed_ks = [k for k in range(self.k_from, self.k_to + 1)\n                     if k not in self.clusterings]\n\n        if needed_ks:\n            self.__launch_tasks(needed_ks)\n        else:\n            # If we don't need to recompute anything, just set the results to\n            # what they were before\n            self.update_results()\n\n    def cluster(self):\n        # Check if the k already has a computed clustering\n        if self.k in self.clusterings:\n            self.send_data()\n            return\n\n        # Check if there is enough data\n        if not self.enough_data_instances(self.k):\n            self.Error.not_enough_data(len(self.data), self.k)\n            return\n\n        self.__launch_tasks([self.k])\n\n    def commit(self):\n        self.cancel()\n        self.clear_messages()\n\n        # Some time may pass before the new scores are computed, so clear the\n        # old scores to avoid potential confusion. Hiding the mainArea could\n        # cause flickering when the clusters are computed quickly, so this is\n        # the better alternative\n        self.table_model.clear_scores()\n        self.mainArea.setVisible(self.optimize_k and self.data is not None and\n                                 self.has_attributes)\n\n        if self.data is None:\n            self.send_data()\n            return\n\n        if not self.has_attributes:\n            self.Error.no_attributes()\n            self.send_data()\n            return\n\n        if self.optimize_k:\n            self.run_optimization()\n        else:\n            self.cluster()\n\n        QTimer.singleShot(100, self.adjustSize)\n\n    def invalidate(self, unconditional=False):\n        self.cancel()\n        self.Error.clear()\n        self.Warning.clear()\n        self.clusterings = {}\n        self.table_model.clear_scores()\n\n        if unconditional:\n            self.unconditional_commit()\n        else:\n            self.commit()\n\n    def update_results(self):\n        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in\n                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]\n        best_row = max(\n            range(len(scores)), default=0,\n            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]\n        )\n        self.table_model.set_scores(scores, self.k_from)\n        self.apply_selection(best_row)\n        self.table_view.setFocus(Qt.OtherFocusReason)\n        self.table_view.resizeRowsToContents()\n\n    def apply_selection(self, best_row):\n        pending = best_row\n        if self.__pending_selection is not None:\n            pending = self.__pending_selection\n            self.__pending_selection = None\n        self.table_view.selectRow(pending)\n\n    def selected_row(self):\n        indices = self.table_view.selectedIndexes()\n        if not indices:\n            return None\n        return indices[0].row()\n\n    def select_row(self):\n        self.selection = self.selected_row()\n        self.send_data()\n\n    def preproces(self, data):\n        if self.normalize:\n            if sp.issparse(data.X):\n                self.Warning.no_sparse_normalization()\n            else:\n                data = Normalize()(data)\n        for preprocessor in KMeans.preprocessors:  # use same preprocessors than\n            data = preprocessor(data)\n        return data\n\n    def send_data(self):\n        if self.optimize_k:\n            row = self.selected_row()\n            k = self.k_from + row if row is not None else None\n        else:\n            k = self.k\n\n        km = self.clusterings.get(k)\n        if self.data is None or km is None or isinstance(km, str):\n            self._set_output_summary(None)\n            self.Outputs.annotated_data.send(None)\n            self.Outputs.centroids.send(None)\n            return\n\n        domain = self.data.domain\n        cluster_var = DiscreteVariable(\n            get_unique_names(domain, \"Cluster\"),\n            values=[\"C%d\" % (x + 1) for x in range(km.k)]\n        )\n        clust_ids = km.labels\n        silhouette_var = ContinuousVariable(\n            get_unique_names(domain, \"Silhouette\"))\n        if km.silhouette_samples is not None:\n            self.Warning.no_silhouettes.clear()\n            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5\n            clust_scores = []\n            for i in range(km.k):\n                in_clust = clust_ids == i\n                if in_clust.any():\n                    clust_scores.append(np.mean(scores[in_clust]))\n                else:\n                    clust_scores.append(0.)\n            clust_scores = np.atleast_2d(clust_scores).T\n        else:\n            self.Warning.no_silhouettes()\n            scores = np.nan\n            clust_scores = np.full((km.k, 1), np.nan)\n\n        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])\n        new_table = self.data.transform(new_domain)\n        new_table.get_column_view(cluster_var)[0][:] = clust_ids\n        new_table.get_column_view(silhouette_var)[0][:] = scores\n\n        domain_attributes = set(domain.attributes)\n        centroid_attributes = [\n            attr.compute_value.variable\n            if isinstance(attr.compute_value, ReplaceUnknowns)\n            and attr.compute_value.variable in domain_attributes\n            else attr\n            for attr in km.domain.attributes]\n        centroid_domain = add_columns(\n            Domain(centroid_attributes, [], domain.metas),\n            metas=[cluster_var, silhouette_var])\n        centroids = Table(\n            centroid_domain, km.centroids, None,\n            np.hstack((np.full((km.k, len(domain.metas)), np.nan),\n                       np.arange(km.k).reshape(km.k, 1),\n                       clust_scores))\n        )\n        if self.data.name == Table.name:\n            centroids.name = \"centroids\"\n        else:\n            centroids.name = f\"{self.data.name} centroids\"\n\n        self._set_output_summary(new_table)\n        self.Outputs.annotated_data.send(new_table)\n        self.Outputs.centroids.send(centroids)\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data):\n        self.data, old_data = data, self.data\n        self.selection = None\n        self._set_input_summary()\n\n        # Do not needlessly recluster the data if X hasn't changed\n        if old_data and self.data and array_equal(self.data.X, old_data.X):\n            if self.auto_commit:\n                self.send_data()\n        else:\n            self.invalidate(unconditional=True)\n\n    def _set_input_summary(self):\n        summary = len(self.data) if self.data else self.info.NoInput\n        details = format_summary_details(self.data) if self.data else \"\"\n        self.info.set_input_summary(summary, details)\n\n    def _set_output_summary(self, output):\n        summary = len(output) if output else self.info.NoOutput\n        details = format_summary_details(output) if output else \"\"\n        self.info.set_output_summary(summary, details)\n\n    def send_report(self):\n        # False positives (Setting is not recognized as int)\n        # pylint: disable=invalid-sequence-index\n        if self.optimize_k and self.selected_row() is not None:\n            k_clusters = self.k_from + self.selected_row()\n        else:\n            k_clusters = self.k\n        init_method = self.INIT_METHODS[self.smart_init][0]\n        init_method = init_method[0].lower() + init_method[1:]\n        self.report_items((\n            (\"Number of clusters\", k_clusters),\n            (\"Optimization\", \"{}, {} re-runs limited to {} steps\".format(\n                init_method, self.n_init, self.max_iterations))))\n        if self.data is not None:\n            self.report_data(\"Data\", self.data)\n            if self.optimize_k:\n                self.report_table(\n                    \"Silhouette scores for different numbers of clusters\",\n                    self.table_view)\n\n    def onDeleteWidget(self):\n        self.cancel()\n        super().onDeleteWidget()\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWKMeans).run(Table(\"heart_disease\"))\n",
            "new_file_content": "from concurrent.futures import Future\nfrom typing import Optional, List, Dict\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom AnyQt.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QThread, \\\n    pyqtSlot as Slot\nfrom AnyQt.QtGui import QIntValidator\nfrom AnyQt.QtWidgets import QGridLayout, QTableView\nfrom sklearn.metrics import silhouette_samples, silhouette_score\n\nfrom Orange.clustering import KMeans\nfrom Orange.clustering.kmeans import KMeansModel\nfrom Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable\nfrom Orange.data.util import get_unique_names, array_equal\nfrom Orange.preprocess import Normalize\nfrom Orange.preprocess.impute import ReplaceUnknowns\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.annotated_data import \\\n    ANNOTATED_DATA_SIGNAL_NAME, add_columns\nfrom Orange.widgets.utils.concurrent import ThreadExecutor, FutureSetWatcher\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.widget import Input, Output\n\n\nRANDOM_STATE = 0\nSILHOUETTE_MAX_SAMPLES = 5000\n\n\nclass ClusterTableModel(QAbstractTableModel):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.scores = []\n        self.start_k = 0\n\n    def rowCount(self, index=QModelIndex()):\n        return 0 if index.isValid() else len(self.scores)\n\n    @staticmethod\n    def columnCount(_index=QModelIndex()):\n        return 1\n\n    def flags(self, index):\n        if isinstance(self.scores[index.row()], str):\n            return Qt.NoItemFlags\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def set_scores(self, scores, start_k):\n        self.modelAboutToBeReset.emit()\n        self.scores = scores\n        self.start_k = start_k\n        self.modelReset.emit()\n\n    def clear_scores(self):\n        self.modelAboutToBeReset.emit()\n        self.scores = []\n        self.start_k = 0\n        self.modelReset.emit()\n\n    def data(self, index, role=Qt.DisplayRole):\n        score = self.scores[index.row()]\n        valid = not isinstance(score, str)\n        if role == Qt.DisplayRole:\n            return \"{:.3f}\".format(score) if valid else \"NA\"\n        elif role == Qt.TextAlignmentRole:\n            return Qt.AlignVCenter | Qt.AlignLeft\n        elif role == Qt.ToolTipRole and not valid:\n            return score\n        elif role == gui.BarRatioRole and valid:\n            return score\n        return None\n\n    def headerData(self, row, _orientation, role=Qt.DisplayRole):\n        if role == Qt.DisplayRole:\n            return str(row + self.start_k)\n        return None\n\n\nclass Task:\n    futures = []    # type: List[Future]\n    watcher = ...   # type: FutureSetWatcher\n    cancelled = False\n\n    def __init__(self, futures, watcher):\n        self.futures = futures\n        self.watcher = watcher\n\n    def cancel(self):\n        self.cancelled = True\n        for f in self.futures:\n            f.cancel()\n\n\nclass NotEnoughData(ValueError):\n    pass\n\n\nclass OWKMeans(widget.OWWidget):\n    name = \"k-Means\"\n    description = \"k-Means clustering algorithm with silhouette-based \" \\\n                  \"quality estimation.\"\n    icon = \"icons/KMeans.svg\"\n    priority = 2100\n    keywords = [\"kmeans\", \"clustering\"]\n\n    class Inputs:\n        data = Input(\"Data\", Table)\n\n    class Outputs:\n        annotated_data = Output(\n            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,\n            replaces=[\"Annotated Data\"]\n        )\n        centroids = Output(\"Centroids\", Table)\n\n    class Error(widget.OWWidget.Error):\n        failed = widget.Msg(\"Clustering failed\\nError: {}\")\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_attributes = widget.Msg(\"Data is missing features.\")\n\n    class Warning(widget.OWWidget.Warning):\n        no_silhouettes = widget.Msg(\n            \"Silhouette scores are not computed for >{} samples\".format(\n                SILHOUETTE_MAX_SAMPLES)\n        )\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_sparse_normalization = widget.Msg(\"Sparse data cannot be normalized\")\n\n    INIT_METHODS = ((\"Initialize with KMeans++\", \"k-means++\"),\n                    (\"Random initialization\", \"random\"))\n\n    resizing_enabled = False\n    buttons_area_orientation = Qt.Vertical\n\n    k = Setting(3)\n    k_from = Setting(2)\n    k_to = Setting(8)\n    optimize_k = Setting(False)\n    max_iterations = Setting(300)\n    n_init = Setting(10)\n    smart_init = Setting(0)  # KMeans++\n    selection = Setting(None, schema_only=True)  # type: Optional[int]\n    auto_commit = Setting(True)\n    normalize = Setting(True)\n\n    settings_version = 2\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        # type: (Dict, int) -> None\n        if version < 2:\n            if 'auto_apply' in settings:\n                settings['auto_commit'] = settings.get('auto_apply', True)\n                settings.pop('auto_apply', None)\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None  # type: Optional[Table]\n        self.__pending_selection = self.selection  # type: Optional[int]\n        self.clusterings = {}\n\n        self.__executor = ThreadExecutor(parent=self)\n        self.__task = None  # type: Optional[Task]\n\n        self._set_input_summary()\n        self._set_output_summary(None)\n\n        layout = QGridLayout()\n        bg = gui.radioButtonsInBox(\n            self.controlArea, self, \"optimize_k\", orientation=layout,\n            box=\"Number of Clusters\", callback=self.update_method,\n        )\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"Fixed:\", addToLayout=False), 1, 1)\n        sb = gui.hBox(None, margin=0)\n        gui.spin(\n            sb, self, \"k\", minv=2, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)\n        gui.rubber(sb)\n        layout.addWidget(sb, 1, 2)\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"From\", addToLayout=False), 2, 1)\n        ftobox = gui.hBox(None)\n        ftobox.layout().setContentsMargins(0, 0, 0, 0)\n        layout.addWidget(ftobox, 2, 2)\n        gui.spin(\n            ftobox, self, \"k_from\", minv=2, maxv=29,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_from)\n        gui.widgetLabel(ftobox, \"to\")\n        gui.spin(\n            ftobox, self, \"k_to\", minv=3, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_to)\n        gui.rubber(ftobox)\n\n        box = gui.vBox(self.controlArea, \"Preprocessing\")\n        gui.checkBox(box, self, \"normalize\", \"Normalize columns\",\n                     callback=self.invalidate)\n\n        box = gui.vBox(self.controlArea, \"Initialization\")\n        gui.comboBox(\n            box, self, \"smart_init\", items=[m[0] for m in self.INIT_METHODS],\n            callback=self.invalidate)\n\n        layout = QGridLayout()\n        gui.widgetBox(box, orientation=layout)\n        layout.addWidget(gui.widgetLabel(None, \"Re-runs: \"), 0, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 0, 1)\n        gui.lineEdit(\n            sb, self, \"n_init\", controlWidth=60,\n            valueType=int, validator=QIntValidator(), callback=self.invalidate)\n        layout.addWidget(\n            gui.widgetLabel(None, \"Maximum iterations: \"), 1, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 1, 1)\n        gui.lineEdit(\n            sb, self, \"max_iterations\", controlWidth=60, valueType=int,\n            validator=QIntValidator(), callback=self.invalidate)\n\n        self.apply_button = gui.auto_apply(self.buttonsArea, self, \"auto_commit\", box=None,\n                                           commit=self.commit)\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.mainArea, box=\"Silhouette Scores\")\n        self.mainArea.setVisible(self.optimize_k)\n        self.table_model = ClusterTableModel(self)\n        table = self.table_view = QTableView(self.mainArea)\n        table.setModel(self.table_model)\n        table.setSelectionMode(QTableView.SingleSelection)\n        table.setSelectionBehavior(QTableView.SelectRows)\n        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))\n        table.selectionModel().selectionChanged.connect(self.select_row)\n        table.setMaximumWidth(200)\n        table.horizontalHeader().setStretchLastSection(True)\n        table.horizontalHeader().hide()\n        table.setShowGrid(False)\n        box.layout().addWidget(table)\n\n    def adjustSize(self):\n        self.ensurePolished()\n        s = self.sizeHint()\n        self.resize(s)\n\n    def update_method(self):\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_k(self):\n        self.optimize_k = False\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_from(self):\n        self.k_to = max(self.k_from + 1, self.k_to)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_to(self):\n        self.k_from = min(self.k_from, self.k_to - 1)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def enough_data_instances(self, k):\n        \"\"\"k cannot be larger than the number of data instances.\"\"\"\n        return len(self.data) >= k\n\n    @property\n    def has_attributes(self):\n        return len(self.data.domain.attributes)\n\n    @staticmethod\n    def _compute_clustering(data, k, init, n_init, max_iter, random_state):\n        # type: (Table, int, str, int, int, bool) -> KMeansModel\n        if k > len(data):\n            raise NotEnoughData()\n\n        model = KMeans(\n            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,\n            random_state=random_state, preprocessors=[]\n        ).get_model(data)\n\n        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:\n            model.silhouette_samples = silhouette_samples(data.X, model.labels)\n            model.silhouette = np.mean(model.silhouette_samples)\n        else:\n            model.silhouette_samples = None\n            model.silhouette = \\\n                silhouette_score(data.X, model.labels,\n                                 sample_size=SILHOUETTE_MAX_SAMPLES,\n                                 random_state=RANDOM_STATE)\n\n        return model\n\n    @Slot(int, int)\n    def __progress_changed(self, n, d):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        self.progressBarSet(100 * n / d)\n\n    @Slot(int, Exception)\n    def __on_exception(self, idx, ex):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        if isinstance(ex, NotEnoughData):\n            self.Error.not_enough_data(len(self.data), self.k_from + idx)\n\n        # Only show failed message if there is only 1 k to compute\n        elif not self.optimize_k:\n            self.Error.failed(str(ex))\n\n        self.clusterings[self.k_from + idx] = str(ex)\n\n    @Slot(int, object)\n    def __clustering_complete(self, _, result):\n        # type: (int, KMeansModel) -> None\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        self.clusterings[result.k] = result\n\n    @Slot()\n    def __commit_finished(self):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        assert self.data is not None\n\n        self.__task = None\n        self.setInvalidated(False)\n        self.progressBarFinished()\n\n        if self.optimize_k:\n            self.update_results()\n\n        if self.optimize_k and all(isinstance(self.clusterings[i], str)\n                                   for i in range(self.k_from, self.k_to + 1)):\n            # Show the error of the last clustering\n            self.Error.failed(self.clusterings[self.k_to])\n\n        self.send_data()\n\n    def __launch_tasks(self, ks):\n        # type: (List[int]) -> None\n        \"\"\"Execute clustering in separate threads for all given ks.\"\"\"\n        preprocessed_data = self.preproces(self.data)\n        futures = [self.__executor.submit(\n            self._compute_clustering,\n            data=preprocessed_data,\n            k=k,\n            init=self.INIT_METHODS[self.smart_init][1],\n            n_init=self.n_init,\n            max_iter=self.max_iterations,\n            random_state=RANDOM_STATE,\n        ) for k in ks]\n        watcher = FutureSetWatcher(futures)\n        watcher.resultReadyAt.connect(self.__clustering_complete)\n        watcher.progressChanged.connect(self.__progress_changed)\n        watcher.exceptionReadyAt.connect(self.__on_exception)\n        watcher.doneAll.connect(self.__commit_finished)\n\n        self.__task = Task(futures, watcher)\n        self.progressBarInit()\n        self.setInvalidated(True)\n\n    def cancel(self):\n        if self.__task is not None:\n            task, self.__task = self.__task, None\n            task.cancel()\n\n            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)\n            task.watcher.progressChanged.disconnect(self.__progress_changed)\n            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)\n            task.watcher.doneAll.disconnect(self.__commit_finished)\n\n            self.progressBarFinished()\n            self.setInvalidated(False)\n\n    def run_optimization(self):\n        if not self.enough_data_instances(self.k_from):\n            self.Error.not_enough_data(len(self.data), self.k_from)\n            return\n\n        if not self.enough_data_instances(self.k_to):\n            self.Warning.not_enough_data(len(self.data), self.k_to)\n            return\n\n        needed_ks = [k for k in range(self.k_from, self.k_to + 1)\n                     if k not in self.clusterings]\n\n        if needed_ks:\n            self.__launch_tasks(needed_ks)\n        else:\n            # If we don't need to recompute anything, just set the results to\n            # what they were before\n            self.update_results()\n\n    def cluster(self):\n        # Check if the k already has a computed clustering\n        if self.k in self.clusterings:\n            self.send_data()\n            return\n\n        # Check if there is enough data\n        if not self.enough_data_instances(self.k):\n            self.Error.not_enough_data(len(self.data), self.k)\n            return\n\n        self.__launch_tasks([self.k])\n\n    def commit(self):\n        self.cancel()\n        self.clear_messages()\n\n        # Some time may pass before the new scores are computed, so clear the\n        # old scores to avoid potential confusion. Hiding the mainArea could\n        # cause flickering when the clusters are computed quickly, so this is\n        # the better alternative\n        self.table_model.clear_scores()\n        self.mainArea.setVisible(self.optimize_k and self.data is not None and\n                                 self.has_attributes)\n\n        if self.data is None:\n            self.send_data()\n            return\n\n        if not self.has_attributes:\n            self.Error.no_attributes()\n            self.send_data()\n            return\n\n        if self.optimize_k:\n            self.run_optimization()\n        else:\n            self.cluster()\n\n        QTimer.singleShot(100, self.adjustSize)\n\n    def invalidate(self, unconditional=False):\n        self.cancel()\n        self.Error.clear()\n        self.Warning.clear()\n        self.clusterings = {}\n        self.table_model.clear_scores()\n\n        if unconditional:\n            self.unconditional_commit()\n        else:\n            self.commit()\n\n    def update_results(self):\n        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in\n                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]\n        best_row = max(\n            range(len(scores)), default=0,\n            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]\n        )\n        self.table_model.set_scores(scores, self.k_from)\n        self.apply_selection(best_row)\n        self.table_view.setFocus(Qt.OtherFocusReason)\n        self.table_view.resizeRowsToContents()\n\n    def apply_selection(self, best_row):\n        pending = best_row\n        if self.__pending_selection is not None:\n            pending = self.__pending_selection\n            self.__pending_selection = None\n        self.table_view.selectRow(pending)\n\n    def selected_row(self):\n        indices = self.table_view.selectedIndexes()\n        if not indices:\n            return None\n        return indices[0].row()\n\n    def select_row(self):\n        self.selection = self.selected_row()\n        self.send_data()\n\n    def preproces(self, data):\n        if self.normalize:\n            if sp.issparse(data.X):\n                self.Warning.no_sparse_normalization()\n            else:\n                data = Normalize()(data)\n        for preprocessor in KMeans.preprocessors:  # use same preprocessors than\n            data = preprocessor(data)\n        return data\n\n    def send_data(self):\n        if self.optimize_k:\n            row = self.selected_row()\n            k = self.k_from + row if row is not None else None\n        else:\n            k = self.k\n\n        km = self.clusterings.get(k)\n        if self.data is None or km is None or isinstance(km, str):\n            self._set_output_summary(None)\n            self.Outputs.annotated_data.send(None)\n            self.Outputs.centroids.send(None)\n            return\n\n        domain = self.data.domain\n        cluster_var = DiscreteVariable(\n            get_unique_names(domain, \"Cluster\"),\n            values=[\"C%d\" % (x + 1) for x in range(km.k)]\n        )\n        clust_ids = km.labels\n        silhouette_var = ContinuousVariable(\n            get_unique_names(domain, \"Silhouette\"))\n        if km.silhouette_samples is not None:\n            self.Warning.no_silhouettes.clear()\n            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5\n            clust_scores = []\n            for i in range(km.k):\n                in_clust = clust_ids == i\n                if in_clust.any():\n                    clust_scores.append(np.mean(scores[in_clust]))\n                else:\n                    clust_scores.append(0.)\n            clust_scores = np.atleast_2d(clust_scores).T\n        else:\n            self.Warning.no_silhouettes()\n            scores = np.nan\n            clust_scores = np.full((km.k, 1), np.nan)\n\n        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])\n        new_table = self.data.transform(new_domain)\n        new_table.get_column_view(cluster_var)[0][:] = clust_ids\n        new_table.get_column_view(silhouette_var)[0][:] = scores\n\n        domain_attributes = set(domain.attributes)\n        centroid_attributes = [\n            attr.compute_value.variable\n            if isinstance(attr.compute_value, ReplaceUnknowns)\n            and attr.compute_value.variable in domain_attributes\n            else attr\n            for attr in km.domain.attributes]\n        centroid_domain = add_columns(\n            Domain(centroid_attributes, [], domain.metas),\n            metas=[cluster_var, silhouette_var])\n        centroids = Table(\n            centroid_domain, km.centroids, None,\n            np.hstack((np.full((km.k, len(domain.metas)), np.nan),\n                       np.arange(km.k).reshape(km.k, 1),\n                       clust_scores))\n        )\n        if self.data.name == Table.name:\n            centroids.name = \"centroids\"\n        else:\n            centroids.name = f\"{self.data.name} centroids\"\n\n        self._set_output_summary(new_table)\n        self.Outputs.annotated_data.send(new_table)\n        self.Outputs.centroids.send(centroids)\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data):\n        self.data, old_data = data, self.data\n        self.selection = None\n        self._set_input_summary()\n\n        self.controls.normalize.setDisabled(\n            bool(self.data) and sp.issparse(self.data.X))\n\n        # Do not needlessly recluster the data if X hasn't changed\n        if old_data and self.data and array_equal(self.data.X, old_data.X):\n            if self.auto_commit:\n                self.send_data()\n        else:\n            self.invalidate(unconditional=True)\n\n    def _set_input_summary(self):\n        summary = len(self.data) if self.data else self.info.NoInput\n        details = format_summary_details(self.data) if self.data else \"\"\n        self.info.set_input_summary(summary, details)\n\n    def _set_output_summary(self, output):\n        summary = len(output) if output else self.info.NoOutput\n        details = format_summary_details(output) if output else \"\"\n        self.info.set_output_summary(summary, details)\n\n    def send_report(self):\n        # False positives (Setting is not recognized as int)\n        # pylint: disable=invalid-sequence-index\n        if self.optimize_k and self.selected_row() is not None:\n            k_clusters = self.k_from + self.selected_row()\n        else:\n            k_clusters = self.k\n        init_method = self.INIT_METHODS[self.smart_init][0]\n        init_method = init_method[0].lower() + init_method[1:]\n        self.report_items((\n            (\"Number of clusters\", k_clusters),\n            (\"Optimization\", \"{}, {} re-runs limited to {} steps\".format(\n                init_method, self.n_init, self.max_iterations))))\n        if self.data is not None:\n            self.report_data(\"Data\", self.data)\n            if self.optimize_k:\n                self.report_table(\n                    \"Silhouette scores for different numbers of clusters\",\n                    self.table_view)\n\n    def onDeleteWidget(self):\n        self.cancel()\n        super().onDeleteWidget()\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWKMeans).run(Table(\"heart_disease\"))\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/unsupervised/owkmeans.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "932731a5b",
                "new_commit_hash": "3c0b5e42d",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/unsupervised/owkmeans.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/unsupervised/owkmeans.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 576,
                            "length": 6
                        },
                        "new_range": {
                            "start": 576,
                            "length": 9
                        },
                        "section": "class OWKMeans(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.selection = None",
                                "type": "context"
                            },
                            {
                                "content": "        self._set_input_summary()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.controls.normalize.setDisabled(",
                                "type": "added"
                            },
                            {
                                "content": "            bool(self.data) and sp.issparse(self.data.X))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        # Do not needlessly recluster the data if X hasn't changed",
                                "type": "context"
                            },
                            {
                                "content": "        if old_data and self.data and array_equal(self.data.X, old_data.X):",
                                "type": "context"
                            },
                            {
                                "content": "            if self.auto_commit:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/owkmeans.py",
                            "type": "method",
                            "name": "OWKMeans.set_data",
                            "content": "class OWKMeans(widget.OWWidget):\n    name = \"k-Means\"\n    description = \"k-Means clustering algorithm with silhouette-based \" \\\n                  \"quality estimation.\"\n    icon = \"icons/KMeans.svg\"\n    priority = 2100\n    keywords = [\"kmeans\", \"clustering\"]\n\n    class Inputs:\n        data = Input(\"Data\", Table)\n\n    class Outputs:\n        annotated_data = Output(\n            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,\n            replaces=[\"Annotated Data\"]\n        )\n        centroids = Output(\"Centroids\", Table)\n\n    class Error(widget.OWWidget.Error):\n        failed = widget.Msg(\"Clustering failed\\nError: {}\")\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_attributes = widget.Msg(\"Data is missing features.\")\n\n    class Warning(widget.OWWidget.Warning):\n        no_silhouettes = widget.Msg(\n            \"Silhouette scores are not computed for >{} samples\".format(\n                SILHOUETTE_MAX_SAMPLES)\n        )\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_sparse_normalization = widget.Msg(\"Sparse data cannot be normalized\")\n\n    INIT_METHODS = ((\"Initialize with KMeans++\", \"k-means++\"),\n                    (\"Random initialization\", \"random\"))\n\n    resizing_enabled = False\n    buttons_area_orientation = Qt.Vertical\n\n    k = Setting(3)\n    k_from = Setting(2)\n    k_to = Setting(8)\n    optimize_k = Setting(False)\n    max_iterations = Setting(300)\n    n_init = Setting(10)\n    smart_init = Setting(0)  # KMeans++\n    selection = Setting(None, schema_only=True)  # type: Optional[int]\n    auto_commit = Setting(True)\n    normalize = Setting(True)\n\n    settings_version = 2\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        # type: (Dict, int) -> None\n        if version < 2:\n            if 'auto_apply' in settings:\n                settings['auto_commit'] = settings.get('auto_apply', True)\n                settings.pop('auto_apply', None)\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None  # type: Optional[Table]\n        self.__pending_selection = self.selection  # type: Optional[int]\n        self.clusterings = {}\n\n        self.__executor = ThreadExecutor(parent=self)\n        self.__task = None  # type: Optional[Task]\n\n        self._set_input_summary()\n        self._set_output_summary(None)\n\n        layout = QGridLayout()\n        bg = gui.radioButtonsInBox(\n            self.controlArea, self, \"optimize_k\", orientation=layout,\n            box=\"Number of Clusters\", callback=self.update_method,\n        )\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"Fixed:\", addToLayout=False), 1, 1)\n        sb = gui.hBox(None, margin=0)\n        gui.spin(\n            sb, self, \"k\", minv=2, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)\n        gui.rubber(sb)\n        layout.addWidget(sb, 1, 2)\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"From\", addToLayout=False), 2, 1)\n        ftobox = gui.hBox(None)\n        ftobox.layout().setContentsMargins(0, 0, 0, 0)\n        layout.addWidget(ftobox, 2, 2)\n        gui.spin(\n            ftobox, self, \"k_from\", minv=2, maxv=29,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_from)\n        gui.widgetLabel(ftobox, \"to\")\n        gui.spin(\n            ftobox, self, \"k_to\", minv=3, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_to)\n        gui.rubber(ftobox)\n\n        box = gui.vBox(self.controlArea, \"Preprocessing\")\n        gui.checkBox(box, self, \"normalize\", \"Normalize columns\",\n                     callback=self.invalidate)\n\n        box = gui.vBox(self.controlArea, \"Initialization\")\n        gui.comboBox(\n            box, self, \"smart_init\", items=[m[0] for m in self.INIT_METHODS],\n            callback=self.invalidate)\n\n        layout = QGridLayout()\n        gui.widgetBox(box, orientation=layout)\n        layout.addWidget(gui.widgetLabel(None, \"Re-runs: \"), 0, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 0, 1)\n        gui.lineEdit(\n            sb, self, \"n_init\", controlWidth=60,\n            valueType=int, validator=QIntValidator(), callback=self.invalidate)\n        layout.addWidget(\n            gui.widgetLabel(None, \"Maximum iterations: \"), 1, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 1, 1)\n        gui.lineEdit(\n            sb, self, \"max_iterations\", controlWidth=60, valueType=int,\n            validator=QIntValidator(), callback=self.invalidate)\n\n        self.apply_button = gui.auto_apply(self.buttonsArea, self, \"auto_commit\", box=None,\n                                           commit=self.commit)\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.mainArea, box=\"Silhouette Scores\")\n        self.mainArea.setVisible(self.optimize_k)\n        self.table_model = ClusterTableModel(self)\n        table = self.table_view = QTableView(self.mainArea)\n        table.setModel(self.table_model)\n        table.setSelectionMode(QTableView.SingleSelection)\n        table.setSelectionBehavior(QTableView.SelectRows)\n        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))\n        table.selectionModel().selectionChanged.connect(self.select_row)\n        table.setMaximumWidth(200)\n        table.horizontalHeader().setStretchLastSection(True)\n        table.horizontalHeader().hide()\n        table.setShowGrid(False)\n        box.layout().addWidget(table)\n\n    def adjustSize(self):\n        self.ensurePolished()\n        s = self.sizeHint()\n        self.resize(s)\n\n    def update_method(self):\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_k(self):\n        self.optimize_k = False\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_from(self):\n        self.k_to = max(self.k_from + 1, self.k_to)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_to(self):\n        self.k_from = min(self.k_from, self.k_to - 1)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def enough_data_instances(self, k):\n        \"\"\"k cannot be larger than the number of data instances.\"\"\"\n        return len(self.data) >= k\n\n    @property\n    def has_attributes(self):\n        return len(self.data.domain.attributes)\n\n    @staticmethod\n    def _compute_clustering(data, k, init, n_init, max_iter, random_state):\n        # type: (Table, int, str, int, int, bool) -> KMeansModel\n        if k > len(data):\n            raise NotEnoughData()\n\n        model = KMeans(\n            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,\n            random_state=random_state, preprocessors=[]\n        ).get_model(data)\n\n        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:\n            model.silhouette_samples = silhouette_samples(data.X, model.labels)\n            model.silhouette = np.mean(model.silhouette_samples)\n        else:\n            model.silhouette_samples = None\n            model.silhouette = \\\n                silhouette_score(data.X, model.labels,\n                                 sample_size=SILHOUETTE_MAX_SAMPLES,\n                                 random_state=RANDOM_STATE)\n\n        return model\n\n    @Slot(int, int)\n    def __progress_changed(self, n, d):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        self.progressBarSet(100 * n / d)\n\n    @Slot(int, Exception)\n    def __on_exception(self, idx, ex):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        if isinstance(ex, NotEnoughData):\n            self.Error.not_enough_data(len(self.data), self.k_from + idx)\n\n        # Only show failed message if there is only 1 k to compute\n        elif not self.optimize_k:\n            self.Error.failed(str(ex))\n\n        self.clusterings[self.k_from + idx] = str(ex)\n\n    @Slot(int, object)\n    def __clustering_complete(self, _, result):\n        # type: (int, KMeansModel) -> None\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        self.clusterings[result.k] = result\n\n    @Slot()\n    def __commit_finished(self):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        assert self.data is not None\n\n        self.__task = None\n        self.setInvalidated(False)\n        self.progressBarFinished()\n\n        if self.optimize_k:\n            self.update_results()\n\n        if self.optimize_k and all(isinstance(self.clusterings[i], str)\n                                   for i in range(self.k_from, self.k_to + 1)):\n            # Show the error of the last clustering\n            self.Error.failed(self.clusterings[self.k_to])\n\n        self.send_data()\n\n    def __launch_tasks(self, ks):\n        # type: (List[int]) -> None\n        \"\"\"Execute clustering in separate threads for all given ks.\"\"\"\n        preprocessed_data = self.preproces(self.data)\n        futures = [self.__executor.submit(\n            self._compute_clustering,\n            data=preprocessed_data,\n            k=k,\n            init=self.INIT_METHODS[self.smart_init][1],\n            n_init=self.n_init,\n            max_iter=self.max_iterations,\n            random_state=RANDOM_STATE,\n        ) for k in ks]\n        watcher = FutureSetWatcher(futures)\n        watcher.resultReadyAt.connect(self.__clustering_complete)\n        watcher.progressChanged.connect(self.__progress_changed)\n        watcher.exceptionReadyAt.connect(self.__on_exception)\n        watcher.doneAll.connect(self.__commit_finished)\n\n        self.__task = Task(futures, watcher)\n        self.progressBarInit()\n        self.setInvalidated(True)\n\n    def cancel(self):\n        if self.__task is not None:\n            task, self.__task = self.__task, None\n            task.cancel()\n\n            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)\n            task.watcher.progressChanged.disconnect(self.__progress_changed)\n            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)\n            task.watcher.doneAll.disconnect(self.__commit_finished)\n\n            self.progressBarFinished()\n            self.setInvalidated(False)\n\n    def run_optimization(self):\n        if not self.enough_data_instances(self.k_from):\n            self.Error.not_enough_data(len(self.data), self.k_from)\n            return\n\n        if not self.enough_data_instances(self.k_to):\n            self.Warning.not_enough_data(len(self.data), self.k_to)\n            return\n\n        needed_ks = [k for k in range(self.k_from, self.k_to + 1)\n                     if k not in self.clusterings]\n\n        if needed_ks:\n            self.__launch_tasks(needed_ks)\n        else:\n            # If we don't need to recompute anything, just set the results to\n            # what they were before\n            self.update_results()\n\n    def cluster(self):\n        # Check if the k already has a computed clustering\n        if self.k in self.clusterings:\n            self.send_data()\n            return\n\n        # Check if there is enough data\n        if not self.enough_data_instances(self.k):\n            self.Error.not_enough_data(len(self.data), self.k)\n            return\n\n        self.__launch_tasks([self.k])\n\n    def commit(self):\n        self.cancel()\n        self.clear_messages()\n\n        # Some time may pass before the new scores are computed, so clear the\n        # old scores to avoid potential confusion. Hiding the mainArea could\n        # cause flickering when the clusters are computed quickly, so this is\n        # the better alternative\n        self.table_model.clear_scores()\n        self.mainArea.setVisible(self.optimize_k and self.data is not None and\n                                 self.has_attributes)\n\n        if self.data is None:\n            self.send_data()\n            return\n\n        if not self.has_attributes:\n            self.Error.no_attributes()\n            self.send_data()\n            return\n\n        if self.optimize_k:\n            self.run_optimization()\n        else:\n            self.cluster()\n\n        QTimer.singleShot(100, self.adjustSize)\n\n    def invalidate(self, unconditional=False):\n        self.cancel()\n        self.Error.clear()\n        self.Warning.clear()\n        self.clusterings = {}\n        self.table_model.clear_scores()\n\n        if unconditional:\n            self.unconditional_commit()\n        else:\n            self.commit()\n\n    def update_results(self):\n        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in\n                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]\n        best_row = max(\n            range(len(scores)), default=0,\n            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]\n        )\n        self.table_model.set_scores(scores, self.k_from)\n        self.apply_selection(best_row)\n        self.table_view.setFocus(Qt.OtherFocusReason)\n        self.table_view.resizeRowsToContents()\n\n    def apply_selection(self, best_row):\n        pending = best_row\n        if self.__pending_selection is not None:\n            pending = self.__pending_selection\n            self.__pending_selection = None\n        self.table_view.selectRow(pending)\n\n    def selected_row(self):\n        indices = self.table_view.selectedIndexes()\n        if not indices:\n            return None\n        return indices[0].row()\n\n    def select_row(self):\n        self.selection = self.selected_row()\n        self.send_data()\n\n    def preproces(self, data):\n        if self.normalize:\n            if sp.issparse(data.X):\n                self.Warning.no_sparse_normalization()\n            else:\n                data = Normalize()(data)\n        for preprocessor in KMeans.preprocessors:  # use same preprocessors than\n            data = preprocessor(data)\n        return data\n\n    def send_data(self):\n        if self.optimize_k:\n            row = self.selected_row()\n            k = self.k_from + row if row is not None else None\n        else:\n            k = self.k\n\n        km = self.clusterings.get(k)\n        if self.data is None or km is None or isinstance(km, str):\n            self._set_output_summary(None)\n            self.Outputs.annotated_data.send(None)\n            self.Outputs.centroids.send(None)\n            return\n\n        domain = self.data.domain\n        cluster_var = DiscreteVariable(\n            get_unique_names(domain, \"Cluster\"),\n            values=[\"C%d\" % (x + 1) for x in range(km.k)]\n        )\n        clust_ids = km.labels\n        silhouette_var = ContinuousVariable(\n            get_unique_names(domain, \"Silhouette\"))\n        if km.silhouette_samples is not None:\n            self.Warning.no_silhouettes.clear()\n            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5\n            clust_scores = []\n            for i in range(km.k):\n                in_clust = clust_ids == i\n                if in_clust.any():\n                    clust_scores.append(np.mean(scores[in_clust]))\n                else:\n                    clust_scores.append(0.)\n            clust_scores = np.atleast_2d(clust_scores).T\n        else:\n            self.Warning.no_silhouettes()\n            scores = np.nan\n            clust_scores = np.full((km.k, 1), np.nan)\n\n        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])\n        new_table = self.data.transform(new_domain)\n        new_table.get_column_view(cluster_var)[0][:] = clust_ids\n        new_table.get_column_view(silhouette_var)[0][:] = scores\n\n        domain_attributes = set(domain.attributes)\n        centroid_attributes = [\n            attr.compute_value.variable\n            if isinstance(attr.compute_value, ReplaceUnknowns)\n            and attr.compute_value.variable in domain_attributes\n            else attr\n            for attr in km.domain.attributes]\n        centroid_domain = add_columns(\n            Domain(centroid_attributes, [], domain.metas),\n            metas=[cluster_var, silhouette_var])\n        centroids = Table(\n            centroid_domain, km.centroids, None,\n            np.hstack((np.full((km.k, len(domain.metas)), np.nan),\n                       np.arange(km.k).reshape(km.k, 1),\n                       clust_scores))\n        )\n        if self.data.name == Table.name:\n            centroids.name = \"centroids\"\n        else:\n            centroids.name = f\"{self.data.name} centroids\"\n\n        self._set_output_summary(new_table)\n        self.Outputs.annotated_data.send(new_table)\n        self.Outputs.centroids.send(centroids)\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data):\n        self.data, old_data = data, self.data\n        self.selection = None\n        self._set_input_summary()\n\n        self.controls.normalize.setDisabled(\n            bool(self.data) and sp.issparse(self.data.X))\n\n        # Do not needlessly recluster the data if X hasn't changed\n        if old_data and self.data and array_equal(self.data.X, old_data.X):\n            if self.auto_commit:\n                self.send_data()\n        else:\n            self.invalidate(unconditional=True)\n\n    def _set_input_summary(self):\n        summary = len(self.data) if self.data else self.info.NoInput\n        details = format_summary_details(self.data) if self.data else \"\"\n        self.info.set_input_summary(summary, details)\n\n    def _set_output_summary(self, output):\n        summary = len(output) if output else self.info.NoOutput\n        details = format_summary_details(output) if output else \"\"\n        self.info.set_output_summary(summary, details)\n\n    def send_report(self):\n        # False positives (Setting is not recognized as int)\n        # pylint: disable=invalid-sequence-index\n        if self.optimize_k and self.selected_row() is not None:\n            k_clusters = self.k_from + self.selected_row()\n        else:\n            k_clusters = self.k\n        init_method = self.INIT_METHODS[self.smart_init][0]\n        init_method = init_method[0].lower() + init_method[1:]\n        self.report_items((\n            (\"Number of clusters\", k_clusters),\n            (\"Optimization\", \"{}, {} re-runs limited to {} steps\".format(\n                init_method, self.n_init, self.max_iterations))))\n        if self.data is not None:\n            self.report_data(\"Data\", self.data)\n            if self.optimize_k:\n                self.report_table(\n                    \"Silhouette scores for different numbers of clusters\",\n                    self.table_view)\n\n    def onDeleteWidget(self):\n        self.cancel()\n        super().onDeleteWidget()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 574,
                            "end_lineno": 587,
                            "parent": {
                                "file_name": "Orange/widgets/unsupervised/owkmeans.py",
                                "type": "class",
                                "name": "OWKMeans",
                                "content": "class OWKMeans(widget.OWWidget):\n    name = \"k-Means\"\n    description = \"k-Means clustering algorithm with silhouette-based \" \\\n                  \"quality estimation.\"\n    icon = \"icons/KMeans.svg\"\n    priority = 2100\n    keywords = [\"kmeans\", \"clustering\"]\n\n    class Inputs:\n        data = Input(\"Data\", Table)\n\n    class Outputs:\n        annotated_data = Output(\n            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,\n            replaces=[\"Annotated Data\"]\n        )\n        centroids = Output(\"Centroids\", Table)\n\n    class Error(widget.OWWidget.Error):\n        failed = widget.Msg(\"Clustering failed\\nError: {}\")\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_attributes = widget.Msg(\"Data is missing features.\")\n\n    class Warning(widget.OWWidget.Warning):\n        no_silhouettes = widget.Msg(\n            \"Silhouette scores are not computed for >{} samples\".format(\n                SILHOUETTE_MAX_SAMPLES)\n        )\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_sparse_normalization = widget.Msg(\"Sparse data cannot be normalized\")\n\n    INIT_METHODS = ((\"Initialize with KMeans++\", \"k-means++\"),\n                    (\"Random initialization\", \"random\"))\n\n    resizing_enabled = False\n    buttons_area_orientation = Qt.Vertical\n\n    k = Setting(3)\n    k_from = Setting(2)\n    k_to = Setting(8)\n    optimize_k = Setting(False)\n    max_iterations = Setting(300)\n    n_init = Setting(10)\n    smart_init = Setting(0)  # KMeans++\n    selection = Setting(None, schema_only=True)  # type: Optional[int]\n    auto_commit = Setting(True)\n    normalize = Setting(True)\n\n    settings_version = 2\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        # type: (Dict, int) -> None\n        if version < 2:\n            if 'auto_apply' in settings:\n                settings['auto_commit'] = settings.get('auto_apply', True)\n                settings.pop('auto_apply', None)\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None  # type: Optional[Table]\n        self.__pending_selection = self.selection  # type: Optional[int]\n        self.clusterings = {}\n\n        self.__executor = ThreadExecutor(parent=self)\n        self.__task = None  # type: Optional[Task]\n\n        self._set_input_summary()\n        self._set_output_summary(None)\n\n        layout = QGridLayout()\n        bg = gui.radioButtonsInBox(\n            self.controlArea, self, \"optimize_k\", orientation=layout,\n            box=\"Number of Clusters\", callback=self.update_method,\n        )\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"Fixed:\", addToLayout=False), 1, 1)\n        sb = gui.hBox(None, margin=0)\n        gui.spin(\n            sb, self, \"k\", minv=2, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)\n        gui.rubber(sb)\n        layout.addWidget(sb, 1, 2)\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"From\", addToLayout=False), 2, 1)\n        ftobox = gui.hBox(None)\n        ftobox.layout().setContentsMargins(0, 0, 0, 0)\n        layout.addWidget(ftobox, 2, 2)\n        gui.spin(\n            ftobox, self, \"k_from\", minv=2, maxv=29,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_from)\n        gui.widgetLabel(ftobox, \"to\")\n        gui.spin(\n            ftobox, self, \"k_to\", minv=3, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_to)\n        gui.rubber(ftobox)\n\n        box = gui.vBox(self.controlArea, \"Preprocessing\")\n        gui.checkBox(box, self, \"normalize\", \"Normalize columns\",\n                     callback=self.invalidate)\n\n        box = gui.vBox(self.controlArea, \"Initialization\")\n        gui.comboBox(\n            box, self, \"smart_init\", items=[m[0] for m in self.INIT_METHODS],\n            callback=self.invalidate)\n\n        layout = QGridLayout()\n        gui.widgetBox(box, orientation=layout)\n        layout.addWidget(gui.widgetLabel(None, \"Re-runs: \"), 0, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 0, 1)\n        gui.lineEdit(\n            sb, self, \"n_init\", controlWidth=60,\n            valueType=int, validator=QIntValidator(), callback=self.invalidate)\n        layout.addWidget(\n            gui.widgetLabel(None, \"Maximum iterations: \"), 1, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 1, 1)\n        gui.lineEdit(\n            sb, self, \"max_iterations\", controlWidth=60, valueType=int,\n            validator=QIntValidator(), callback=self.invalidate)\n\n        self.apply_button = gui.auto_apply(self.buttonsArea, self, \"auto_commit\", box=None,\n                                           commit=self.commit)\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.mainArea, box=\"Silhouette Scores\")\n        self.mainArea.setVisible(self.optimize_k)\n        self.table_model = ClusterTableModel(self)\n        table = self.table_view = QTableView(self.mainArea)\n        table.setModel(self.table_model)\n        table.setSelectionMode(QTableView.SingleSelection)\n        table.setSelectionBehavior(QTableView.SelectRows)\n        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))\n        table.selectionModel().selectionChanged.connect(self.select_row)\n        table.setMaximumWidth(200)\n        table.horizontalHeader().setStretchLastSection(True)\n        table.horizontalHeader().hide()\n        table.setShowGrid(False)\n        box.layout().addWidget(table)\n\n    def adjustSize(self):\n        self.ensurePolished()\n        s = self.sizeHint()\n        self.resize(s)\n\n    def update_method(self):\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_k(self):\n        self.optimize_k = False\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_from(self):\n        self.k_to = max(self.k_from + 1, self.k_to)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_to(self):\n        self.k_from = min(self.k_from, self.k_to - 1)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def enough_data_instances(self, k):\n        \"\"\"k cannot be larger than the number of data instances.\"\"\"\n        return len(self.data) >= k\n\n    @property\n    def has_attributes(self):\n        return len(self.data.domain.attributes)\n\n    @staticmethod\n    def _compute_clustering(data, k, init, n_init, max_iter, random_state):\n        # type: (Table, int, str, int, int, bool) -> KMeansModel\n        if k > len(data):\n            raise NotEnoughData()\n\n        model = KMeans(\n            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,\n            random_state=random_state, preprocessors=[]\n        ).get_model(data)\n\n        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:\n            model.silhouette_samples = silhouette_samples(data.X, model.labels)\n            model.silhouette = np.mean(model.silhouette_samples)\n        else:\n            model.silhouette_samples = None\n            model.silhouette = \\\n                silhouette_score(data.X, model.labels,\n                                 sample_size=SILHOUETTE_MAX_SAMPLES,\n                                 random_state=RANDOM_STATE)\n\n        return model\n\n    @Slot(int, int)\n    def __progress_changed(self, n, d):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        self.progressBarSet(100 * n / d)\n\n    @Slot(int, Exception)\n    def __on_exception(self, idx, ex):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        if isinstance(ex, NotEnoughData):\n            self.Error.not_enough_data(len(self.data), self.k_from + idx)\n\n        # Only show failed message if there is only 1 k to compute\n        elif not self.optimize_k:\n            self.Error.failed(str(ex))\n\n        self.clusterings[self.k_from + idx] = str(ex)\n\n    @Slot(int, object)\n    def __clustering_complete(self, _, result):\n        # type: (int, KMeansModel) -> None\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        self.clusterings[result.k] = result\n\n    @Slot()\n    def __commit_finished(self):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        assert self.data is not None\n\n        self.__task = None\n        self.setInvalidated(False)\n        self.progressBarFinished()\n\n        if self.optimize_k:\n            self.update_results()\n\n        if self.optimize_k and all(isinstance(self.clusterings[i], str)\n                                   for i in range(self.k_from, self.k_to + 1)):\n            # Show the error of the last clustering\n            self.Error.failed(self.clusterings[self.k_to])\n\n        self.send_data()\n\n    def __launch_tasks(self, ks):\n        # type: (List[int]) -> None\n        \"\"\"Execute clustering in separate threads for all given ks.\"\"\"\n        preprocessed_data = self.preproces(self.data)\n        futures = [self.__executor.submit(\n            self._compute_clustering,\n            data=preprocessed_data,\n            k=k,\n            init=self.INIT_METHODS[self.smart_init][1],\n            n_init=self.n_init,\n            max_iter=self.max_iterations,\n            random_state=RANDOM_STATE,\n        ) for k in ks]\n        watcher = FutureSetWatcher(futures)\n        watcher.resultReadyAt.connect(self.__clustering_complete)\n        watcher.progressChanged.connect(self.__progress_changed)\n        watcher.exceptionReadyAt.connect(self.__on_exception)\n        watcher.doneAll.connect(self.__commit_finished)\n\n        self.__task = Task(futures, watcher)\n        self.progressBarInit()\n        self.setInvalidated(True)\n\n    def cancel(self):\n        if self.__task is not None:\n            task, self.__task = self.__task, None\n            task.cancel()\n\n            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)\n            task.watcher.progressChanged.disconnect(self.__progress_changed)\n            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)\n            task.watcher.doneAll.disconnect(self.__commit_finished)\n\n            self.progressBarFinished()\n            self.setInvalidated(False)\n\n    def run_optimization(self):\n        if not self.enough_data_instances(self.k_from):\n            self.Error.not_enough_data(len(self.data), self.k_from)\n            return\n\n        if not self.enough_data_instances(self.k_to):\n            self.Warning.not_enough_data(len(self.data), self.k_to)\n            return\n\n        needed_ks = [k for k in range(self.k_from, self.k_to + 1)\n                     if k not in self.clusterings]\n\n        if needed_ks:\n            self.__launch_tasks(needed_ks)\n        else:\n            # If we don't need to recompute anything, just set the results to\n            # what they were before\n            self.update_results()\n\n    def cluster(self):\n        # Check if the k already has a computed clustering\n        if self.k in self.clusterings:\n            self.send_data()\n            return\n\n        # Check if there is enough data\n        if not self.enough_data_instances(self.k):\n            self.Error.not_enough_data(len(self.data), self.k)\n            return\n\n        self.__launch_tasks([self.k])\n\n    def commit(self):\n        self.cancel()\n        self.clear_messages()\n\n        # Some time may pass before the new scores are computed, so clear the\n        # old scores to avoid potential confusion. Hiding the mainArea could\n        # cause flickering when the clusters are computed quickly, so this is\n        # the better alternative\n        self.table_model.clear_scores()\n        self.mainArea.setVisible(self.optimize_k and self.data is not None and\n                                 self.has_attributes)\n\n        if self.data is None:\n            self.send_data()\n            return\n\n        if not self.has_attributes:\n            self.Error.no_attributes()\n            self.send_data()\n            return\n\n        if self.optimize_k:\n            self.run_optimization()\n        else:\n            self.cluster()\n\n        QTimer.singleShot(100, self.adjustSize)\n\n    def invalidate(self, unconditional=False):\n        self.cancel()\n        self.Error.clear()\n        self.Warning.clear()\n        self.clusterings = {}\n        self.table_model.clear_scores()\n\n        if unconditional:\n            self.unconditional_commit()\n        else:\n            self.commit()\n\n    def update_results(self):\n        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in\n                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]\n        best_row = max(\n            range(len(scores)), default=0,\n            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]\n        )\n        self.table_model.set_scores(scores, self.k_from)\n        self.apply_selection(best_row)\n        self.table_view.setFocus(Qt.OtherFocusReason)\n        self.table_view.resizeRowsToContents()\n\n    def apply_selection(self, best_row):\n        pending = best_row\n        if self.__pending_selection is not None:\n            pending = self.__pending_selection\n            self.__pending_selection = None\n        self.table_view.selectRow(pending)\n\n    def selected_row(self):\n        indices = self.table_view.selectedIndexes()\n        if not indices:\n            return None\n        return indices[0].row()\n\n    def select_row(self):\n        self.selection = self.selected_row()\n        self.send_data()\n\n    def preproces(self, data):\n        if self.normalize:\n            if sp.issparse(data.X):\n                self.Warning.no_sparse_normalization()\n            else:\n                data = Normalize()(data)\n        for preprocessor in KMeans.preprocessors:  # use same preprocessors than\n            data = preprocessor(data)\n        return data\n\n    def send_data(self):\n        if self.optimize_k:\n            row = self.selected_row()\n            k = self.k_from + row if row is not None else None\n        else:\n            k = self.k\n\n        km = self.clusterings.get(k)\n        if self.data is None or km is None or isinstance(km, str):\n            self._set_output_summary(None)\n            self.Outputs.annotated_data.send(None)\n            self.Outputs.centroids.send(None)\n            return\n\n        domain = self.data.domain\n        cluster_var = DiscreteVariable(\n            get_unique_names(domain, \"Cluster\"),\n            values=[\"C%d\" % (x + 1) for x in range(km.k)]\n        )\n        clust_ids = km.labels\n        silhouette_var = ContinuousVariable(\n            get_unique_names(domain, \"Silhouette\"))\n        if km.silhouette_samples is not None:\n            self.Warning.no_silhouettes.clear()\n            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5\n            clust_scores = []\n            for i in range(km.k):\n                in_clust = clust_ids == i\n                if in_clust.any():\n                    clust_scores.append(np.mean(scores[in_clust]))\n                else:\n                    clust_scores.append(0.)\n            clust_scores = np.atleast_2d(clust_scores).T\n        else:\n            self.Warning.no_silhouettes()\n            scores = np.nan\n            clust_scores = np.full((km.k, 1), np.nan)\n\n        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])\n        new_table = self.data.transform(new_domain)\n        new_table.get_column_view(cluster_var)[0][:] = clust_ids\n        new_table.get_column_view(silhouette_var)[0][:] = scores\n\n        domain_attributes = set(domain.attributes)\n        centroid_attributes = [\n            attr.compute_value.variable\n            if isinstance(attr.compute_value, ReplaceUnknowns)\n            and attr.compute_value.variable in domain_attributes\n            else attr\n            for attr in km.domain.attributes]\n        centroid_domain = add_columns(\n            Domain(centroid_attributes, [], domain.metas),\n            metas=[cluster_var, silhouette_var])\n        centroids = Table(\n            centroid_domain, km.centroids, None,\n            np.hstack((np.full((km.k, len(domain.metas)), np.nan),\n                       np.arange(km.k).reshape(km.k, 1),\n                       clust_scores))\n        )\n        if self.data.name == Table.name:\n            centroids.name = \"centroids\"\n        else:\n            centroids.name = f\"{self.data.name} centroids\"\n\n        self._set_output_summary(new_table)\n        self.Outputs.annotated_data.send(new_table)\n        self.Outputs.centroids.send(centroids)\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data):\n        self.data, old_data = data, self.data\n        self.selection = None\n        self._set_input_summary()\n\n        self.controls.normalize.setDisabled(\n            bool(self.data) and sp.issparse(self.data.X))\n\n        # Do not needlessly recluster the data if X hasn't changed\n        if old_data and self.data and array_equal(self.data.X, old_data.X):\n            if self.auto_commit:\n                self.send_data()\n        else:\n            self.invalidate(unconditional=True)\n\n    def _set_input_summary(self):\n        summary = len(self.data) if self.data else self.info.NoInput\n        details = format_summary_details(self.data) if self.data else \"\"\n        self.info.set_input_summary(summary, details)\n\n    def _set_output_summary(self, output):\n        summary = len(output) if output else self.info.NoOutput\n        details = format_summary_details(output) if output else \"\"\n        self.info.set_output_summary(summary, details)\n\n    def send_report(self):\n        # False positives (Setting is not recognized as int)\n        # pylint: disable=invalid-sequence-index\n        if self.optimize_k and self.selected_row() is not None:\n            k_clusters = self.k_from + self.selected_row()\n        else:\n            k_clusters = self.k\n        init_method = self.INIT_METHODS[self.smart_init][0]\n        init_method = init_method[0].lower() + init_method[1:]\n        self.report_items((\n            (\"Number of clusters\", k_clusters),\n            (\"Optimization\", \"{}, {} re-runs limited to {} steps\".format(\n                init_method, self.n_init, self.max_iterations))))\n        if self.data is not None:\n            self.report_data(\"Data\", self.data)\n            if self.optimize_k:\n                self.report_table(\n                    \"Silhouette scores for different numbers of clusters\",\n                    self.table_view)\n\n    def onDeleteWidget(self):\n        self.cancel()\n        super().onDeleteWidget()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 102,
                                "end_lineno": 621,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/unsupervised/owkmeans.py",
                            "type": "class",
                            "name": "OWKMeans",
                            "content": "class OWKMeans(widget.OWWidget):\n    name = \"k-Means\"\n    description = \"k-Means clustering algorithm with silhouette-based \" \\\n                  \"quality estimation.\"\n    icon = \"icons/KMeans.svg\"\n    priority = 2100\n    keywords = [\"kmeans\", \"clustering\"]\n\n    class Inputs:\n        data = Input(\"Data\", Table)\n\n    class Outputs:\n        annotated_data = Output(\n            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,\n            replaces=[\"Annotated Data\"]\n        )\n        centroids = Output(\"Centroids\", Table)\n\n    class Error(widget.OWWidget.Error):\n        failed = widget.Msg(\"Clustering failed\\nError: {}\")\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_attributes = widget.Msg(\"Data is missing features.\")\n\n    class Warning(widget.OWWidget.Warning):\n        no_silhouettes = widget.Msg(\n            \"Silhouette scores are not computed for >{} samples\".format(\n                SILHOUETTE_MAX_SAMPLES)\n        )\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_sparse_normalization = widget.Msg(\"Sparse data cannot be normalized\")\n\n    INIT_METHODS = ((\"Initialize with KMeans++\", \"k-means++\"),\n                    (\"Random initialization\", \"random\"))\n\n    resizing_enabled = False\n    buttons_area_orientation = Qt.Vertical\n\n    k = Setting(3)\n    k_from = Setting(2)\n    k_to = Setting(8)\n    optimize_k = Setting(False)\n    max_iterations = Setting(300)\n    n_init = Setting(10)\n    smart_init = Setting(0)  # KMeans++\n    selection = Setting(None, schema_only=True)  # type: Optional[int]\n    auto_commit = Setting(True)\n    normalize = Setting(True)\n\n    settings_version = 2\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        # type: (Dict, int) -> None\n        if version < 2:\n            if 'auto_apply' in settings:\n                settings['auto_commit'] = settings.get('auto_apply', True)\n                settings.pop('auto_apply', None)\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None  # type: Optional[Table]\n        self.__pending_selection = self.selection  # type: Optional[int]\n        self.clusterings = {}\n\n        self.__executor = ThreadExecutor(parent=self)\n        self.__task = None  # type: Optional[Task]\n\n        self._set_input_summary()\n        self._set_output_summary(None)\n\n        layout = QGridLayout()\n        bg = gui.radioButtonsInBox(\n            self.controlArea, self, \"optimize_k\", orientation=layout,\n            box=\"Number of Clusters\", callback=self.update_method,\n        )\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"Fixed:\", addToLayout=False), 1, 1)\n        sb = gui.hBox(None, margin=0)\n        gui.spin(\n            sb, self, \"k\", minv=2, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)\n        gui.rubber(sb)\n        layout.addWidget(sb, 1, 2)\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"From\", addToLayout=False), 2, 1)\n        ftobox = gui.hBox(None)\n        ftobox.layout().setContentsMargins(0, 0, 0, 0)\n        layout.addWidget(ftobox, 2, 2)\n        gui.spin(\n            ftobox, self, \"k_from\", minv=2, maxv=29,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_from)\n        gui.widgetLabel(ftobox, \"to\")\n        gui.spin(\n            ftobox, self, \"k_to\", minv=3, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_to)\n        gui.rubber(ftobox)\n\n        box = gui.vBox(self.controlArea, \"Preprocessing\")\n        gui.checkBox(box, self, \"normalize\", \"Normalize columns\",\n                     callback=self.invalidate)\n\n        box = gui.vBox(self.controlArea, \"Initialization\")\n        gui.comboBox(\n            box, self, \"smart_init\", items=[m[0] for m in self.INIT_METHODS],\n            callback=self.invalidate)\n\n        layout = QGridLayout()\n        gui.widgetBox(box, orientation=layout)\n        layout.addWidget(gui.widgetLabel(None, \"Re-runs: \"), 0, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 0, 1)\n        gui.lineEdit(\n            sb, self, \"n_init\", controlWidth=60,\n            valueType=int, validator=QIntValidator(), callback=self.invalidate)\n        layout.addWidget(\n            gui.widgetLabel(None, \"Maximum iterations: \"), 1, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 1, 1)\n        gui.lineEdit(\n            sb, self, \"max_iterations\", controlWidth=60, valueType=int,\n            validator=QIntValidator(), callback=self.invalidate)\n\n        self.apply_button = gui.auto_apply(self.buttonsArea, self, \"auto_commit\", box=None,\n                                           commit=self.commit)\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.mainArea, box=\"Silhouette Scores\")\n        self.mainArea.setVisible(self.optimize_k)\n        self.table_model = ClusterTableModel(self)\n        table = self.table_view = QTableView(self.mainArea)\n        table.setModel(self.table_model)\n        table.setSelectionMode(QTableView.SingleSelection)\n        table.setSelectionBehavior(QTableView.SelectRows)\n        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))\n        table.selectionModel().selectionChanged.connect(self.select_row)\n        table.setMaximumWidth(200)\n        table.horizontalHeader().setStretchLastSection(True)\n        table.horizontalHeader().hide()\n        table.setShowGrid(False)\n        box.layout().addWidget(table)\n\n    def adjustSize(self):\n        self.ensurePolished()\n        s = self.sizeHint()\n        self.resize(s)\n\n    def update_method(self):\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_k(self):\n        self.optimize_k = False\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_from(self):\n        self.k_to = max(self.k_from + 1, self.k_to)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_to(self):\n        self.k_from = min(self.k_from, self.k_to - 1)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def enough_data_instances(self, k):\n        \"\"\"k cannot be larger than the number of data instances.\"\"\"\n        return len(self.data) >= k\n\n    @property\n    def has_attributes(self):\n        return len(self.data.domain.attributes)\n\n    @staticmethod\n    def _compute_clustering(data, k, init, n_init, max_iter, random_state):\n        # type: (Table, int, str, int, int, bool) -> KMeansModel\n        if k > len(data):\n            raise NotEnoughData()\n\n        model = KMeans(\n            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,\n            random_state=random_state, preprocessors=[]\n        ).get_model(data)\n\n        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:\n            model.silhouette_samples = silhouette_samples(data.X, model.labels)\n            model.silhouette = np.mean(model.silhouette_samples)\n        else:\n            model.silhouette_samples = None\n            model.silhouette = \\\n                silhouette_score(data.X, model.labels,\n                                 sample_size=SILHOUETTE_MAX_SAMPLES,\n                                 random_state=RANDOM_STATE)\n\n        return model\n\n    @Slot(int, int)\n    def __progress_changed(self, n, d):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        self.progressBarSet(100 * n / d)\n\n    @Slot(int, Exception)\n    def __on_exception(self, idx, ex):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        if isinstance(ex, NotEnoughData):\n            self.Error.not_enough_data(len(self.data), self.k_from + idx)\n\n        # Only show failed message if there is only 1 k to compute\n        elif not self.optimize_k:\n            self.Error.failed(str(ex))\n\n        self.clusterings[self.k_from + idx] = str(ex)\n\n    @Slot(int, object)\n    def __clustering_complete(self, _, result):\n        # type: (int, KMeansModel) -> None\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        self.clusterings[result.k] = result\n\n    @Slot()\n    def __commit_finished(self):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        assert self.data is not None\n\n        self.__task = None\n        self.setInvalidated(False)\n        self.progressBarFinished()\n\n        if self.optimize_k:\n            self.update_results()\n\n        if self.optimize_k and all(isinstance(self.clusterings[i], str)\n                                   for i in range(self.k_from, self.k_to + 1)):\n            # Show the error of the last clustering\n            self.Error.failed(self.clusterings[self.k_to])\n\n        self.send_data()\n\n    def __launch_tasks(self, ks):\n        # type: (List[int]) -> None\n        \"\"\"Execute clustering in separate threads for all given ks.\"\"\"\n        preprocessed_data = self.preproces(self.data)\n        futures = [self.__executor.submit(\n            self._compute_clustering,\n            data=preprocessed_data,\n            k=k,\n            init=self.INIT_METHODS[self.smart_init][1],\n            n_init=self.n_init,\n            max_iter=self.max_iterations,\n            random_state=RANDOM_STATE,\n        ) for k in ks]\n        watcher = FutureSetWatcher(futures)\n        watcher.resultReadyAt.connect(self.__clustering_complete)\n        watcher.progressChanged.connect(self.__progress_changed)\n        watcher.exceptionReadyAt.connect(self.__on_exception)\n        watcher.doneAll.connect(self.__commit_finished)\n\n        self.__task = Task(futures, watcher)\n        self.progressBarInit()\n        self.setInvalidated(True)\n\n    def cancel(self):\n        if self.__task is not None:\n            task, self.__task = self.__task, None\n            task.cancel()\n\n            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)\n            task.watcher.progressChanged.disconnect(self.__progress_changed)\n            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)\n            task.watcher.doneAll.disconnect(self.__commit_finished)\n\n            self.progressBarFinished()\n            self.setInvalidated(False)\n\n    def run_optimization(self):\n        if not self.enough_data_instances(self.k_from):\n            self.Error.not_enough_data(len(self.data), self.k_from)\n            return\n\n        if not self.enough_data_instances(self.k_to):\n            self.Warning.not_enough_data(len(self.data), self.k_to)\n            return\n\n        needed_ks = [k for k in range(self.k_from, self.k_to + 1)\n                     if k not in self.clusterings]\n\n        if needed_ks:\n            self.__launch_tasks(needed_ks)\n        else:\n            # If we don't need to recompute anything, just set the results to\n            # what they were before\n            self.update_results()\n\n    def cluster(self):\n        # Check if the k already has a computed clustering\n        if self.k in self.clusterings:\n            self.send_data()\n            return\n\n        # Check if there is enough data\n        if not self.enough_data_instances(self.k):\n            self.Error.not_enough_data(len(self.data), self.k)\n            return\n\n        self.__launch_tasks([self.k])\n\n    def commit(self):\n        self.cancel()\n        self.clear_messages()\n\n        # Some time may pass before the new scores are computed, so clear the\n        # old scores to avoid potential confusion. Hiding the mainArea could\n        # cause flickering when the clusters are computed quickly, so this is\n        # the better alternative\n        self.table_model.clear_scores()\n        self.mainArea.setVisible(self.optimize_k and self.data is not None and\n                                 self.has_attributes)\n\n        if self.data is None:\n            self.send_data()\n            return\n\n        if not self.has_attributes:\n            self.Error.no_attributes()\n            self.send_data()\n            return\n\n        if self.optimize_k:\n            self.run_optimization()\n        else:\n            self.cluster()\n\n        QTimer.singleShot(100, self.adjustSize)\n\n    def invalidate(self, unconditional=False):\n        self.cancel()\n        self.Error.clear()\n        self.Warning.clear()\n        self.clusterings = {}\n        self.table_model.clear_scores()\n\n        if unconditional:\n            self.unconditional_commit()\n        else:\n            self.commit()\n\n    def update_results(self):\n        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in\n                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]\n        best_row = max(\n            range(len(scores)), default=0,\n            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]\n        )\n        self.table_model.set_scores(scores, self.k_from)\n        self.apply_selection(best_row)\n        self.table_view.setFocus(Qt.OtherFocusReason)\n        self.table_view.resizeRowsToContents()\n\n    def apply_selection(self, best_row):\n        pending = best_row\n        if self.__pending_selection is not None:\n            pending = self.__pending_selection\n            self.__pending_selection = None\n        self.table_view.selectRow(pending)\n\n    def selected_row(self):\n        indices = self.table_view.selectedIndexes()\n        if not indices:\n            return None\n        return indices[0].row()\n\n    def select_row(self):\n        self.selection = self.selected_row()\n        self.send_data()\n\n    def preproces(self, data):\n        if self.normalize:\n            if sp.issparse(data.X):\n                self.Warning.no_sparse_normalization()\n            else:\n                data = Normalize()(data)\n        for preprocessor in KMeans.preprocessors:  # use same preprocessors than\n            data = preprocessor(data)\n        return data\n\n    def send_data(self):\n        if self.optimize_k:\n            row = self.selected_row()\n            k = self.k_from + row if row is not None else None\n        else:\n            k = self.k\n\n        km = self.clusterings.get(k)\n        if self.data is None or km is None or isinstance(km, str):\n            self._set_output_summary(None)\n            self.Outputs.annotated_data.send(None)\n            self.Outputs.centroids.send(None)\n            return\n\n        domain = self.data.domain\n        cluster_var = DiscreteVariable(\n            get_unique_names(domain, \"Cluster\"),\n            values=[\"C%d\" % (x + 1) for x in range(km.k)]\n        )\n        clust_ids = km.labels\n        silhouette_var = ContinuousVariable(\n            get_unique_names(domain, \"Silhouette\"))\n        if km.silhouette_samples is not None:\n            self.Warning.no_silhouettes.clear()\n            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5\n            clust_scores = []\n            for i in range(km.k):\n                in_clust = clust_ids == i\n                if in_clust.any():\n                    clust_scores.append(np.mean(scores[in_clust]))\n                else:\n                    clust_scores.append(0.)\n            clust_scores = np.atleast_2d(clust_scores).T\n        else:\n            self.Warning.no_silhouettes()\n            scores = np.nan\n            clust_scores = np.full((km.k, 1), np.nan)\n\n        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])\n        new_table = self.data.transform(new_domain)\n        new_table.get_column_view(cluster_var)[0][:] = clust_ids\n        new_table.get_column_view(silhouette_var)[0][:] = scores\n\n        domain_attributes = set(domain.attributes)\n        centroid_attributes = [\n            attr.compute_value.variable\n            if isinstance(attr.compute_value, ReplaceUnknowns)\n            and attr.compute_value.variable in domain_attributes\n            else attr\n            for attr in km.domain.attributes]\n        centroid_domain = add_columns(\n            Domain(centroid_attributes, [], domain.metas),\n            metas=[cluster_var, silhouette_var])\n        centroids = Table(\n            centroid_domain, km.centroids, None,\n            np.hstack((np.full((km.k, len(domain.metas)), np.nan),\n                       np.arange(km.k).reshape(km.k, 1),\n                       clust_scores))\n        )\n        if self.data.name == Table.name:\n            centroids.name = \"centroids\"\n        else:\n            centroids.name = f\"{self.data.name} centroids\"\n\n        self._set_output_summary(new_table)\n        self.Outputs.annotated_data.send(new_table)\n        self.Outputs.centroids.send(centroids)\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data):\n        self.data, old_data = data, self.data\n        self.selection = None\n        self._set_input_summary()\n\n        self.controls.normalize.setDisabled(\n            bool(self.data) and sp.issparse(self.data.X))\n\n        # Do not needlessly recluster the data if X hasn't changed\n        if old_data and self.data and array_equal(self.data.X, old_data.X):\n            if self.auto_commit:\n                self.send_data()\n        else:\n            self.invalidate(unconditional=True)\n\n    def _set_input_summary(self):\n        summary = len(self.data) if self.data else self.info.NoInput\n        details = format_summary_details(self.data) if self.data else \"\"\n        self.info.set_input_summary(summary, details)\n\n    def _set_output_summary(self, output):\n        summary = len(output) if output else self.info.NoOutput\n        details = format_summary_details(output) if output else \"\"\n        self.info.set_output_summary(summary, details)\n\n    def send_report(self):\n        # False positives (Setting is not recognized as int)\n        # pylint: disable=invalid-sequence-index\n        if self.optimize_k and self.selected_row() is not None:\n            k_clusters = self.k_from + self.selected_row()\n        else:\n            k_clusters = self.k\n        init_method = self.INIT_METHODS[self.smart_init][0]\n        init_method = init_method[0].lower() + init_method[1:]\n        self.report_items((\n            (\"Number of clusters\", k_clusters),\n            (\"Optimization\", \"{}, {} re-runs limited to {} steps\".format(\n                init_method, self.n_init, self.max_iterations))))\n        if self.data is not None:\n            self.report_data(\"Data\", self.data)\n            if self.optimize_k:\n                self.report_table(\n                    \"Silhouette scores for different numbers of clusters\",\n                    self.table_view)\n\n    def onDeleteWidget(self):\n        self.cancel()\n        super().onDeleteWidget()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 102,
                            "end_lineno": 621,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# pylint: disable=protected-access\nimport unittest\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom AnyQt.QtCore import Qt\nfrom AnyQt.QtWidgets import QRadioButton\nfrom sklearn.metrics import silhouette_score\n\nimport Orange.clustering\nfrom Orange.data import Table, Domain\nfrom Orange.widgets import gui\nfrom Orange.widgets.tests.base import WidgetTest\nfrom Orange.widgets.unsupervised.owkmeans import OWKMeans, ClusterTableModel\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nclass TestClusterTableModel(unittest.TestCase):\n    def test_model(self):\n        model = ClusterTableModel()\n        model.set_scores([\"bad\", 0.250, \"another bad\"], 3)\n\n        self.assertEqual(model.start_k, 3)\n        self.assertEqual(model.rowCount(), 3)\n        ind0, ind1 = model.index(0, 0), model.index(1, 0)\n        self.assertEqual(model.flags(ind0), Qt.NoItemFlags)\n        self.assertEqual(model.flags(ind1),\n                         Qt.ItemIsEnabled | Qt.ItemIsSelectable)\n        data = model.data\n        self.assertEqual(data(ind0), \"NA\")\n        self.assertEqual(data(ind1), \"0.250\")\n        self.assertEqual(data(ind0, Qt.ToolTipRole), \"bad\")\n        self.assertIsNone(data(ind1, Qt.ToolTipRole))\n        self.assertIsNone(data(ind0, gui.BarRatioRole))\n        self.assertAlmostEqual(data(ind1, gui.BarRatioRole), 0.250)\n\n        self.assertAlmostEqual(data(ind1, Qt.TextAlignmentRole),\n                               Qt.AlignVCenter | Qt.AlignLeft)\n\n        self.assertEqual(model.headerData(1, Qt.Vertical), \"4\")\n\n\nclass TestOWKMeans(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(\n            OWKMeans, stored_settings={\"auto_commit\": False, \"version\": 2}\n        )  # type: OWKMeans\n        self.data = Table(\"heart_disease\")\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_migrate_version_1_settings(self):\n        widget = self.create_widget(\n            OWKMeans,\n            stored_settings={'auto_apply': False}\n        )\n        self.assertFalse(widget.auto_commit)\n\n    def test_optimization_report_display(self):\n        \"\"\"Check visibility of the table after selecting number of clusters\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.optimize_k = True\n        radio_buttons = self.widget.controls.optimize_k.findChildren(QRadioButton)\n\n        radio_buttons[0].click()\n        self.assertFalse(self.widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        radio_buttons[1].click()\n        self.assertTrue(self.widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.widget.apply_button.button.click()\n\n        self.wait_until_finished()\n        self.assertEqual(self.widget.table_view.model().rowCount() > 0, True)\n\n    def test_changing_k_changes_radio(self):\n        widget = self.widget\n        widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n\n        widget.optimize_k = True\n\n        widget.controls.k.setValue(5)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_from.setValue(5)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n        widget.controls.k.setValue(3)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_to.setValue(9)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n    def test_k_limits(self):\n        widget = self.widget\n        widget.controls.k_from.setValue(10)\n        self.assertEqual(widget.k_from, 10)\n        self.assertEqual(widget.k_to, 11)\n\n        widget.controls.k_to.setValue(4)\n        self.assertEqual(widget.k_from, 3)\n        self.assertEqual(widget.k_to, 4)\n\n    def test_no_data_hides_main_area(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.optimize_k = True\n        widget.k_from, widget.k_to = 3, 4\n\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n    def test_data_limits(self):\n        \"\"\"Data error should be shown when `k` > n or when `k_from` > n. Data\n        warning should be shown when `k_to` > n\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n\n        self.send_signal(self.widget.Inputs.data, self.data[:5])\n\n        widget.k = 10\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k = 3\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 10\n        widget.optimize_k = True\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 4\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 6\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n        self.assertTrue(widget.Warning.not_enough_data.is_shown())\n\n    def test_use_cache(self):\n        \"\"\"Cache various clusterings for the dataset until data changes.\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n        self.send_signal(self.widget.Inputs.data, self.data)\n\n        with patch.object(widget, \"_compute_clustering\",\n                          wraps=widget._compute_clustering) as compute:\n\n            widget.k = 3\n            widget.optimize_k = False\n\n            self.commit_and_wait()\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 3)\n\n            widget.k_from = 2\n            widget.k_to = 3\n            widget.optimize_k = True\n\n            compute.reset_mock()\n            self.commit_and_wait()\n            # Since 3 was already computed before when we weren't optimizing,\n            # we only need to compute for 3\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 2)\n\n            # Commiting again should not recompute the clusterings\n            compute.reset_mock()\n            self.commit_and_wait()\n            # compute.assert_not_called unfortunately didn't exist before 3.5\n            self.assertFalse(compute.called)\n\n    def test_data_on_output(self):\n        \"\"\"Check if data is on output after create widget and run\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.apply_button.button.click()\n        self.assertNotEqual(self.widget.data, None)\n        # Disconnect the data\n        self.send_signal(self.widget.Inputs.data, None)\n        # removing data should have cleared the output\n        self.assertEqual(self.widget.data, None)\n\n    def test_centroids_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        self.send_signal(widget.Inputs.data, self.data)\n        self.commit_and_wait()\n        widget.clusterings[widget.k].labels = np.array([0] * 100 + [1] * 203).flatten()\n        widget.clusterings[widget.k].silhouette_samples = np.arange(303) / 303\n        widget.send_data()\n        out = self.get_output(widget.Outputs.centroids)\n        np.testing.assert_array_almost_equal(\n            np.array([[0, np.mean(np.arctan(np.arange(100) / 303)) / np.pi + 0.5],\n                      [1, np.mean(np.arctan(np.arange(100, 303) / 303)) / np.pi + 0.5],\n                      [2, 0], [3, 0]]), out.metas.astype(float))\n        self.assertEqual(out.name, \"heart_disease centroids\")\n\n    def test_centroids_domain_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        heart_disease = Table(\"heart_disease\")\n        heart_disease.name = Table.name  # untitled\n        self.send_signal(widget.Inputs.data, heart_disease)\n        self.commit_and_wait()\n\n        in_attrs = heart_disease.domain.attributes\n        out = self.get_output(widget.Outputs.centroids)\n        out_attrs = out.domain.attributes\n        out_names = {attr.name for attr in out_attrs}\n        for attr in in_attrs:\n            self.assertEqual(\n                attr.name in out_names, attr.is_continuous,\n                f\"at attribute '{attr.name}'\"\n            )\n        self.assertEqual(\n            len(out_attrs),\n            sum(attr.is_continuous or len(attr.values) for attr in in_attrs))\n        self.assertEqual(out.name, \"centroids\")\n\n    class KMeansFail(Orange.clustering.KMeans):\n        fail_on = set()\n\n        def fit(self, X, Y=None):\n            # when not optimizing, params is empty?!\n            k = self.params.get(\"n_clusters\", 3)\n            if k in self.fail_on:\n                raise ValueError(\"k={} fails\".format(k))\n            return super().fit(X, Y)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_optimization_fails(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k_from, widget.k_to = 3, 8\n        widget.optimize_k = True\n\n        self.KMeansFail.fail_on = {3, 5, 7}\n        model = widget.table_view.model()\n\n        with patch.object(\n                model, \"set_scores\", wraps=model.set_scores) as set_scores:\n            self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n            scores, start_k = set_scores.call_args[0]\n            X = self.widget.preproces(self.data).X\n            self.assertEqual(\n                scores,\n                [km if isinstance(km, str) else silhouette_score(\n                    X, km(self.data))\n                 for km in (widget.clusterings[k] for k in range(3, 9))]\n            )\n            self.assertEqual(start_k, 3)\n\n        self.assertIsInstance(widget.clusterings[3], str)\n        self.assertIsInstance(widget.clusterings[5], str)\n        self.assertIsInstance(widget.clusterings[7], str)\n        self.assertNotIsInstance(widget.clusterings[4], str)\n        self.assertNotIsInstance(widget.clusterings[6], str)\n        self.assertNotIsInstance(widget.clusterings[8], str)\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 1)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set(range(3, 9))\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertTrue(widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 0)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_run_fails(self):\n        self.widget.k = 3\n        self.widget.auto_commit = True\n        self.widget.optimize_k = False\n        self.KMeansFail.fail_on = {3}\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertTrue(self.widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        self.widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(self.widget.Error.failed.is_shown())\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_select_best_row(self):\n        widget = self.widget\n        widget.k_from, widget.k_to = 2, 6\n        widget.optimize_k = True\n        widget.normalize = False\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset without normalization,\n        # the best selection is 3 clusters, so row no. 1\n        self.assertEqual(widget.selected_row(), 1)\n\n        self.widget.controls.normalize.toggle()\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset with normalization,\n        # the best selection is 2 clusters, so row no. 0\n        self.assertEqual(widget.selected_row(), 0)\n\n        widget.clusterings = {k: \"error\" for k in range(2, 7)}\n        widget.update_results()\n        self.assertEqual(widget.selected_row(), None)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n\n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, self.data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_called()\n        normalization.reset_mock()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        widget.controls.normalize.click()\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n    def test_report(self):\n        widget = self.widget\n        widget.k = 4\n        widget.optimize_k = False\n        with patch.object(widget, \"report_items\") as report_items, \\\n                patch.object(widget, \"report_data\") as report_data, \\\n                patch.object(widget, \"report_table\") as report_table, \\\n                patch.object(widget, \"selected_row\", new=Mock(return_value=42)):\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 4))\n            self.assertEqual(items[1][0], \"Optimization\")\n            self.assertFalse(report_data.called)\n            self.assertFalse(report_table.called)\n\n            widget.data = data = Mock()\n            widget.send_report()\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertFalse(report_table.called)\n\n            report_data.reset_mock()\n            report_items.reset_mock()\n            widget.k_from, widget.k_to = 2, 3\n            widget.optimize_k = True\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 44))\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertIs(report_table.call_args[0][1],\n                          widget.table_view)\n\n    def test_not_enough_rows(self):\n        \"\"\"\n        Widget should not crash when there is less rows than k_from.\n        GH-2172\n        \"\"\"\n        table = self.data[0:1, :]\n        self.widget.controls.k_from.setValue(2)\n        self.widget.controls.k_to.setValue(9)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_from_to_table(self):\n        \"\"\"\n        From and To spins and number of rows in a scores table changes.\n        GH-2172\n        \"\"\"\n        k_from, k_to = 2, 9\n        self.widget.controls.k_from.setValue(k_from)\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        check = lambda x: 2 if x - k_from + 1 < 2 else x - k_from + 1\n        for i in range(k_from, k_to):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n        for i in range(k_to, k_from, -1):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n\n    def test_silhouette_column(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k = 4\n        widget.optimize_k = False\n\n        # Avoid randomness in the test\n        random = np.random.RandomState(0)  # pylint: disable=no-member\n        table = Table.from_numpy(None, random.rand(110, 2))\n        with patch(\n                \"Orange.widgets.unsupervised.owkmeans.SILHOUETTE_MAX_SAMPLES\",\n                100):\n            self.send_signal(self.widget.Inputs.data, table)\n            outtable = self.get_output(widget.Outputs.annotated_data)\n            outtable = outtable.get_column_view(\"Silhouette\")[0]\n        self.assertTrue(np.all(np.isnan(outtable)))\n        self.assertTrue(widget.Warning.no_silhouettes.is_shown())\n\n        self.send_signal(self.widget.Inputs.data, table[:100])\n        outtable = self.get_output(widget.Outputs.annotated_data)\n        outtable = outtable.get_column_view(\"Silhouette\")[0]\n        np.testing.assert_array_less(outtable, 1.01)\n        np.testing.assert_array_less(-0.01, outtable)\n        self.assertFalse(widget.Warning.no_silhouettes.is_shown())\n\n    def test_invalidate_clusterings_cancels_jobs(self):\n        widget = self.widget\n        widget.auto_commit = False\n\n        # Send the data without waiting\n        self.send_signal(widget.Inputs.data, self.data)\n        widget.unconditional_commit()\n        # Now, invalidate by changing max_iter\n        widget.max_iterations = widget.max_iterations + 1\n        widget.invalidate()\n        self.wait_until_finished()\n\n        self.assertEqual(widget.clusterings, {})\n\n    def test_do_not_recluster_on_same_data(self):\n        \"\"\"Do not recluster data points when targets or metas change.\"\"\"\n\n        # Prepare some dummy data\n        x = np.eye(5)\n        y1, y2 = np.ones((5, 1)), np.ones((5, 2))\n        meta1, meta2 = np.ones((5, 1)), np.ones((5, 2))\n\n        table1 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y1, metas=meta1),\n            X=x, Y=y1, metas=meta1,\n        )\n        # X is same, should not cause update\n        table2 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y2, metas=meta2),\n            X=x, Y=y2, metas=meta2,\n        )\n        # X is different, should cause update\n        table3 = table1.copy()\n        table3.X[:, 0] = 1\n\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.send_signal(self.widget.Inputs.data, table1)\n            self.commit_and_wait()\n            commit.reset_mock()\n\n            # Sending data with same X should not recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table2)\n            commit.assert_not_called()\n\n            # Sending data with different X should recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table3)\n            commit.assert_called_once()\n\n    def test_correct_smart_init(self):\n        # due to a bug where wrong init was passed to _compute_clustering\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n        self.widget.smart_init = 0\n        self.widget.clusterings = {}\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"k-means++\")\n        self.widget.invalidate()  # reset caches\n        self.widget.smart_init = 1\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"random\")\n\n    def test_always_same_cluster(self):\n        \"\"\"The same random state should always return the same clusters\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n\n        def cluster():\n            self.widget.invalidate()  # reset caches\n            self.commit_and_wait()\n            return self.get_output(self.widget.Outputs.annotated_data).metas[:, 0]\n\n        def assert_all_same(l):\n            for a1, a2 in zip(l, l[1:]):\n                np.testing.assert_equal(a1, a2)\n\n        self.widget.smart_init = 0\n        assert_all_same([cluster() for _ in range(5)])\n\n        self.widget.smart_init = 1\n        assert_all_same([cluster() for _ in range(5)])\n\n    def test_error_no_attributes(self):\n        domain = Domain([])\n        table = Table.from_domain(domain, n_rows=10)\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, table)\n        self.assertTrue(self.widget.Error.no_attributes.is_shown())\n\n    def test_saved_selection(self):\n        self.widget.send_data = Mock()\n        self.widget.optimize_k = True\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.wait_until_finished()\n        self.widget.table_view.selectRow(2)\n        self.assertEqual(self.widget.selected_row(), 2)\n        self.assertEqual(self.widget.send_data.call_count, 3)\n        settings = self.widget.settingsHandler.pack_data(self.widget)\n\n        w = self.create_widget(OWKMeans, stored_settings=settings)\n        w.send_data = Mock()\n        self.send_signal(w.Inputs.data, self.data, widget=w)\n        self.wait_until_finished(widget=w)\n        self.assertEqual(w.send_data.call_count, 2)\n        self.assertEqual(self.widget.selected_row(), w.selected_row())\n\n    def test_summary(self):\n        \"\"\"Check if the status bar updates\"\"\"\n        info = self.widget.info\n        no_input, no_output = \"No data on input\", \"No data on output\"\n\n        self.send_signal(self.widget.Inputs.data, self.data)\n        summary, details = f\"{len(self.data)}\", format_summary_details(self.data)\n        self.assertEqual(info._StateInfo__input_summary.brief, summary)\n        self.assertEqual(info._StateInfo__input_summary.details, details)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        summary, details = f\"{len(output)}\", format_summary_details(output)\n        self.assertEqual(info._StateInfo__output_summary.brief, summary)\n        self.assertEqual(info._StateInfo__output_summary.details, details)\n\n        self.send_signal(self.widget.Inputs.data, None)\n        self.assertEqual(info._StateInfo__input_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__input_summary.details, no_input)\n        self.assertEqual(info._StateInfo__output_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__output_summary.details, no_output)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "new_file_content": "# pylint: disable=protected-access\nimport unittest\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom AnyQt.QtCore import Qt\nfrom AnyQt.QtWidgets import QRadioButton\nfrom sklearn.metrics import silhouette_score\n\nimport Orange.clustering\nfrom Orange.data import Table, Domain\nfrom Orange.widgets import gui\nfrom Orange.widgets.tests.base import WidgetTest\nfrom Orange.widgets.unsupervised.owkmeans import OWKMeans, ClusterTableModel\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nclass TestClusterTableModel(unittest.TestCase):\n    def test_model(self):\n        model = ClusterTableModel()\n        model.set_scores([\"bad\", 0.250, \"another bad\"], 3)\n\n        self.assertEqual(model.start_k, 3)\n        self.assertEqual(model.rowCount(), 3)\n        ind0, ind1 = model.index(0, 0), model.index(1, 0)\n        self.assertEqual(model.flags(ind0), Qt.NoItemFlags)\n        self.assertEqual(model.flags(ind1),\n                         Qt.ItemIsEnabled | Qt.ItemIsSelectable)\n        data = model.data\n        self.assertEqual(data(ind0), \"NA\")\n        self.assertEqual(data(ind1), \"0.250\")\n        self.assertEqual(data(ind0, Qt.ToolTipRole), \"bad\")\n        self.assertIsNone(data(ind1, Qt.ToolTipRole))\n        self.assertIsNone(data(ind0, gui.BarRatioRole))\n        self.assertAlmostEqual(data(ind1, gui.BarRatioRole), 0.250)\n\n        self.assertAlmostEqual(data(ind1, Qt.TextAlignmentRole),\n                               Qt.AlignVCenter | Qt.AlignLeft)\n\n        self.assertEqual(model.headerData(1, Qt.Vertical), \"4\")\n\n\nclass TestOWKMeans(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(\n            OWKMeans, stored_settings={\"auto_commit\": False, \"version\": 2}\n        )  # type: OWKMeans\n        self.data = Table(\"heart_disease\")\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_migrate_version_1_settings(self):\n        widget = self.create_widget(\n            OWKMeans,\n            stored_settings={'auto_apply': False}\n        )\n        self.assertFalse(widget.auto_commit)\n\n    def test_optimization_report_display(self):\n        \"\"\"Check visibility of the table after selecting number of clusters\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.optimize_k = True\n        radio_buttons = self.widget.controls.optimize_k.findChildren(QRadioButton)\n\n        radio_buttons[0].click()\n        self.assertFalse(self.widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        radio_buttons[1].click()\n        self.assertTrue(self.widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.widget.apply_button.button.click()\n\n        self.wait_until_finished()\n        self.assertEqual(self.widget.table_view.model().rowCount() > 0, True)\n\n    def test_changing_k_changes_radio(self):\n        widget = self.widget\n        widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n\n        widget.optimize_k = True\n\n        widget.controls.k.setValue(5)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_from.setValue(5)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n        widget.controls.k.setValue(3)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_to.setValue(9)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n    def test_k_limits(self):\n        widget = self.widget\n        widget.controls.k_from.setValue(10)\n        self.assertEqual(widget.k_from, 10)\n        self.assertEqual(widget.k_to, 11)\n\n        widget.controls.k_to.setValue(4)\n        self.assertEqual(widget.k_from, 3)\n        self.assertEqual(widget.k_to, 4)\n\n    def test_no_data_hides_main_area(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.optimize_k = True\n        widget.k_from, widget.k_to = 3, 4\n\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n    def test_data_limits(self):\n        \"\"\"Data error should be shown when `k` > n or when `k_from` > n. Data\n        warning should be shown when `k_to` > n\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n\n        self.send_signal(self.widget.Inputs.data, self.data[:5])\n\n        widget.k = 10\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k = 3\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 10\n        widget.optimize_k = True\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 4\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 6\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n        self.assertTrue(widget.Warning.not_enough_data.is_shown())\n\n    def test_use_cache(self):\n        \"\"\"Cache various clusterings for the dataset until data changes.\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n        self.send_signal(self.widget.Inputs.data, self.data)\n\n        with patch.object(widget, \"_compute_clustering\",\n                          wraps=widget._compute_clustering) as compute:\n\n            widget.k = 3\n            widget.optimize_k = False\n\n            self.commit_and_wait()\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 3)\n\n            widget.k_from = 2\n            widget.k_to = 3\n            widget.optimize_k = True\n\n            compute.reset_mock()\n            self.commit_and_wait()\n            # Since 3 was already computed before when we weren't optimizing,\n            # we only need to compute for 3\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 2)\n\n            # Commiting again should not recompute the clusterings\n            compute.reset_mock()\n            self.commit_and_wait()\n            # compute.assert_not_called unfortunately didn't exist before 3.5\n            self.assertFalse(compute.called)\n\n    def test_data_on_output(self):\n        \"\"\"Check if data is on output after create widget and run\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.apply_button.button.click()\n        self.assertNotEqual(self.widget.data, None)\n        # Disconnect the data\n        self.send_signal(self.widget.Inputs.data, None)\n        # removing data should have cleared the output\n        self.assertEqual(self.widget.data, None)\n\n    def test_centroids_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        self.send_signal(widget.Inputs.data, self.data)\n        self.commit_and_wait()\n        widget.clusterings[widget.k].labels = np.array([0] * 100 + [1] * 203).flatten()\n        widget.clusterings[widget.k].silhouette_samples = np.arange(303) / 303\n        widget.send_data()\n        out = self.get_output(widget.Outputs.centroids)\n        np.testing.assert_array_almost_equal(\n            np.array([[0, np.mean(np.arctan(np.arange(100) / 303)) / np.pi + 0.5],\n                      [1, np.mean(np.arctan(np.arange(100, 303) / 303)) / np.pi + 0.5],\n                      [2, 0], [3, 0]]), out.metas.astype(float))\n        self.assertEqual(out.name, \"heart_disease centroids\")\n\n    def test_centroids_domain_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        heart_disease = Table(\"heart_disease\")\n        heart_disease.name = Table.name  # untitled\n        self.send_signal(widget.Inputs.data, heart_disease)\n        self.commit_and_wait()\n\n        in_attrs = heart_disease.domain.attributes\n        out = self.get_output(widget.Outputs.centroids)\n        out_attrs = out.domain.attributes\n        out_names = {attr.name for attr in out_attrs}\n        for attr in in_attrs:\n            self.assertEqual(\n                attr.name in out_names, attr.is_continuous,\n                f\"at attribute '{attr.name}'\"\n            )\n        self.assertEqual(\n            len(out_attrs),\n            sum(attr.is_continuous or len(attr.values) for attr in in_attrs))\n        self.assertEqual(out.name, \"centroids\")\n\n    class KMeansFail(Orange.clustering.KMeans):\n        fail_on = set()\n\n        def fit(self, X, Y=None):\n            # when not optimizing, params is empty?!\n            k = self.params.get(\"n_clusters\", 3)\n            if k in self.fail_on:\n                raise ValueError(\"k={} fails\".format(k))\n            return super().fit(X, Y)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_optimization_fails(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k_from, widget.k_to = 3, 8\n        widget.optimize_k = True\n\n        self.KMeansFail.fail_on = {3, 5, 7}\n        model = widget.table_view.model()\n\n        with patch.object(\n                model, \"set_scores\", wraps=model.set_scores) as set_scores:\n            self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n            scores, start_k = set_scores.call_args[0]\n            X = self.widget.preproces(self.data).X\n            self.assertEqual(\n                scores,\n                [km if isinstance(km, str) else silhouette_score(\n                    X, km(self.data))\n                 for km in (widget.clusterings[k] for k in range(3, 9))]\n            )\n            self.assertEqual(start_k, 3)\n\n        self.assertIsInstance(widget.clusterings[3], str)\n        self.assertIsInstance(widget.clusterings[5], str)\n        self.assertIsInstance(widget.clusterings[7], str)\n        self.assertNotIsInstance(widget.clusterings[4], str)\n        self.assertNotIsInstance(widget.clusterings[6], str)\n        self.assertNotIsInstance(widget.clusterings[8], str)\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 1)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set(range(3, 9))\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertTrue(widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 0)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_run_fails(self):\n        self.widget.k = 3\n        self.widget.auto_commit = True\n        self.widget.optimize_k = False\n        self.KMeansFail.fail_on = {3}\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertTrue(self.widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        self.widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(self.widget.Error.failed.is_shown())\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_select_best_row(self):\n        widget = self.widget\n        widget.k_from, widget.k_to = 2, 6\n        widget.optimize_k = True\n        widget.normalize = False\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset without normalization,\n        # the best selection is 3 clusters, so row no. 1\n        self.assertEqual(widget.selected_row(), 1)\n\n        self.widget.controls.normalize.toggle()\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset with normalization,\n        # the best selection is 2 clusters, so row no. 0\n        self.assertEqual(widget.selected_row(), 0)\n\n        widget.clusterings = {k: \"error\" for k in range(2, 7)}\n        widget.update_results()\n        self.assertEqual(widget.selected_row(), None)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n        norm_check = widget.controls.normalize\n\n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertFalse(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertTrue(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertFalse(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, self.data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertTrue(norm_check.isEnabled())\n        normalization.assert_called()\n        normalization.reset_mock()\n\n        widget.controls.normalize.click()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertFalse(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n    def test_report(self):\n        widget = self.widget\n        widget.k = 4\n        widget.optimize_k = False\n        with patch.object(widget, \"report_items\") as report_items, \\\n                patch.object(widget, \"report_data\") as report_data, \\\n                patch.object(widget, \"report_table\") as report_table, \\\n                patch.object(widget, \"selected_row\", new=Mock(return_value=42)):\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 4))\n            self.assertEqual(items[1][0], \"Optimization\")\n            self.assertFalse(report_data.called)\n            self.assertFalse(report_table.called)\n\n            widget.data = data = Mock()\n            widget.send_report()\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertFalse(report_table.called)\n\n            report_data.reset_mock()\n            report_items.reset_mock()\n            widget.k_from, widget.k_to = 2, 3\n            widget.optimize_k = True\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 44))\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertIs(report_table.call_args[0][1],\n                          widget.table_view)\n\n    def test_not_enough_rows(self):\n        \"\"\"\n        Widget should not crash when there is less rows than k_from.\n        GH-2172\n        \"\"\"\n        table = self.data[0:1, :]\n        self.widget.controls.k_from.setValue(2)\n        self.widget.controls.k_to.setValue(9)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_from_to_table(self):\n        \"\"\"\n        From and To spins and number of rows in a scores table changes.\n        GH-2172\n        \"\"\"\n        k_from, k_to = 2, 9\n        self.widget.controls.k_from.setValue(k_from)\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        check = lambda x: 2 if x - k_from + 1 < 2 else x - k_from + 1\n        for i in range(k_from, k_to):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n        for i in range(k_to, k_from, -1):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n\n    def test_silhouette_column(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k = 4\n        widget.optimize_k = False\n\n        # Avoid randomness in the test\n        random = np.random.RandomState(0)  # pylint: disable=no-member\n        table = Table.from_numpy(None, random.rand(110, 2))\n        with patch(\n                \"Orange.widgets.unsupervised.owkmeans.SILHOUETTE_MAX_SAMPLES\",\n                100):\n            self.send_signal(self.widget.Inputs.data, table)\n            outtable = self.get_output(widget.Outputs.annotated_data)\n            outtable = outtable.get_column_view(\"Silhouette\")[0]\n        self.assertTrue(np.all(np.isnan(outtable)))\n        self.assertTrue(widget.Warning.no_silhouettes.is_shown())\n\n        self.send_signal(self.widget.Inputs.data, table[:100])\n        outtable = self.get_output(widget.Outputs.annotated_data)\n        outtable = outtable.get_column_view(\"Silhouette\")[0]\n        np.testing.assert_array_less(outtable, 1.01)\n        np.testing.assert_array_less(-0.01, outtable)\n        self.assertFalse(widget.Warning.no_silhouettes.is_shown())\n\n    def test_invalidate_clusterings_cancels_jobs(self):\n        widget = self.widget\n        widget.auto_commit = False\n\n        # Send the data without waiting\n        self.send_signal(widget.Inputs.data, self.data)\n        widget.unconditional_commit()\n        # Now, invalidate by changing max_iter\n        widget.max_iterations = widget.max_iterations + 1\n        widget.invalidate()\n        self.wait_until_finished()\n\n        self.assertEqual(widget.clusterings, {})\n\n    def test_do_not_recluster_on_same_data(self):\n        \"\"\"Do not recluster data points when targets or metas change.\"\"\"\n\n        # Prepare some dummy data\n        x = np.eye(5)\n        y1, y2 = np.ones((5, 1)), np.ones((5, 2))\n        meta1, meta2 = np.ones((5, 1)), np.ones((5, 2))\n\n        table1 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y1, metas=meta1),\n            X=x, Y=y1, metas=meta1,\n        )\n        # X is same, should not cause update\n        table2 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y2, metas=meta2),\n            X=x, Y=y2, metas=meta2,\n        )\n        # X is different, should cause update\n        table3 = table1.copy()\n        table3.X[:, 0] = 1\n\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.send_signal(self.widget.Inputs.data, table1)\n            self.commit_and_wait()\n            commit.reset_mock()\n\n            # Sending data with same X should not recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table2)\n            commit.assert_not_called()\n\n            # Sending data with different X should recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table3)\n            commit.assert_called_once()\n\n    def test_correct_smart_init(self):\n        # due to a bug where wrong init was passed to _compute_clustering\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n        self.widget.smart_init = 0\n        self.widget.clusterings = {}\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"k-means++\")\n        self.widget.invalidate()  # reset caches\n        self.widget.smart_init = 1\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"random\")\n\n    def test_always_same_cluster(self):\n        \"\"\"The same random state should always return the same clusters\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n\n        def cluster():\n            self.widget.invalidate()  # reset caches\n            self.commit_and_wait()\n            return self.get_output(self.widget.Outputs.annotated_data).metas[:, 0]\n\n        def assert_all_same(l):\n            for a1, a2 in zip(l, l[1:]):\n                np.testing.assert_equal(a1, a2)\n\n        self.widget.smart_init = 0\n        assert_all_same([cluster() for _ in range(5)])\n\n        self.widget.smart_init = 1\n        assert_all_same([cluster() for _ in range(5)])\n\n    def test_error_no_attributes(self):\n        domain = Domain([])\n        table = Table.from_domain(domain, n_rows=10)\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, table)\n        self.assertTrue(self.widget.Error.no_attributes.is_shown())\n\n    def test_saved_selection(self):\n        self.widget.send_data = Mock()\n        self.widget.optimize_k = True\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.wait_until_finished()\n        self.widget.table_view.selectRow(2)\n        self.assertEqual(self.widget.selected_row(), 2)\n        self.assertEqual(self.widget.send_data.call_count, 3)\n        settings = self.widget.settingsHandler.pack_data(self.widget)\n\n        w = self.create_widget(OWKMeans, stored_settings=settings)\n        w.send_data = Mock()\n        self.send_signal(w.Inputs.data, self.data, widget=w)\n        self.wait_until_finished(widget=w)\n        self.assertEqual(w.send_data.call_count, 2)\n        self.assertEqual(self.widget.selected_row(), w.selected_row())\n\n    def test_summary(self):\n        \"\"\"Check if the status bar updates\"\"\"\n        info = self.widget.info\n        no_input, no_output = \"No data on input\", \"No data on output\"\n\n        self.send_signal(self.widget.Inputs.data, self.data)\n        summary, details = f\"{len(self.data)}\", format_summary_details(self.data)\n        self.assertEqual(info._StateInfo__input_summary.brief, summary)\n        self.assertEqual(info._StateInfo__input_summary.details, details)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        summary, details = f\"{len(output)}\", format_summary_details(output)\n        self.assertEqual(info._StateInfo__output_summary.brief, summary)\n        self.assertEqual(info._StateInfo__output_summary.details, details)\n\n        self.send_signal(self.widget.Inputs.data, None)\n        self.assertEqual(info._StateInfo__input_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__input_summary.details, no_input)\n        self.assertEqual(info._StateInfo__output_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__output_summary.details, no_output)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/unsupervised/tests/test_owkmeans.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "27671c06b",
                "new_commit_hash": "1e08f9850",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/unsupervised/tests/test_owkmeans.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/unsupervised/tests/test_owkmeans.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 341,
                            "length": 33
                        },
                        "new_range": {
                            "start": 341,
                            "length": 37
                        },
                        "section": "class TestOWKMeans(WidgetTest):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        normalization = normalize.return_value = Mock(return_value=self.data)",
                                "type": "context"
                            },
                            {
                                "content": "        widget = self.widget",
                                "type": "context"
                            },
                            {
                                "content": "        widget.normalize = True",
                                "type": "context"
                            },
                            {
                                "content": "        norm_check = widget.controls.normalize",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))",
                                "type": "context"
                            },
                            {
                                "content": "        data = Table.from_numpy(None, x)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, data)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(norm_check.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        normalization.assert_not_called()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, None)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(norm_check.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        normalization.assert_not_called()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, data)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(norm_check.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        normalization.assert_not_called()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, self.data)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(norm_check.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        normalization.assert_called()",
                                "type": "context"
                            },
                            {
                                "content": "        normalization.reset_mock()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, data)",
                                "type": "deleted"
                            },
                            {
                                "content": "        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())",
                                "type": "deleted"
                            },
                            {
                                "content": "        normalization.assert_not_called()",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        widget.controls.normalize.click()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(widget.Inputs.data, data)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(norm_check.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        normalization.assert_not_called()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_report(self):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owkmeans.py",
                            "type": "class",
                            "name": "TestOWKMeans",
                            "content": "class TestOWKMeans(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(\n            OWKMeans, stored_settings={\"auto_commit\": False, \"version\": 2}\n        )  # type: OWKMeans\n        self.data = Table(\"heart_disease\")\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_migrate_version_1_settings(self):\n        widget = self.create_widget(\n            OWKMeans,\n            stored_settings={'auto_apply': False}\n        )\n        self.assertFalse(widget.auto_commit)\n\n    def test_optimization_report_display(self):\n        \"\"\"Check visibility of the table after selecting number of clusters\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.optimize_k = True\n        radio_buttons = self.widget.controls.optimize_k.findChildren(QRadioButton)\n\n        radio_buttons[0].click()\n        self.assertFalse(self.widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        radio_buttons[1].click()\n        self.assertTrue(self.widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.widget.apply_button.button.click()\n\n        self.wait_until_finished()\n        self.assertEqual(self.widget.table_view.model().rowCount() > 0, True)\n\n    def test_changing_k_changes_radio(self):\n        widget = self.widget\n        widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n\n        widget.optimize_k = True\n\n        widget.controls.k.setValue(5)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_from.setValue(5)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n        widget.controls.k.setValue(3)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_to.setValue(9)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n    def test_k_limits(self):\n        widget = self.widget\n        widget.controls.k_from.setValue(10)\n        self.assertEqual(widget.k_from, 10)\n        self.assertEqual(widget.k_to, 11)\n\n        widget.controls.k_to.setValue(4)\n        self.assertEqual(widget.k_from, 3)\n        self.assertEqual(widget.k_to, 4)\n\n    def test_no_data_hides_main_area(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.optimize_k = True\n        widget.k_from, widget.k_to = 3, 4\n\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n    def test_data_limits(self):\n        \"\"\"Data error should be shown when `k` > n or when `k_from` > n. Data\n        warning should be shown when `k_to` > n\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n\n        self.send_signal(self.widget.Inputs.data, self.data[:5])\n\n        widget.k = 10\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k = 3\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 10\n        widget.optimize_k = True\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 4\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 6\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n        self.assertTrue(widget.Warning.not_enough_data.is_shown())\n\n    def test_use_cache(self):\n        \"\"\"Cache various clusterings for the dataset until data changes.\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n        self.send_signal(self.widget.Inputs.data, self.data)\n\n        with patch.object(widget, \"_compute_clustering\",\n                          wraps=widget._compute_clustering) as compute:\n\n            widget.k = 3\n            widget.optimize_k = False\n\n            self.commit_and_wait()\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 3)\n\n            widget.k_from = 2\n            widget.k_to = 3\n            widget.optimize_k = True\n\n            compute.reset_mock()\n            self.commit_and_wait()\n            # Since 3 was already computed before when we weren't optimizing,\n            # we only need to compute for 3\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 2)\n\n            # Commiting again should not recompute the clusterings\n            compute.reset_mock()\n            self.commit_and_wait()\n            # compute.assert_not_called unfortunately didn't exist before 3.5\n            self.assertFalse(compute.called)\n\n    def test_data_on_output(self):\n        \"\"\"Check if data is on output after create widget and run\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.apply_button.button.click()\n        self.assertNotEqual(self.widget.data, None)\n        # Disconnect the data\n        self.send_signal(self.widget.Inputs.data, None)\n        # removing data should have cleared the output\n        self.assertEqual(self.widget.data, None)\n\n    def test_centroids_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        self.send_signal(widget.Inputs.data, self.data)\n        self.commit_and_wait()\n        widget.clusterings[widget.k].labels = np.array([0] * 100 + [1] * 203).flatten()\n        widget.clusterings[widget.k].silhouette_samples = np.arange(303) / 303\n        widget.send_data()\n        out = self.get_output(widget.Outputs.centroids)\n        np.testing.assert_array_almost_equal(\n            np.array([[0, np.mean(np.arctan(np.arange(100) / 303)) / np.pi + 0.5],\n                      [1, np.mean(np.arctan(np.arange(100, 303) / 303)) / np.pi + 0.5],\n                      [2, 0], [3, 0]]), out.metas.astype(float))\n        self.assertEqual(out.name, \"heart_disease centroids\")\n\n    def test_centroids_domain_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        heart_disease = Table(\"heart_disease\")\n        heart_disease.name = Table.name  # untitled\n        self.send_signal(widget.Inputs.data, heart_disease)\n        self.commit_and_wait()\n\n        in_attrs = heart_disease.domain.attributes\n        out = self.get_output(widget.Outputs.centroids)\n        out_attrs = out.domain.attributes\n        out_names = {attr.name for attr in out_attrs}\n        for attr in in_attrs:\n            self.assertEqual(\n                attr.name in out_names, attr.is_continuous,\n                f\"at attribute '{attr.name}'\"\n            )\n        self.assertEqual(\n            len(out_attrs),\n            sum(attr.is_continuous or len(attr.values) for attr in in_attrs))\n        self.assertEqual(out.name, \"centroids\")\n\n    class KMeansFail(Orange.clustering.KMeans):\n        fail_on = set()\n\n        def fit(self, X, Y=None):\n            # when not optimizing, params is empty?!\n            k = self.params.get(\"n_clusters\", 3)\n            if k in self.fail_on:\n                raise ValueError(\"k={} fails\".format(k))\n            return super().fit(X, Y)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_optimization_fails(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k_from, widget.k_to = 3, 8\n        widget.optimize_k = True\n\n        self.KMeansFail.fail_on = {3, 5, 7}\n        model = widget.table_view.model()\n\n        with patch.object(\n                model, \"set_scores\", wraps=model.set_scores) as set_scores:\n            self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n            scores, start_k = set_scores.call_args[0]\n            X = self.widget.preproces(self.data).X\n            self.assertEqual(\n                scores,\n                [km if isinstance(km, str) else silhouette_score(\n                    X, km(self.data))\n                 for km in (widget.clusterings[k] for k in range(3, 9))]\n            )\n            self.assertEqual(start_k, 3)\n\n        self.assertIsInstance(widget.clusterings[3], str)\n        self.assertIsInstance(widget.clusterings[5], str)\n        self.assertIsInstance(widget.clusterings[7], str)\n        self.assertNotIsInstance(widget.clusterings[4], str)\n        self.assertNotIsInstance(widget.clusterings[6], str)\n        self.assertNotIsInstance(widget.clusterings[8], str)\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 1)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set(range(3, 9))\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertTrue(widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 0)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_run_fails(self):\n        self.widget.k = 3\n        self.widget.auto_commit = True\n        self.widget.optimize_k = False\n        self.KMeansFail.fail_on = {3}\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertTrue(self.widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        self.widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(self.widget.Error.failed.is_shown())\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_select_best_row(self):\n        widget = self.widget\n        widget.k_from, widget.k_to = 2, 6\n        widget.optimize_k = True\n        widget.normalize = False\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset without normalization,\n        # the best selection is 3 clusters, so row no. 1\n        self.assertEqual(widget.selected_row(), 1)\n\n        self.widget.controls.normalize.toggle()\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset with normalization,\n        # the best selection is 2 clusters, so row no. 0\n        self.assertEqual(widget.selected_row(), 0)\n\n        widget.clusterings = {k: \"error\" for k in range(2, 7)}\n        widget.update_results()\n        self.assertEqual(widget.selected_row(), None)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n\n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, self.data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_called()\n        normalization.reset_mock()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        widget.controls.normalize.click()\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n    def test_report(self):\n        widget = self.widget\n        widget.k = 4\n        widget.optimize_k = False\n        with patch.object(widget, \"report_items\") as report_items, \\\n                patch.object(widget, \"report_data\") as report_data, \\\n                patch.object(widget, \"report_table\") as report_table, \\\n                patch.object(widget, \"selected_row\", new=Mock(return_value=42)):\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 4))\n            self.assertEqual(items[1][0], \"Optimization\")\n            self.assertFalse(report_data.called)\n            self.assertFalse(report_table.called)\n\n            widget.data = data = Mock()\n            widget.send_report()\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertFalse(report_table.called)\n\n            report_data.reset_mock()\n            report_items.reset_mock()\n            widget.k_from, widget.k_to = 2, 3\n            widget.optimize_k = True\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 44))\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertIs(report_table.call_args[0][1],\n                          widget.table_view)\n\n    def test_not_enough_rows(self):\n        \"\"\"\n        Widget should not crash when there is less rows than k_from.\n        GH-2172\n        \"\"\"\n        table = self.data[0:1, :]\n        self.widget.controls.k_from.setValue(2)\n        self.widget.controls.k_to.setValue(9)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_from_to_table(self):\n        \"\"\"\n        From and To spins and number of rows in a scores table changes.\n        GH-2172\n        \"\"\"\n        k_from, k_to = 2, 9\n        self.widget.controls.k_from.setValue(k_from)\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        check = lambda x: 2 if x - k_from + 1 < 2 else x - k_from + 1\n        for i in range(k_from, k_to):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n        for i in range(k_to, k_from, -1):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n\n    def test_silhouette_column(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k = 4\n        widget.optimize_k = False\n\n        # Avoid randomness in the test\n        random = np.random.RandomState(0)  # pylint: disable=no-member\n        table = Table.from_numpy(None, random.rand(110, 2))\n        with patch(\n                \"Orange.widgets.unsupervised.owkmeans.SILHOUETTE_MAX_SAMPLES\",\n                100):\n            self.send_signal(self.widget.Inputs.data, table)\n            outtable = self.get_output(widget.Outputs.annotated_data)\n            outtable = outtable.get_column_view(\"Silhouette\")[0]\n        self.assertTrue(np.all(np.isnan(outtable)))\n        self.assertTrue(widget.Warning.no_silhouettes.is_shown())\n\n        self.send_signal(self.widget.Inputs.data, table[:100])\n        outtable = self.get_output(widget.Outputs.annotated_data)\n        outtable = outtable.get_column_view(\"Silhouette\")[0]\n        np.testing.assert_array_less(outtable, 1.01)\n        np.testing.assert_array_less(-0.01, outtable)\n        self.assertFalse(widget.Warning.no_silhouettes.is_shown())\n\n    def test_invalidate_clusterings_cancels_jobs(self):\n        widget = self.widget\n        widget.auto_commit = False\n\n        # Send the data without waiting\n        self.send_signal(widget.Inputs.data, self.data)\n        widget.unconditional_commit()\n        # Now, invalidate by changing max_iter\n        widget.max_iterations = widget.max_iterations + 1\n        widget.invalidate()\n        self.wait_until_finished()\n\n        self.assertEqual(widget.clusterings, {})\n\n    def test_do_not_recluster_on_same_data(self):\n        \"\"\"Do not recluster data points when targets or metas change.\"\"\"\n\n        # Prepare some dummy data\n        x = np.eye(5)\n        y1, y2 = np.ones((5, 1)), np.ones((5, 2))\n        meta1, meta2 = np.ones((5, 1)), np.ones((5, 2))\n\n        table1 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y1, metas=meta1),\n            X=x, Y=y1, metas=meta1,\n        )\n        # X is same, should not cause update\n        table2 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y2, metas=meta2),\n            X=x, Y=y2, metas=meta2,\n        )\n        # X is different, should cause update\n        table3 = table1.copy()\n        table3.X[:, 0] = 1\n\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.send_signal(self.widget.Inputs.data, table1)\n            self.commit_and_wait()\n            commit.reset_mock()\n\n            # Sending data with same X should not recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table2)\n            commit.assert_not_called()\n\n            # Sending data with different X should recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table3)\n            commit.assert_called_once()\n\n    def test_correct_smart_init(self):\n        # due to a bug where wrong init was passed to _compute_clustering\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n        self.widget.smart_init = 0\n        self.widget.clusterings = {}\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"k-means++\")\n        self.widget.invalidate()  # reset caches\n        self.widget.smart_init = 1\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"random\")\n\n    def test_always_same_cluster(self):\n        \"\"\"The same random state should always return the same clusters\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n\n        def cluster():\n            self.widget.invalidate()  # reset caches\n            self.commit_and_wait()\n            return self.get_output(self.widget.Outputs.annotated_data).metas[:, 0]\n\n        def assert_all_same(l):\n            for a1, a2 in zip(l, l[1:]):\n                np.testing.assert_equal(a1, a2)\n\n        self.widget.smart_init = 0\n        assert_all_same([cluster() for _ in range(5)])\n\n        self.widget.smart_init = 1\n        assert_all_same([cluster() for _ in range(5)])\n\n    def test_error_no_attributes(self):\n        domain = Domain([])\n        table = Table.from_domain(domain, n_rows=10)\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, table)\n        self.assertTrue(self.widget.Error.no_attributes.is_shown())\n\n    def test_saved_selection(self):\n        self.widget.send_data = Mock()\n        self.widget.optimize_k = True\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.wait_until_finished()\n        self.widget.table_view.selectRow(2)\n        self.assertEqual(self.widget.selected_row(), 2)\n        self.assertEqual(self.widget.send_data.call_count, 3)\n        settings = self.widget.settingsHandler.pack_data(self.widget)\n\n        w = self.create_widget(OWKMeans, stored_settings=settings)\n        w.send_data = Mock()\n        self.send_signal(w.Inputs.data, self.data, widget=w)\n        self.wait_until_finished(widget=w)\n        self.assertEqual(w.send_data.call_count, 2)\n        self.assertEqual(self.widget.selected_row(), w.selected_row())\n\n    def test_summary(self):\n        \"\"\"Check if the status bar updates\"\"\"\n        info = self.widget.info\n        no_input, no_output = \"No data on input\", \"No data on output\"\n\n        self.send_signal(self.widget.Inputs.data, self.data)\n        summary, details = f\"{len(self.data)}\", format_summary_details(self.data)\n        self.assertEqual(info._StateInfo__input_summary.brief, summary)\n        self.assertEqual(info._StateInfo__input_summary.details, details)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        summary, details = f\"{len(output)}\", format_summary_details(output)\n        self.assertEqual(info._StateInfo__output_summary.brief, summary)\n        self.assertEqual(info._StateInfo__output_summary.details, details)\n\n        self.send_signal(self.widget.Inputs.data, None)\n        self.assertEqual(info._StateInfo__input_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__input_summary.details, no_input)\n        self.assertEqual(info._StateInfo__output_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__output_summary.details, no_output)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 45,
                            "end_lineno": 582,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/unsupervised/tests/test_owkmeans.py",
                            "type": "method",
                            "name": "TestOWKMeans.test_normalize_sparse",
                            "content": "class TestOWKMeans(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(\n            OWKMeans, stored_settings={\"auto_commit\": False, \"version\": 2}\n        )  # type: OWKMeans\n        self.data = Table(\"heart_disease\")\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_migrate_version_1_settings(self):\n        widget = self.create_widget(\n            OWKMeans,\n            stored_settings={'auto_apply': False}\n        )\n        self.assertFalse(widget.auto_commit)\n\n    def test_optimization_report_display(self):\n        \"\"\"Check visibility of the table after selecting number of clusters\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.optimize_k = True\n        radio_buttons = self.widget.controls.optimize_k.findChildren(QRadioButton)\n\n        radio_buttons[0].click()\n        self.assertFalse(self.widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        radio_buttons[1].click()\n        self.assertTrue(self.widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.widget.apply_button.button.click()\n\n        self.wait_until_finished()\n        self.assertEqual(self.widget.table_view.model().rowCount() > 0, True)\n\n    def test_changing_k_changes_radio(self):\n        widget = self.widget\n        widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n\n        widget.optimize_k = True\n\n        widget.controls.k.setValue(5)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_from.setValue(5)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n        widget.controls.k.setValue(3)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_to.setValue(9)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n    def test_k_limits(self):\n        widget = self.widget\n        widget.controls.k_from.setValue(10)\n        self.assertEqual(widget.k_from, 10)\n        self.assertEqual(widget.k_to, 11)\n\n        widget.controls.k_to.setValue(4)\n        self.assertEqual(widget.k_from, 3)\n        self.assertEqual(widget.k_to, 4)\n\n    def test_no_data_hides_main_area(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.optimize_k = True\n        widget.k_from, widget.k_to = 3, 4\n\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n    def test_data_limits(self):\n        \"\"\"Data error should be shown when `k` > n or when `k_from` > n. Data\n        warning should be shown when `k_to` > n\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n\n        self.send_signal(self.widget.Inputs.data, self.data[:5])\n\n        widget.k = 10\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k = 3\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 10\n        widget.optimize_k = True\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 4\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 6\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n        self.assertTrue(widget.Warning.not_enough_data.is_shown())\n\n    def test_use_cache(self):\n        \"\"\"Cache various clusterings for the dataset until data changes.\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n        self.send_signal(self.widget.Inputs.data, self.data)\n\n        with patch.object(widget, \"_compute_clustering\",\n                          wraps=widget._compute_clustering) as compute:\n\n            widget.k = 3\n            widget.optimize_k = False\n\n            self.commit_and_wait()\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 3)\n\n            widget.k_from = 2\n            widget.k_to = 3\n            widget.optimize_k = True\n\n            compute.reset_mock()\n            self.commit_and_wait()\n            # Since 3 was already computed before when we weren't optimizing,\n            # we only need to compute for 3\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 2)\n\n            # Commiting again should not recompute the clusterings\n            compute.reset_mock()\n            self.commit_and_wait()\n            # compute.assert_not_called unfortunately didn't exist before 3.5\n            self.assertFalse(compute.called)\n\n    def test_data_on_output(self):\n        \"\"\"Check if data is on output after create widget and run\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.apply_button.button.click()\n        self.assertNotEqual(self.widget.data, None)\n        # Disconnect the data\n        self.send_signal(self.widget.Inputs.data, None)\n        # removing data should have cleared the output\n        self.assertEqual(self.widget.data, None)\n\n    def test_centroids_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        self.send_signal(widget.Inputs.data, self.data)\n        self.commit_and_wait()\n        widget.clusterings[widget.k].labels = np.array([0] * 100 + [1] * 203).flatten()\n        widget.clusterings[widget.k].silhouette_samples = np.arange(303) / 303\n        widget.send_data()\n        out = self.get_output(widget.Outputs.centroids)\n        np.testing.assert_array_almost_equal(\n            np.array([[0, np.mean(np.arctan(np.arange(100) / 303)) / np.pi + 0.5],\n                      [1, np.mean(np.arctan(np.arange(100, 303) / 303)) / np.pi + 0.5],\n                      [2, 0], [3, 0]]), out.metas.astype(float))\n        self.assertEqual(out.name, \"heart_disease centroids\")\n\n    def test_centroids_domain_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        heart_disease = Table(\"heart_disease\")\n        heart_disease.name = Table.name  # untitled\n        self.send_signal(widget.Inputs.data, heart_disease)\n        self.commit_and_wait()\n\n        in_attrs = heart_disease.domain.attributes\n        out = self.get_output(widget.Outputs.centroids)\n        out_attrs = out.domain.attributes\n        out_names = {attr.name for attr in out_attrs}\n        for attr in in_attrs:\n            self.assertEqual(\n                attr.name in out_names, attr.is_continuous,\n                f\"at attribute '{attr.name}'\"\n            )\n        self.assertEqual(\n            len(out_attrs),\n            sum(attr.is_continuous or len(attr.values) for attr in in_attrs))\n        self.assertEqual(out.name, \"centroids\")\n\n    class KMeansFail(Orange.clustering.KMeans):\n        fail_on = set()\n\n        def fit(self, X, Y=None):\n            # when not optimizing, params is empty?!\n            k = self.params.get(\"n_clusters\", 3)\n            if k in self.fail_on:\n                raise ValueError(\"k={} fails\".format(k))\n            return super().fit(X, Y)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_optimization_fails(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k_from, widget.k_to = 3, 8\n        widget.optimize_k = True\n\n        self.KMeansFail.fail_on = {3, 5, 7}\n        model = widget.table_view.model()\n\n        with patch.object(\n                model, \"set_scores\", wraps=model.set_scores) as set_scores:\n            self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n            scores, start_k = set_scores.call_args[0]\n            X = self.widget.preproces(self.data).X\n            self.assertEqual(\n                scores,\n                [km if isinstance(km, str) else silhouette_score(\n                    X, km(self.data))\n                 for km in (widget.clusterings[k] for k in range(3, 9))]\n            )\n            self.assertEqual(start_k, 3)\n\n        self.assertIsInstance(widget.clusterings[3], str)\n        self.assertIsInstance(widget.clusterings[5], str)\n        self.assertIsInstance(widget.clusterings[7], str)\n        self.assertNotIsInstance(widget.clusterings[4], str)\n        self.assertNotIsInstance(widget.clusterings[6], str)\n        self.assertNotIsInstance(widget.clusterings[8], str)\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 1)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set(range(3, 9))\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertTrue(widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 0)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_run_fails(self):\n        self.widget.k = 3\n        self.widget.auto_commit = True\n        self.widget.optimize_k = False\n        self.KMeansFail.fail_on = {3}\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertTrue(self.widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        self.widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(self.widget.Error.failed.is_shown())\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_select_best_row(self):\n        widget = self.widget\n        widget.k_from, widget.k_to = 2, 6\n        widget.optimize_k = True\n        widget.normalize = False\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset without normalization,\n        # the best selection is 3 clusters, so row no. 1\n        self.assertEqual(widget.selected_row(), 1)\n\n        self.widget.controls.normalize.toggle()\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset with normalization,\n        # the best selection is 2 clusters, so row no. 0\n        self.assertEqual(widget.selected_row(), 0)\n\n        widget.clusterings = {k: \"error\" for k in range(2, 7)}\n        widget.update_results()\n        self.assertEqual(widget.selected_row(), None)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n\n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, self.data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_called()\n        normalization.reset_mock()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        widget.controls.normalize.click()\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n    def test_report(self):\n        widget = self.widget\n        widget.k = 4\n        widget.optimize_k = False\n        with patch.object(widget, \"report_items\") as report_items, \\\n                patch.object(widget, \"report_data\") as report_data, \\\n                patch.object(widget, \"report_table\") as report_table, \\\n                patch.object(widget, \"selected_row\", new=Mock(return_value=42)):\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 4))\n            self.assertEqual(items[1][0], \"Optimization\")\n            self.assertFalse(report_data.called)\n            self.assertFalse(report_table.called)\n\n            widget.data = data = Mock()\n            widget.send_report()\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertFalse(report_table.called)\n\n            report_data.reset_mock()\n            report_items.reset_mock()\n            widget.k_from, widget.k_to = 2, 3\n            widget.optimize_k = True\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 44))\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertIs(report_table.call_args[0][1],\n                          widget.table_view)\n\n    def test_not_enough_rows(self):\n        \"\"\"\n        Widget should not crash when there is less rows than k_from.\n        GH-2172\n        \"\"\"\n        table = self.data[0:1, :]\n        self.widget.controls.k_from.setValue(2)\n        self.widget.controls.k_to.setValue(9)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_from_to_table(self):\n        \"\"\"\n        From and To spins and number of rows in a scores table changes.\n        GH-2172\n        \"\"\"\n        k_from, k_to = 2, 9\n        self.widget.controls.k_from.setValue(k_from)\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        check = lambda x: 2 if x - k_from + 1 < 2 else x - k_from + 1\n        for i in range(k_from, k_to):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n        for i in range(k_to, k_from, -1):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n\n    def test_silhouette_column(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k = 4\n        widget.optimize_k = False\n\n        # Avoid randomness in the test\n        random = np.random.RandomState(0)  # pylint: disable=no-member\n        table = Table.from_numpy(None, random.rand(110, 2))\n        with patch(\n                \"Orange.widgets.unsupervised.owkmeans.SILHOUETTE_MAX_SAMPLES\",\n                100):\n            self.send_signal(self.widget.Inputs.data, table)\n            outtable = self.get_output(widget.Outputs.annotated_data)\n            outtable = outtable.get_column_view(\"Silhouette\")[0]\n        self.assertTrue(np.all(np.isnan(outtable)))\n        self.assertTrue(widget.Warning.no_silhouettes.is_shown())\n\n        self.send_signal(self.widget.Inputs.data, table[:100])\n        outtable = self.get_output(widget.Outputs.annotated_data)\n        outtable = outtable.get_column_view(\"Silhouette\")[0]\n        np.testing.assert_array_less(outtable, 1.01)\n        np.testing.assert_array_less(-0.01, outtable)\n        self.assertFalse(widget.Warning.no_silhouettes.is_shown())\n\n    def test_invalidate_clusterings_cancels_jobs(self):\n        widget = self.widget\n        widget.auto_commit = False\n\n        # Send the data without waiting\n        self.send_signal(widget.Inputs.data, self.data)\n        widget.unconditional_commit()\n        # Now, invalidate by changing max_iter\n        widget.max_iterations = widget.max_iterations + 1\n        widget.invalidate()\n        self.wait_until_finished()\n\n        self.assertEqual(widget.clusterings, {})\n\n    def test_do_not_recluster_on_same_data(self):\n        \"\"\"Do not recluster data points when targets or metas change.\"\"\"\n\n        # Prepare some dummy data\n        x = np.eye(5)\n        y1, y2 = np.ones((5, 1)), np.ones((5, 2))\n        meta1, meta2 = np.ones((5, 1)), np.ones((5, 2))\n\n        table1 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y1, metas=meta1),\n            X=x, Y=y1, metas=meta1,\n        )\n        # X is same, should not cause update\n        table2 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y2, metas=meta2),\n            X=x, Y=y2, metas=meta2,\n        )\n        # X is different, should cause update\n        table3 = table1.copy()\n        table3.X[:, 0] = 1\n\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.send_signal(self.widget.Inputs.data, table1)\n            self.commit_and_wait()\n            commit.reset_mock()\n\n            # Sending data with same X should not recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table2)\n            commit.assert_not_called()\n\n            # Sending data with different X should recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table3)\n            commit.assert_called_once()\n\n    def test_correct_smart_init(self):\n        # due to a bug where wrong init was passed to _compute_clustering\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n        self.widget.smart_init = 0\n        self.widget.clusterings = {}\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"k-means++\")\n        self.widget.invalidate()  # reset caches\n        self.widget.smart_init = 1\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"random\")\n\n    def test_always_same_cluster(self):\n        \"\"\"The same random state should always return the same clusters\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n\n        def cluster():\n            self.widget.invalidate()  # reset caches\n            self.commit_and_wait()\n            return self.get_output(self.widget.Outputs.annotated_data).metas[:, 0]\n\n        def assert_all_same(l):\n            for a1, a2 in zip(l, l[1:]):\n                np.testing.assert_equal(a1, a2)\n\n        self.widget.smart_init = 0\n        assert_all_same([cluster() for _ in range(5)])\n\n        self.widget.smart_init = 1\n        assert_all_same([cluster() for _ in range(5)])\n\n    def test_error_no_attributes(self):\n        domain = Domain([])\n        table = Table.from_domain(domain, n_rows=10)\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, table)\n        self.assertTrue(self.widget.Error.no_attributes.is_shown())\n\n    def test_saved_selection(self):\n        self.widget.send_data = Mock()\n        self.widget.optimize_k = True\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.wait_until_finished()\n        self.widget.table_view.selectRow(2)\n        self.assertEqual(self.widget.selected_row(), 2)\n        self.assertEqual(self.widget.send_data.call_count, 3)\n        settings = self.widget.settingsHandler.pack_data(self.widget)\n\n        w = self.create_widget(OWKMeans, stored_settings=settings)\n        w.send_data = Mock()\n        self.send_signal(w.Inputs.data, self.data, widget=w)\n        self.wait_until_finished(widget=w)\n        self.assertEqual(w.send_data.call_count, 2)\n        self.assertEqual(self.widget.selected_row(), w.selected_row())\n\n    def test_summary(self):\n        \"\"\"Check if the status bar updates\"\"\"\n        info = self.widget.info\n        no_input, no_output = \"No data on input\", \"No data on output\"\n\n        self.send_signal(self.widget.Inputs.data, self.data)\n        summary, details = f\"{len(self.data)}\", format_summary_details(self.data)\n        self.assertEqual(info._StateInfo__input_summary.brief, summary)\n        self.assertEqual(info._StateInfo__input_summary.details, details)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        summary, details = f\"{len(output)}\", format_summary_details(output)\n        self.assertEqual(info._StateInfo__output_summary.brief, summary)\n        self.assertEqual(info._StateInfo__output_summary.details, details)\n\n        self.send_signal(self.widget.Inputs.data, None)\n        self.assertEqual(info._StateInfo__input_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__input_summary.details, no_input)\n        self.assertEqual(info._StateInfo__output_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__output_summary.details, no_output)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 340,
                            "end_lineno": 371,
                            "parent": {
                                "file_name": "Orange/widgets/unsupervised/tests/test_owkmeans.py",
                                "type": "class",
                                "name": "TestOWKMeans",
                                "content": "class TestOWKMeans(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(\n            OWKMeans, stored_settings={\"auto_commit\": False, \"version\": 2}\n        )  # type: OWKMeans\n        self.data = Table(\"heart_disease\")\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_migrate_version_1_settings(self):\n        widget = self.create_widget(\n            OWKMeans,\n            stored_settings={'auto_apply': False}\n        )\n        self.assertFalse(widget.auto_commit)\n\n    def test_optimization_report_display(self):\n        \"\"\"Check visibility of the table after selecting number of clusters\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.optimize_k = True\n        radio_buttons = self.widget.controls.optimize_k.findChildren(QRadioButton)\n\n        radio_buttons[0].click()\n        self.assertFalse(self.widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        radio_buttons[1].click()\n        self.assertTrue(self.widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.widget.apply_button.button.click()\n\n        self.wait_until_finished()\n        self.assertEqual(self.widget.table_view.model().rowCount() > 0, True)\n\n    def test_changing_k_changes_radio(self):\n        widget = self.widget\n        widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n\n        widget.optimize_k = True\n\n        widget.controls.k.setValue(5)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_from.setValue(5)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n        widget.controls.k.setValue(3)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_to.setValue(9)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n    def test_k_limits(self):\n        widget = self.widget\n        widget.controls.k_from.setValue(10)\n        self.assertEqual(widget.k_from, 10)\n        self.assertEqual(widget.k_to, 11)\n\n        widget.controls.k_to.setValue(4)\n        self.assertEqual(widget.k_from, 3)\n        self.assertEqual(widget.k_to, 4)\n\n    def test_no_data_hides_main_area(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.optimize_k = True\n        widget.k_from, widget.k_to = 3, 4\n\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n    def test_data_limits(self):\n        \"\"\"Data error should be shown when `k` > n or when `k_from` > n. Data\n        warning should be shown when `k_to` > n\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n\n        self.send_signal(self.widget.Inputs.data, self.data[:5])\n\n        widget.k = 10\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k = 3\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 10\n        widget.optimize_k = True\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 4\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 6\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n        self.assertTrue(widget.Warning.not_enough_data.is_shown())\n\n    def test_use_cache(self):\n        \"\"\"Cache various clusterings for the dataset until data changes.\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n        self.send_signal(self.widget.Inputs.data, self.data)\n\n        with patch.object(widget, \"_compute_clustering\",\n                          wraps=widget._compute_clustering) as compute:\n\n            widget.k = 3\n            widget.optimize_k = False\n\n            self.commit_and_wait()\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 3)\n\n            widget.k_from = 2\n            widget.k_to = 3\n            widget.optimize_k = True\n\n            compute.reset_mock()\n            self.commit_and_wait()\n            # Since 3 was already computed before when we weren't optimizing,\n            # we only need to compute for 3\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 2)\n\n            # Commiting again should not recompute the clusterings\n            compute.reset_mock()\n            self.commit_and_wait()\n            # compute.assert_not_called unfortunately didn't exist before 3.5\n            self.assertFalse(compute.called)\n\n    def test_data_on_output(self):\n        \"\"\"Check if data is on output after create widget and run\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.apply_button.button.click()\n        self.assertNotEqual(self.widget.data, None)\n        # Disconnect the data\n        self.send_signal(self.widget.Inputs.data, None)\n        # removing data should have cleared the output\n        self.assertEqual(self.widget.data, None)\n\n    def test_centroids_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        self.send_signal(widget.Inputs.data, self.data)\n        self.commit_and_wait()\n        widget.clusterings[widget.k].labels = np.array([0] * 100 + [1] * 203).flatten()\n        widget.clusterings[widget.k].silhouette_samples = np.arange(303) / 303\n        widget.send_data()\n        out = self.get_output(widget.Outputs.centroids)\n        np.testing.assert_array_almost_equal(\n            np.array([[0, np.mean(np.arctan(np.arange(100) / 303)) / np.pi + 0.5],\n                      [1, np.mean(np.arctan(np.arange(100, 303) / 303)) / np.pi + 0.5],\n                      [2, 0], [3, 0]]), out.metas.astype(float))\n        self.assertEqual(out.name, \"heart_disease centroids\")\n\n    def test_centroids_domain_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        heart_disease = Table(\"heart_disease\")\n        heart_disease.name = Table.name  # untitled\n        self.send_signal(widget.Inputs.data, heart_disease)\n        self.commit_and_wait()\n\n        in_attrs = heart_disease.domain.attributes\n        out = self.get_output(widget.Outputs.centroids)\n        out_attrs = out.domain.attributes\n        out_names = {attr.name for attr in out_attrs}\n        for attr in in_attrs:\n            self.assertEqual(\n                attr.name in out_names, attr.is_continuous,\n                f\"at attribute '{attr.name}'\"\n            )\n        self.assertEqual(\n            len(out_attrs),\n            sum(attr.is_continuous or len(attr.values) for attr in in_attrs))\n        self.assertEqual(out.name, \"centroids\")\n\n    class KMeansFail(Orange.clustering.KMeans):\n        fail_on = set()\n\n        def fit(self, X, Y=None):\n            # when not optimizing, params is empty?!\n            k = self.params.get(\"n_clusters\", 3)\n            if k in self.fail_on:\n                raise ValueError(\"k={} fails\".format(k))\n            return super().fit(X, Y)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_optimization_fails(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k_from, widget.k_to = 3, 8\n        widget.optimize_k = True\n\n        self.KMeansFail.fail_on = {3, 5, 7}\n        model = widget.table_view.model()\n\n        with patch.object(\n                model, \"set_scores\", wraps=model.set_scores) as set_scores:\n            self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n            scores, start_k = set_scores.call_args[0]\n            X = self.widget.preproces(self.data).X\n            self.assertEqual(\n                scores,\n                [km if isinstance(km, str) else silhouette_score(\n                    X, km(self.data))\n                 for km in (widget.clusterings[k] for k in range(3, 9))]\n            )\n            self.assertEqual(start_k, 3)\n\n        self.assertIsInstance(widget.clusterings[3], str)\n        self.assertIsInstance(widget.clusterings[5], str)\n        self.assertIsInstance(widget.clusterings[7], str)\n        self.assertNotIsInstance(widget.clusterings[4], str)\n        self.assertNotIsInstance(widget.clusterings[6], str)\n        self.assertNotIsInstance(widget.clusterings[8], str)\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 1)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set(range(3, 9))\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertTrue(widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 0)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_run_fails(self):\n        self.widget.k = 3\n        self.widget.auto_commit = True\n        self.widget.optimize_k = False\n        self.KMeansFail.fail_on = {3}\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertTrue(self.widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        self.widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(self.widget.Error.failed.is_shown())\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_select_best_row(self):\n        widget = self.widget\n        widget.k_from, widget.k_to = 2, 6\n        widget.optimize_k = True\n        widget.normalize = False\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset without normalization,\n        # the best selection is 3 clusters, so row no. 1\n        self.assertEqual(widget.selected_row(), 1)\n\n        self.widget.controls.normalize.toggle()\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset with normalization,\n        # the best selection is 2 clusters, so row no. 0\n        self.assertEqual(widget.selected_row(), 0)\n\n        widget.clusterings = {k: \"error\" for k in range(2, 7)}\n        widget.update_results()\n        self.assertEqual(widget.selected_row(), None)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n\n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, self.data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_called()\n        normalization.reset_mock()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n        widget.controls.normalize.click()\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        normalization.assert_not_called()\n\n    def test_report(self):\n        widget = self.widget\n        widget.k = 4\n        widget.optimize_k = False\n        with patch.object(widget, \"report_items\") as report_items, \\\n                patch.object(widget, \"report_data\") as report_data, \\\n                patch.object(widget, \"report_table\") as report_table, \\\n                patch.object(widget, \"selected_row\", new=Mock(return_value=42)):\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 4))\n            self.assertEqual(items[1][0], \"Optimization\")\n            self.assertFalse(report_data.called)\n            self.assertFalse(report_table.called)\n\n            widget.data = data = Mock()\n            widget.send_report()\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertFalse(report_table.called)\n\n            report_data.reset_mock()\n            report_items.reset_mock()\n            widget.k_from, widget.k_to = 2, 3\n            widget.optimize_k = True\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 44))\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertIs(report_table.call_args[0][1],\n                          widget.table_view)\n\n    def test_not_enough_rows(self):\n        \"\"\"\n        Widget should not crash when there is less rows than k_from.\n        GH-2172\n        \"\"\"\n        table = self.data[0:1, :]\n        self.widget.controls.k_from.setValue(2)\n        self.widget.controls.k_to.setValue(9)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_from_to_table(self):\n        \"\"\"\n        From and To spins and number of rows in a scores table changes.\n        GH-2172\n        \"\"\"\n        k_from, k_to = 2, 9\n        self.widget.controls.k_from.setValue(k_from)\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        check = lambda x: 2 if x - k_from + 1 < 2 else x - k_from + 1\n        for i in range(k_from, k_to):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n        for i in range(k_to, k_from, -1):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n\n    def test_silhouette_column(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k = 4\n        widget.optimize_k = False\n\n        # Avoid randomness in the test\n        random = np.random.RandomState(0)  # pylint: disable=no-member\n        table = Table.from_numpy(None, random.rand(110, 2))\n        with patch(\n                \"Orange.widgets.unsupervised.owkmeans.SILHOUETTE_MAX_SAMPLES\",\n                100):\n            self.send_signal(self.widget.Inputs.data, table)\n            outtable = self.get_output(widget.Outputs.annotated_data)\n            outtable = outtable.get_column_view(\"Silhouette\")[0]\n        self.assertTrue(np.all(np.isnan(outtable)))\n        self.assertTrue(widget.Warning.no_silhouettes.is_shown())\n\n        self.send_signal(self.widget.Inputs.data, table[:100])\n        outtable = self.get_output(widget.Outputs.annotated_data)\n        outtable = outtable.get_column_view(\"Silhouette\")[0]\n        np.testing.assert_array_less(outtable, 1.01)\n        np.testing.assert_array_less(-0.01, outtable)\n        self.assertFalse(widget.Warning.no_silhouettes.is_shown())\n\n    def test_invalidate_clusterings_cancels_jobs(self):\n        widget = self.widget\n        widget.auto_commit = False\n\n        # Send the data without waiting\n        self.send_signal(widget.Inputs.data, self.data)\n        widget.unconditional_commit()\n        # Now, invalidate by changing max_iter\n        widget.max_iterations = widget.max_iterations + 1\n        widget.invalidate()\n        self.wait_until_finished()\n\n        self.assertEqual(widget.clusterings, {})\n\n    def test_do_not_recluster_on_same_data(self):\n        \"\"\"Do not recluster data points when targets or metas change.\"\"\"\n\n        # Prepare some dummy data\n        x = np.eye(5)\n        y1, y2 = np.ones((5, 1)), np.ones((5, 2))\n        meta1, meta2 = np.ones((5, 1)), np.ones((5, 2))\n\n        table1 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y1, metas=meta1),\n            X=x, Y=y1, metas=meta1,\n        )\n        # X is same, should not cause update\n        table2 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y2, metas=meta2),\n            X=x, Y=y2, metas=meta2,\n        )\n        # X is different, should cause update\n        table3 = table1.copy()\n        table3.X[:, 0] = 1\n\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.send_signal(self.widget.Inputs.data, table1)\n            self.commit_and_wait()\n            commit.reset_mock()\n\n            # Sending data with same X should not recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table2)\n            commit.assert_not_called()\n\n            # Sending data with different X should recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table3)\n            commit.assert_called_once()\n\n    def test_correct_smart_init(self):\n        # due to a bug where wrong init was passed to _compute_clustering\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n        self.widget.smart_init = 0\n        self.widget.clusterings = {}\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"k-means++\")\n        self.widget.invalidate()  # reset caches\n        self.widget.smart_init = 1\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"random\")\n\n    def test_always_same_cluster(self):\n        \"\"\"The same random state should always return the same clusters\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n\n        def cluster():\n            self.widget.invalidate()  # reset caches\n            self.commit_and_wait()\n            return self.get_output(self.widget.Outputs.annotated_data).metas[:, 0]\n\n        def assert_all_same(l):\n            for a1, a2 in zip(l, l[1:]):\n                np.testing.assert_equal(a1, a2)\n\n        self.widget.smart_init = 0\n        assert_all_same([cluster() for _ in range(5)])\n\n        self.widget.smart_init = 1\n        assert_all_same([cluster() for _ in range(5)])\n\n    def test_error_no_attributes(self):\n        domain = Domain([])\n        table = Table.from_domain(domain, n_rows=10)\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, table)\n        self.assertTrue(self.widget.Error.no_attributes.is_shown())\n\n    def test_saved_selection(self):\n        self.widget.send_data = Mock()\n        self.widget.optimize_k = True\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.wait_until_finished()\n        self.widget.table_view.selectRow(2)\n        self.assertEqual(self.widget.selected_row(), 2)\n        self.assertEqual(self.widget.send_data.call_count, 3)\n        settings = self.widget.settingsHandler.pack_data(self.widget)\n\n        w = self.create_widget(OWKMeans, stored_settings=settings)\n        w.send_data = Mock()\n        self.send_signal(w.Inputs.data, self.data, widget=w)\n        self.wait_until_finished(widget=w)\n        self.assertEqual(w.send_data.call_count, 2)\n        self.assertEqual(self.widget.selected_row(), w.selected_row())\n\n    def test_summary(self):\n        \"\"\"Check if the status bar updates\"\"\"\n        info = self.widget.info\n        no_input, no_output = \"No data on input\", \"No data on output\"\n\n        self.send_signal(self.widget.Inputs.data, self.data)\n        summary, details = f\"{len(self.data)}\", format_summary_details(self.data)\n        self.assertEqual(info._StateInfo__input_summary.brief, summary)\n        self.assertEqual(info._StateInfo__input_summary.details, details)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        summary, details = f\"{len(output)}\", format_summary_details(output)\n        self.assertEqual(info._StateInfo__output_summary.brief, summary)\n        self.assertEqual(info._StateInfo__output_summary.details, details)\n\n        self.send_signal(self.widget.Inputs.data, None)\n        self.assertEqual(info._StateInfo__input_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__input_summary.details, no_input)\n        self.assertEqual(info._StateInfo__output_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__output_summary.details, no_output)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 45,
                                "end_lineno": 582,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "498d2ca86bd2ebf54712ba90c0affacc43fb2a14^",
    "new_commit_hash": "498d2ca86bd2ebf54712ba90c0affacc43fb2a14",
    "commit_message": "KMeans: Disable normalization check box for sparse data",
    "commit_date": "2021-02-19T14:04:11+01:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "498d2ca86bd2ebf54712ba90c0affacc43fb2a14",
    "test_file_codes": [
        "# pylint: disable=protected-access\nimport unittest\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom AnyQt.QtCore import Qt\nfrom AnyQt.QtWidgets import QRadioButton\nfrom sklearn.metrics import silhouette_score\n\nimport Orange.clustering\nfrom Orange.data import Table, Domain\nfrom Orange.widgets import gui\nfrom Orange.widgets.tests.base import WidgetTest\nfrom Orange.widgets.unsupervised.owkmeans import OWKMeans, ClusterTableModel\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nclass TestClusterTableModel(unittest.TestCase):\n    def test_model(self):\n        model = ClusterTableModel()\n        model.set_scores([\"bad\", 0.250, \"another bad\"], 3)\n\n        self.assertEqual(model.start_k, 3)\n        self.assertEqual(model.rowCount(), 3)\n        ind0, ind1 = model.index(0, 0), model.index(1, 0)\n        self.assertEqual(model.flags(ind0), Qt.NoItemFlags)\n        self.assertEqual(model.flags(ind1),\n                         Qt.ItemIsEnabled | Qt.ItemIsSelectable)\n        data = model.data\n        self.assertEqual(data(ind0), \"NA\")\n        self.assertEqual(data(ind1), \"0.250\")\n        self.assertEqual(data(ind0, Qt.ToolTipRole), \"bad\")\n        self.assertIsNone(data(ind1, Qt.ToolTipRole))\n        self.assertIsNone(data(ind0, gui.BarRatioRole))\n        self.assertAlmostEqual(data(ind1, gui.BarRatioRole), 0.250)\n\n        self.assertAlmostEqual(data(ind1, Qt.TextAlignmentRole),\n                               Qt.AlignVCenter | Qt.AlignLeft)\n\n        self.assertEqual(model.headerData(1, Qt.Vertical), \"4\")\n\n\nclass TestOWKMeans(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(\n            OWKMeans, stored_settings={\"auto_commit\": False, \"version\": 2}\n        )  # type: OWKMeans\n        self.data = Table(\"heart_disease\")\n\n    def tearDown(self):\n        self.widget.onDeleteWidget()\n        super().tearDown()\n\n    def test_migrate_version_1_settings(self):\n        widget = self.create_widget(\n            OWKMeans,\n            stored_settings={'auto_apply': False}\n        )\n        self.assertFalse(widget.auto_commit)\n\n    def test_optimization_report_display(self):\n        \"\"\"Check visibility of the table after selecting number of clusters\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.optimize_k = True\n        radio_buttons = self.widget.controls.optimize_k.findChildren(QRadioButton)\n\n        radio_buttons[0].click()\n        self.assertFalse(self.widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        radio_buttons[1].click()\n        self.assertTrue(self.widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.widget.apply_button.button.click()\n\n        self.wait_until_finished()\n        self.assertEqual(self.widget.table_view.model().rowCount() > 0, True)\n\n    def test_changing_k_changes_radio(self):\n        widget = self.widget\n        widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n\n        widget.optimize_k = True\n\n        widget.controls.k.setValue(5)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_from.setValue(5)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n        widget.controls.k.setValue(3)\n        self.assertFalse(widget.optimize_k)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n        widget.controls.k_to.setValue(9)\n        self.assertTrue(widget.optimize_k)\n        self.assertFalse(self.widget.mainArea.isHidden())\n\n    def test_k_limits(self):\n        widget = self.widget\n        widget.controls.k_from.setValue(10)\n        self.assertEqual(widget.k_from, 10)\n        self.assertEqual(widget.k_to, 11)\n\n        widget.controls.k_to.setValue(4)\n        self.assertEqual(widget.k_from, 3)\n        self.assertEqual(widget.k_to, 4)\n\n    def test_no_data_hides_main_area(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.optimize_k = True\n        widget.k_from, widget.k_to = 3, 4\n\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertFalse(self.widget.mainArea.isHidden())\n        self.send_signal(self.widget.Inputs.data, None, wait=5000)\n        self.assertTrue(self.widget.mainArea.isHidden())\n\n    def test_data_limits(self):\n        \"\"\"Data error should be shown when `k` > n or when `k_from` > n. Data\n        warning should be shown when `k_to` > n\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n\n        self.send_signal(self.widget.Inputs.data, self.data[:5])\n\n        widget.k = 10\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k = 3\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 10\n        widget.optimize_k = True\n        self.commit_and_wait()\n        self.assertTrue(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 4\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n\n        widget.k_from = 3\n        widget.k_to = 6\n        self.commit_and_wait()\n        self.assertFalse(widget.Error.not_enough_data.is_shown())\n        self.assertTrue(widget.Warning.not_enough_data.is_shown())\n\n    def test_use_cache(self):\n        \"\"\"Cache various clusterings for the dataset until data changes.\"\"\"\n        widget = self.widget\n        widget.auto_commit = False\n        self.send_signal(self.widget.Inputs.data, self.data)\n\n        with patch.object(widget, \"_compute_clustering\",\n                          wraps=widget._compute_clustering) as compute:\n\n            widget.k = 3\n            widget.optimize_k = False\n\n            self.commit_and_wait()\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 3)\n\n            widget.k_from = 2\n            widget.k_to = 3\n            widget.optimize_k = True\n\n            compute.reset_mock()\n            self.commit_and_wait()\n            # Since 3 was already computed before when we weren't optimizing,\n            # we only need to compute for 3\n            self.assertEqual(compute.call_count, 1)\n            self.assertEqual(compute.call_args[1]['k'], 2)\n\n            # Commiting again should not recompute the clusterings\n            compute.reset_mock()\n            self.commit_and_wait()\n            # compute.assert_not_called unfortunately didn't exist before 3.5\n            self.assertFalse(compute.called)\n\n    def test_data_on_output(self):\n        \"\"\"Check if data is on output after create widget and run\"\"\"\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.widget.apply_button.button.click()\n        self.assertNotEqual(self.widget.data, None)\n        # Disconnect the data\n        self.send_signal(self.widget.Inputs.data, None)\n        # removing data should have cleared the output\n        self.assertEqual(self.widget.data, None)\n\n    def test_centroids_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        self.send_signal(widget.Inputs.data, self.data)\n        self.commit_and_wait()\n        widget.clusterings[widget.k].labels = np.array([0] * 100 + [1] * 203).flatten()\n        widget.clusterings[widget.k].silhouette_samples = np.arange(303) / 303\n        widget.send_data()\n        out = self.get_output(widget.Outputs.centroids)\n        np.testing.assert_array_almost_equal(\n            np.array([[0, np.mean(np.arctan(np.arange(100) / 303)) / np.pi + 0.5],\n                      [1, np.mean(np.arctan(np.arange(100, 303) / 303)) / np.pi + 0.5],\n                      [2, 0], [3, 0]]), out.metas.astype(float))\n        self.assertEqual(out.name, \"heart_disease centroids\")\n\n    def test_centroids_domain_on_output(self):\n        widget = self.widget\n        widget.optimize_k = False\n        widget.k = 4\n        heart_disease = Table(\"heart_disease\")\n        heart_disease.name = Table.name  # untitled\n        self.send_signal(widget.Inputs.data, heart_disease)\n        self.commit_and_wait()\n\n        in_attrs = heart_disease.domain.attributes\n        out = self.get_output(widget.Outputs.centroids)\n        out_attrs = out.domain.attributes\n        out_names = {attr.name for attr in out_attrs}\n        for attr in in_attrs:\n            self.assertEqual(\n                attr.name in out_names, attr.is_continuous,\n                f\"at attribute '{attr.name}'\"\n            )\n        self.assertEqual(\n            len(out_attrs),\n            sum(attr.is_continuous or len(attr.values) for attr in in_attrs))\n        self.assertEqual(out.name, \"centroids\")\n\n    class KMeansFail(Orange.clustering.KMeans):\n        fail_on = set()\n\n        def fit(self, X, Y=None):\n            # when not optimizing, params is empty?!\n            k = self.params.get(\"n_clusters\", 3)\n            if k in self.fail_on:\n                raise ValueError(\"k={} fails\".format(k))\n            return super().fit(X, Y)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_optimization_fails(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k_from, widget.k_to = 3, 8\n        widget.optimize_k = True\n\n        self.KMeansFail.fail_on = {3, 5, 7}\n        model = widget.table_view.model()\n\n        with patch.object(\n                model, \"set_scores\", wraps=model.set_scores) as set_scores:\n            self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n            scores, start_k = set_scores.call_args[0]\n            X = self.widget.preproces(self.data).X\n            self.assertEqual(\n                scores,\n                [km if isinstance(km, str) else silhouette_score(\n                    X, km(self.data))\n                 for km in (widget.clusterings[k] for k in range(3, 9))]\n            )\n            self.assertEqual(start_k, 3)\n\n        self.assertIsInstance(widget.clusterings[3], str)\n        self.assertIsInstance(widget.clusterings[5], str)\n        self.assertIsInstance(widget.clusterings[7], str)\n        self.assertNotIsInstance(widget.clusterings[4], str)\n        self.assertNotIsInstance(widget.clusterings[6], str)\n        self.assertNotIsInstance(widget.clusterings[8], str)\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 1)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set(range(3, 9))\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertTrue(widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(widget.Error.failed.is_shown())\n        self.assertEqual(widget.selected_row(), 0)\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.KMeans\", new=KMeansFail)\n    def test_run_fails(self):\n        self.widget.k = 3\n        self.widget.auto_commit = True\n        self.widget.optimize_k = False\n        self.KMeansFail.fail_on = {3}\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        self.assertTrue(self.widget.Error.failed.is_shown())\n        self.assertIsNone(self.get_output(self.widget.Outputs.annotated_data))\n\n        self.KMeansFail.fail_on = set()\n        self.widget.invalidate()\n        self.wait_until_finished()\n        self.assertFalse(self.widget.Error.failed.is_shown())\n        self.assertIsNotNone(self.get_output(self.widget.Outputs.annotated_data))\n\n    def test_select_best_row(self):\n        widget = self.widget\n        widget.k_from, widget.k_to = 2, 6\n        widget.optimize_k = True\n        widget.normalize = False\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset without normalization,\n        # the best selection is 3 clusters, so row no. 1\n        self.assertEqual(widget.selected_row(), 1)\n\n        self.widget.controls.normalize.toggle()\n        self.send_signal(self.widget.Inputs.data, Table(\"housing\"), wait=5000)\n        self.commit_and_wait()\n        widget.update_results()\n        # for housing dataset with normalization,\n        # the best selection is 2 clusters, so row no. 0\n        self.assertEqual(widget.selected_row(), 0)\n\n        widget.clusterings = {k: \"error\" for k in range(2, 7)}\n        widget.update_results()\n        self.assertEqual(widget.selected_row(), None)\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n        norm_check = widget.controls.normalize\n\n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertFalse(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertTrue(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertFalse(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n        self.send_signal(widget.Inputs.data, self.data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertTrue(norm_check.isEnabled())\n        normalization.assert_called()\n        normalization.reset_mock()\n\n        widget.controls.normalize.click()\n\n        self.send_signal(widget.Inputs.data, data)\n        self.assertFalse(widget.Warning.no_sparse_normalization.is_shown())\n        self.assertFalse(norm_check.isEnabled())\n        normalization.assert_not_called()\n\n    def test_report(self):\n        widget = self.widget\n        widget.k = 4\n        widget.optimize_k = False\n        with patch.object(widget, \"report_items\") as report_items, \\\n                patch.object(widget, \"report_data\") as report_data, \\\n                patch.object(widget, \"report_table\") as report_table, \\\n                patch.object(widget, \"selected_row\", new=Mock(return_value=42)):\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 4))\n            self.assertEqual(items[1][0], \"Optimization\")\n            self.assertFalse(report_data.called)\n            self.assertFalse(report_table.called)\n\n            widget.data = data = Mock()\n            widget.send_report()\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertFalse(report_table.called)\n\n            report_data.reset_mock()\n            report_items.reset_mock()\n            widget.k_from, widget.k_to = 2, 3\n            widget.optimize_k = True\n            widget.send_report()\n            items = report_items.call_args[0][0]\n            self.assertEqual(items[0], (\"Number of clusters\", 44))\n            self.assertIs(report_data.call_args[0][1], data)\n            self.assertIs(report_table.call_args[0][1],\n                          widget.table_view)\n\n    def test_not_enough_rows(self):\n        \"\"\"\n        Widget should not crash when there is less rows than k_from.\n        GH-2172\n        \"\"\"\n        table = self.data[0:1, :]\n        self.widget.controls.k_from.setValue(2)\n        self.widget.controls.k_to.setValue(9)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_from_to_table(self):\n        \"\"\"\n        From and To spins and number of rows in a scores table changes.\n        GH-2172\n        \"\"\"\n        k_from, k_to = 2, 9\n        self.widget.controls.k_from.setValue(k_from)\n        self.send_signal(self.widget.Inputs.data, self.data, wait=5000)\n        check = lambda x: 2 if x - k_from + 1 < 2 else x - k_from + 1\n        for i in range(k_from, k_to):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n        for i in range(k_to, k_from, -1):\n            self.widget.controls.k_to.setValue(i)\n            self.commit_and_wait()\n            self.assertEqual(len(self.widget.table_view.model().scores), check(i))\n\n    def test_silhouette_column(self):\n        widget = self.widget\n        widget.auto_commit = True\n        widget.k = 4\n        widget.optimize_k = False\n\n        # Avoid randomness in the test\n        random = np.random.RandomState(0)  # pylint: disable=no-member\n        table = Table.from_numpy(None, random.rand(110, 2))\n        with patch(\n                \"Orange.widgets.unsupervised.owkmeans.SILHOUETTE_MAX_SAMPLES\",\n                100):\n            self.send_signal(self.widget.Inputs.data, table)\n            outtable = self.get_output(widget.Outputs.annotated_data)\n            outtable = outtable.get_column_view(\"Silhouette\")[0]\n        self.assertTrue(np.all(np.isnan(outtable)))\n        self.assertTrue(widget.Warning.no_silhouettes.is_shown())\n\n        self.send_signal(self.widget.Inputs.data, table[:100])\n        outtable = self.get_output(widget.Outputs.annotated_data)\n        outtable = outtable.get_column_view(\"Silhouette\")[0]\n        np.testing.assert_array_less(outtable, 1.01)\n        np.testing.assert_array_less(-0.01, outtable)\n        self.assertFalse(widget.Warning.no_silhouettes.is_shown())\n\n    def test_invalidate_clusterings_cancels_jobs(self):\n        widget = self.widget\n        widget.auto_commit = False\n\n        # Send the data without waiting\n        self.send_signal(widget.Inputs.data, self.data)\n        widget.unconditional_commit()\n        # Now, invalidate by changing max_iter\n        widget.max_iterations = widget.max_iterations + 1\n        widget.invalidate()\n        self.wait_until_finished()\n\n        self.assertEqual(widget.clusterings, {})\n\n    def test_do_not_recluster_on_same_data(self):\n        \"\"\"Do not recluster data points when targets or metas change.\"\"\"\n\n        # Prepare some dummy data\n        x = np.eye(5)\n        y1, y2 = np.ones((5, 1)), np.ones((5, 2))\n        meta1, meta2 = np.ones((5, 1)), np.ones((5, 2))\n\n        table1 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y1, metas=meta1),\n            X=x, Y=y1, metas=meta1,\n        )\n        # X is same, should not cause update\n        table2 = Table.from_numpy(\n            domain=Domain.from_numpy(X=x, Y=y2, metas=meta2),\n            X=x, Y=y2, metas=meta2,\n        )\n        # X is different, should cause update\n        table3 = table1.copy()\n        table3.X[:, 0] = 1\n\n        with patch.object(self.widget, 'unconditional_commit') as commit:\n            self.send_signal(self.widget.Inputs.data, table1)\n            self.commit_and_wait()\n            commit.reset_mock()\n\n            # Sending data with same X should not recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table2)\n            commit.assert_not_called()\n\n            # Sending data with different X should recompute the clustering\n            self.send_signal(self.widget.Inputs.data, table3)\n            commit.assert_called_once()\n\n    def test_correct_smart_init(self):\n        # due to a bug where wrong init was passed to _compute_clustering\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n        self.widget.smart_init = 0\n        self.widget.clusterings = {}\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"k-means++\")\n        self.widget.invalidate()  # reset caches\n        self.widget.smart_init = 1\n        with patch.object(self.widget, \"_compute_clustering\",\n                          wraps=self.widget._compute_clustering) as compute:\n            self.commit_and_wait()\n            self.assertEqual(compute.call_args[1]['init'], \"random\")\n\n    def test_always_same_cluster(self):\n        \"\"\"The same random state should always return the same clusters\"\"\"\n        self.send_signal(self.widget.Inputs.data, self.data[::10], wait=5000)\n\n        def cluster():\n            self.widget.invalidate()  # reset caches\n            self.commit_and_wait()\n            return self.get_output(self.widget.Outputs.annotated_data).metas[:, 0]\n\n        def assert_all_same(l):\n            for a1, a2 in zip(l, l[1:]):\n                np.testing.assert_equal(a1, a2)\n\n        self.widget.smart_init = 0\n        assert_all_same([cluster() for _ in range(5)])\n\n        self.widget.smart_init = 1\n        assert_all_same([cluster() for _ in range(5)])\n\n    def test_error_no_attributes(self):\n        domain = Domain([])\n        table = Table.from_domain(domain, n_rows=10)\n        self.widget.auto_commit = True\n        self.send_signal(self.widget.Inputs.data, table)\n        self.assertTrue(self.widget.Error.no_attributes.is_shown())\n\n    def test_saved_selection(self):\n        self.widget.send_data = Mock()\n        self.widget.optimize_k = True\n        self.send_signal(self.widget.Inputs.data, self.data)\n        self.wait_until_finished()\n        self.widget.table_view.selectRow(2)\n        self.assertEqual(self.widget.selected_row(), 2)\n        self.assertEqual(self.widget.send_data.call_count, 3)\n        settings = self.widget.settingsHandler.pack_data(self.widget)\n\n        w = self.create_widget(OWKMeans, stored_settings=settings)\n        w.send_data = Mock()\n        self.send_signal(w.Inputs.data, self.data, widget=w)\n        self.wait_until_finished(widget=w)\n        self.assertEqual(w.send_data.call_count, 2)\n        self.assertEqual(self.widget.selected_row(), w.selected_row())\n\n    def test_summary(self):\n        \"\"\"Check if the status bar updates\"\"\"\n        info = self.widget.info\n        no_input, no_output = \"No data on input\", \"No data on output\"\n\n        self.send_signal(self.widget.Inputs.data, self.data)\n        summary, details = f\"{len(self.data)}\", format_summary_details(self.data)\n        self.assertEqual(info._StateInfo__input_summary.brief, summary)\n        self.assertEqual(info._StateInfo__input_summary.details, details)\n        output = self.get_output(self.widget.Outputs.annotated_data)\n        summary, details = f\"{len(output)}\", format_summary_details(output)\n        self.assertEqual(info._StateInfo__output_summary.brief, summary)\n        self.assertEqual(info._StateInfo__output_summary.details, details)\n\n        self.send_signal(self.widget.Inputs.data, None)\n        self.assertEqual(info._StateInfo__input_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__input_summary.details, no_input)\n        self.assertEqual(info._StateInfo__output_summary.brief, \"-\")\n        self.assertEqual(info._StateInfo__output_summary.details, no_output)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nCompiling Orange/classification/_tree_scorers.pyx because it changed.\nCompiling Orange/data/_contingency.pyx because it changed.\nCompiling Orange/data/_io.pyx because it changed.\nCompiling Orange/data/_valuecount.pyx because it changed.\nCompiling Orange/data/_variable.pyx because it changed.\nCompiling Orange/distance/_distance.pyx because it changed.\nCompiling Orange/preprocess/_discretize.pyx because it changed.\nCompiling Orange/preprocess/_relieff.pyx because it changed.\nCompiling Orange/projection/_som.pyx because it changed.\n[1/9] Cythonizing Orange/classification/_tree_scorers.pyx\n[2/9] Cythonizing Orange/data/_contingency.pyx\n[3/9] Cythonizing Orange/data/_io.pyx\n[4/9] Cythonizing Orange/data/_valuecount.pyx\n[5/9] Cythonizing Orange/data/_variable.pyx\n[6/9] Cythonizing Orange/distance/_distance.pyx\n[7/9] Cythonizing Orange/preprocess/_discretize.pyx\n[8/9] Cythonizing Orange/preprocess/_relieff.pyx\n[9/9] Cythonizing Orange/projection/_som.pyx\nbuilding 'Orange.classification._tree_scorers' extension\ncreating build\ncreating build/temp.linux-x86_64-3.7\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_tree_scorers.c -o build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o\ncreating build/lib.linux-x86_64-3.7\ncreating build/lib.linux-x86_64-3.7/Orange\ncreating build/lib.linux-x86_64-3.7/Orange/classification\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_contingency.c -o build/temp.linux-x86_64-3.7/Orange/data/_contingency.o\ncreating build/lib.linux-x86_64-3.7/Orange/data\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_io.c -o build/temp.linux-x86_64-3.7/Orange/data/_io.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_valuecount.c -o build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_variable.c -o build/temp.linux-x86_64-3.7/Orange/data/_variable.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/distance/_distance.c -o build/temp.linux-x86_64-3.7/Orange/distance/_distance.o\ncreating build/lib.linux-x86_64-3.7/Orange/distance\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_discretize.c -o build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o\ncreating build/lib.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_relieff.cpp -o build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/projection/_som.c -o build/temp.linux-x86_64-3.7/Orange/projection/_som.o\ncreating build/lib.linux-x86_64-3.7/Orange/projection\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._simple_tree' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_simple_tree.c -o build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/widgets/utils/_grid_density.cpp -o build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o\ncreating build/lib.linux-x86_64-3.7/Orange/widgets\ncreating build/lib.linux-x86_64-3.7/Orange/widgets/utils\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nrunning develop\nrunning egg_info\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.28.0.dev0+498d2ca to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14\nProcessing dependencies for Orange3==3.28.0.dev0+498d2ca\nSearching for XlsxWriter==3.2.0\nBest match: XlsxWriter 3.2.0\nAdding XlsxWriter 3.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==1.0.2\nBest match: scikit-learn 1.0.2\nAdding scikit-learn 1.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.4\nBest match: pyqtgraph 0.12.4\nAdding pyqtgraph 0.12.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for openpyxl==3.1.3\nBest match: openpyxl 3.1.3\nAdding openpyxl 3.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for httpx==0.14.3\nBest match: httpx 0.14.3\nAdding httpx 0.14.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for chardet==3.0.4\nBest match: chardet 3.0.4\nAdding chardet 3.0.4 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for baycomp==1.0.3\nBest match: baycomp 1.0.3\nAdding baycomp 1.0.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine==5.15.6\nBest match: PyQtWebEngine 5.15.6\nAdding PyQtWebEngine 5.15.6 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for PyQt5==5.15.10\nBest match: PyQt5 5.15.10\nAdding PyQt5 5.15.10 to easy-install.pth file\nInstalling pylupdate5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling pyrcc5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling pyuic5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for threadpoolctl==3.1.0\nBest match: threadpoolctl 3.1.0\nAdding threadpoolctl 3.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for et-xmlfile==1.1.0\nBest match: et-xmlfile 1.1.0\nAdding et-xmlfile 1.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for httpcore==0.10.2\nBest match: httpcore 0.10.2\nAdding httpcore 0.10.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for sniffio==1.3.1\nBest match: sniffio 1.3.1\nAdding sniffio 1.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for rfc3986==1.5.0\nBest match: rfc3986 1.5.0\nAdding rfc3986 1.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine-Qt5==5.15.16\nBest match: PyQtWebEngine-Qt5 5.15.16\nAdding PyQtWebEngine-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for PyQt5-sip==12.13.0\nBest match: PyQt5-sip 12.13.0\nAdding PyQt5-sip 12.13.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for PyQt5-Qt5==5.15.16\nBest match: PyQt5-Qt5 5.15.16\nAdding PyQt5-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for h11==0.9.0\nBest match: h11 0.9.0\nAdding h11 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.28.0.dev0+498d2ca\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 142ms\nPrepared 18 packages in 16ms\nInstalled 18 packages in 67ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 53 packages in 305ms\nInstalled 47 packages in 696ms\n + baycomp==1.0.3\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==3.0.4\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + cycler==0.11.0\n + et-xmlfile==1.1.0\n + fonttools==4.38.0\n + h11==0.9.0\n + httpcore==0.10.2\n + httpx==0.14.3\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + more-itertools==9.1.0\n + networkx==2.6.3\n + openpyxl==3.1.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pillow==9.5.0\n + pip==24.0\n + pycparser==2.21\n + pyparsing==3.1.4\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + rfc3986==1.5.0\n + scikit-learn==1.0.2\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + sniffio==1.3.1\n + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 39 packages in 136ms\nInstalled 13 packages in 314ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + platformdirs==4.0.0\n + pyqtgraph==0.12.4\n + qasync==0.25.0\n + requests-cache==1.1.1\n + url-normalize==1.4.3\nResolved 2 packages in 3ms\nInstalled 2 packages in 23ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nResolved 19 packages in 39ms\nInstalled 5 packages in 969ms\n + catboost==1.2.7\n + graphviz==0.20.1\n + plotly==5.18.0\n + tenacity==8.2.3\n + xgboost==1.6.2\nAudited 2 packages in 1ms\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3413:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3413 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4869:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4869 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5615:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5615 |           for (__pyx_t_19 = 0; __pyx_t_19 < __pyx_t_18; __pyx_t_19+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5736:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5736 |           __pyx_t_20 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5742:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5742 |           __pyx_t_20 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7202:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7202 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7857:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7857 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7917:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7917 |               __pyx_t_15 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_14 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:4120:43: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4120 |           __pyx_v_value = ((__pyx_v_value * __pyx_v_decs) + (__pyx_v_c & 0xf));\n      |                            ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/data/_io.c:4318:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4318 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 223, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:683:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  683 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1590:7: note: ‘__pyx_v_attr_index’ was declared here\n 1590 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:3183:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3183 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:774:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:790:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3863:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3863 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3862:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3862 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4934:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4934 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4933:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4933 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4732:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4732 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4307:7: note: ‘__pyx_v_win_y’ was declared here\n 4307 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4721:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4721 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4306:7: note: ‘__pyx_v_win_x’ was declared here\n 4306 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5682:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5682 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5681:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5681 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:3184:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3184 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2887:7: note: ‘__pyx_v_win_y’ was declared here\n 2887 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3173:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3173 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2886:7: note: ‘__pyx_v_win_x’ was declared here\n 2886 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3357:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3357 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/setuptools/command/easy_install.py:159: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n  EasyInstallDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching 'COPYING'\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14\ncollected 32 items\n\nr2e_tests/test_1.py sss.............................                     [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestClusterTableModel::test_model\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_always_same_cluster\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_centroids_domain_on_output\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_centroids_on_output\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_changing_k_changes_radio\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_correct_smart_init\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_data_limits\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_data_on_output\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_do_not_recluster_on_same_data\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_error_no_attributes\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_from_to_table\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_image_export\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_invalidate_clusterings_cancels_jobs\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_k_limits\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_migrate_version_1_settings\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_no_data_hides_main_area\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_normalize_sparse\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_not_enough_rows\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_optimization_fails\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_optimization_report_display\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_report\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_run_fails\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_saved_selection\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_select_best_row\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_silhouette_column\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_summary\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_use_cache\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\n======================== 29 passed, 3 skipped in 15.40s ========================\nWidgets left: 0    Max widgets: 3000 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_498d2ca86bd2ebf54712ba90c0affacc43fb2a14\ncollected 32 items\n\nr2e_tests/test_1.py sss..................F..........                     [100%]\n\n=================================== FAILURES ===================================\n______________________ TestOWKMeans.test_normalize_sparse ______________________\n\nself = <r2e_tests.test_1.TestOWKMeans testMethod=test_normalize_sparse>\nnormalize = <MagicMock name='Normalize' id='125309489620752'>\n\n    @patch(\"Orange.widgets.unsupervised.owkmeans.Normalize\")\n    def test_normalize_sparse(self, normalize):\n        normalization = normalize.return_value = Mock(return_value=self.data)\n        widget = self.widget\n        widget.normalize = True\n        norm_check = widget.controls.normalize\n    \n        x = sp.csr_matrix(np.random.randint(0, 2, (5, 10)))\n        data = Table.from_numpy(None, x)\n    \n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.Warning.no_sparse_normalization.is_shown())\n>       self.assertFalse(norm_check.isEnabled())\nE       AssertionError: True is not false\n\nr2e_tests/test_1.py:351: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestClusterTableModel::test_model\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_always_same_cluster\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_centroids_domain_on_output\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_centroids_on_output\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_changing_k_changes_radio\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_correct_smart_init\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_data_limits\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_data_on_output\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_do_not_recluster_on_same_data\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_error_no_attributes\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_from_to_table\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_image_export\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_invalidate_clusterings_cancels_jobs\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_k_limits\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_migrate_version_1_settings\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_no_data_hides_main_area\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_not_enough_rows\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_optimization_fails\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_optimization_report_display\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_report\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_run_fails\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_saved_selection\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_select_best_row\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_silhouette_column\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_summary\nPASSED r2e_tests/test_1.py::TestOWKMeans::test_use_cache\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestOWKMeans::test_normalize_sparse - AssertionEr...\n=================== 1 failed, 28 passed, 3 skipped in 12.70s ===================\nWidgets left: 0    Max widgets: 3000 \n\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/unsupervised/owkmeans.py Orange/widgets/unsupervised/tests/test_owkmeans.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Normalization Option Remains Enabled for Sparse Data in KMeans Widget

**Description:**
When using the KMeans widget with sparse datasets, the normalization checkbox is still enabled. This allows users to attempt normalization on sparse data, which is not supported and can lead to unexpected behavior or errors.

**Example:**
```python
from Orange.widgets.unsupervised.owkmeans import OWKMeans
from Orange.data import Table
import scipy.sparse as sp

# Create sparse data
sparse_matrix = sp.csr_matrix([[0, 1, 0], [1, 0, 1]])
data = Table.from_numpy(None, sparse_matrix)

# Initialize KMeans widget
widget = OWKMeans()
widget.normalize = True

# Send sparse data to the widget
widget.send_signal(widget.Inputs.data, data)

# Check if normalization checkbox is enabled
is_enabled = widget.controls.normalize.isEnabled()
print("Normalization enabled:", is_enabled)
```

**Expected Behavior:**
The normalization checkbox should be disabled (`is_enabled` should be `False`) when the input data is sparse, preventing users from attempting unsupported normalization.

**Actual Behavior:**
The normalization checkbox remains enabled (`is_enabled` is `True`) even when the input data is sparse, allowing users to try normalizing sparse data, which can cause issues.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/unsupervised/owkmeans.py
from concurrent.futures import Future
from typing import Optional, List, Dict

import numpy as np
import scipy.sparse as sp

from AnyQt.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QThread, \
    pyqtSlot as Slot
from AnyQt.QtGui import QIntValidator
from AnyQt.QtWidgets import QGridLayout, QTableView
from sklearn.metrics import silhouette_samples, silhouette_score

from Orange.clustering import KMeans
from Orange.clustering.kmeans import KMeansModel
from Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable
from Orange.data.util import get_unique_names, array_equal
from Orange.preprocess import Normalize
from Orange.preprocess.impute import ReplaceUnknowns
from Orange.widgets import widget, gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils.annotated_data import \
    ANNOTATED_DATA_SIGNAL_NAME, add_columns
from Orange.widgets.utils.concurrent import ThreadExecutor, FutureSetWatcher
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.widget import Input, Output


RANDOM_STATE = 0
SILHOUETTE_MAX_SAMPLES = 5000


class ClusterTableModel(QAbstractTableModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.scores = []
        self.start_k = 0

    def rowCount(self, index=QModelIndex()):
        return 0 if index.isValid() else len(self.scores)

    @staticmethod
    def columnCount(_index=QModelIndex()):
        return 1

    def flags(self, index):
        if isinstance(self.scores[index.row()], str):
            return Qt.NoItemFlags
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def set_scores(self, scores, start_k):
        self.modelAboutToBeReset.emit()
        self.scores = scores
        self.start_k = start_k
        self.modelReset.emit()

    def clear_scores(self):
        self.modelAboutToBeReset.emit()
        self.scores = []
        self.start_k = 0
        self.modelReset.emit()

    def data(self, index, role=Qt.DisplayRole):
        score = self.scores[index.row()]
        valid = not isinstance(score, str)
        if role == Qt.DisplayRole:
            return "{:.3f}".format(score) if valid else "NA"
        elif role == Qt.TextAlignmentRole:
            return Qt.AlignVCenter | Qt.AlignLeft
        elif role == Qt.ToolTipRole and not valid:
            return score
        elif role == gui.BarRatioRole and valid:
            return score
        return None

    def headerData(self, row, _orientation, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return str(row + self.start_k)
        return None


class Task:
    futures = []    # type: List[Future]
    watcher = ...   # type: FutureSetWatcher
    cancelled = False

    def __init__(self, futures, watcher):
        self.futures = futures
        self.watcher = watcher

    def cancel(self):
        self.cancelled = True
        for f in self.futures:
            f.cancel()


class NotEnoughData(ValueError):
    pass


class OWKMeans(widget.OWWidget):
    name = "k-Means"
    description = "k-Means clustering algorithm with silhouette-based " \
                  "quality estimation."
    icon = "icons/KMeans.svg"
    priority = 2100
    keywords = ["kmeans", "clustering"]

    class Inputs:
        data = Input("Data", Table)

    class Outputs:
        annotated_data = Output(
            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,
            replaces=["Annotated Data"]
        )
        centroids = Output("Centroids", Table)

    class Error(widget.OWWidget.Error):
        failed = widget.Msg("Clustering failed\nError: {}")
        not_enough_data = widget.Msg(
            "Too few ({}) unique data instances for {} clusters"
        )
        no_attributes = widget.Msg("Data is missing features.")

    class Warning(widget.OWWidget.Warning):
        no_silhouettes = widget.Msg(
            "Silhouette scores are not computed for >{} samples".format(
                SILHOUETTE_MAX_SAMPLES)
        )
        not_enough_data = widget.Msg(
            "Too few ({}) unique data instances for {} clusters"
        )
        no_sparse_normalization = widget.Msg("Sparse data cannot be normalized")

    INIT_METHODS = (("Initialize with KMeans++", "k-means++"),
                    ("Random initialization", "random"))

    resizing_enabled = False
    buttons_area_orientation = Qt.Vertical

    k = Setting(3)
    k_from = Setting(2)
    k_to = Setting(8)
    optimize_k = Setting(False)
    max_iterations = Setting(300)
    n_init = Setting(10)
    smart_init = Setting(0)  # KMeans++
    selection = Setting(None, schema_only=True)  # type: Optional[int]
    auto_commit = Setting(True)
    normalize = Setting(True)

    settings_version = 2

    @classmethod
    def migrate_settings(cls, settings, version):
        # type: (Dict, int) -> None
        if version < 2:
            if 'auto_apply' in settings:
                settings['auto_commit'] = settings.get('auto_apply', True)
                settings.pop('auto_apply', None)

    def __init__(self):
        super().__init__()

        self.data = None  # type: Optional[Table]
        self.__pending_selection = self.selection  # type: Optional[int]
        self.clusterings = {}

        self.__executor = ThreadExecutor(parent=self)
        self.__task = None  # type: Optional[Task]

        self._set_input_summary()
        self._set_output_summary(None)

        layout = QGridLayout()
        bg = gui.radioButtonsInBox(
            self.controlArea, self, "optimize_k", orientation=layout,
            box="Number of Clusters", callback=self.update_method,
        )

        layout.addWidget(
            gui.appendRadioButton(bg, "Fixed:", addToLayout=False), 1, 1)
        sb = gui.hBox(None, margin=0)
        gui.spin(
            sb, self, "k", minv=2, maxv=30,
            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)
        gui.rubber(sb)
        layout.addWidget(sb, 1, 2)

        layout.addWidget(
            gui.appendRadioButton(bg, "From", addToLayout=False), 2, 1)
        ftobox = gui.hBox(None)
        ftobox.layout().setContentsMargins(0, 0, 0, 0)
        layout.addWidget(ftobox, 2, 2)
        gui.spin(
            ftobox, self, "k_from", minv=2, maxv=29,
            controlWidth=60, alignment=Qt.AlignRight,
            callback=self.update_from)
        gui.widgetLabel(ftobox, "to")
        gui.spin(
            ftobox, self, "k_to", minv=3, maxv=30,
            controlWidth=60, alignment=Qt.AlignRight,
            callback=self.update_to)
        gui.rubber(ftobox)

        box = gui.vBox(self.controlArea, "Preprocessing")
        gui.checkBox(box, self, "normalize", "Normalize columns",
                     callback=self.invalidate)

        box = gui.vBox(self.controlArea, "Initialization")
        gui.comboBox(
            box, self, "smart_init", items=[m[0] for m in self.INIT_METHODS],
            callback=self.invalidate)

        layout = QGridLayout()
        gui.widgetBox(box, orientation=layout)
        layout.addWidget(gui.widgetLabel(None, "Re-runs: "), 0, 0, Qt.AlignLeft)
        sb = gui.hBox(None, margin=0)
        layout.addWidget(sb, 0, 1)
        gui.lineEdit(
            sb, self, "n_init", controlWidth=60,
            valueType=int, validator=QIntValidator(), callback=self.invalidate)
        layout.addWidget(
            gui.widgetLabel(None, "Maximum iterations: "), 1, 0, Qt.AlignLeft)
        sb = gui.hBox(None, margin=0)
        layout.addWidget(sb, 1, 1)
        gui.lineEdit(
            sb, self, "max_iterations", controlWidth=60, valueType=int,
            validator=QIntValidator(), callback=self.invalidate)

        self.apply_button = gui.auto_apply(self.buttonsArea, self, "auto_commit", box=None,
                                           commit=self.commit)
        gui.rubber(self.controlArea)

        box = gui.vBox(self.mainArea, box="Silhouette Scores")
        self.mainArea.setVisible(self.optimize_k)
        self.table_model = ClusterTableModel(self)
        table = self.table_view = QTableView(self.mainArea)
        table.setModel(self.table_model)
        table.setSelectionMode(QTableView.SingleSelection)
        table.setSelectionBehavior(QTableView.SelectRows)
        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))
        table.selectionModel().selectionChanged.connect(self.select_row)
        table.setMaximumWidth(200)
        table.horizontalHeader().setStretchLastSection(True)
        table.horizontalHeader().hide()
        table.setShowGrid(False)
        box.layout().addWidget(table)

    def adjustSize(self):
        self.ensurePolished()
        s = self.sizeHint()
        self.resize(s)

    def update_method(self):
        self.table_model.clear_scores()
        self.commit()

    def update_k(self):
        self.optimize_k = False
        self.table_model.clear_scores()
        self.commit()

    def update_from(self):
        self.k_to = max(self.k_from + 1, self.k_to)
        self.optimize_k = True
        self.table_model.clear_scores()
        self.commit()

    def update_to(self):
        self.k_from = min(self.k_from, self.k_to - 1)
        self.optimize_k = True
        self.table_model.clear_scores()
        self.commit()

    def enough_data_instances(self, k):
        """k cannot be larger than the number of data instances."""
        return len(self.data) >= k

    @property
    def has_attributes(self):
        return len(self.data.domain.attributes)

    @staticmethod
    def _compute_clustering(data, k, init, n_init, max_iter, random_state):
        # type: (Table, int, str, int, int, bool) -> KMeansModel
        if k > len(data):
            raise NotEnoughData()

        model = KMeans(
            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,
            random_state=random_state, preprocessors=[]
        ).get_model(data)

        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:
            model.silhouette_samples = silhouette_samples(data.X, model.labels)
            model.silhouette = np.mean(model.silhouette_samples)
        else:
            model.silhouette_samples = None
            model.silhouette = \
                silhouette_score(data.X, model.labels,
                                 sample_size=SILHOUETTE_MAX_SAMPLES,
                                 random_state=RANDOM_STATE)

        return model

    @Slot(int, int)
    def __progress_changed(self, n, d):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        self.progressBarSet(100 * n / d)

    @Slot(int, Exception)
    def __on_exception(self, idx, ex):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None

        if isinstance(ex, NotEnoughData):
            self.Error.not_enough_data(len(self.data), self.k_from + idx)

        # Only show failed message if there is only 1 k to compute
        elif not self.optimize_k:
            self.Error.failed(str(ex))

        self.clusterings[self.k_from + idx] = str(ex)

    @Slot(int, object)
    def __clustering_complete(self, _, result):
        # type: (int, KMeansModel) -> None
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None

        self.clusterings[result.k] = result

    @Slot()
    def __commit_finished(self):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        assert self.data is not None

        self.__task = None
        self.setInvalidated(False)
        self.progressBarFinished()

        if self.optimize_k:
            self.update_results()

        if self.optimize_k and all(isinstance(self.clusterings[i], str)
                                   for i in range(self.k_from, self.k_to + 1)):
            # Show the error of the last clustering
            self.Error.failed(self.clusterings[self.k_to])

        self.send_data()

    def __launch_tasks(self, ks):
        # type: (List[int]) -> None
        """Execute clustering in separate threads for all given ks."""
        preprocessed_data = self.preproces(self.data)
        futures = [self.__executor.submit(
            self._compute_clustering,
            data=preprocessed_data,
            k=k,
            init=self.INIT_METHODS[self.smart_init][1],
            n_init=self.n_init,
            max_iter=self.max_iterations,
            random_state=RANDOM_STATE,
        ) for k in ks]
        watcher = FutureSetWatcher(futures)
        watcher.resultReadyAt.connect(self.__clustering_complete)
        watcher.progressChanged.connect(self.__progress_changed)
        watcher.exceptionReadyAt.connect(self.__on_exception)
        watcher.doneAll.connect(self.__commit_finished)

        self.__task = Task(futures, watcher)
        self.progressBarInit()
        self.setInvalidated(True)

    def cancel(self):
        if self.__task is not None:
            task, self.__task = self.__task, None
            task.cancel()

            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)
            task.watcher.progressChanged.disconnect(self.__progress_changed)
            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)
            task.watcher.doneAll.disconnect(self.__commit_finished)

            self.progressBarFinished()
            self.setInvalidated(False)

    def run_optimization(self):
        if not self.enough_data_instances(self.k_from):
            self.Error.not_enough_data(len(self.data), self.k_from)
            return

        if not self.enough_data_instances(self.k_to):
            self.Warning.not_enough_data(len(self.data), self.k_to)
            return

        needed_ks = [k for k in range(self.k_from, self.k_to + 1)
                     if k not in self.clusterings]

        if needed_ks:
            self.__launch_tasks(needed_ks)
        else:
            # If we don't need to recompute anything, just set the results to
            # what they were before
            self.update_results()

    def cluster(self):
        # Check if the k already has a computed clustering
        if self.k in self.clusterings:
            self.send_data()
            return

        # Check if there is enough data
        if not self.enough_data_instances(self.k):
            self.Error.not_enough_data(len(self.data), self.k)
            return

        self.__launch_tasks([self.k])

    def commit(self):
        self.cancel()
        self.clear_messages()

        # Some time may pass before the new scores are computed, so clear the
        # old scores to avoid potential confusion. Hiding the mainArea could
        # cause flickering when the clusters are computed quickly, so this is
        # the better alternative
        self.table_model.clear_scores()
        self.mainArea.setVisible(self.optimize_k and self.data is not None and
                                 self.has_attributes)

        if self.data is None:
            self.send_data()
            return

        if not self.has_attributes:
            self.Error.no_attributes()
            self.send_data()
            return

        if self.optimize_k:
            self.run_optimization()
        else:
            self.cluster()

        QTimer.singleShot(100, self.adjustSize)

    def invalidate(self, unconditional=False):
        self.cancel()
        self.Error.clear()
        self.Warning.clear()
        self.clusterings = {}
        self.table_model.clear_scores()

        if unconditional:
            self.unconditional_commit()
        else:
            self.commit()

    def update_results(self):
        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in
                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]
        best_row = max(
            range(len(scores)), default=0,
            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]
        )
        self.table_model.set_scores(scores, self.k_from)
        self.apply_selection(best_row)
        self.table_view.setFocus(Qt.OtherFocusReason)
        self.table_view.resizeRowsToContents()

    def apply_selection(self, best_row):
        pending = best_row
        if self.__pending_selection is not None:
            pending = self.__pending_selection
            self.__pending_selection = None
        self.table_view.selectRow(pending)

    def selected_row(self):
        indices = self.table_view.selectedIndexes()
        if not indices:
            return None
        return indices[0].row()

    def select_row(self):
        self.selection = self.selected_row()
        self.send_data()

    def preproces(self, data):
        if self.normalize:
            if sp.issparse(data.X):
                self.Warning.no_sparse_normalization()
            else:
                data = Normalize()(data)
        for preprocessor in KMeans.preprocessors:  # use same preprocessors than
            data = preprocessor(data)
        return data

    def send_data(self):
        if self.optimize_k:
            row = self.selected_row()
            k = self.k_from + row if row is not None else None
        else:
            k = self.k

        km = self.clusterings.get(k)
        if self.data is None or km is None or isinstance(km, str):
            self._set_output_summary(None)
            self.Outputs.annotated_data.send(None)
            self.Outputs.centroids.send(None)
            return

        domain = self.data.domain
        cluster_var = DiscreteVariable(
            get_unique_names(domain, "Cluster"),
            values=["C%d" % (x + 1) for x in range(km.k)]
        )
        clust_ids = km.labels
        silhouette_var = ContinuousVariable(
            get_unique_names(domain, "Silhouette"))
        if km.silhouette_samples is not None:
            self.Warning.no_silhouettes.clear()
            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5
            clust_scores = []
            for i in range(km.k):
                in_clust = clust_ids == i
                if in_clust.any():
                    clust_scores.append(np.mean(scores[in_clust]))
                else:
                    clust_scores.append(0.)
            clust_scores = np.atleast_2d(clust_scores).T
        else:
            self.Warning.no_silhouettes()
            scores = np.nan
            clust_scores = np.full((km.k, 1), np.nan)

        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])
        new_table = self.data.transform(new_domain)
        new_table.get_column_view(cluster_var)[0][:] = clust_ids
        new_table.get_column_view(silhouette_var)[0][:] = scores

        domain_attributes = set(domain.attributes)
        centroid_attributes = [
            attr.compute_value.variable
            if isinstance(attr.compute_value, ReplaceUnknowns)
            and attr.compute_value.variable in domain_attributes
            else attr
            for attr in km.domain.attributes]
        centroid_domain = add_columns(
            Domain(centroid_attributes, [], domain.metas),
            metas=[cluster_var, silhouette_var])
        centroids = Table(
            centroid_domain, km.centroids, None,
            np.hstack((np.full((km.k, len(domain.metas)), np.nan),
                       np.arange(km.k).reshape(km.k, 1),
                       clust_scores))
        )
        if self.data.name == Table.name:
            centroids.name = "centroids"
        else:
            centroids.name = f"{self.data.name} centroids"

        self._set_output_summary(new_table)
        self.Outputs.annotated_data.send(new_table)
        self.Outputs.centroids.send(centroids)

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        self.data, old_data = data, self.data
        self.selection = None
        self._set_input_summary()

        # Do not needlessly recluster the data if X hasn't changed
        if old_data and self.data and array_equal(self.data.X, old_data.X):
            if self.auto_commit:
                self.send_data()
        else:
            self.invalidate(unconditional=True)

    def _set_input_summary(self):
        summary = len(self.data) if self.data else self.info.NoInput
        details = format_summary_details(self.data) if self.data else ""
        self.info.set_input_summary(summary, details)

    def _set_output_summary(self, output):
        summary = len(output) if output else self.info.NoOutput
        details = format_summary_details(output) if output else ""
        self.info.set_output_summary(summary, details)

    def send_report(self):
        # False positives (Setting is not recognized as int)
        # pylint: disable=invalid-sequence-index
        if self.optimize_k and self.selected_row() is not None:
            k_clusters = self.k_from + self.selected_row()
        else:
            k_clusters = self.k
        init_method = self.INIT_METHODS[self.smart_init][0]
        init_method = init_method[0].lower() + init_method[1:]
        self.report_items((
            ("Number of clusters", k_clusters),
            ("Optimization", "{}, {} re-runs limited to {} steps".format(
                init_method, self.n_init, self.max_iterations))))
        if self.data is not None:
            self.report_data("Data", self.data)
            if self.optimize_k:
                self.report_table(
                    "Silhouette scores for different numbers of clusters",
                    self.table_view)

    def onDeleteWidget(self):
        self.cancel()
        super().onDeleteWidget()


if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWKMeans).run(Table("heart_disease"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Normalization Option Remains Enabled for Sparse Data in KMeans Widget

**Description:**
When using the KMeans widget with sparse datasets, the normalization checkbox is still enabled. This allows users to attempt normalization on sparse data, which is not supported and can lead to unexpected behavior or errors.

**Example:**
```python
from Orange.widgets.unsupervised.owkmeans import OWKMeans
from Orange.data import Table
import scipy.sparse as sp

# Create sparse data
sparse_matrix = sp.csr_matrix([[0, 1, 0], [1, 0, 1]])
data = Table.from_numpy(None, sparse_matrix)

# Initialize KMeans widget
widget = OWKMeans()
widget.normalize = True

# Send sparse data to the widget
widget.send_signal(widget.Inputs.data, data)

# Check if normalization checkbox is enabled
is_enabled = widget.controls.normalize.isEnabled()
print("Normalization enabled:", is_enabled)
```

**Expected Behavior:**
The normalization checkbox should be disabled (`is_enabled` should be `False`) when the input data is sparse, preventing users from attempting unsupported normalization.

**Actual Behavior:**
The normalization checkbox remains enabled (`is_enabled` is `True`) even when the input data is sparse, allowing users to try normalizing sparse data, which can cause issues.
[/ISSUE]
{
    "TestClusterTableModel.test_model": "PASSED",
    "TestOWKMeans.test_always_same_cluster": "PASSED",
    "TestOWKMeans.test_centroids_domain_on_output": "PASSED",
    "TestOWKMeans.test_centroids_on_output": "PASSED",
    "TestOWKMeans.test_changing_k_changes_radio": "PASSED",
    "TestOWKMeans.test_correct_smart_init": "PASSED",
    "TestOWKMeans.test_data_limits": "PASSED",
    "TestOWKMeans.test_data_on_output": "PASSED",
    "TestOWKMeans.test_do_not_recluster_on_same_data": "PASSED",
    "TestOWKMeans.test_error_no_attributes": "PASSED",
    "TestOWKMeans.test_from_to_table": "PASSED",
    "TestOWKMeans.test_image_export": "PASSED",
    "TestOWKMeans.test_invalidate_clusterings_cancels_jobs": "PASSED",
    "TestOWKMeans.test_k_limits": "PASSED",
    "TestOWKMeans.test_migrate_version_1_settings": "PASSED",
    "TestOWKMeans.test_minimum_size": "PASSED",
    "TestOWKMeans.test_msg_base_class": "PASSED",
    "TestOWKMeans.test_no_data_hides_main_area": "PASSED",
    "TestOWKMeans.test_normalize_sparse": "PASSED",
    "TestOWKMeans.test_not_enough_rows": "PASSED",
    "TestOWKMeans.test_optimization_fails": "PASSED",
    "TestOWKMeans.test_optimization_report_display": "PASSED",
    "TestOWKMeans.test_report": "PASSED",
    "TestOWKMeans.test_run_fails": "PASSED",
    "TestOWKMeans.test_saved_selection": "PASSED",
    "TestOWKMeans.test_select_best_row": "PASSED",
    "TestOWKMeans.test_silhouette_column": "PASSED",
    "TestOWKMeans.test_summary": "PASSED",
    "TestOWKMeans.test_use_cache": "PASSED"
}
diff --git a/Orange/widgets/unsupervised/owkmeans.py b/Orange/widgets/unsupervised/owkmeans.py
index 932731a5b..3c0b5e42d 100644
--- a/Orange/widgets/unsupervised/owkmeans.py
+++ b/Orange/widgets/unsupervised/owkmeans.py
@@ -576,6 +576,9 @@ class OWKMeans(widget.OWWidget):
         self.selection = None
         self._set_input_summary()
 
+        self.controls.normalize.setDisabled(
+            bool(self.data) and sp.issparse(self.data.X))
+
         # Do not needlessly recluster the data if X hasn't changed
         if old_data and self.data and array_equal(self.data.X, old_data.X):
             if self.auto_commit:

{"Orange/widgets/unsupervised/owkmeans.py": "from concurrent.futures import Future\nfrom typing import Optional, List, Dict\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom AnyQt.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QThread, \\\n    pyqtSlot as Slot\nfrom AnyQt.QtGui import QIntValidator\nfrom AnyQt.QtWidgets import QGridLayout, QTableView\nfrom sklearn.metrics import silhouette_samples, silhouette_score\n\nfrom Orange.clustering import KMeans\nfrom Orange.clustering.kmeans import KMeansModel\nfrom Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable\nfrom Orange.data.util import get_unique_names, array_equal\nfrom Orange.preprocess import Normalize\nfrom Orange.preprocess.impute import ReplaceUnknowns\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.annotated_data import \\\n    ANNOTATED_DATA_SIGNAL_NAME, add_columns\nfrom Orange.widgets.utils.concurrent import ThreadExecutor, FutureSetWatcher\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.widget import Input, Output\n\n\nRANDOM_STATE = 0\nSILHOUETTE_MAX_SAMPLES = 5000\n\n\nclass ClusterTableModel(QAbstractTableModel):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.scores = []\n        self.start_k = 0\n\n    def rowCount(self, index=QModelIndex()):\n        return 0 if index.isValid() else len(self.scores)\n\n    @staticmethod\n    def columnCount(_index=QModelIndex()):\n        return 1\n\n    def flags(self, index):\n        if isinstance(self.scores[index.row()], str):\n            return Qt.NoItemFlags\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def set_scores(self, scores, start_k):\n        self.modelAboutToBeReset.emit()\n        self.scores = scores\n        self.start_k = start_k\n        self.modelReset.emit()\n\n    def clear_scores(self):\n        self.modelAboutToBeReset.emit()\n        self.scores = []\n        self.start_k = 0\n        self.modelReset.emit()\n\n    def data(self, index, role=Qt.DisplayRole):\n        score = self.scores[index.row()]\n        valid = not isinstance(score, str)\n        if role == Qt.DisplayRole:\n            return \"{:.3f}\".format(score) if valid else \"NA\"\n        elif role == Qt.TextAlignmentRole:\n            return Qt.AlignVCenter | Qt.AlignLeft\n        elif role == Qt.ToolTipRole and not valid:\n            return score\n        elif role == gui.BarRatioRole and valid:\n            return score\n        return None\n\n    def headerData(self, row, _orientation, role=Qt.DisplayRole):\n        if role == Qt.DisplayRole:\n            return str(row + self.start_k)\n        return None\n\n\nclass Task:\n    futures = []    # type: List[Future]\n    watcher = ...   # type: FutureSetWatcher\n    cancelled = False\n\n    def __init__(self, futures, watcher):\n        self.futures = futures\n        self.watcher = watcher\n\n    def cancel(self):\n        self.cancelled = True\n        for f in self.futures:\n            f.cancel()\n\n\nclass NotEnoughData(ValueError):\n    pass\n\n\nclass OWKMeans(widget.OWWidget):\n    name = \"k-Means\"\n    description = \"k-Means clustering algorithm with silhouette-based \" \\\n                  \"quality estimation.\"\n    icon = \"icons/KMeans.svg\"\n    priority = 2100\n    keywords = [\"kmeans\", \"clustering\"]\n\n    class Inputs:\n        data = Input(\"Data\", Table)\n\n    class Outputs:\n        annotated_data = Output(\n            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,\n            replaces=[\"Annotated Data\"]\n        )\n        centroids = Output(\"Centroids\", Table)\n\n    class Error(widget.OWWidget.Error):\n        failed = widget.Msg(\"Clustering failed\\nError: {}\")\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_attributes = widget.Msg(\"Data is missing features.\")\n\n    class Warning(widget.OWWidget.Warning):\n        no_silhouettes = widget.Msg(\n            \"Silhouette scores are not computed for >{} samples\".format(\n                SILHOUETTE_MAX_SAMPLES)\n        )\n        not_enough_data = widget.Msg(\n            \"Too few ({}) unique data instances for {} clusters\"\n        )\n        no_sparse_normalization = widget.Msg(\"Sparse data cannot be normalized\")\n\n    INIT_METHODS = ((\"Initialize with KMeans++\", \"k-means++\"),\n                    (\"Random initialization\", \"random\"))\n\n    resizing_enabled = False\n    buttons_area_orientation = Qt.Vertical\n\n    k = Setting(3)\n    k_from = Setting(2)\n    k_to = Setting(8)\n    optimize_k = Setting(False)\n    max_iterations = Setting(300)\n    n_init = Setting(10)\n    smart_init = Setting(0)  # KMeans++\n    selection = Setting(None, schema_only=True)  # type: Optional[int]\n    auto_commit = Setting(True)\n    normalize = Setting(True)\n\n    settings_version = 2\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        # type: (Dict, int) -> None\n        if version < 2:\n            if 'auto_apply' in settings:\n                settings['auto_commit'] = settings.get('auto_apply', True)\n                settings.pop('auto_apply', None)\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None  # type: Optional[Table]\n        self.__pending_selection = self.selection  # type: Optional[int]\n        self.clusterings = {}\n\n        self.__executor = ThreadExecutor(parent=self)\n        self.__task = None  # type: Optional[Task]\n\n        self._set_input_summary()\n        self._set_output_summary(None)\n\n        layout = QGridLayout()\n        bg = gui.radioButtonsInBox(\n            self.controlArea, self, \"optimize_k\", orientation=layout,\n            box=\"Number of Clusters\", callback=self.update_method,\n        )\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"Fixed:\", addToLayout=False), 1, 1)\n        sb = gui.hBox(None, margin=0)\n        gui.spin(\n            sb, self, \"k\", minv=2, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)\n        gui.rubber(sb)\n        layout.addWidget(sb, 1, 2)\n\n        layout.addWidget(\n            gui.appendRadioButton(bg, \"From\", addToLayout=False), 2, 1)\n        ftobox = gui.hBox(None)\n        ftobox.layout().setContentsMargins(0, 0, 0, 0)\n        layout.addWidget(ftobox, 2, 2)\n        gui.spin(\n            ftobox, self, \"k_from\", minv=2, maxv=29,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_from)\n        gui.widgetLabel(ftobox, \"to\")\n        gui.spin(\n            ftobox, self, \"k_to\", minv=3, maxv=30,\n            controlWidth=60, alignment=Qt.AlignRight,\n            callback=self.update_to)\n        gui.rubber(ftobox)\n\n        box = gui.vBox(self.controlArea, \"Preprocessing\")\n        gui.checkBox(box, self, \"normalize\", \"Normalize columns\",\n                     callback=self.invalidate)\n\n        box = gui.vBox(self.controlArea, \"Initialization\")\n        gui.comboBox(\n            box, self, \"smart_init\", items=[m[0] for m in self.INIT_METHODS],\n            callback=self.invalidate)\n\n        layout = QGridLayout()\n        gui.widgetBox(box, orientation=layout)\n        layout.addWidget(gui.widgetLabel(None, \"Re-runs: \"), 0, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 0, 1)\n        gui.lineEdit(\n            sb, self, \"n_init\", controlWidth=60,\n            valueType=int, validator=QIntValidator(), callback=self.invalidate)\n        layout.addWidget(\n            gui.widgetLabel(None, \"Maximum iterations: \"), 1, 0, Qt.AlignLeft)\n        sb = gui.hBox(None, margin=0)\n        layout.addWidget(sb, 1, 1)\n        gui.lineEdit(\n            sb, self, \"max_iterations\", controlWidth=60, valueType=int,\n            validator=QIntValidator(), callback=self.invalidate)\n\n        self.apply_button = gui.auto_apply(self.buttonsArea, self, \"auto_commit\", box=None,\n                                           commit=self.commit)\n        gui.rubber(self.controlArea)\n\n        box = gui.vBox(self.mainArea, box=\"Silhouette Scores\")\n        self.mainArea.setVisible(self.optimize_k)\n        self.table_model = ClusterTableModel(self)\n        table = self.table_view = QTableView(self.mainArea)\n        table.setModel(self.table_model)\n        table.setSelectionMode(QTableView.SingleSelection)\n        table.setSelectionBehavior(QTableView.SelectRows)\n        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))\n        table.selectionModel().selectionChanged.connect(self.select_row)\n        table.setMaximumWidth(200)\n        table.horizontalHeader().setStretchLastSection(True)\n        table.horizontalHeader().hide()\n        table.setShowGrid(False)\n        box.layout().addWidget(table)\n\n    def adjustSize(self):\n        self.ensurePolished()\n        s = self.sizeHint()\n        self.resize(s)\n\n    def update_method(self):\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_k(self):\n        self.optimize_k = False\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_from(self):\n        self.k_to = max(self.k_from + 1, self.k_to)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def update_to(self):\n        self.k_from = min(self.k_from, self.k_to - 1)\n        self.optimize_k = True\n        self.table_model.clear_scores()\n        self.commit()\n\n    def enough_data_instances(self, k):\n        \"\"\"k cannot be larger than the number of data instances.\"\"\"\n        return len(self.data) >= k\n\n    @property\n    def has_attributes(self):\n        return len(self.data.domain.attributes)\n\n    @staticmethod\n    def _compute_clustering(data, k, init, n_init, max_iter, random_state):\n        # type: (Table, int, str, int, int, bool) -> KMeansModel\n        if k > len(data):\n            raise NotEnoughData()\n\n        model = KMeans(\n            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,\n            random_state=random_state, preprocessors=[]\n        ).get_model(data)\n\n        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:\n            model.silhouette_samples = silhouette_samples(data.X, model.labels)\n            model.silhouette = np.mean(model.silhouette_samples)\n        else:\n            model.silhouette_samples = None\n            model.silhouette = \\\n                silhouette_score(data.X, model.labels,\n                                 sample_size=SILHOUETTE_MAX_SAMPLES,\n                                 random_state=RANDOM_STATE)\n\n        return model\n\n    @Slot(int, int)\n    def __progress_changed(self, n, d):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        self.progressBarSet(100 * n / d)\n\n    @Slot(int, Exception)\n    def __on_exception(self, idx, ex):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        if isinstance(ex, NotEnoughData):\n            self.Error.not_enough_data(len(self.data), self.k_from + idx)\n\n        # Only show failed message if there is only 1 k to compute\n        elif not self.optimize_k:\n            self.Error.failed(str(ex))\n\n        self.clusterings[self.k_from + idx] = str(ex)\n\n    @Slot(int, object)\n    def __clustering_complete(self, _, result):\n        # type: (int, KMeansModel) -> None\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n\n        self.clusterings[result.k] = result\n\n    @Slot()\n    def __commit_finished(self):\n        assert QThread.currentThread() is self.thread()\n        assert self.__task is not None\n        assert self.data is not None\n\n        self.__task = None\n        self.setInvalidated(False)\n        self.progressBarFinished()\n\n        if self.optimize_k:\n            self.update_results()\n\n        if self.optimize_k and all(isinstance(self.clusterings[i], str)\n                                   for i in range(self.k_from, self.k_to + 1)):\n            # Show the error of the last clustering\n            self.Error.failed(self.clusterings[self.k_to])\n\n        self.send_data()\n\n    def __launch_tasks(self, ks):\n        # type: (List[int]) -> None\n        \"\"\"Execute clustering in separate threads for all given ks.\"\"\"\n        preprocessed_data = self.preproces(self.data)\n        futures = [self.__executor.submit(\n            self._compute_clustering,\n            data=preprocessed_data,\n            k=k,\n            init=self.INIT_METHODS[self.smart_init][1],\n            n_init=self.n_init,\n            max_iter=self.max_iterations,\n            random_state=RANDOM_STATE,\n        ) for k in ks]\n        watcher = FutureSetWatcher(futures)\n        watcher.resultReadyAt.connect(self.__clustering_complete)\n        watcher.progressChanged.connect(self.__progress_changed)\n        watcher.exceptionReadyAt.connect(self.__on_exception)\n        watcher.doneAll.connect(self.__commit_finished)\n\n        self.__task = Task(futures, watcher)\n        self.progressBarInit()\n        self.setInvalidated(True)\n\n    def cancel(self):\n        if self.__task is not None:\n            task, self.__task = self.__task, None\n            task.cancel()\n\n            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)\n            task.watcher.progressChanged.disconnect(self.__progress_changed)\n            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)\n            task.watcher.doneAll.disconnect(self.__commit_finished)\n\n            self.progressBarFinished()\n            self.setInvalidated(False)\n\n    def run_optimization(self):\n        if not self.enough_data_instances(self.k_from):\n            self.Error.not_enough_data(len(self.data), self.k_from)\n            return\n\n        if not self.enough_data_instances(self.k_to):\n            self.Warning.not_enough_data(len(self.data), self.k_to)\n            return\n\n        needed_ks = [k for k in range(self.k_from, self.k_to + 1)\n                     if k not in self.clusterings]\n\n        if needed_ks:\n            self.__launch_tasks(needed_ks)\n        else:\n            # If we don't need to recompute anything, just set the results to\n            # what they were before\n            self.update_results()\n\n    def cluster(self):\n        # Check if the k already has a computed clustering\n        if self.k in self.clusterings:\n            self.send_data()\n            return\n\n        # Check if there is enough data\n        if not self.enough_data_instances(self.k):\n            self.Error.not_enough_data(len(self.data), self.k)\n            return\n\n        self.__launch_tasks([self.k])\n\n    def commit(self):\n        self.cancel()\n        self.clear_messages()\n\n        # Some time may pass before the new scores are computed, so clear the\n        # old scores to avoid potential confusion. Hiding the mainArea could\n        # cause flickering when the clusters are computed quickly, so this is\n        # the better alternative\n        self.table_model.clear_scores()\n        self.mainArea.setVisible(self.optimize_k and self.data is not None and\n                                 self.has_attributes)\n\n        if self.data is None:\n            self.send_data()\n            return\n\n        if not self.has_attributes:\n            self.Error.no_attributes()\n            self.send_data()\n            return\n\n        if self.optimize_k:\n            self.run_optimization()\n        else:\n            self.cluster()\n\n        QTimer.singleShot(100, self.adjustSize)\n\n    def invalidate(self, unconditional=False):\n        self.cancel()\n        self.Error.clear()\n        self.Warning.clear()\n        self.clusterings = {}\n        self.table_model.clear_scores()\n\n        if unconditional:\n            self.unconditional_commit()\n        else:\n            self.commit()\n\n    def update_results(self):\n        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in\n                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]\n        best_row = max(\n            range(len(scores)), default=0,\n            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]\n        )\n        self.table_model.set_scores(scores, self.k_from)\n        self.apply_selection(best_row)\n        self.table_view.setFocus(Qt.OtherFocusReason)\n        self.table_view.resizeRowsToContents()\n\n    def apply_selection(self, best_row):\n        pending = best_row\n        if self.__pending_selection is not None:\n            pending = self.__pending_selection\n            self.__pending_selection = None\n        self.table_view.selectRow(pending)\n\n    def selected_row(self):\n        indices = self.table_view.selectedIndexes()\n        if not indices:\n            return None\n        return indices[0].row()\n\n    def select_row(self):\n        self.selection = self.selected_row()\n        self.send_data()\n\n    def preproces(self, data):\n        if self.normalize:\n            if sp.issparse(data.X):\n                self.Warning.no_sparse_normalization()\n            else:\n                data = Normalize()(data)\n        for preprocessor in KMeans.preprocessors:  # use same preprocessors than\n            data = preprocessor(data)\n        return data\n\n    def send_data(self):\n        if self.optimize_k:\n            row = self.selected_row()\n            k = self.k_from + row if row is not None else None\n        else:\n            k = self.k\n\n        km = self.clusterings.get(k)\n        if self.data is None or km is None or isinstance(km, str):\n            self._set_output_summary(None)\n            self.Outputs.annotated_data.send(None)\n            self.Outputs.centroids.send(None)\n            return\n\n        domain = self.data.domain\n        cluster_var = DiscreteVariable(\n            get_unique_names(domain, \"Cluster\"),\n            values=[\"C%d\" % (x + 1) for x in range(km.k)]\n        )\n        clust_ids = km.labels\n        silhouette_var = ContinuousVariable(\n            get_unique_names(domain, \"Silhouette\"))\n        if km.silhouette_samples is not None:\n            self.Warning.no_silhouettes.clear()\n            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5\n            clust_scores = []\n            for i in range(km.k):\n                in_clust = clust_ids == i\n                if in_clust.any():\n                    clust_scores.append(np.mean(scores[in_clust]))\n                else:\n                    clust_scores.append(0.)\n            clust_scores = np.atleast_2d(clust_scores).T\n        else:\n            self.Warning.no_silhouettes()\n            scores = np.nan\n            clust_scores = np.full((km.k, 1), np.nan)\n\n        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])\n        new_table = self.data.transform(new_domain)\n        new_table.get_column_view(cluster_var)[0][:] = clust_ids\n        new_table.get_column_view(silhouette_var)[0][:] = scores\n\n        domain_attributes = set(domain.attributes)\n        centroid_attributes = [\n            attr.compute_value.variable\n            if isinstance(attr.compute_value, ReplaceUnknowns)\n            and attr.compute_value.variable in domain_attributes\n            else attr\n            for attr in km.domain.attributes]\n        centroid_domain = add_columns(\n            Domain(centroid_attributes, [], domain.metas),\n            metas=[cluster_var, silhouette_var])\n        centroids = Table(\n            centroid_domain, km.centroids, None,\n            np.hstack((np.full((km.k, len(domain.metas)), np.nan),\n                       np.arange(km.k).reshape(km.k, 1),\n                       clust_scores))\n        )\n        if self.data.name == Table.name:\n            centroids.name = \"centroids\"\n        else:\n            centroids.name = f\"{self.data.name} centroids\"\n\n        self._set_output_summary(new_table)\n        self.Outputs.annotated_data.send(new_table)\n        self.Outputs.centroids.send(centroids)\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data):\n        self.data, old_data = data, self.data\n        self.selection = None\n        self._set_input_summary()\n\n        # Do not needlessly recluster the data if X hasn't changed\n        if old_data and self.data and array_equal(self.data.X, old_data.X):\n            if self.auto_commit:\n                self.send_data()\n        else:\n            self.invalidate(unconditional=True)\n\n    def _set_input_summary(self):\n        summary = len(self.data) if self.data else self.info.NoInput\n        details = format_summary_details(self.data) if self.data else \"\"\n        self.info.set_input_summary(summary, details)\n\n    def _set_output_summary(self, output):\n        summary = len(output) if output else self.info.NoOutput\n        details = format_summary_details(output) if output else \"\"\n        self.info.set_output_summary(summary, details)\n\n    def send_report(self):\n        # False positives (Setting is not recognized as int)\n        # pylint: disable=invalid-sequence-index\n        if self.optimize_k and self.selected_row() is not None:\n            k_clusters = self.k_from + self.selected_row()\n        else:\n            k_clusters = self.k\n        init_method = self.INIT_METHODS[self.smart_init][0]\n        init_method = init_method[0].lower() + init_method[1:]\n        self.report_items((\n            (\"Number of clusters\", k_clusters),\n            (\"Optimization\", \"{}, {} re-runs limited to {} steps\".format(\n                init_method, self.n_init, self.max_iterations))))\n        if self.data is not None:\n            self.report_data(\"Data\", self.data)\n            if self.optimize_k:\n                self.report_table(\n                    \"Silhouette scores for different numbers of clusters\",\n                    self.table_view)\n\n    def onDeleteWidget(self):\n        self.cancel()\n        super().onDeleteWidget()\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWKMeans).run(Table(\"heart_disease\"))\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Normalization Option Remains Enabled for Sparse Data in KMeans Widget

**Description:**
When using the KMeans widget with sparse datasets, the normalization checkbox is still enabled. This allows users to attempt normalization on sparse data, which is not supported and can lead to unexpected behavior or errors.

**Example:**
```python
from Orange.widgets.unsupervised.owkmeans import OWKMeans
from Orange.data import Table
import scipy.sparse as sp

# Create sparse data
sparse_matrix = sp.csr_matrix([[0, 1, 0], [1, 0, 1]])
data = Table.from_numpy(None, sparse_matrix)

# Initialize KMeans widget
widget = OWKMeans()
widget.normalize = True

# Send sparse data to the widget
widget.send_signal(widget.Inputs.data, data)

# Check if normalization checkbox is enabled
is_enabled = widget.controls.normalize.isEnabled()
print("Normalization enabled:", is_enabled)
```

**Expected Behavior:**
The normalization checkbox should be disabled (`is_enabled` should be `False`) when the input data is sparse, preventing users from attempting unsupported normalization.

**Actual Behavior:**
The normalization checkbox remains enabled (`is_enabled` is `True`) even when the input data is sparse, allowing users to try normalizing sparse data, which can cause issues.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/unsupervised/owkmeans.py
from concurrent.futures import Future
from typing import Optional, List, Dict

import numpy as np
import scipy.sparse as sp

from AnyQt.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QThread, \
    pyqtSlot as Slot
from AnyQt.QtGui import QIntValidator
from AnyQt.QtWidgets import QGridLayout, QTableView
from sklearn.metrics import silhouette_samples, silhouette_score

from Orange.clustering import KMeans
from Orange.clustering.kmeans import KMeansModel
from Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable
from Orange.data.util import get_unique_names, array_equal
from Orange.preprocess import Normalize
from Orange.preprocess.impute import ReplaceUnknowns
from Orange.widgets import widget, gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils.annotated_data import \
    ANNOTATED_DATA_SIGNAL_NAME, add_columns
from Orange.widgets.utils.concurrent import ThreadExecutor, FutureSetWatcher
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.widget import Input, Output


RANDOM_STATE = 0
SILHOUETTE_MAX_SAMPLES = 5000


class ClusterTableModel(QAbstractTableModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.scores = []
        self.start_k = 0

    def rowCount(self, index=QModelIndex()):
        return 0 if index.isValid() else len(self.scores)

    @staticmethod
    def columnCount(_index=QModelIndex()):
        return 1

    def flags(self, index):
        if isinstance(self.scores[index.row()], str):
            return Qt.NoItemFlags
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def set_scores(self, scores, start_k):
        self.modelAboutToBeReset.emit()
        self.scores = scores
        self.start_k = start_k
        self.modelReset.emit()

    def clear_scores(self):
        self.modelAboutToBeReset.emit()
        self.scores = []
        self.start_k = 0
        self.modelReset.emit()

    def data(self, index, role=Qt.DisplayRole):
        score = self.scores[index.row()]
        valid = not isinstance(score, str)
        if role == Qt.DisplayRole:
            return "{:.3f}".format(score) if valid else "NA"
        elif role == Qt.TextAlignmentRole:
            return Qt.AlignVCenter | Qt.AlignLeft
        elif role == Qt.ToolTipRole and not valid:
            return score
        elif role == gui.BarRatioRole and valid:
            return score
        return None

    def headerData(self, row, _orientation, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return str(row + self.start_k)
        return None


class Task:
    futures = []    # type: List[Future]
    watcher = ...   # type: FutureSetWatcher
    cancelled = False

    def __init__(self, futures, watcher):
        self.futures = futures
        self.watcher = watcher

    def cancel(self):
        self.cancelled = True
        for f in self.futures:
            f.cancel()


class NotEnoughData(ValueError):
    pass


class OWKMeans(widget.OWWidget):
    name = "k-Means"
    description = "k-Means clustering algorithm with silhouette-based " \
                  "quality estimation."
    icon = "icons/KMeans.svg"
    priority = 2100
    keywords = ["kmeans", "clustering"]

    class Inputs:
        data = Input("Data", Table)

    class Outputs:
        annotated_data = Output(
            ANNOTATED_DATA_SIGNAL_NAME, Table, default=True,
            replaces=["Annotated Data"]
        )
        centroids = Output("Centroids", Table)

    class Error(widget.OWWidget.Error):
        failed = widget.Msg("Clustering failed\nError: {}")
        not_enough_data = widget.Msg(
            "Too few ({}) unique data instances for {} clusters"
        )
        no_attributes = widget.Msg("Data is missing features.")

    class Warning(widget.OWWidget.Warning):
        no_silhouettes = widget.Msg(
            "Silhouette scores are not computed for >{} samples".format(
                SILHOUETTE_MAX_SAMPLES)
        )
        not_enough_data = widget.Msg(
            "Too few ({}) unique data instances for {} clusters"
        )
        no_sparse_normalization = widget.Msg("Sparse data cannot be normalized")

    INIT_METHODS = (("Initialize with KMeans++", "k-means++"),
                    ("Random initialization", "random"))

    resizing_enabled = False
    buttons_area_orientation = Qt.Vertical

    k = Setting(3)
    k_from = Setting(2)
    k_to = Setting(8)
    optimize_k = Setting(False)
    max_iterations = Setting(300)
    n_init = Setting(10)
    smart_init = Setting(0)  # KMeans++
    selection = Setting(None, schema_only=True)  # type: Optional[int]
    auto_commit = Setting(True)
    normalize = Setting(True)

    settings_version = 2

    @classmethod
    def migrate_settings(cls, settings, version):
        # type: (Dict, int) -> None
        if version < 2:
            if 'auto_apply' in settings:
                settings['auto_commit'] = settings.get('auto_apply', True)
                settings.pop('auto_apply', None)

    def __init__(self):
        super().__init__()

        self.data = None  # type: Optional[Table]
        self.__pending_selection = self.selection  # type: Optional[int]
        self.clusterings = {}

        self.__executor = ThreadExecutor(parent=self)
        self.__task = None  # type: Optional[Task]

        self._set_input_summary()
        self._set_output_summary(None)

        layout = QGridLayout()
        bg = gui.radioButtonsInBox(
            self.controlArea, self, "optimize_k", orientation=layout,
            box="Number of Clusters", callback=self.update_method,
        )

        layout.addWidget(
            gui.appendRadioButton(bg, "Fixed:", addToLayout=False), 1, 1)
        sb = gui.hBox(None, margin=0)
        gui.spin(
            sb, self, "k", minv=2, maxv=30,
            controlWidth=60, alignment=Qt.AlignRight, callback=self.update_k)
        gui.rubber(sb)
        layout.addWidget(sb, 1, 2)

        layout.addWidget(
            gui.appendRadioButton(bg, "From", addToLayout=False), 2, 1)
        ftobox = gui.hBox(None)
        ftobox.layout().setContentsMargins(0, 0, 0, 0)
        layout.addWidget(ftobox, 2, 2)
        gui.spin(
            ftobox, self, "k_from", minv=2, maxv=29,
            controlWidth=60, alignment=Qt.AlignRight,
            callback=self.update_from)
        gui.widgetLabel(ftobox, "to")
        gui.spin(
            ftobox, self, "k_to", minv=3, maxv=30,
            controlWidth=60, alignment=Qt.AlignRight,
            callback=self.update_to)
        gui.rubber(ftobox)

        box = gui.vBox(self.controlArea, "Preprocessing")
        gui.checkBox(box, self, "normalize", "Normalize columns",
                     callback=self.invalidate)

        box = gui.vBox(self.controlArea, "Initialization")
        gui.comboBox(
            box, self, "smart_init", items=[m[0] for m in self.INIT_METHODS],
            callback=self.invalidate)

        layout = QGridLayout()
        gui.widgetBox(box, orientation=layout)
        layout.addWidget(gui.widgetLabel(None, "Re-runs: "), 0, 0, Qt.AlignLeft)
        sb = gui.hBox(None, margin=0)
        layout.addWidget(sb, 0, 1)
        gui.lineEdit(
            sb, self, "n_init", controlWidth=60,
            valueType=int, validator=QIntValidator(), callback=self.invalidate)
        layout.addWidget(
            gui.widgetLabel(None, "Maximum iterations: "), 1, 0, Qt.AlignLeft)
        sb = gui.hBox(None, margin=0)
        layout.addWidget(sb, 1, 1)
        gui.lineEdit(
            sb, self, "max_iterations", controlWidth=60, valueType=int,
            validator=QIntValidator(), callback=self.invalidate)

        self.apply_button = gui.auto_apply(self.buttonsArea, self, "auto_commit", box=None,
                                           commit=self.commit)
        gui.rubber(self.controlArea)

        box = gui.vBox(self.mainArea, box="Silhouette Scores")
        self.mainArea.setVisible(self.optimize_k)
        self.table_model = ClusterTableModel(self)
        table = self.table_view = QTableView(self.mainArea)
        table.setModel(self.table_model)
        table.setSelectionMode(QTableView.SingleSelection)
        table.setSelectionBehavior(QTableView.SelectRows)
        table.setItemDelegate(gui.ColoredBarItemDelegate(self, color=Qt.cyan))
        table.selectionModel().selectionChanged.connect(self.select_row)
        table.setMaximumWidth(200)
        table.horizontalHeader().setStretchLastSection(True)
        table.horizontalHeader().hide()
        table.setShowGrid(False)
        box.layout().addWidget(table)

    def adjustSize(self):
        self.ensurePolished()
        s = self.sizeHint()
        self.resize(s)

    def update_method(self):
        self.table_model.clear_scores()
        self.commit()

    def update_k(self):
        self.optimize_k = False
        self.table_model.clear_scores()
        self.commit()

    def update_from(self):
        self.k_to = max(self.k_from + 1, self.k_to)
        self.optimize_k = True
        self.table_model.clear_scores()
        self.commit()

    def update_to(self):
        self.k_from = min(self.k_from, self.k_to - 1)
        self.optimize_k = True
        self.table_model.clear_scores()
        self.commit()

    def enough_data_instances(self, k):
        """k cannot be larger than the number of data instances."""
        return len(self.data) >= k

    @property
    def has_attributes(self):
        return len(self.data.domain.attributes)

    @staticmethod
    def _compute_clustering(data, k, init, n_init, max_iter, random_state):
        # type: (Table, int, str, int, int, bool) -> KMeansModel
        if k > len(data):
            raise NotEnoughData()

        model = KMeans(
            n_clusters=k, init=init, n_init=n_init, max_iter=max_iter,
            random_state=random_state, preprocessors=[]
        ).get_model(data)

        if data.X.shape[0] <= SILHOUETTE_MAX_SAMPLES:
            model.silhouette_samples = silhouette_samples(data.X, model.labels)
            model.silhouette = np.mean(model.silhouette_samples)
        else:
            model.silhouette_samples = None
            model.silhouette = \
                silhouette_score(data.X, model.labels,
                                 sample_size=SILHOUETTE_MAX_SAMPLES,
                                 random_state=RANDOM_STATE)

        return model

    @Slot(int, int)
    def __progress_changed(self, n, d):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        self.progressBarSet(100 * n / d)

    @Slot(int, Exception)
    def __on_exception(self, idx, ex):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None

        if isinstance(ex, NotEnoughData):
            self.Error.not_enough_data(len(self.data), self.k_from + idx)

        # Only show failed message if there is only 1 k to compute
        elif not self.optimize_k:
            self.Error.failed(str(ex))

        self.clusterings[self.k_from + idx] = str(ex)

    @Slot(int, object)
    def __clustering_complete(self, _, result):
        # type: (int, KMeansModel) -> None
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None

        self.clusterings[result.k] = result

    @Slot()
    def __commit_finished(self):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        assert self.data is not None

        self.__task = None
        self.setInvalidated(False)
        self.progressBarFinished()

        if self.optimize_k:
            self.update_results()

        if self.optimize_k and all(isinstance(self.clusterings[i], str)
                                   for i in range(self.k_from, self.k_to + 1)):
            # Show the error of the last clustering
            self.Error.failed(self.clusterings[self.k_to])

        self.send_data()

    def __launch_tasks(self, ks):
        # type: (List[int]) -> None
        """Execute clustering in separate threads for all given ks."""
        preprocessed_data = self.preproces(self.data)
        futures = [self.__executor.submit(
            self._compute_clustering,
            data=preprocessed_data,
            k=k,
            init=self.INIT_METHODS[self.smart_init][1],
            n_init=self.n_init,
            max_iter=self.max_iterations,
            random_state=RANDOM_STATE,
        ) for k in ks]
        watcher = FutureSetWatcher(futures)
        watcher.resultReadyAt.connect(self.__clustering_complete)
        watcher.progressChanged.connect(self.__progress_changed)
        watcher.exceptionReadyAt.connect(self.__on_exception)
        watcher.doneAll.connect(self.__commit_finished)

        self.__task = Task(futures, watcher)
        self.progressBarInit()
        self.setInvalidated(True)

    def cancel(self):
        if self.__task is not None:
            task, self.__task = self.__task, None
            task.cancel()

            task.watcher.resultReadyAt.disconnect(self.__clustering_complete)
            task.watcher.progressChanged.disconnect(self.__progress_changed)
            task.watcher.exceptionReadyAt.disconnect(self.__on_exception)
            task.watcher.doneAll.disconnect(self.__commit_finished)

            self.progressBarFinished()
            self.setInvalidated(False)

    def run_optimization(self):
        if not self.enough_data_instances(self.k_from):
            self.Error.not_enough_data(len(self.data), self.k_from)
            return

        if not self.enough_data_instances(self.k_to):
            self.Warning.not_enough_data(len(self.data), self.k_to)
            return

        needed_ks = [k for k in range(self.k_from, self.k_to + 1)
                     if k not in self.clusterings]

        if needed_ks:
            self.__launch_tasks(needed_ks)
        else:
            # If we don't need to recompute anything, just set the results to
            # what they were before
            self.update_results()

    def cluster(self):
        # Check if the k already has a computed clustering
        if self.k in self.clusterings:
            self.send_data()
            return

        # Check if there is enough data
        if not self.enough_data_instances(self.k):
            self.Error.not_enough_data(len(self.data), self.k)
            return

        self.__launch_tasks([self.k])

    def commit(self):
        self.cancel()
        self.clear_messages()

        # Some time may pass before the new scores are computed, so clear the
        # old scores to avoid potential confusion. Hiding the mainArea could
        # cause flickering when the clusters are computed quickly, so this is
        # the better alternative
        self.table_model.clear_scores()
        self.mainArea.setVisible(self.optimize_k and self.data is not None and
                                 self.has_attributes)

        if self.data is None:
            self.send_data()
            return

        if not self.has_attributes:
            self.Error.no_attributes()
            self.send_data()
            return

        if self.optimize_k:
            self.run_optimization()
        else:
            self.cluster()

        QTimer.singleShot(100, self.adjustSize)

    def invalidate(self, unconditional=False):
        self.cancel()
        self.Error.clear()
        self.Warning.clear()
        self.clusterings = {}
        self.table_model.clear_scores()

        if unconditional:
            self.unconditional_commit()
        else:
            self.commit()

    def update_results(self):
        scores = [mk if isinstance(mk, str) else mk.silhouette for mk in
                  (self.clusterings[k] for k in range(self.k_from, self.k_to + 1))]
        best_row = max(
            range(len(scores)), default=0,
            key=lambda x: 0 if isinstance(scores[x], str) else scores[x]
        )
        self.table_model.set_scores(scores, self.k_from)
        self.apply_selection(best_row)
        self.table_view.setFocus(Qt.OtherFocusReason)
        self.table_view.resizeRowsToContents()

    def apply_selection(self, best_row):
        pending = best_row
        if self.__pending_selection is not None:
            pending = self.__pending_selection
            self.__pending_selection = None
        self.table_view.selectRow(pending)

    def selected_row(self):
        indices = self.table_view.selectedIndexes()
        if not indices:
            return None
        return indices[0].row()

    def select_row(self):
        self.selection = self.selected_row()
        self.send_data()

    def preproces(self, data):
        if self.normalize:
            if sp.issparse(data.X):
                self.Warning.no_sparse_normalization()
            else:
                data = Normalize()(data)
        for preprocessor in KMeans.preprocessors:  # use same preprocessors than
            data = preprocessor(data)
        return data

    def send_data(self):
        if self.optimize_k:
            row = self.selected_row()
            k = self.k_from + row if row is not None else None
        else:
            k = self.k

        km = self.clusterings.get(k)
        if self.data is None or km is None or isinstance(km, str):
            self._set_output_summary(None)
            self.Outputs.annotated_data.send(None)
            self.Outputs.centroids.send(None)
            return

        domain = self.data.domain
        cluster_var = DiscreteVariable(
            get_unique_names(domain, "Cluster"),
            values=["C%d" % (x + 1) for x in range(km.k)]
        )
        clust_ids = km.labels
        silhouette_var = ContinuousVariable(
            get_unique_names(domain, "Silhouette"))
        if km.silhouette_samples is not None:
            self.Warning.no_silhouettes.clear()
            scores = np.arctan(km.silhouette_samples) / np.pi + 0.5
            clust_scores = []
            for i in range(km.k):
                in_clust = clust_ids == i
                if in_clust.any():
                    clust_scores.append(np.mean(scores[in_clust]))
                else:
                    clust_scores.append(0.)
            clust_scores = np.atleast_2d(clust_scores).T
        else:
            self.Warning.no_silhouettes()
            scores = np.nan
            clust_scores = np.full((km.k, 1), np.nan)

        new_domain = add_columns(domain, metas=[cluster_var, silhouette_var])
        new_table = self.data.transform(new_domain)
        new_table.get_column_view(cluster_var)[0][:] = clust_ids
        new_table.get_column_view(silhouette_var)[0][:] = scores

        domain_attributes = set(domain.attributes)
        centroid_attributes = [
            attr.compute_value.variable
            if isinstance(attr.compute_value, ReplaceUnknowns)
            and attr.compute_value.variable in domain_attributes
            else attr
            for attr in km.domain.attributes]
        centroid_domain = add_columns(
            Domain(centroid_attributes, [], domain.metas),
            metas=[cluster_var, silhouette_var])
        centroids = Table(
            centroid_domain, km.centroids, None,
            np.hstack((np.full((km.k, len(domain.metas)), np.nan),
                       np.arange(km.k).reshape(km.k, 1),
                       clust_scores))
        )
        if self.data.name == Table.name:
            centroids.name = "centroids"
        else:
            centroids.name = f"{self.data.name} centroids"

        self._set_output_summary(new_table)
        self.Outputs.annotated_data.send(new_table)
        self.Outputs.centroids.send(centroids)

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        self.data, old_data = data, self.data
        self.selection = None
        self._set_input_summary()

        # Do not needlessly recluster the data if X hasn't changed
        if old_data and self.data and array_equal(self.data.X, old_data.X):
            if self.auto_commit:
                self.send_data()
        else:
            self.invalidate(unconditional=True)

    def _set_input_summary(self):
        summary = len(self.data) if self.data else self.info.NoInput
        details = format_summary_details(self.data) if self.data else ""
        self.info.set_input_summary(summary, details)

    def _set_output_summary(self, output):
        summary = len(output) if output else self.info.NoOutput
        details = format_summary_details(output) if output else ""
        self.info.set_output_summary(summary, details)

    def send_report(self):
        # False positives (Setting is not recognized as int)
        # pylint: disable=invalid-sequence-index
        if self.optimize_k and self.selected_row() is not None:
            k_clusters = self.k_from + self.selected_row()
        else:
            k_clusters = self.k
        init_method = self.INIT_METHODS[self.smart_init][0]
        init_method = init_method[0].lower() + init_method[1:]
        self.report_items((
            ("Number of clusters", k_clusters),
            ("Optimization", "{}, {} re-runs limited to {} steps".format(
                init_method, self.n_init, self.max_iterations))))
        if self.data is not None:
            self.report_data("Data", self.data)
            if self.optimize_k:
                self.report_table(
                    "Silhouette scores for different numbers of clusters",
                    self.table_view)

    def onDeleteWidget(self):
        self.cancel()
        super().onDeleteWidget()


if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWKMeans).run(Table("heart_disease"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user