Skip to content

Commit

Permalink
[FEATURE] Add method descents (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
VascoSch92 authored May 31, 2024
1 parent 51fb054 commit 39a422b
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ The version is represented by three digits: a.b.c.
FEATURE:
- `symmetria.Permutation`: add `ascents` method
- `symmetria.CyclePermutation`: add `ascents` method
- `symmetria.Permutation`: add `descents` method
- `symmetria.CyclePermutation`: add `descents` method


## \[0.0.4\] - 2024-05-28

Expand Down
7 changes: 6 additions & 1 deletion docs/source/pages/API_reference/elements/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Here, **P** denotes the class ``Permutation``, **C** the class ``Cycle``, and **
- ✅
- ✅
* - ``ascents``
- Return the ascents positions of the permutation
- Return the positions of the permutation ascents
- ✅
- ❌
- ✅
Expand All @@ -60,6 +60,11 @@ Here, **P** denotes the class ``Permutation``, **C** the class ``Cycle``, and **
- ✅
- ✅
- ✅
* - ``descents``
- Return the positions of the permutation descents
- ✅
- ❌
- ✅
* - ``inverse``
- Compute the inverse of the permutation
- ✅
Expand Down
24 changes: 23 additions & 1 deletion symmetria/elements/cycle_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def ascents(self) -> List[int]:
[3]
"""
permutation = symmetria.Permutation.from_cycle_decomposition(self)
return [idx + 1 for idx in range(len(permutation) - 1) if permutation.image[idx] < permutation.image[idx + 1]]
return permutation.ascents()

def cycle_decomposition(self) -> "CycleDecomposition":
"""Return the cycle decomposition of the permutation.
Expand Down Expand Up @@ -444,6 +444,28 @@ def cycle_type(self) -> Tuple[int]:
"""
return tuple(sorted(len(cycle) for cycle in self))

def descents(self) -> List[int]:
r"""Return the descents of the cycle decomposition.
Recall that a descent of a permutation :math:`\sigma \in S_n`, where :math:`n \in \mathbb{N}`, is any position
:math:`i<n` such that :math:`\sigma(i) > \sigma(i+1)`.
:return: The descents of the cycle decomposition.
:rtype: List[int]
:example:
>>> from symmetria import Cycle, CycleDecomposition
...
>>> CycleDecomposition(Cycle(1, 2, 3)).descents()
[2]
>>> CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)).descents()
[]
>>> CycleDecomposition(Cycle(2, 3), Cycle(4, 5, 1)).descents()
[1, 2, 4]
"""
permutation = symmetria.Permutation.from_cycle_decomposition(self)
return permutation.descents()

@property
def domain(self) -> Iterable[int]:
"""Return an iterable containing the elements of the domain of the cycle decomposition.
Expand Down
21 changes: 21 additions & 0 deletions symmetria/elements/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,27 @@ def cycle_type(self) -> Tuple[int]:
"""
return tuple(sorted(len(cycle) for cycle in self.cycle_decomposition()))

def descents(self) -> List[int]:
r"""Return the descents of the permutation.
Recall that a descent of a permutation :math:`\sigma \in S_n`, where :math:`n \in \mathbb{N}`, is any position
:math:`i<n` such that :math:`\sigma(i) > \sigma(i+1)`.
:return: The descents of the permutation.
:rtype: List[int]
:example:
>>> from symmetria import Permutation
...
>>> Permutation(1, 2, 3).descents()
[]
>>> Permutation(3, 4, 5, 2, 1, 6, 7).descents()
[3, 4]
>>> Permutation(4, 3, 2, 1).descents()
[1, 2, 3]
"""
return [idx + 1 for idx in range(len(self) - 1) if self.image[idx] > self.image[idx + 1]]

@property
def domain(self) -> Iterable[int]:
"""Return an iterable containing the elements of the domain of the permutation.
Expand Down
9 changes: 8 additions & 1 deletion tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def validate_from_cycle_decomposition(class_: Any, constructor: Dict, expected_v
def validate_ascents(item: Any, expected_value: List[int]) -> None:
if item.ascents() != expected_value:
raise ValueError(
f"The expression `{item.rep()}.ascents()` must evaluate {expected_value}, " f"but got {item.ascents()}."
f"The expression `{item.rep()}.ascents()` must evaluate {expected_value}, but got {item.ascents()}."
)


Expand All @@ -75,6 +75,13 @@ def validate_cycle_notation(item: Any, expected_value: str) -> None:
)


