# Source code for pymatgen.analysis.chemenv.coordination_environments.coordination_geometries

    # coding: utf-8
    # Copyright (c) Pymatgen Development Team.
    # Distributed under the terms of the MIT License.

    """
    This module contains the class describing the coordination geometries that can exist in a given structure. These
    "model" coordination geometries are described in the following articles :
     - Pure Appl. Chem., Vol. 79, No. 10, pp. 1779--1799, 2007.
     - Acta Cryst. A, Vol. 46, No. 1, pp. 1--11, 1990.
    The module also contains descriptors of part of these geometries (plane of separation, ...) that are used in the
    identification algorithms.
    """

    __author__ = "David Waroquiers"
    __copyright__ = "Copyright 2012, The Materials Project"
    __credits__ = "Geoffroy Hautier"
    __version__ = "2.0"
    __maintainer__ = "David Waroquiers"
    __email__ = "david.waroquiers@gmail.com"
    __date__ = "Feb 20, 2016"

    import numpy as np
    from scipy.special import factorial
    import itertools
    import abc
    from monty.json import MSONable, MontyDecoder
    import json
    import os

    module_dir = os.path.dirname(os.path.abspath(__file__))

    UNKNOWN_ENVIRONMENT_SYMBOL = 'UNKNOWN'
    UNCLEAR_ENVIRONMENT_SYMBOL = 'UNCLEAR'
    EXPLICIT_PERMUTATIONS = 'EXPLICIT_PERMUTATIONS'
    SEPARATION_PLANE = 'SEPARATION_PLANE'

    class AbstractChemenvAlgorithm(MSONable, metaclass=abc.ABCMeta):
        """
        Base class used to define a Chemenv algorithm used to identify the correct permutation for the computation
        of the Continuous Symmetry Measure.
        """

        def __init__(self, algorithm_type):
            """
            Base constructor for ChemenvAlgorithm.

            Args:
                algorithm_type (str): Type of algorithm.
            """
            self._algorithm_type = algorithm_type

        @abc.abstractmethod
        def as_dict(self):
            """
            A JSON serializable dict representation of the algorithm
            """
            pass

        @property
        def algorithm_type(self):
            """
            Return the type of algorithm.

            Returns: Type of the algorithm
            """
            return self._algorithm_type

        @abc.abstractmethod
        def __str__(self):
            return

    class ExplicitPermutationsAlgorithm(AbstractChemenvAlgorithm):
        """
        Class representing the algorithm doing the explicit permutations for the calculation of
        the Continuous Symmetry Measure.
        """
        def __init__(self, permutations):
            """
                Initializes a separation plane for a given perfect coordination geometry.

            Args:
                permutations: Permutations used for this algorithm.
            """
            super().__init__(
                algorithm_type=EXPLICIT_PERMUTATIONS)
            self._permutations = permutations

        def __str__(self):
            return self.algorithm_type

        @property
        def permutations(self):
            """
            Return the permutations to be performed for this algorithm.

            Returns: Permutations to be performed.
            """
            return self._permutations

        @property
        def as_dict(self):
            """
            Return the JSON serializable dict representation of this ExplicitPermutationsAlgorithm algorithm.

            Returns: a JSON serializable dict representation of this ExplicitPermutationsAlgorithm algorithm.
            """
            return {"@module": self.__class__.__module__,
                    "@class": self.__class__.__name__,
                    "permutations": self._permutations}

        @classmethod
        def from_dict(cls, dd):
            """
            Reconstructs the ExplicitPermutationsAlgorithm algorithm from its JSON serializable dict representation.

            Args:
                dd: a JSON serializable dict representation of an ExplicitPermutationsAlgorithm algorithm.

            Returns: an ExplicitPermutationsAlgorithm algorithm.
            """
            return cls(dd['permutations'])

    class SeparationPlane(AbstractChemenvAlgorithm):
        """
        Class representing the algorithm using separation planes for the calculation of
        the Continuous Symmetry Measure.
        """
        def __init__(self, plane_points, mirror_plane=False, ordered_plane=False,
                     point_groups=None,
                     ordered_point_groups=None,  # include_inverted_plane=False,
                     # do_inverse_pt_gp_permutations=False, plane_type='MIRROR',
                     explicit_permutations=None, minimum_number_of_points=None,
                     explicit_optimized_permutations=None,
                     multiplicity=None,
                     other_plane_points=None):  # , plane_safe_permutations=False):
            """
                Initializes a separation plane for a given perfect coordination geometry

            Args:
                plane_points: Indices of the points that are in the plane in the perfect structure (and should be
                    found in the defective one as well).
                mirror_plane: True if the separation plane is a mirror plane, in which case there is a correspondence
                    of the points in each point_group (can reduce the number of permutations).
                ordered_plane: True if the order of the points in the plane can be taken into account to reduce the
                    number of permutations.
                point_groups: Indices of the points in the two groups of points separated by the plane.
                ordered_point_groups: Whether the order of the points in each group of points can be taken into account to
                    reduce the number of permutations.
                explicit_permutations: Explicit permutations to be performed in this separation plane algorithm.
                minimum_number_of_points: Minimum number of points needed to initialize a separation plane
                    for this algorithm.
                explicit_optimized_permutations: Optimized set of explicit permutations to be performed in this
                    separation plane algorithm.
                multiplicity: Number of such planes in the model geometry.
                other_plane_points: Indices of the points that are in the plane in the perfect structure for the other
                    planes. The multiplicity should be equal to the length of this list + 1 ("main" separation plane +
                    the other ones).
                """
            super().__init__(algorithm_type=SEPARATION_PLANE)
            self.mirror_plane = mirror_plane
            self.plane_points = plane_points
            self.point_groups = point_groups
            if len(point_groups[0]) > len(point_groups[1]):
                raise RuntimeError(
                    "The number of points in the first group should be\n"
                    "less than or equal to the number of points in the second group")
            self._hash = 10000 * len(plane_points) + 100 * len(
                point_groups[0]) + len(point_groups[1])
            self.ordered_plane = ordered_plane
            self.ordered_point_groups = [False,
                                         False] if ordered_point_groups is None else ordered_point_groups
            # self._ordered_indices = list(point_groups[0])
            # self._ordered_indices.extend(plane_points)
            # self._ordered_indices.extend(point_groups[1])
            # self._inv_ordered_indices = np.argsort(self._ordered_indices)
            self.explicit_permutations = explicit_permutations
            self.explicit_optimized_permutations = explicit_optimized_permutations
            self._safe_permutations = None
            if self.explicit_optimized_permutations is not None:
                self._permutations = self.explicit_optimized_permutations
            elif self.explicit_permutations is not None:
                self._permutations = self.explicit_permutations
            self.multiplicity = multiplicity
            self.other_plane_points = other_plane_points
            self.minimum_number_of_points = minimum_number_of_points
            self.maximum_number_of_points = len(self.plane_points)
            self._ref_separation_perm = list(self.point_groups[0])
            self._ref_separation_perm.extend(list(self.plane_points))
            self._ref_separation_perm.extend(list(self.point_groups[1]))
            self._argsorted_ref_separation_perm = list(
                np.argsort(self._ref_separation_perm))
            self.separation = (len(point_groups[0]), len(plane_points), len(point_groups[1]))

        # @property
        # def ordered_indices(self):
        #     """
        #     Ordered indices of the separation plane.
        #
        #     Examples:
        #          For a separation plane of type 2|4|3, with plane_points indices [0, 3, 5, 8] and
        #          point_groups indices [1, 4] and [2, 7, 6], the list of ordered indices is :
        #          [0, 3, 5, 8, 1, 4, 2, 7, 6].
        #
        #     Returns: list of ordered indices of this separation plane.
        #     """
        #     return self._ordered_indices
        #
        # @property
        # def inv_ordered_indices(self):
        #     return self._inv_ordered_indices

        @property
        def permutations(self):
            """
            Permutations used for this separation plane algorithm.

            Returns: List of permutations to be performed.
            """
            return self._permutations

        @property
        def ref_separation_perm(self):
            """
            Ordered indices of the separation plane.

            Examples:
                 For a separation plane of type 2|4|3, with plane_points indices [0, 3, 5, 8] and
                 point_groups indices [1, 4] and [2, 7, 6], the list of ordered indices is :
                 [0, 3, 5, 8, 1, 4, 2, 7, 6].

            Returns: list of ordered indices of this separation plane.
            """
            return self._ref_separation_perm

        @property
        def argsorted_ref_separation_perm(self):
            """
            "Arg sorted" ordered indices of the separation plane.

            This is used in the identification of the final permutation to be used.

            Returns: list of the "arg sorted" ordered indices of the separation plane.
            """
            return self._argsorted_ref_separation_perm

        def safe_separation_permutations(self, ordered_plane=False,
                                         ordered_point_groups=None,
                                         add_opposite=False):
            """
            Simple and safe permutations for this separation plane.

            This is not meant to be used in production. Default configuration for ChemEnv does not use this method.
            Args:
                ordered_plane: Whether the order of the points in the plane can be used to reduce the
                    number of permutations.
                ordered_point_groups: Whether the order of the points in each point group can be used to reduce the
                    number of permutations.
                add_opposite: Whether to add the permutations from the second group before the first group as well.

            Returns: List of safe permutations.
            """
            s0 = list(range(len(self.point_groups[0])))
            plane = list(range(len(self.point_groups[0]),
                               len(self.point_groups[0]) + len(self.plane_points)))
            s1 = list(range(len(self.point_groups[0]) + len(self.plane_points),
                            len(self.point_groups[0]) + len(self.plane_points) + len(
                                self.point_groups[1])))
            ordered_point_groups = [False,
                                    False] if ordered_point_groups is None else ordered_point_groups

            def rotate(s, n):
                return s[-n:] + s[:-n]
            if ordered_plane and self.ordered_plane:
                plane_perms = [rotate(plane, ii) for ii in range(len(plane))]
                inv_plane = plane[::-1]
                plane_perms.extend(
                    [rotate(inv_plane, ii) for ii in range(len(inv_plane))])
            else:
                plane_perms = list(itertools.permutations(plane))
            if ordered_point_groups[0] and self.ordered_point_groups[0]:
                s0_perms = [rotate(s0, ii) for ii in range(len(s0))]
                inv_s0 = s0[::-1]
                s0_perms.extend([rotate(inv_s0, ii) for ii in range(len(inv_s0))])
            else:
                s0_perms = list(itertools.permutations(s0))
            if ordered_point_groups[1] and self.ordered_point_groups[1]:
                s1_perms = [rotate(s1, ii) for ii in range(len(s1))]
                inv_s1 = s1[::-1]
                s1_perms.extend([rotate(inv_s1, ii) for ii in range(len(inv_s1))])
            else:
                s1_perms = list(itertools.permutations(s1))
            if self._safe_permutations is None:
                self._safe_permutations = []
                for perm_side1 in s0_perms:
                    for perm_sep_plane in plane_perms:
                        for perm_side2 in s1_perms:
                            perm = list(perm_side1)
                            perm.extend(list(perm_sep_plane))
                            perm.extend(list(perm_side2))
                            self._safe_permutations.append(perm)
                            if add_opposite:
                                perm = list(perm_side2)
                                perm.extend(list(perm_sep_plane))
                                perm.extend(list(perm_side1))
                                self._safe_permutations.append(perm)
            return self._safe_permutations

        @property
        def as_dict(self):
            """
            Return the JSON serializable dict representation of this SeparationPlane algorithm.

            Returns: a JSON serializable dict representation of this SeparationPlane algorithm.
            """
            return {"@module": self.__class__.__module__,
                    "@class": self.__class__.__name__,
                    "plane_points": self.plane_points,
                    "mirror_plane": self.mirror_plane,
                    "ordered_plane": self.ordered_plane,
                    "point_groups": self.point_groups,
                    "ordered_point_groups": self.ordered_point_groups,
                    "explicit_permutations": [eperm.tolist() for eperm in self.explicit_permutations]
                    if self.explicit_permutations is not None else None,
                    "explicit_optimized_permutations": [eoperm.tolist()
                                                        for eoperm in self.explicit_optimized_permutations]
                    if self.explicit_optimized_permutations is not None else None,
                    "multiplicity": self.multiplicity,
                    "other_plane_points": self.other_plane_points,
                    "minimum_number_of_points": self.minimum_number_of_points}

        @classmethod
        def from_dict(cls, dd):
            """
            Reconstructs the SeparationPlane algorithm from its JSON serializable dict representation.

            Args:
                dd: a JSON serializable dict representation of an SeparationPlane algorithm.

            Returns: a SeparationPlane algorithm.
            """
            eop = [np.array(eoperm) for eoperm in dd[
                'explicit_optimized_permutations']] if ('explicit_optimized_permutations' in dd and
                                                        dd['explicit_optimized_permutations'] is not None) else None
            return cls(plane_points=dd['plane_points'],
                       mirror_plane=dd['mirror_plane'],
                       ordered_plane=dd['ordered_plane'],
                       point_groups=dd['point_groups'],
                       ordered_point_groups=dd['ordered_point_groups'],
                       explicit_permutations=[np.array(eperm) for eperm in dd['explicit_permutations']],
                       explicit_optimized_permutations=eop,
                       multiplicity=dd[
                           'multiplicity'] if 'multiplicity' in dd else None,
                       other_plane_points=dd[
                           'other_plane_points'] if 'other_plane_points' in dd else None,
                       minimum_number_of_points=dd['minimum_number_of_points'])

        def __str__(self):
            out = 'Separation plane algorithm with the following reference separation :\n'
            out += '[{}] | [{}] | [{}]'.format(
                '-'.join(str(pp) for pp in [self.point_groups[0]]),
                '-'.join(str(pp) for pp in [self.plane_points]),
                '-'.join(str(pp) for pp in [self.point_groups[1]]),
            )
            return out

    class CoordinationGeometry:
        """
        Class used to store the ideal representation of a chemical environment or "coordination geometry".
        """
        # Default value of continuous symmetry measure beyond which no further
        # search is performed for the separation plane algorithms
        CSM_SKIP_SEPARATION_PLANE_ALGO = 10.0

        class NeighborsSetsHints:
            """
            Class used to describe neighbors sets hints.

            This allows to possibly get a lower coordination from a capped-like model polyhedron.
            """

            ALLOWED_HINTS_TYPES = ['single_cap', 'double_cap', 'triple_cap']

            def __init__(self, hints_type, options):
                """
                Constructor for this NeighborsSetsHints.

                Args:
                    hints_type: type of hint (single, double or triple cap)
                    options: options for the "hinting", e.g. the maximum csm value beyond which no additional
                        neighbors set could be found from a "cap hint".
                """
                if hints_type not in self.ALLOWED_HINTS_TYPES:
                    raise ValueError('Type "{}" for NeighborsSetsHints is not allowed'.format(type))
                self.hints_type = hints_type
                self.options = options

            def hints(self, hints_info):
                """
                Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new
                neighbors set.

                Args:
                    hints_info: Info needed to build new "hinted" neighbors set.

                Returns: Voronoi indices of the new "hinted" neighbors set.
                """
                if hints_info['csm'] > self.options['csm_max']:
                    return []
                return object.__getattribute__(self, '{}_hints'.format(self.hints_type))(hints_info)

            def single_cap_hints(self, hints_info):
                """
                Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new
                neighbors set, in case of a "Single cap" hint.

                Args:
                    hints_info: Info needed to build new "hinted" neighbors set.

                Returns: Voronoi indices of the new "hinted" neighbors set.
                """
                cap_index_perfect = self.options['cap_index']
                nb_set = hints_info['nb_set']
                permutation = hints_info['permutation']
                nb_set_voronoi_indices_perfect_aligned = nb_set.get_neighb_voronoi_indices(permutation=permutation)
                cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[cap_index_perfect]
                new_site_voronoi_indices = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices.remove(cap_voronoi_index)
                return [new_site_voronoi_indices]

            def double_cap_hints(self, hints_info):
                """
                Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new
                neighbors set, in case of a "Double cap" hint.

                Args:
                    hints_info: Info needed to build new "hinted" neighbors set.

                Returns: Voronoi indices of the new "hinted" neighbors set.
                """
                first_cap_index_perfect = self.options['first_cap_index']
                second_cap_index_perfect = self.options['second_cap_index']
                nb_set = hints_info['nb_set']
                permutation = hints_info['permutation']
                nb_set_voronoi_indices_perfect_aligned = nb_set.get_neighb_voronoi_indices(permutation=permutation)
                first_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[first_cap_index_perfect]
                second_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[second_cap_index_perfect]
                new_site_voronoi_indices1 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices2 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices3 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices1.remove(first_cap_voronoi_index)
                new_site_voronoi_indices2.remove(second_cap_voronoi_index)
                new_site_voronoi_indices3.remove(first_cap_voronoi_index)
                new_site_voronoi_indices3.remove(second_cap_voronoi_index)
                return [new_site_voronoi_indices1, new_site_voronoi_indices2, new_site_voronoi_indices3]

            def triple_cap_hints(self, hints_info):
                """
                Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new
                neighbors set, in case of a "Triple cap" hint.

                Args:
                    hints_info: Info needed to build new "hinted" neighbors set.

                Returns: Voronoi indices of the new "hinted" neighbors set.
                """
                first_cap_index_perfect = self.options['first_cap_index']
                second_cap_index_perfect = self.options['second_cap_index']
                third_cap_index_perfect = self.options['third_cap_index']
                nb_set = hints_info['nb_set']
                permutation = hints_info['permutation']
                nb_set_voronoi_indices_perfect_aligned = nb_set.get_neighb_voronoi_indices(permutation=permutation)
                first_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[first_cap_index_perfect]
                second_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[second_cap_index_perfect]
                third_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[third_cap_index_perfect]
                new_site_voronoi_indices1 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices2 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices3 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices4 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices5 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices6 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices7 = list(nb_set.site_voronoi_indices)
                new_site_voronoi_indices1.remove(first_cap_voronoi_index)
                new_site_voronoi_indices2.remove(second_cap_voronoi_index)
                new_site_voronoi_indices3.remove(third_cap_voronoi_index)
                new_site_voronoi_indices4.remove(second_cap_voronoi_index)
                new_site_voronoi_indices4.remove(third_cap_voronoi_index)
                new_site_voronoi_indices5.remove(first_cap_voronoi_index)
                new_site_voronoi_indices5.remove(third_cap_voronoi_index)
                new_site_voronoi_indices6.remove(first_cap_voronoi_index)
                new_site_voronoi_indices6.remove(second_cap_voronoi_index)
                new_site_voronoi_indices7.remove(first_cap_voronoi_index)
                new_site_voronoi_indices7.remove(second_cap_voronoi_index)
                new_site_voronoi_indices7.remove(third_cap_voronoi_index)
                return [new_site_voronoi_indices1, new_site_voronoi_indices2, new_site_voronoi_indices3,
                        new_site_voronoi_indices4, new_site_voronoi_indices5, new_site_voronoi_indices6,
                        new_site_voronoi_indices7]

            def as_dict(self):
                """
                A JSON serializable dict representation of this NeighborsSetsHints
                """
                return {'hints_type': self.hints_type,
                        'options': self.options}

            @classmethod
            def from_dict(cls, dd):
                """
                Reconstructs the NeighborsSetsHints from its JSON serializable dict representation.

                Args:
                    dd: a JSON serializable dict representation of a NeighborsSetsHints.

                Returns: a NeighborsSetsHints.
                """
                return cls(hints_type=dd['hints_type'],
                           options=dd['options'])

        def __init__(self, mp_symbol, name, alternative_names=None,
                     IUPAC_symbol=None, IUCr_symbol=None, coordination=None,
                     central_site=np.zeros(3), points=None, solid_angles=None,
                     permutations_safe_override=False,
                     deactivate=False, faces=None,
                     edges=None,
                     algorithms=None,
                     equivalent_indices=None,
                     neighbors_sets_hints=None):
            """
            Initializes one "coordination geometry" according to [Pure Appl. Chem., Vol. 79, No. 10, pp. 1779--1799, 2007]
            and [Acta Cryst. A, Vol. 46, No. 1, pp. 1--11, 1990].

            Args:
                mp_symbol: Symbol used internally for the coordination geometry.
                name: Name of the coordination geometry.
                alternative_names: Alternative names for this coordination geometry.
                IUPAC_symbol: The IUPAC symbol of this coordination geometry.
                IUCr_symbol: The IUCr symbol of this coordination geometry.
                coordination: The coordination number of this coordination geometry (number of neighboring atoms).
                central_site: The coordinates of the central site of this coordination geometry.
                points: The list of the coordinates of all the points of this coordination geometry.
                solid_angles: The list of solid angles for each neighbor in this coordination geometry.
                permutations_safe_override: Computes all the permutations if set to True (overrides the plane separation
                    algorithms or any other algorithm, for testing purposes)
                deactivate: Whether to deactivate this coordination geometry
                faces: List of the faces with their vertices given in a clockwise or anticlockwise order, for drawing
                    purposes.
                edges: List of edges, for drawing purposes.
                algorithms: Algorithms used to identify this coordination geometry.
                equivalent_indices: The equivalent sets of indices in this coordination geometry (can be used to skip
                    equivalent permutations that have already been performed).
                neighbors_sets_hints: Neighors sets hints for this coordination geometry.
            """
            self._mp_symbol = mp_symbol
            self.name = name
            self.alternative_names = alternative_names if alternative_names is not None else []
            self.IUPACsymbol = IUPAC_symbol
            self.IUCrsymbol = IUCr_symbol
            self.coordination = coordination
            self.central_site = np.array(central_site)
            self.points = points
            self._solid_angles = solid_angles
            self.permutations_safe_override = permutations_safe_override
            # self.plane_safe_permutations = plane_safe_permutations
            # self.setup_permutations(permutations)
            self.deactivate = deactivate
            self._faces = faces
            self._edges = edges
            self._algorithms = algorithms
            if points is not None:
                self.centroid = np.mean(np.array(points), axis=0)
            else:
                self.centroid = None
            self.equivalent_indices = equivalent_indices
            self.neighbors_sets_hints = neighbors_sets_hints
            self._pauling_stability_ratio = None

        def as_dict(self):
            """
            A JSON serializable dict representation of this CoordinationGeometry.
            """
            return {'mp_symbol': self._mp_symbol,
                    'name': self.name,
                    'alternative_names': self.alternative_names,
                    'IUPAC_symbol': self.IUPACsymbol,
                    'IUCr_symbol': self.IUCrsymbol,
                    'coordination': self.coordination,
                    'central_site': [float(xx) for xx in self.central_site],
                    'points': [[float(xx) for xx in pp] for pp in
                               self.points] if self.points is not None else None,
                    'solid_angles': [float(ang) for ang in
                                     self._solid_angles] if self._solid_angles is not None else None,
                    'deactivate': self.deactivate,
                    '_faces': self._faces,
                    '_edges': self._edges,
                    '_algorithms': [algo.as_dict for algo in
                                    self._algorithms] if self._algorithms is not None else None,
                    'equivalent_indices': self.equivalent_indices,
                    'neighbors_sets_hints': [nbsh.as_dict() for nbsh in self.neighbors_sets_hints]
                    if self.neighbors_sets_hints is not None else None}

        @classmethod
        def from_dict(cls, dd):
            """
            Reconstructs the CoordinationGeometry from its JSON serializable dict representation.

            Args:
                dd: a JSON serializable dict representation of a CoordinationGeometry.

            Returns: a CoordinationGeometry.
            """
            dec = MontyDecoder()
            return cls(mp_symbol=dd['mp_symbol'],
                       name=dd['name'],
                       alternative_names=dd['alternative_names'],
                       IUPAC_symbol=dd['IUPAC_symbol'],
                       IUCr_symbol=dd['IUCr_symbol'],
                       coordination=dd['coordination'],
                       central_site=dd['central_site'],
                       points=dd['points'],
                       solid_angles=(dd['solid_angles'] if 'solid_angles' in dd
                                     else [4.0 * np.pi / dd['coordination']] * dd[
                           'coordination']),
                       deactivate=dd['deactivate'],
                       faces=dd['_faces'],
                       edges=dd['_edges'],
                       algorithms=[dec.process_decoded(algo_d)
                                   for algo_d in dd['_algorithms']] if dd['_algorithms'] is not None else None,
                       equivalent_indices=dd[
                           'equivalent_indices'] if 'equivalent_indices' in dd else None,
                       neighbors_sets_hints=[cls.NeighborsSetsHints.from_dict(nbshd)
                                             for nbshd in dd['neighbors_sets_hints']]
                       if ('neighbors_sets_hints' in dd and dd['neighbors_sets_hints'] is not None) else None)

        def __str__(self):
            symbol = ''
            if self.IUPAC_symbol is not None:
                symbol += ' (IUPAC: {s}'.format(s=self.IUPAC_symbol)
                if self.IUCr_symbol is not None:
                    symbol += ' || IUCr: {s})'.format(s=self.IUCr_symbol)
                else:
                    symbol += ')'
            elif self.IUCr_symbol is not None:
                symbol += ' (IUCr: {s})'.format(s=self.IUCr_symbol)
            outs = ['Coordination geometry type : {n}{s}\n'.format(n=self.name,
                                                                   s=symbol),
                    '  - coordination number : {c}'.format(c=self.coordination)]
            if self.points is None:
                outs.append('... not yet implemented')
            else:
                outs.append('  - list of points :')
                for pp in self.points:
                    outs.append('    - {p}'.format(p=pp))
            outs.append(
                '------------------------------------------------------------')
            outs.append('')

            return '\n'.join(outs)

        def __repr__(self):
            symbol = ''
            if self.IUPAC_symbol is not None:
                symbol += ' (IUPAC: {s}'.format(s=self.IUPAC_symbol)
                if self.IUCr_symbol is not None:
                    symbol += ' || IUCr: {s})'.format(s=self.IUCr_symbol)
                else:
                    symbol += ')'
            elif self.IUCr_symbol is not None:
                symbol += ' (IUCr: {s})'.format(s=self.IUCr_symbol)
            outs = ['Coordination geometry type : {n}{s}\n'.format(n=self.name,
                                                                   s=symbol),
                    '  - coordination number : {c}'.format(c=self.coordination)]
            outs.append(
                '------------------------------------------------------------')
            outs.append('')
            return '\n'.join(outs)

        def __len__(self):
            return self.coordination

        def set_permutations_safe_override(self, permutations_safe_override):
            """
            Setup ChemEnv so that a safe set of permutations are used.

            Args:
                permutations_safe_override: Whether to use safe permutations.
            """
            self.permutations_safe_override = permutations_safe_override
            # self.setup_permutations()

        # @property
        # def csm_skip_algo(self):
        #     return self.CSM_SKIP_SEPARATION_PLANE_ALGO

        @property
        def distfactor_max(self):
            """
            The maximum distfactor for the perfect CoordinationGeometry.

            Returns: Maximum distfactor for the perfect CoordinationGeometry (usually 1.0 for symmetric polyhedrons).
            """
            dists = [np.linalg.norm(pp - self.central_site) for pp in self.points]
            return np.max(dists) / np.min(dists)

        @property
        def coordination_number(self):
            """
            Returns the coordination number of this coordination geometry.
            """
            return self.coordination

        @property
        def pauling_stability_ratio(self):
            """
            Returns the theoretical Pauling stability ratio (rC/rA) for this environment.
            """
            if self._pauling_stability_ratio is None:
                if self.ce_symbol in ['S:1', 'L:2']:
                    self._pauling_stability_ratio = 0.0
                else:
                    mindist_anions = 1000000.0
                    mindist_cation_anion = 1000000.0
                    for ipt1 in range(len(self.points)):
                        pt1 = np.array(self.points[ipt1])
                        mindist_cation_anion = min(mindist_cation_anion,
                                                   np.linalg.norm(pt1 - self.central_site))
                        for ipt2 in range(ipt1 + 1, len(self.points)):
                            pt2 = np.array(self.points[ipt2])
                            mindist_anions = min(mindist_anions,
                                                 np.linalg.norm(pt1 - pt2))
                    anion_radius = mindist_anions / 2.0
                    cation_radius = mindist_cation_anion - anion_radius
                    self._pauling_stability_ratio = cation_radius / anion_radius
            return self._pauling_stability_ratio

        @property
        def mp_symbol(self):
            """
            Returns the MP symbol of this coordination geometry.
            """
            return self._mp_symbol

        @property
        def ce_symbol(self):
            """
            Returns the symbol of this coordination geometry.
            """
            return self._mp_symbol

        def get_coordination_number(self):
            """
            Returns the coordination number of this coordination geometry.
            """
            return self.coordination

        def is_implemented(self):
            """
            Returns True if this coordination geometry is implemented.
            """
            return bool(self.points)

        def get_name(self):
            """
            Returns the name of this coordination geometry.
            """
            return self.name

        @property
        def IUPAC_symbol(self):
            """
            Returns the IUPAC symbol of this coordination geometry.
            """
            return self.IUPACsymbol

        @property
        def IUPAC_symbol_str(self):
            """
            Returns a string representation of the IUPAC symbol of this coordination geometry.
            """
            return str(self.IUPACsymbol)

        @property
        def IUCr_symbol(self):
            """
            Returns the IUCr symbol of this coordination geometry.
            """
            return self.IUCrsymbol

        @property
        def IUCr_symbol_str(self):
            """
            Returns a string representation of the IUCr symbol of this coordination geometry.
            """
            return str(self.IUCrsymbol)

        @property
        def number_of_permutations(self):
            """
            Returns the number of permutations of this coordination geometry.
            """
            if self.permutations_safe_override:
                return factorial(self.coordination)
            elif self.permutations is None:
                return factorial(self.coordination)
            return len(self.permutations)

        def ref_permutation(self, permutation):
            """
            Returns the reference permutation for a set of equivalent permutations.

            Can be useful to skip permutations that have already been performed.

            Args:
                permutation: Current permutation

            Returns: Reference permutation of the perfect CoordinationGeometry.
            """
            perms = []
            for eqv_indices in self.equivalent_indices:
                perms.append(tuple([permutation[ii] for ii in eqv_indices]))
            perms.sort()
            return perms[0]

        @property
        def algorithms(self):
            """
            Returns the list of algorithms that are used to identify this coordination geometry.
            """
            return self._algorithms

        def get_central_site(self):
            """
            Returns the central site of this coordination geometry.
            """
            return self.central_site

        def faces(self, sites, permutation=None):
            """
            Returns the list of faces of this coordination geometry. Each face is given as a
            list of its vertices coordinates.
            """
            if permutation is None:
                coords = [site.coords for site in sites]
            else:
                coords = [sites[ii].coords for ii in permutation]
            return [[coords[ii] for ii in f] for f in self._faces]

        def edges(self, sites, permutation=None, input='sites'):
            """
            Returns the list of edges of this coordination geometry. Each edge is given as a
            list of its end vertices coordinates.
            """
            if input == 'sites':
                coords = [site.coords for site in sites]
            elif input == 'coords':
                coords = sites
            # if permutation is None:
            #     coords = [site.coords for site in sites]
            # else:
            #     coords = [sites[ii].coords for ii in permutation]
            if permutation is not None:
                coords = [coords[ii] for ii in permutation]
            return [[coords[ii] for ii in e] for e in self._edges]

        def solid_angles(self, permutation=None):
            """
            Returns the list of "perfect" solid angles Each edge is given as a
            list of its end vertices coordinates.
            """
            if permutation is None:
                return self._solid_angles
            else:
                return [self._solid_angles[ii] for ii in permutation]

        def get_pmeshes(self, sites, permutation=None):
            """
            Returns the pmesh strings used for jmol to show this geometry.
            """
            pmeshes = []
            # _vertices = [site.coords for site in sites]
            if permutation is None:
                _vertices = [site.coords for site in sites]
            else:
                _vertices = [sites[ii].coords for ii in permutation]
            _face_centers = []
            number_of_faces = 0
            for face in self._faces:
                if len(face) in [3, 4]:
                    number_of_faces += 1
                else:
                    number_of_faces += len(face)

                _face_centers.append(np.array([np.mean([_vertices[face_vertex][ii]
                                                        for face_vertex in face])
                                               for ii in range(3)]))

            out = '{}\n'.format(len(_vertices) + len(_face_centers))
            for vv in _vertices:
                out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(vv[0], vv[1], vv[2])
            for fc in _face_centers:
                out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(fc[0], fc[1], fc[2])
            out += '{:d}\n'.format(number_of_faces)
            for iface, face in enumerate(self._faces):
                if len(face) == 3:
                    out += '4\n'
                elif len(face) == 4:
                    out += '5\n'
                else:
                    for ii in range(len(face)):
                        out += '4\n'
                        out += '{:d}\n'.format(len(_vertices) + iface)
                        out += '{:d}\n'.format(face[ii])
                        out += '{:d}\n'.format(face[np.mod(ii + 1, len(face))])
                        out += '{:d}\n'.format(len(_vertices) + iface)
                if len(face) in [3, 4]:
                    for face_vertex in face:
                        out += '{:d}\n'.format(face_vertex)
                    out += '{:d}\n'.format(face[0])
            pmeshes.append({"pmesh_string": out})
            return pmeshes

    class AllCoordinationGeometries(dict):
        """
        Class used to store all the reference "coordination geometries" (list with instances of the CoordinationGeometry
        classes)
        """

        def __init__(self, permutations_safe_override=False, only_symbols=None):
            """
            Initializes the list of Coordination Geometries.

            Args:
                permutations_safe_override: Whether to use safe permutations.
                only_symbols: Whether to restrict the list of environments to be identified.
            """
            dict.__init__(self)
            self.cg_list = list()
            if only_symbols is None:
                f = open(
                    '{}/coordination_geometries_files/allcg.txt'.format(module_dir),
                    'r')
                data = f.readlines()
                f.close()
                for line in data:
                    cg_file = '{}/{}'.format(module_dir, line.strip())
                    f = open(cg_file, 'r')
                    dd = json.load(f)
                    f.close()
                    self.cg_list.append(CoordinationGeometry.from_dict(dd))
            else:
                for symbol in only_symbols:
                    fsymbol = symbol.replace(':', '#')
                    cg_file = '{}/coordination_geometries_files/{}.json'.format(
                        module_dir, fsymbol)
                    f = open(cg_file, 'r')
                    dd = json.load(f)
                    f.close()
                    self.cg_list.append(CoordinationGeometry.from_dict(dd))

            self.cg_list.append(CoordinationGeometry(UNKNOWN_ENVIRONMENT_SYMBOL,
                                                     "Unknown environment",
                                                     deactivate=True))
            self.cg_list.append(CoordinationGeometry(UNCLEAR_ENVIRONMENT_SYMBOL,
                                                     "Unclear environment",
                                                     deactivate=True))
            if permutations_safe_override:
                for cg in self.cg_list:
                    cg.set_permutations_safe_override(True)

            self.minpoints = {}
            self.maxpoints = {}
            self.separations_cg = {}
            for cn in range(6, 21):
                for cg in self.get_implemented_geometries(coordination=cn):
                    if only_symbols is not None and cg.ce_symbol not in only_symbols:
                        continue
                    if cn not in self.separations_cg:
                        self.minpoints[cn] = 1000
                        self.maxpoints[cn] = 0
                        self.separations_cg[cn] = {}
                    for algo in cg.algorithms:
                        sep = (len(algo.point_groups[0]),
                               len(algo.plane_points),
                               len(algo.point_groups[1]))
                        if sep not in self.separations_cg[cn]:
                            self.separations_cg[cn][sep] = []
                        self.separations_cg[cn][sep].append(cg.mp_symbol)
                        self.minpoints[cn] = min(self.minpoints[cn], algo.minimum_number_of_points)
                        self.maxpoints[cn] = max(self.maxpoints[cn], algo.maximum_number_of_points)
            self.maxpoints_inplane = {cn: max([sep[1] for sep in seps.keys()]) for cn, seps in self.separations_cg.items()}

        def __getitem__(self, key):
            return self.get_geometry_from_mp_symbol(key)

        def __contains__(self, item):
            try:
                self[item]
                return True
            except LookupError:
                return False

        def __repr__(self):
            """
            Returns a string with the list of coordination geometries.
            """
            outs = ['', '#=================================#',
                    '# List of coordination geometries #',
                    '#=================================#', '']
            for cg in self.cg_list:
                outs.append(repr(cg))

            return '\n'.join(outs)

        def __str__(self):
            """
            Returns a string with the list of coordination geometries that are implemented.
            """
            outs = ['', '#=======================================================#',
                    '# List of coordination geometries currently implemented #',
                    '#=======================================================#', '']
            for cg in self.cg_list:
                if cg.is_implemented():
                    outs.append(str(cg))

            return '\n'.join(outs)

        def get_geometries(self, coordination=None, returned='cg'):
            """
            Returns a list of coordination geometries with the given coordination number.

            Args:
                coordination: The coordination number of which the list of coordination geometries are returned.
                returned: Type of objects in the list.
            """
            geom = list()
            if coordination is None:
                for gg in self.cg_list:
                    if returned == 'cg':
                        geom.append(gg)
                    elif returned == 'mp_symbol':
                        geom.append(gg.mp_symbol)
            else:
                for gg in self.cg_list:
                    if gg.get_coordination_number() == coordination:
                        if returned == 'cg':
                            geom.append(gg)
                        elif returned == 'mp_symbol':
                            geom.append(gg.mp_symbol)
            return geom

        def get_symbol_name_mapping(self, coordination=None):
            """
            Return a dictionary mapping the symbol of a CoordinationGeometry to its name.

            Args:
                coordination: Whether to restrict the dictionary to a given coordination.

            Returns: Dictionary mapping the symbol of a CoordinationGeometry to its name.
            """
            geom = {}
            if coordination is None:
                for gg in self.cg_list:
                    geom[gg.mp_symbol] = gg.name
            else:
                for gg in self.cg_list:
                    if gg.get_coordination_number() == coordination:
                        geom[gg.mp_symbol] = gg.name
            return geom

        def get_symbol_cn_mapping(self, coordination=None):
            """
            Return a dictionary mapping the symbol of a CoordinationGeometry to its coordination.

            Args:
                coordination: Whether to restrict the dictionary to a given coordination.

            Returns: Dictionary mapping the symbol of a CoordinationGeometry to its coordination.
            """
            geom = {}
            if coordination is None:
                for gg in self.cg_list:
                    geom[gg.mp_symbol] = gg.coordination_number
            else:
                for gg in self.cg_list:
                    if gg.get_coordination_number() == coordination:
                        geom[gg.mp_symbol] = gg.coordination_number
            return geom

        def get_implemented_geometries(self, coordination=None, returned='cg',
                                       include_deactivated=False):
            """
            Returns a list of the implemented coordination geometries with the given coordination number.

            Args:
                coordination: The coordination number of which the list of implemented coordination geometries
                    are returned.
                returned: Type of objects in the list.
                include_deactivated: Whether to include CoordinationGeometry that are deactivated.
            """
            geom = list()
            if coordination is None:
                for gg in self.cg_list:
                    if gg.points is not None and (
                            (not gg.deactivate) or include_deactivated):
                        if returned == 'cg':
                            geom.append(gg)
                        elif returned == 'mp_symbol':
                            geom.append(gg.mp_symbol)
            else:
                for gg in self.cg_list:
                    if gg.get_coordination_number() == coordination and gg.points is not None and \
                            ((not gg.deactivate) or include_deactivated):
                        if returned == 'cg':
                            geom.append(gg)
                        elif returned == 'mp_symbol':
                            geom.append(gg.mp_symbol)
            return geom

        def get_not_implemented_geometries(self, coordination=None,
                                           returned='mp_symbol'):
            """
            Returns a list of the implemented coordination geometries with the given coordination number.

            Args:
                coordination: The coordination number of which the list of implemented coordination geometries
                    are returned.
                returned: Type of objects in the list.
            """
            geom = list()
            if coordination is None:
                for gg in self.cg_list:
                    if gg.points is None:
                        if returned == 'cg':
                            geom.append(gg)
                        elif returned == 'mp_symbol':
                            geom.append(gg.mp_symbol)
            else:
                for gg in self.cg_list:
                    if gg.get_coordination_number() == coordination and gg.points is None:
                        if returned == 'cg':
                            geom.append(gg)
                        elif returned == 'mp_symbol':
                            geom.append(gg.mp_symbol)
            return geom

        def get_geometry_from_name(self, name):
            """
            Returns the coordination geometry of the given name.

            Args:
                name: The name of the coordination geometry.
            """
            for gg in self.cg_list:
                if gg.name == name or name in gg.alternative_names:
                    return gg
            raise LookupError(
                'No coordination geometry found with name "{name}"'.format(
                    name=name))

        def get_geometry_from_IUPAC_symbol(self, IUPAC_symbol):
            """
            Returns the coordination geometry of the given IUPAC symbol.

            Args:
                IUPAC_symbol: The IUPAC symbol of the coordination geometry.
            """
            for gg in self.cg_list:
                if gg.IUPAC_symbol == IUPAC_symbol:
                    return gg
            raise LookupError(
                'No coordination geometry found with IUPAC symbol "{symbol}"'.format(
                    symbol=IUPAC_symbol))

        def get_geometry_from_IUCr_symbol(self, IUCr_symbol):
            """
            Returns the coordination geometry of the given IUCr symbol.

            Args:
                IUCr_symbol: The IUCr symbol of the coordination geometry.
            """
            for gg in self.cg_list:
                if gg.IUCr_symbol == IUCr_symbol:
                    return gg
            raise LookupError(
                'No coordination geometry found with IUCr symbol "{symbol}"'.format(
                    symbol=IUCr_symbol))

        def get_geometry_from_mp_symbol(self, mp_symbol):
            """
            Returns the coordination geometry of the given mp_symbol.

            Args:
                mp_symbol: The mp_symbol of the coordination geometry.
            """
            for gg in self.cg_list:
                if gg.mp_symbol == mp_symbol:
                    return gg
            raise LookupError(
                'No coordination geometry found with mp_symbol "{symbol}"'.format(
                    symbol=mp_symbol))

        def is_a_valid_coordination_geometry(self, mp_symbol=None,
                                             IUPAC_symbol=None, IUCr_symbol=None,
                                             name=None, cn=None):
            """
            Checks whether a given coordination geometry is valid (exists) and whether the parameters are coherent with
            each other.

            Args:
                mp_symbol: The mp_symbol of the coordination geometry.
                IUPAC_symbol: The IUPAC_symbol of the coordination geometry.
                IUCr_symbol: The IUCr_symbol of the coordination geometry.
                name: The name of the coordination geometry.
                cn: The coordination of the coordination geometry.
            """
            if name is not None:
                raise NotImplementedError(
                    'is_a_valid_coordination_geometry not implemented for the name')
            if mp_symbol is None and IUPAC_symbol is None and IUCr_symbol is None:
                raise SyntaxError(
                    'missing argument for is_a_valid_coordination_geometry : at least one of mp_symbol, '
                    'IUPAC_symbol and IUCr_symbol must be passed to the function')
            if mp_symbol is not None:
                try:
                    cg = self.get_geometry_from_mp_symbol(mp_symbol)
                    if IUPAC_symbol is not None:
                        if IUPAC_symbol != cg.IUPAC_symbol:
                            return False
                    if IUCr_symbol is not None:
                        if IUCr_symbol != cg.IUCr_symbol:
                            return False
                    if cn is not None:
                        if int(cn) != int(cg.coordination_number):
                            return False
                    return True
                except LookupError:
                    return False
            elif IUPAC_symbol is not None:
                try:
                    cg = self.get_geometry_from_IUPAC_symbol(IUPAC_symbol)
                    if IUCr_symbol is not None:
                        if IUCr_symbol != cg.IUCr_symbol:
                            return False
                    if cn is not None:
                        if cn != cg.coordination_number:
                            return False
                    return True
                except LookupError:
                    return False
            elif IUCr_symbol is not None:
                try:
                    cg = self.get_geometry_from_IUCr_symbol(IUCr_symbol)
                    if cn is not None:
                        if cn != cg.coordination_number:
                            return False
                    return True
                except LookupError:
                    return True
            raise Exception('Should not be here !')

        def pretty_print(self, type='implemented_geometries', maxcn=8, additional_info=None):
            """
            Return a string with a list of the Coordination Geometries.

            Args:
                type: Type of string to be returned (all_geometries, all_geometries_latex_images, all_geometries_latex,
                    implemented_geometries).
                maxcn: Maximum coordination.
                additional_info: Whether to add some additional info for each coordination geometry.

            Returns: String describing the list of coordination geometries.
            """
            if type == 'all_geometries_latex_images':
                mystring = ''
                for cn in range(1, maxcn + 1):
                    mystring += '\\section*{{Coordination {cn}}}\n\n'.format(cn=cn)
                    for cg in self.get_implemented_geometries(coordination=cn,
                                                              returned='cg'):
                        mystring += '\\subsubsection*{{{mp} : {name}}}\n\n'.format(
                            mp=cg.mp_symbol, name=cg.get_name())
                        mystring += 'IUPAC : {iupac}\n\nIUCr : {iucr}\n\n'.format(
                            iupac=cg.IUPAC_symbol, iucr=cg.IUCr_symbol)
                        mystring += '\\begin{center}\n'
                        mystring += '\\includegraphics[scale=0.15]{{images/{let}_{cif}.png}}\n'.format(
                            let=cg.mp_symbol.split(':')[0],
                            cif=cg.mp_symbol.split(':')[1])
                        mystring += '\\end{center}\n\n'
                    for cg in self.get_not_implemented_geometries(cn,
                                                                  returned='cg'):
                        mystring += '\\subsubsection*{{{mp} : {name}}}\n\n'.format(
                            mp=cg.mp_symbol, name=cg.get_name())
                        mystring += 'IUPAC : {iupac}\n\nIUCr : {iucr}\n\n'.format(
                            iupac=cg.IUPAC_symbol, iucr=cg.IUCr_symbol)
            elif type == 'all_geometries_latex':
                mystring = ''
                for cn in range(1, maxcn + 1):
                    mystring += '\\subsection*{{Coordination {cn}}}\n\n'.format(
                        cn=cn)
                    mystring += '\\begin{itemize}\n'
                    for cg in self.get_implemented_geometries(coordination=cn,
                                                              returned='cg'):
                        mystring += '\\item {mp} $\\rightarrow$ {name} '.format(
                            mp=cg.mp_symbol.replace('_',
                                                    '\\_'),
                            name=cg.get_name())
                        mystring += '(IUPAC : {iupac} - IUCr : {iucr})\n'.format(
                            iupac=cg.IUPAC_symbol_str,
                            iucr=cg.IUCr_symbol_str.replace('[', '$[$').replace(']',
                                                                                '$]$'))
                    for cg in self.get_not_implemented_geometries(cn,
                                                                  returned='cg'):
                        mystring += '\\item {mp} $\\rightarrow$ {name} '.format(
                            mp=cg.mp_symbol.replace('_',
                                                    '\\_'),
                            name=cg.get_name())
                        mystring += '(IUPAC : {iupac} - IUCr : {iucr})\n'.format(
                            iupac=cg.IUPAC_symbol_str,
                            iucr=cg.IUCr_symbol_str.replace('[', '$[$').replace(']',
                                                                                '$]$'))
                    mystring += '\\end{itemize}\n\n'
            else:
                mystring = '+-------------------------+\n| Coordination geometries |\n+-------------------------+\n\n'
                for cn in range(1, maxcn + 1):
                    mystring += '==>> CN = {cn} <<==\n'.format(cn=cn)
                    if type == 'implemented_geometries':
                        for cg in self.get_implemented_geometries(coordination=cn):
                            if additional_info is not None:
                                if 'nb_hints' in additional_info:
                                    if cg.neighbors_sets_hints is not None:
                                        addinfo = ' *'
                                    else:
                                        addinfo = ''
                                else:
                                    addinfo = ''
                            else:
                                addinfo = ''
                            mystring += ' - {mp} : {name}{addinfo}\n'.format(mp=cg.mp_symbol,
                                                                             name=cg.get_name(),
                                                                             addinfo=addinfo)
                    elif type == 'all_geometries':
                        for cg in self.get_geometries(coordination=cn):
                            mystring += ' - {mp} : {name}\n'.format(mp=cg.mp_symbol,
                                                                    name=cg.get_name())
                    mystring += '\n'
            return mystring