# Source code for MDAnalysis.analysis.encore.clustering.ClusterCollection

    # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
    # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
    #
    # MDAnalysis --- https://www.mdanalysis.org
    # Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
    # (see the file AUTHORS for the full list of names)
    #
    # Released under the Lesser GNU Public Licence, v2.1 or any higher version
    #
    # Please cite your use of MDAnalysis in published work:
    #
    # R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
    # D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
    # MDAnalysis: A Python package for the rapid analysis of molecular dynamics
    # simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
    # Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
    # doi: 10.25080/majora-629e541a-00e
    #
    # N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
    # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
    # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
    #
    """
    Cluster representation --- :mod:`MDAnalysis.analysis.encore.clustering.ClusterCollection`
    =========================================================================================

    The module contains the Cluster and ClusterCollection classes which are
    designed to store results from clustering algorithms.

    :Author: Matteo Tiberti, Wouter Boomsma, Tone Bengtsen

    .. versionadded:: 0.16.0

    .. deprecated:: 2.8.0
       This module is deprecated in favour of the
       MDAKit `mdaencore <https://mdanalysis.org/mdaencore/>`_ and will be removed
       in MDAnalysis 3.0.0.

    """
    import numpy as np

    class Cluster(object):
        """
        Generic Cluster class for clusters with centroids.

        Attributes
        ----------

        id : int
            Cluster ID number. Useful for the ClustersCollection class

        metadata : iterable
            dict of lists or numpy.array, containing metadata for the cluster
            elements. The iterable must return the same number of elements as
            those that belong to the cluster.

        size : int
            number of elements.

        centroid : element object
            cluster centroid.

        elements : numpy.array
            array containing the cluster elements.
        """

        def __init__(self, elem_list=None, centroid=None, idn=None, metadata=None):
            """Class constructor. If elem_list is None, an empty cluster is created
                and the remaining arguments ignored.

            Parameters
            ----------

            elem_list : numpy.array or None
                numpy array of cluster elements

            centroid : None or element object
                centroid

            idn : int
                cluster ID

            metadata : iterable
                metadata, one value for each cluster element. The iterable
                must have the same length as the elements array.

            """

            self.id = idn

            if elem_list is None:
                self.size = 0
                self.elements = np.array([])
                self.centroid = None
                self.metadata = {}
                return

            self.metadata = {}
            self.elements = elem_list
            if centroid not in self.elements:
                raise LookupError(
                    "Centroid of cluster not found in the element list"
                )

            self.centroid = centroid
            self.size = self.elements.shape[0]
            if metadata:
                for name, data in metadata.items():
                    if len(data) != self.size:
                        raise TypeError(
                            'Size of metadata having label "{0}" '
                            "is not equal to the number of cluster "
                            "elements".format(name)
                        )
                self.add_metadata(name, data)

        def __iter__(self):
            """
            Iterate over elements in cluster
            """
            return iter(self.elements)

        def __len__(self):
            """
            Size of cluster
            """
            return len(self.elements)

        def add_metadata(self, name, data):
            if len(data) != self.size:
                raise TypeError(
                    "Size of metadata is not equal to the number of "
                    "cluster elements"
                )
            self.metadata[name] = np.array(data)

        def __repr__(self):
            """
            Textual representation
            """
            if self.size == 0:
                return "<Cluster with no elements>"
            else:
                return "<Cluster with {0} elements, centroid={1}, id={2}>".format(
                    self.size, self.centroid, self.id
                )

    class ClusterCollection(object):
        """Clusters collection class; this class represents the results of a full
        clustering run. It stores a group of clusters defined as
        encore.clustering.Cluster objects.

        Attributes
        ----------

        clusters : list
            list of of Cluster objects which are part of the Cluster collection

        """

        def __init__(self, elements=None, metadata=None):
            """Class constructor. If elements is None, an empty cluster collection
            will be created. Otherwise, the constructor takes as input an
            iterable of ints, for instance:

            [ a, a, a, a, b, b, b, c, c, ... , z, z ]

            the variables a,b,c,...,z are cluster centroids, here as cluster
            element numbers (i.e. 3 means the 4th element of the ordered input
            for clustering). The array maps a correspondence between
            cluster elements (which are implicitly associated with the
            position in the array) with centroids, i. e. defines clusters.
            For instance:

            [ 1, 1, 1, 4, 4, 5 ]

            means that elements 0, 1, 2 form a cluster which has 1 as centroid,
            elements 3 and 4 form a cluster which has 4 as centroid, and
            element 5 has its own cluster.

            Parameters
            ----------

            elements : iterable of ints or None
                clustering results. See the previous description for details

            metadata : {str:list, str:list,...} or None
                metadata for the data elements. The list must be of the same
                size as the elements array, with one value per element.

            """
            idn = 0
            if elements is None:
                self.clusters = None
                return

            if not len(set((type(el) for el in elements))) == 1:
                raise TypeError("all the elements must have the same type")
            self.clusters = []
            elements_array = np.array(elements)
            centroids = np.unique(elements_array)
            for i in centroids:
                if elements[i] != i:
                    raise ValueError(
                        "element {0}, which is a centroid, doesn't "
                        "belong to its own cluster".format(elements[i])
                    )
            for c in centroids:
                this_metadata = {}
                this_array = np.where(elements_array == c)
                if metadata:
                    for k, v in metadata.items():
                        this_metadata[k] = np.asarray(v)[this_array]
                self.clusters.append(
                    Cluster(
                        elem_list=this_array[0],
                        idn=idn,
                        centroid=c,
                        metadata=this_metadata,
                    )
                )

                idn += 1

        def get_ids(self):
            """
            Get the ID numbers of the clusters

            Returns
            -------

            ids : list of int
            list of cluster ids
            """
            return [v.id for v in self.clusters]

        def get_centroids(self):
            """
            Get the centroids of the clusters

            Returns
            -------

            centroids : list of cluster element objects
            list of cluster centroids
            """

            return [v.centroid for v in self.clusters]

        def __iter__(self):
            """
            Iterate over clusters

            """
            return iter(self.clusters)

        def __len__(self):
            """
            Length of clustering collection
            """
            return len(self.clusters)

        def __repr__(self):
            """
            Textual representation
            """
            if self.clusters is None:
                return "<ClusterCollection with no clusters>"
            else:
                return "<ClusterCollection with {0} clusters>".format(
                    len(self.clusters)
                )