diff --git a/symmetria/elements/_interface.py b/symmetria/elements/_interface.py index 8ffaecf..e57766a 100644 --- a/symmetria/elements/_interface.py +++ b/symmetria/elements/_interface.py @@ -7,12 +7,12 @@ class _Element(ABC): @abstractmethod def __bool__(self) -> bool: - """Implement method `bool( )`.""" + """Implement method `bool()`.""" raise NotImplementedError @abstractmethod def __call__(self, item: Any) -> Any: - """Implement method `self( )`.""" + """Implement call on an item.""" raise NotImplementedError @abstractmethod @@ -32,17 +32,17 @@ def __mul__(self, other): @abstractmethod def __pow__(self, power: int) -> "_Element": - """Implement method `__pow__()`.""" + """Implement power of an element.""" raise NotImplementedError @abstractmethod def __repr__(self) -> str: - """Implement method `__repr__( )`.""" + """Implement representaiton method.""" raise NotImplementedError @abstractmethod def __str__(self) -> str: - """Implement method `__str__( )`.""" + """Implement method `str()`.""" raise NotImplementedError # TODO: decide if we want to implement this method also for the classes Cycle and CycleDecomposition @@ -114,7 +114,7 @@ def is_regular(self) -> bool: @property @abstractmethod def map(self) -> Dict[int, int]: - """Return a dictionary representing the map defining the element.""" + """Return a dictionary representing the map defined the element.""" raise NotImplementedError def name(self) -> str: diff --git a/symmetria/elements/cycle.py b/symmetria/elements/cycle.py index aa4e89e..d189945 100644 --- a/symmetria/elements/cycle.py +++ b/symmetria/elements/cycle.py @@ -194,6 +194,16 @@ def __eq__(self, other: Any) -> bool: :return: True if the cycle is equal to `other`, i.e., they define the same map. Otherwise, False. :rtype: bool + + :example: + >>> from symmetria import Cycle + ... + >>> Cycle(1, 2, 3) == Cycle(3, 1, 2) + True + >>> Cycle(1, 3, 2) == Cycle(1, 2, 3) + False + >>> Cycle(1, 2, 3) == 13 + False """ if isinstance(other, Cycle): lhs_length, rhs_length = len(self), len(other) @@ -209,7 +219,7 @@ def __eq__(self, other: Any) -> bool: def __getitem__(self, item: int) -> int: """Return the value of the cycle at the given index `item`. - The index corresponds to the position in the cycle, starting from 0. + The index corresponds to the position in the cycle in its standardize form, starting from 0. :param item: The index of the cycle. :type item: int @@ -218,6 +228,16 @@ def __getitem__(self, item: int) -> int: :rtype: int :raises IndexError: If the index is out of range. + + :example: + >>> from symmetria import Cycle + ... + >>> cycle = Cycle(1, 3, 2) + >>> for idx in range(len(cycle)): + ... cycle[idx] + 1 + 3 + 2 """ return self._cycle[item] @@ -260,6 +280,10 @@ def __len__(self) -> int: return len(self._cycle) def __mul__(self, other: "Cycle") -> "Cycle": + """ + :raise NotImplementedError: Multiplication between cycles is not supported. However, composition is supported. + Try to call your cycle on the cycle you would like to compose. + """ raise NotImplementedError( "Multiplication between cycles is not supported. However, composition is supported. \n" "Try to call your cycle on the cycle you would like to compose." @@ -274,14 +298,18 @@ def __pow__(self, power: int) -> "Cycle": :return: the power of the cycle. :rtype: Cycle + :raises TypeError: If `power` is not an integer. + :example: >>> from symmetria import Cycle ... - >>> Cycle(1, 3, 2) ** 0 + >>> Cycle(1, 3, 2)**0 Cycle(1, 2, 3) - >>> Cycle(1, 3, 2) ** 1 + >>> Cycle(1, 3, 2)**1 Cycle(1, 3, 2) - >>> Cycle(1, 3, 2) ** -1 + >>> Cycle(1, 3, 2)**-1 + Cycle(1, 2, 3) + >>> Cycle(1, 3, 2)**2 Cycle(1, 2, 3) """ if isinstance(power, int) is False: @@ -293,10 +321,10 @@ def __pow__(self, power: int) -> "Cycle": elif power <= -1: return self.inverse() ** abs(power) else: - return self(self ** (power - 1)) + return Cycle(*[self.map[(self ** (power - 1)).map[idx]] for idx in self.domain]) def __repr__(self) -> str: - r"""Return a string representation of the cycle in the format "Cycle(x, y, z, ...)", + r"""Return a string representation of the cycle in the format `Cycle(x, y, z, ...)`, where :math:`x, y, z, ... \in \mathbb{N}` are the elements of the cycle. :return: A string representation of the cycle. @@ -316,7 +344,10 @@ def __str__(self) -> str: r"""Return a string representation of the cycle in the form of cycle notation. Recall that for a cycle :math:`\sigma` of order n, its cycle notation is given by - :math:`(\sigma(x) \sigma^2(x), ..., \sigma^n(x))`, where x is an element in the support of the cycle. + + .. math:: (\sigma(x)\quad\sigma^2(x)\quad...\quad\sigma^n(x)), + + where x is an element in the support of the cycle, and :math:`n \in \mathbb{N}` is the cycle order. :return: A string representation of the cycle. :rtype: str @@ -334,7 +365,7 @@ def __str__(self) -> str: def cycle_decomposition(self) -> "CycleDecomposition": """Convert the cycle into its cycle decomposition, representing it as a product of disjoint cycles. - In the specific case of a cycle, it converts it from the class `Cycle` to the class `CycleDecomposition`. + In the specific case of a cycle, it converts it from the class ``Cycle`` to the class ``CycleDecomposition``. :return: The cycle decomposition of the permutation. :rtype: CycleDecomposition @@ -357,7 +388,10 @@ def cycle_notation(self) -> str: r"""Return a string representing the cycle notation of the cycle. Recall that for a cycle :math:`\sigma` of order n, its cycle notation is given by - :math:`(\sigma(x) \sigma^2(x), ..., \sigma^n(x))`, where x is an element in the support of the cycle. + + .. math:: (\sigma(x)\quad\sigma^2(x)\quad...\quad\sigma^n(x)), + + where x is an element in the support of the cycle, and :math:`n \in \mathbb{N}` is the cycle order. :return: The cycle notation of the cycle. :rtype: str @@ -409,7 +443,7 @@ def domain(self) -> Iterable[int]: @property def elements(self) -> Tuple[int]: - """Return a tuple containing the elements of the cycle. + """Return a tuple containing the elements of the cycle in its standard form. :return: The elements of the cycle. :rtype: Tuple[int] @@ -417,8 +451,12 @@ def elements(self) -> Tuple[int]: :example: >>> from symmetria import Cycle ... + >>> Cycle(1).elements + (1,) >>> Cycle(3, 1, 2).elements (1, 2, 3) + >>> Cycle(3, 4, 7, 1, 2).elements + (1, 2, 3, 4, 7) """ return self._cycle @@ -470,7 +508,7 @@ def inverse(self) -> "Cycle": the only permutation :math:`\tau \in S_n` such that :math:`\sigma * \tau = \tau * \sigma = id`, where :math:`id` is the identity permutation. - In the case of cycles, it suffices to consider the backward cycle. + Note that for a cycle, its inverse is just the backward cycle. :return: The inverse of the cycle. :rtype: Cycle @@ -634,7 +672,8 @@ def orbit(self, item: Any) -> List[Any]: r"""Compute the orbit of `item` object under the action of the cycle. Recall that the orbit of the action of a cycle :math:`\sigma` on an element x is given by the set - :math:`\{ \sigma^n(x): n \in \mathbb{N}\}`. + + .. math:: \{ \sigma^n(x): n \in \mathbb{N}\}. :param item: The initial element or iterable to compute the orbit for. :type item: Any diff --git a/symmetria/elements/cycle_decomposition.py b/symmetria/elements/cycle_decomposition.py index 9e4be11..1038f1e 100644 --- a/symmetria/elements/cycle_decomposition.py +++ b/symmetria/elements/cycle_decomposition.py @@ -173,13 +173,23 @@ def _call_on_permutation(self, original: "Permutation") -> "Permutation": return symmetria.elements.permutation.Permutation.from_cycle_decomposition(self) * original def __eq__(self, other: Any) -> bool: - """Check if the cycle decomposition is equal to the `another` object. + """Check if the cycle decomposition is equal to the `other` object. :param other: The object to compare with. :type other: Any :return: True if the cycle decomposition is equal to `other`, i.e., they define the same map. Otherwise, False. :rtype: bool + + :example: + >>> from symmetria import Cycle, CycleDecomposition + ... + >>> CycleDecomposition(Cycle(1, 2)) == CycleDecomposition(Cycle(1, 2)) + True + >>> CycleDecomposition(Cycle(1), Cycle(2)) == CycleDecomposition(Cycle(1), Cycle(2)) + True + >>> CycleDecomposition(Cycle(1), Cycle(2, 3)) == CycleDecomposition(Cycle(1)) + False """ if isinstance(other, CycleDecomposition): if len(self) != len(other): @@ -291,6 +301,8 @@ def __pow__(self, power: int) -> "CycleDecomposition": :return: the power of the cycle decomposition. :rtype: Permutation + :raises TypeError: If `power` is not an integer. + :example: >>> from symmetria import Cycle, CycleDecomposition ... @@ -300,6 +312,8 @@ def __pow__(self, power: int) -> "CycleDecomposition": CycleDecomposition(Cycle(1, 2), Cycle(3)) >>> CycleDecomposition(Cycle(1, 2), Cycle(3)) ** -1 CycleDecomposition(Cycle(1, 2), Cycle(3)) + >>> CycleDecomposition(Cycle(1, 3), Cycle(2, 4))**2 + CycleDecomposition(Cycle(1), Cycle(2), Cycle(3), Cycle(4)) """ if isinstance(power, int) is False: raise TypeError(f"Power operation for type {type(power)} not supported.") @@ -313,9 +327,13 @@ def __pow__(self, power: int) -> "CycleDecomposition": return self * (self ** (power - 1)) def __repr__(self) -> str: - r"""Return a string representation of the cycle decomposition in the format - 'CycleDecomposition(Cycle(x, ...), Cycle(y, ...), ...)', where :math:`x, y, ... \in \mathbb{N}` are - the elements of the cycles. + r"""Return a string representation of the cycle decomposition. + + The string representation is in the following format: + + .. math:: 'CycleDecomposition(Cycle(x, ...), Cycle(y, ...), ...)', + + where :math:`x, y, ... \in \mathbb{N}` are the elements of the cycles. :return: A string representation of the cycle decomposition. :rtype: str @@ -333,7 +351,7 @@ def __repr__(self) -> str: return f"CycleDecomposition({', '.join([cycle.__repr__() for cycle in self])})" def __str__(self) -> str: - """Return a string representation of the cycle decmposition in the cycle notation. + """Return a string representation of the cycle decomposition in the cycle notation. :return: A string representation of the cycle decomposition. :rtype: str @@ -646,7 +664,8 @@ def orbit(self, item: Any) -> List[Any]: r"""Compute the orbit of `item` object under the action of the cycle decomposition. Recall that the orbit of the action of a cycle decomposition :math:`\sigma` on an element x is given by the set - :math:`\{ \sigma^n(x): n \in \mathbb{N}\}`. + + ..math:: \{ \sigma^n(x): n \in \mathbb{N}\}. :param item: The initial element or iterable to compute the orbit for. :type item: Any @@ -708,8 +727,9 @@ def sgn(self) -> int: return prod([cycle.sgn() for cycle in self]) def support(self) -> Set[int]: - """Return a set containing the indices in the domain of the permutation whose images are different from - their respective indices, i.e., the set of :math:`n` in the permutation domain which are not mapped to itself. + r"""Return a set containing the indices in the domain of the permutation whose images are different from + their respective indices, i.e., the set of :math:`n \in \mathbb{N}` in the permutation domain which are + not mapped to itself. :return: The support set of the cycle decomposition. :rtype: Set[int] @@ -721,5 +741,7 @@ def support(self) -> Set[int]: set() >>> CycleDecomposition(Cycle(1), Cycle(2, 3)).support() {2, 3} + >>> CycleDecomposition(Cycle(3, 4, 5, 6), Cycle(2, 1)).support() + {1, 2, 3, 4, 5, 6} """ return {element for cycle in self if len(cycle) != 1 for element in cycle.elements} diff --git a/symmetria/elements/permutation.py b/symmetria/elements/permutation.py index a192018..334190c 100644 --- a/symmetria/elements/permutation.py +++ b/symmetria/elements/permutation.py @@ -176,6 +176,16 @@ def __eq__(self, other: Any) -> bool: :return: True if the permutation is equal to `other`, i.e., they define the same map. Otherwise, False. :rtype: bool + + :example: + >>> from symmetria import Permutation + ... + >>> Permutation(1, 2, 3) == Permutation(1, 2, 3) + True + >>> Permutation(1, 2, 3) == Permutation(3, 2, 1) + False + >>> Permutation(1, 2, 3) == 12 + False """ if isinstance(other, Permutation): return self.map == other.map @@ -185,7 +195,9 @@ def __getitem__(self, item: int) -> int: """Return the value of the permutation at the given index `item`. In other words, it returns the image of the permutation at point `item`. - The index corresponds to the position in the permutation, starting from 0. + + .. note:: The index corresponds to the element in the domain of the permutation, i.e., + the index is a number between 1 and the length of the permutation. :param item: The index of the permutation. :type item: int @@ -194,6 +206,16 @@ def __getitem__(self, item: int) -> int: :rtype: int :raises IndexError: If the index is out of range. + + :example: + >>> from symmetria import Permutation + ... + >>> permutation = Permutation(2, 3, 1) + >>> for idx in range(1, len(permutation)+1): + ... permutation[idx] + 2 + 3 + 1 """ return self.map[item] @@ -272,14 +294,18 @@ def __pow__(self, power: int) -> "Permutation": :return: the power of the permutation. :rtype: Permutation + :raises TypeError: If `power` is not an integer. + :example: >>> from symmetria import Permutation ... - >>> Permutation(3, 1, 2) ** 0 + >>> Permutation(3, 1, 2)**0 Permutation(1, 2, 3) - >>> Permutation(3, 1, 2) ** 1 + >>> Permutation(3, 1, 2)**1 Permutation(3, 1, 2) - >>> Permutation(3, 1, 2) ** -1 + >>> Permutation(3, 1, 2)**-1 + Permutation(2, 3, 1) + >>> Permutation(3, 1, 2)**2 Permutation(2, 3, 1) """ if isinstance(power, int) is False: @@ -294,7 +320,7 @@ def __pow__(self, power: int) -> "Permutation": return self * (self ** (power - 1)) def __repr__(self) -> str: - r"""Return a string representation of the permutation in the format "Permutation(x, y, z, ...)", + r"""Return a string representation of the permutation in the format `Permutation(x, y, z, ...)`, where :math:`x, y, z, ... \in \mathbb{N}` are the elements of the permutation. :return: A string representation of the permutation. @@ -311,7 +337,9 @@ def __repr__(self) -> str: return f"Permutation({', '.join([str(self._map[idx]) for idx in self.domain])})" def __str__(self) -> str: - """Return a string representation of the permutation in the form of tuples. + """Return a string representation of the permutation in the form of a tuple. + + The string representation represents the image of the permutation. :return: A string representation of the permutation. :rtype: str @@ -324,7 +352,7 @@ def __str__(self) -> str: >>> print(Permutation(1, 3, 4, 5, 2, 6)) (1, 3, 4, 5, 2, 6) """ - return "(" + ", ".join([str(self[idx]) for idx in self.domain]) + ")" + return str(self.image) if len(self.image) > 1 else f"({self.image[0]})" def cycle_decomposition(self) -> "CycleDecomposition": """Decompose the permutation into its cycle decomposition. @@ -374,7 +402,7 @@ def cycle_type(self) -> Tuple[int]: Recall that the cycle type of the permutation :math:`\sigma` is a sequence of integer, where There is a 1 for every fixed point of :math:`\sigma`, a 2 for every transposition, and so on. - .. note:: Note that the resulting tuple is sorted in ascending order. + .. note:: The resulting tuple is sorted in ascending order. :return: The cycle type of the permutation. :rtype: Tuple[int] @@ -515,6 +543,8 @@ def from_dict(cls, p: Dict[int, int]) -> "Permutation": ... >>> Permutation.from_dict({1: 3, 2: 1, 3: 2}) Permutation(3, 1, 2) + >>> Permutation.from_dict({1: 5, 2: 3, 3: 1, 4: 2, 5:4}) + Permutation(5, 3, 1, 2, 4) """ return Permutation(*[p[idx] for idx in range(1, len(p) + 1)]) @@ -522,7 +552,7 @@ def from_dict(cls, p: Dict[int, int]) -> "Permutation": def image(self) -> Tuple[int]: r"""Return the image of the permutation. - For example, to define the permutation :math:`\sigma \in S_3` given by :math:`\sigma(1)=3, \sigma(2)=1`, and + For example, consider the permutation :math:`\sigma \in S_3` given by :math:`\sigma(1)=3, \sigma(2)=1`, and :math:`\sigma (3)=2`, then the image of :math:`\sigma` is :math:`(3, 1, 2)` . :return: The image of the permutation. @@ -605,6 +635,8 @@ def is_conjugate(self, other: "Permutation") -> bool: :return: True if self and other are conjugated, False otherwise. :rtype: bool + :raises TypeError: If `other` is not a permutation. + :example: >>> from symmetria import Permutation ... @@ -612,9 +644,7 @@ def is_conjugate(self, other: "Permutation") -> bool: True >>> Permutation(1, 2, 3).is_conjugate(Permutation(3, 2, 1)) False - >>> permutation_a = Permutation(3, 2, 5, 4, 1) - >>> permutation_b = Permutation(5, 2, 1, 4, 3) - >>> permutation_a.is_conjugate(permutation_b) + >>> Permutation(3, 2, 5, 4, 1).is_conjugate(Permutation(5, 2, 1, 4, 3)) True """ if isinstance(other, Permutation) is False: @@ -755,7 +785,8 @@ def orbit(self, item: Any) -> List[Any]: r"""Compute the orbit of `item` object under the action of the cycle. Recall that the orbit of the action of a permutation :math:`\sigma` on an element x is given by the set - :math:`\{ \sigma^n(x): n \in \mathbb{N}\}`. + + .. math:: \{ \sigma^n(x): n \in \mathbb{N}\} :param item: The initial element or iterable to compute the orbit for. :type item: Any diff --git a/tests/tests_cycle/test_cases.py b/tests/tests_cycle/test_cases.py index 515fb7d..62f008b 100644 --- a/tests/tests_cycle/test_cases.py +++ b/tests/tests_cycle/test_cases.py @@ -201,8 +201,8 @@ (Cycle(1, 3, 2), 0, Cycle(1, 2, 3)), (Cycle(1, 3, 2), 1, Cycle(1, 3, 2)), (Cycle(1, 3, 2), -1, Cycle(1, 2, 3)), - (Cycle(1, 3, 2), 2, Cycle(1, 3, 2)(Cycle(1, 3, 2))), - (Cycle(1, 3, 2), -2, Cycle(1, 3, 2).inverse()(Cycle(1, 3, 2).inverse())), + (Cycle(1, 3, 2), 2, Cycle(1, 2, 3)), + (Cycle(1, 3, 2), -2, Cycle(1, 2, 3)), ] TEST_POW_ERROR = [ (Cycle(1, 2, 3), "asd", TypeError, "Power"),