diff --git a/CHANGELOG.md b/CHANGELOG.md index fceaf3c..8577c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ The version is represented by three digits: a.b.c. ## Unreleased +FEATURE: +- `symmetria.Permutation`: add `lexicographic_rank` method +- `symmetria.CycleDecomposition`: add `lexicographic_rank` method + ## \[0.3.0\] - 2024-07-15 FEATURE: diff --git a/docs/source/pages/API_reference/elements/index.rst b/docs/source/pages/API_reference/elements/index.rst index 87720ef..c619f50 100644 --- a/docs/source/pages/API_reference/elements/index.rst +++ b/docs/source/pages/API_reference/elements/index.rst @@ -115,6 +115,11 @@ Here, **P** denotes the class ``Permutation``, **C** the class ``Cycle``, and ** - ✅ - ❌ - ✅ + * - ``lexicographic_rank`` + - Return the lexicographic rank of the permutation + - ✅ + - ❌ + - ✅ * - ``map`` - Return the map defining the permutation - ✅ diff --git a/symmetria/elements/cycle_decomposition.py b/symmetria/elements/cycle_decomposition.py index 9efeb23..db6de37 100644 --- a/symmetria/elements/cycle_decomposition.py +++ b/symmetria/elements/cycle_decomposition.py @@ -764,6 +764,27 @@ def is_regular(self) -> bool: """ return all(len(cycle) == len(self[0]) for cycle in self) + def lexicographic_rank(self) -> int: + """Return the lexicographic rank of the cycle decomposition. + + Recall that the lexicographic rank of a permutation refers to its position in the list of all + permutations of the same degree sorted in lexicographic order. + + :return: the lexocographic rank of the cycle decomposition. + :rtype: int + + :example: + >>> from symmetria import Cycle, CycleDecomposition + ... + >>> CycleDecomposition(Cycle(1)).lexicographic_rank() + 1 + >>> CycleDecomposition(Cycle(1, 3, 2)).lexicographic_rank() + 5 + >>> CycleDecomposition(Cycle(1, 3, 2), Cycle(4, 5)).lexicographic_rank() + 50 + """ + return symmetria.elements.permutation.Permutation.from_cycle_decomposition(self).lexicographic_rank() + @property def map(self) -> Dict[int, int]: """Return a dictionary representing the mapping of the cycle decomposition, diff --git a/symmetria/elements/permutation.py b/symmetria/elements/permutation.py index 5d721a5..96c11c9 100644 --- a/symmetria/elements/permutation.py +++ b/symmetria/elements/permutation.py @@ -1,3 +1,4 @@ +from math import factorial from typing import Any, Set, Dict, List, Tuple, Union, Iterable from collections import OrderedDict @@ -876,6 +877,39 @@ def is_regular(self) -> bool: cycle_decomposition = self.cycle_decomposition() return all(len(cycle) == len(cycle_decomposition[0]) for cycle in cycle_decomposition) + def lexicographic_rank(self) -> int: + """Return the lexicographic rank of the permutation. + + Recall that the lexicographic rank of a permutation refers to its position in the list of all + permutations of the same degree sorted in lexicographic order. + + :return: the lexocographic rank of the permutation. + :rtype: int + + :example: + >>> from symmetria import Permutation + ... + >>> Permutation(1).lexicographic_rank() + 1 + >>> Permutation(1, 2, 3).lexicographic_rank() + 1 + >>> Permutation(1, 3, 2).lexicographic_rank() + 2 + >>> Permutation(3, 2, 1, 4).lexicographic_rank() + 15 + """ + n = self.__len__() + rank = 1 + + for i in range(n): + right_smaller = 0 + for j in range(i + 1, n): + if self[i + 1] > self[j + 1]: + right_smaller += 1 + rank += right_smaller * factorial(n - i - 1) + + return rank + @property def map(self) -> Dict[int, int]: """Return a dictionary representing the mapping of the permutation. diff --git a/tests/tests_elements/tests_permutation/test_cases.py b/tests/tests_elements/tests_permutation/test_cases.py index a2cc5bc..b28d1df 100644 --- a/tests/tests_elements/tests_permutation/test_cases.py +++ b/tests/tests_elements/tests_permutation/test_cases.py @@ -225,6 +225,16 @@ (Permutation(2, 1), True), (Permutation(2, 1, 3), False), ] +TEST_LEXICOGRAPHIC_RANK = [ + (Permutation(1), 1), + (Permutation(1, 2), 1), + (Permutation(2, 1), 2), + (Permutation(1, 2, 3), 1), + (Permutation(1, 3, 2), 2), + (Permutation(3, 2, 1), 6), + (Permutation(3, 2, 1, 4), 15), + (Permutation(1, 2, 5, 4, 3), 6), +] TEST_MAP = [ (Permutation(1), {1: 1}), (Permutation(2, 1), {1: 2, 2: 1}), diff --git a/tests/tests_elements/tests_permutation/test_generic_methods.py b/tests/tests_elements/tests_permutation/test_generic_methods.py index 008870c..4596d05 100644 --- a/tests/tests_elements/tests_permutation/test_generic_methods.py +++ b/tests/tests_elements/tests_permutation/test_generic_methods.py @@ -27,6 +27,7 @@ TEST_IS_DERANGEMENT, TEST_ONE_LINE_NOTATION, TEST_IS_CONJUGATE_ERROR, + TEST_LEXICOGRAPHIC_RANK, TEST_CYCLE_DECOMPOSITION, ) @@ -256,6 +257,20 @@ def test_is_regular(permutation, expected_value) -> None: ) +@pytest.mark.parametrize( + argnames="permutation, expected_value", + argvalues=TEST_LEXICOGRAPHIC_RANK, + ids=[f"{p}.lexicographic_rank()={m}" for p, m in TEST_LEXICOGRAPHIC_RANK], +) +def test_lexicographic_rank(permutation, expected_value) -> None: + """Tests for the method `lexicographic_rank()`.""" + _check_values( + expression=f"{permutation.rep()}.lexicographic_rank()", + evaluation=permutation.lexicographic_rank(), + expected=expected_value, + ) + + @pytest.mark.parametrize( argnames="permutation, expected_value", argvalues=TEST_MAP,