From 4273ecbec5a2cc18fb7dda4cf73c386d9f1ee887 Mon Sep 17 00:00:00 2001 From: yaniccd Date: Mon, 23 Mar 2026 15:38:31 +0900 Subject: [PATCH 1/4] conjugacy class algorithm revisited --- .github/CHANGELOG.md | 5 +- haarpy/orthogonal.py | 2 +- haarpy/symmetric.py | 51 ++++++++++++++++++++- haarpy/symplectic.py | 2 +- haarpy/tests/test_symmetric.py | 84 +++++++++------------------------- haarpy/tests/test_unitary.py | 6 +-- haarpy/unitary.py | 4 +- 7 files changed, 82 insertions(+), 72 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 886a077..ee4283e 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -3,10 +3,9 @@ ### New features * Added a new module `orthogonal` allowing the calculation of the orthogonal Weingarten function and its moments [(#28)](https://github.com/polyquantique/haarpy/pull/28). * Added a new module `symplectic` allowing the calculation of the symplectic Weingarten function [(#31)](https://github.com/polyquantique/haarpy/pull/31). -* Added a new module `circular_ensembles` allowing the calculation of the circular orthogonal ensembles and circular symplectic ensembles Weingarten functions [(#32)](https://github.com/polyquantique/haarpy/pull/32). +* Added a new module `circular_ensembles` allowing the calculation of the circular orthogonal ensembles and circular symplectic ensembles Weingarten functions [(#32)](https://github.com/polyquantique/haarpy/pull/32) and moments [(#43)](https://github.com/polyquantique/haarpy/pull/43). * Added a new module `permutation` allowing the calculation of the permutation matrices and centered permuation matrices' Weingarten functions as well as their moments [(#36)](https://github.com/polyquantique/haarpy/pull/36). * Added a new module `partition` allowing to generate partitions of a set as well as implementing some operations on them such as the meet and the join operations [(#36)](https://github.com/polyquantique/haarpy/pull/36). -* Added moment calculation for Haar random symplectic matrices and circular symplectic ensemble [(#43)](https://github.com/polyquantique/haarpy/pull/43). ### Breaking changes @@ -15,11 +14,13 @@ * `weingarten_class()` -> `weingarten_unitary()` * `weingarten_element()` -> `weingarten_unitary()` The argument `degree` has been removed for the degree of the symmetric group is already contained in both the permutation and the conjugacy class. * `coset_type()` -> `coset_type_representative()` +* `get_conjugacy_class()` : `degree` has been removed from the arguments since it is already contained in `permutation` [(#51)](https://github.com/polyquantique/haarpy/pull/51). ### Improvements * The `README` has been improved to describe the expanding capabilities of `haarpy` [(#39)](https://github.com/polyquantique/haarpy/pull/39). * Added references and examples to the docstrings. Slight modification to the docstring format [(#47)](https://github.com/polyquantique/haarpy/pull/47). +* `get_conjugacy_class()` has been modified for efficiency reasons [(#51)](https://github.com/polyquantique/haarpy/pull/51). ### Bug fixes diff --git a/haarpy/orthogonal.py b/haarpy/orthogonal.py index d9c86ab..0a57491 100644 --- a/haarpy/orthogonal.py +++ b/haarpy/orthogonal.py @@ -88,7 +88,7 @@ def zonal_spherical_function(permutation: Permutation, partition: tuple[int]) -> double_partition = tuple(2 * part for part in partition) hyperocta = HyperoctahedralGroup(degree // 2) numerator = sum( - murn_naka_rule(double_partition, get_conjugacy_class(permutation * zeta, degree)) + murn_naka_rule(double_partition, get_conjugacy_class(permutation * zeta)) for zeta in hyperocta.generate() ) return Fraction(numerator, hyperocta.order()) diff --git a/haarpy/symmetric.py b/haarpy/symmetric.py index a1c579f..65e123a 100644 --- a/haarpy/symmetric.py +++ b/haarpy/symmetric.py @@ -38,7 +38,56 @@ @lru_cache -def get_conjugacy_class(perm: Permutation, degree: int) -> tuple: +def get_conjugacy_class(permutation: Permutation) -> tuple[int, ...]: + """Returns the conjugacy class of an element of the symmetric group Sp + + Parameters + ---------- + permutation (Permutation) : permutation of the symmetric group + + Returns + ------- + tuple[int] : the conjugacy class (cycle-type) of the permutation + + Raise + ----- + TypeError : cycle must be of type sympy.combinatorics.Permutation + + Examples + -------- + >>> from sympy.combinatorics import Permutation + >>> from haarpy import get_conjugacy_class + >>> get_conjugacy_class(Permutation(5)(0,1,3)) + (3, 1, 1, 1) + """ + if not isinstance(permutation, Permutation): + raise TypeError("Permutation must be of type sympy.combinatorics.Permutation") + perm_array = permutation.array_form + degree = len(perm_array) + + visited = [False] * degree + cycle_type = [] + + for i in range(degree): + if not visited[i]: + j = i + cycle_len = 0 + + for _ in range(degree): + visited[j] = True + j = perm_array[j] + cycle_len += 1 + if visited[j]: + break + + cycle_type.append(cycle_len) + + cycle_type.sort(reverse=True) + return tuple(cycle_type) + + +@lru_cache +def old_get_conjugacy_class(perm: Permutation, degree: int) -> tuple: """Returns the conjugacy class of an element of the symmetric group Sp Parameters diff --git a/haarpy/symplectic.py b/haarpy/symplectic.py index a25ea34..7a066fe 100644 --- a/haarpy/symplectic.py +++ b/haarpy/symplectic.py @@ -87,7 +87,7 @@ def twisted_spherical_function(permutation: Permutation, partition: tuple[int]) duplicate_partition = tuple(part for part in partition for _ in range(2)) hyperocta = HyperoctahedralGroup(degree // 2) numerator = sum( - murn_naka_rule(duplicate_partition, get_conjugacy_class(~zeta * permutation, degree)) + murn_naka_rule(duplicate_partition, get_conjugacy_class(~zeta * permutation)) * zeta.signature() for zeta in hyperocta.generate() ) diff --git a/haarpy/tests/test_symmetric.py b/haarpy/tests/test_symmetric.py index 0f78a25..127a11a 100644 --- a/haarpy/tests/test_symmetric.py +++ b/haarpy/tests/test_symmetric.py @@ -27,82 +27,42 @@ @pytest.mark.parametrize( - "degree, cycle, conjugacy", + "cycle, conjugacy", [ - (3, Permutation(0, 1, 2), (3,)), - (3, Permutation(2)(0, 1), (2, 1)), - (4, Permutation(0, 1, 2, 3), (4,)), - (4, Permutation(0, 1, 2), (3, 1)), - (4, Permutation(0, 1)(2, 3), (2, 2)), - (5, Permutation(0, 1, 2), (3, 1, 1)), - (5, Permutation(1, 2, 4), (3, 1, 1)), - (5, Permutation(3)(0, 1, 2), (3, 1, 1)), - (6, Permutation(2)(3, 4, 5)(0, 1), (3, 2, 1)), - (6, Permutation(2, 3, 4, 0, 1, 5), (6,)), - (7, Permutation(2, 3, 4, 0, 1, 6), (6, 1)), - (1, Permutation(0), (1,)), + (Permutation(0, 1, 2), (3,)), + (Permutation(2)(0, 1), (2, 1)), + (Permutation(0, 1, 2, 3), (4,)), + (Permutation(3)(0, 1, 2), (3, 1)), + (Permutation(0, 1)(2, 3), (2, 2)), + (Permutation(4)(0, 1, 2), (3, 1, 1)), + (Permutation(1, 2, 4), (3, 1, 1)), + (Permutation(4)(0, 1, 2), (3, 1, 1)), + (Permutation(2)(3, 4, 5)(0, 1), (3, 2, 1)), + (Permutation(2, 3, 4, 0, 1, 5), (6,)), + (Permutation(2, 3, 4, 0, 1, 6), (6, 1)), + (Permutation(0), (1,)), ], ) -def test_get_conjugacy_class(degree, cycle, conjugacy): +def test_get_conjugacy_class(cycle, conjugacy): "Test the normal usage of get_conjugacy_class" - assert ap.get_conjugacy_class(cycle, degree) == conjugacy + assert ap.get_conjugacy_class(cycle) == conjugacy @pytest.mark.parametrize( - "degree, cycle", + "cycle", [ - ("a", Permutation(0, 1, 2)), - (0.1, Permutation(2)(0, 1)), - ((1,), Permutation(0, 1, 2, 3)), - ((5,), Permutation(0, 1, 2)), + ((1, 2, 3)), + ("a"), + (2.0), ], ) -def test_get_conjugacy_class_degree_type_error(degree, cycle): - "Test the degree parameter TypeError" - with pytest.raises(TypeError, match=".*degree must be of type int.*"): - ap.get_conjugacy_class(cycle, degree) - - -@pytest.mark.parametrize("degree", range(-3, 1)) -def test_get_conjugacy_class_degree_value_error(degree): - "Test the degree parameter ValueError" - with pytest.raises( - ValueError, - match=".*The degree you have provided is too low. It must be an integer greater than 0.*", - ): - ap.get_conjugacy_class(Permutation(0, 1, 2), degree) - - -@pytest.mark.parametrize( - "degree, cycle", - [ - (3, (1, 2, 3)), - (4, "a"), - (7, 2.0), - ], -) -def test_get_conjugacy_class_cycle_type_error(degree, cycle): +def test_get_conjugacy_class_cycle_type_error(cycle): "Test the cycle parameter TypeError" with pytest.raises( TypeError, - match=".*Permutation must be of type sympy.combinatorics.permutations.Permutation.*", + match=".*Permutation must be of type sympy.combinatorics.Permutation.*", ): - ap.get_conjugacy_class(cycle, degree) - - -@pytest.mark.parametrize( - "degree, cycle", - [ - (3, Permutation(0, 1, 2, 3)), - (3, Permutation(2, 3)(0, 1)), - (4, Permutation(0, 1, 2, 3, 5)), - (4, Permutation(4, 1)), - ], -) -def test_get_conjugacy_class_cycle_value_error(degree, cycle): - "Test the cycle parameter ValueError if permutation maximum value is greater than the degree" - with pytest.raises(ValueError, match=".*Incompatible degree and permutation cycle.*"): - ap.get_conjugacy_class(cycle, degree) + ap.get_conjugacy_class(cycle) @pytest.mark.parametrize( diff --git a/haarpy/tests/test_unitary.py b/haarpy/tests/test_unitary.py index fdbf62e..3189d3d 100644 --- a/haarpy/tests/test_unitary.py +++ b/haarpy/tests/test_unitary.py @@ -123,7 +123,7 @@ def test_weingarten_unitary_element(cycle, dimension, num, denum): def test_weingarten_reconciliation_numeric(cycle): "Numeric reconciliation of permutation and conjugacy class input" assert ap.weingarten_unitary(cycle, 9) == ap.weingarten_unitary( - ap.get_conjugacy_class(cycle, cycle.size), 9 + ap.get_conjugacy_class(cycle), 9 ) @@ -147,7 +147,7 @@ def test_weingarten_reconciliation_symbolic(cycle): "Symbolic reconciliation of permutation and conjugacy class input" d = Symbol("d") assert ap.weingarten_unitary(cycle, d) == ap.weingarten_unitary( - ap.get_conjugacy_class(cycle, cycle.size), d + ap.get_conjugacy_class(cycle), d ) @@ -216,7 +216,7 @@ def test_gram_orthogonality_elements(n): def test_gram_orthogonality_classes(n): "Test the orthogonality relation between Weingarten matrix and Graham matrix" d = Symbol("d") - weight = lambda g: d ** (g.cycles) * ap.weingarten_unitary(ap.get_conjugacy_class(g, n), d) + weight = lambda g: d ** (g.cycles) * ap.weingarten_unitary(ap.get_conjugacy_class(g), d) orthogonality = sum(len(c) * weight(c.pop()) for c in SymmetricGroup(n).conjugacy_classes()) assert simplify(orthogonality) == 1 diff --git a/haarpy/unitary.py b/haarpy/unitary.py index a7ac36b..3c298c0 100644 --- a/haarpy/unitary.py +++ b/haarpy/unitary.py @@ -128,7 +128,7 @@ def weingarten_unitary(cycle: Union[Permutation, tuple[int]], unitary_dimension: if isinstance(cycle, Permutation): degree = cycle.size - conjugacy_class = get_conjugacy_class(cycle, degree) + conjugacy_class = get_conjugacy_class(cycle) elif isinstance(cycle, (tuple, list)) and all(isinstance(value, int) for value in cycle): degree = sum(cycle) conjugacy_class = tuple(cycle) @@ -209,7 +209,7 @@ def haar_integral_unitary(sequences: tuple[tuple[int]], unitary_dimension: Symbo degree = len(seq_i) class_mapping = Counter( - get_conjugacy_class(cycle_i * ~cycle_j, degree) + get_conjugacy_class(cycle_i * ~cycle_j) for cycle_i, cycle_j in product( stabilizer_coset(seq_i, seq_i_prime), stabilizer_coset(seq_j, seq_j_prime), From e9474734b00c6ce20dbf116ed7d0e1532254b25e Mon Sep 17 00:00:00 2001 From: yaniccd Date: Mon, 23 Mar 2026 15:46:46 +0900 Subject: [PATCH 2/4] conjugacy class algorithm revisited --- haarpy/unitary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/haarpy/unitary.py b/haarpy/unitary.py index 3c298c0..0b9c043 100644 --- a/haarpy/unitary.py +++ b/haarpy/unitary.py @@ -136,7 +136,8 @@ def weingarten_unitary(cycle: Union[Permutation, tuple[int]], unitary_dimension: raise TypeError partition_tuple = tuple( - sum((value * (key,) for key, value in part.items()), ()) for part in partitions(degree) + tuple(summand for summand, mult in partition.items() for _ in range(mult)) + for partition in partitions(degree) ) irrep_dimension_tuple = (irrep_dimension(part) for part in partition_tuple) From 5dd878a44d282b5d8d4c6a502e33fe7c0800757d Mon Sep 17 00:00:00 2001 From: yaniccd Date: Mon, 23 Mar 2026 16:19:33 +0900 Subject: [PATCH 3/4] conjugacy class algorithm revisited --- haarpy/symmetric.py | 50 --------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/haarpy/symmetric.py b/haarpy/symmetric.py index 65e123a..480296d 100644 --- a/haarpy/symmetric.py +++ b/haarpy/symmetric.py @@ -86,56 +86,6 @@ def get_conjugacy_class(permutation: Permutation) -> tuple[int, ...]: return tuple(cycle_type) -@lru_cache -def old_get_conjugacy_class(perm: Permutation, degree: int) -> tuple: - """Returns the conjugacy class of an element of the symmetric group Sp - - Parameters - ---------- - perm (Permutation) : permutation cycle from the symmetric group - degree (integer) : order of the symmetric group - - Returns - ------- - tuple[int] : the conjugacy class in partition form - - Raise - ----- - TypeError : order must be of type int - ValueError : if order is not an integer greaten than 1 - TypeError : cycle must be of type sympy.combinatorics.permutations.Permutation - ValueError : incompatible degree and permutation cycle - - Examples - -------- - >>> from sympy.combinatorics import Permutation - >>> from haarpy import get_conjugacy_class - >>> get_conjugacy_class(Permutation(5)(0,1,3), 6) - (3, 1, 1, 1) - """ - if not isinstance(degree, int): - raise TypeError("degree must be of type int") - - if degree < 1: - raise ValueError( - "The degree you have provided is too low. It must be an integer greater than 0." - ) - if not isinstance(perm, Permutation): - raise TypeError("Permutation must be of type sympy.combinatorics.permutations.Permutation") - - if perm.size > degree: - raise ValueError("Incompatible degree and permutation cycle") - - perm = perm * Permutation(degree - 1) - - return tuple( - sorted( - (key for key, value in perm.cycle_structure.items() for _ in range(value)), - reverse=True, - ) - ) - - def derivative_tableaux( tableau: tuple[tuple[int]], increment: int, partition: tuple[int] ) -> Generator[tuple[tuple[int]], None, None]: From c46ec3d97feb598a8748cea743a6d878278f933b Mon Sep 17 00:00:00 2001 From: Yanic Cardin Date: Tue, 24 Mar 2026 19:47:57 +0900 Subject: [PATCH 4/4] Update CHANGELOG.md in PR #51 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nicolás Quesada <991946+nquesada@users.noreply.github.com> --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index ee4283e..ec3733c 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -20,7 +20,7 @@ * The `README` has been improved to describe the expanding capabilities of `haarpy` [(#39)](https://github.com/polyquantique/haarpy/pull/39). * Added references and examples to the docstrings. Slight modification to the docstring format [(#47)](https://github.com/polyquantique/haarpy/pull/47). -* `get_conjugacy_class()` has been modified for efficiency reasons [(#51)](https://github.com/polyquantique/haarpy/pull/51). +* `get_conjugacy_class()` has been sped up [(#51)](https://github.com/polyquantique/haarpy/pull/51). ### Bug fixes