def validate_descents(item: Any, expected_value: List[int]) -> None:
if item.descents() != expected_value:
raise ValueError(
f"The expression `{item.rep()}.descents()` must evaluate {expected_value}, but got {item.descents()}."
)


def validate_inverse(item: Any, expected_value: Any) -> None:
if item.inverse() != expected_value:
raise ValueError(
Expand Down
5 changes: 5 additions & 0 deletions tests/tests_cycle_decomposition/test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
(CycleDecomposition(Cycle(1, 3, 2), Cycle(4)), (1, 3)),
(CycleDecomposition(Cycle(1, 2), Cycle(3, 4)), (2, 2)),
]
TEST_DESCENTS = [
(CycleDecomposition(Cycle(1, 2, 3)), [2]),
(CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)), []),
(CycleDecomposition(Cycle(2, 3), Cycle(4, 5, 1)), [1, 2, 4]),
]
TEST_INVERSE = [
(CycleDecomposition(Cycle(1, 2, 3)), CycleDecomposition(Cycle(3, 2, 1))),
(CycleDecomposition(Cycle(1, 2), Cycle(3, 4)), CycleDecomposition(Cycle(2, 1), Cycle(4, 3))),
Expand Down
12 changes: 12 additions & 0 deletions tests/tests_cycle_decomposition/test_generic_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
validate_inverse,
validate_is_even,
validate_support,
validate_descents,
validate_cycle_type,
validate_equivalent,
validate_inversions,
Expand All @@ -29,6 +30,7 @@
TEST_INVERSE,
TEST_IS_EVEN,
TEST_SUPPORT,
TEST_DESCENTS,
TEST_CYCLE_TYPE,
TEST_EQUIVALENT,
TEST_INVERSIONS,
Expand Down Expand Up @@ -81,6 +83,16 @@ def test_cycle_type(cycle_decomposition, expected_value) -> None:
validate_cycle_type(item=cycle_decomposition, expected_value=expected_value)


@pytest.mark.parametrize(
argnames="cycle_decomposition, expected_value",
argvalues=TEST_DESCENTS,
ids=[f"{p.__repr__()}.descents()={c}" for p, c in TEST_DESCENTS],
)
def test_descents(cycle_decomposition, expected_value) -> None:
"""Tests for the method `descents()`."""
validate_descents(item=cycle_decomposition, expected_value=expected_value)


@pytest.mark.parametrize(
argnames="cycle_decomposition, expected_value",
argvalues=TEST_INVERSE,
Expand Down
5 changes: 5 additions & 0 deletions tests/tests_permutation/test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
(Permutation(3, 1, 2), "(1 3 2)"),
(Permutation(3, 1, 2, 4, 5, 6), "(1 3 2)(4)(5)(6)"),
]
TEST_DESCENTS = [
(Permutation(1, 2, 3), []),
(Permutation(3, 4, 5, 2, 1, 6, 7), [3, 4]),
(Permutation(4, 3, 2, 1), [1, 2, 3]),
]
TEST_DOMAIN = [
(Permutation(1), range(1, 2)),
(Permutation(2, 1), range(1, 3)),
Expand Down
12 changes: 12 additions & 0 deletions tests/tests_permutation/test_generic_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
validate_inverse,
validate_is_even,
validate_support,
validate_descents,
validate_cycle_type,
validate_equivalent,
validate_inversions,
Expand All @@ -32,6 +33,7 @@
TEST_INVERSE,
TEST_IS_EVEN,
TEST_SUPPORT,
TEST_DESCENTS,
TEST_CYCLE_TYPE,
TEST_EQUIVALENT,
TEST_INVERSIONS,
Expand Down Expand Up @@ -85,6 +87,16 @@ def test_cycle_notation(permutation, expected_value) -> None:
validate_cycle_notation(item=permutation, expected_value=expected_value)


@pytest.mark.parametrize(
argnames="permutation, expected_value",
argvalues=TEST_DESCENTS,
ids=[f"{p.__repr__()}.descents()={c}" for p, c in TEST_DESCENTS],
)
def test_descents(permutation, expected_value) -> None:
"""Tests for the method `descents()`."""
validate_descents(item=permutation, expected_value=expected_value)


@pytest.mark.parametrize(
argnames="permutation, expected_value",
argvalues=TEST_DOMAIN,
Expand Down

0 comments on commit 39a422b

Please sign in to comment.