Source code for haarpy.circular_ensembles

# Copyright 2025 Polyquantique

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#    http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Circular ensembles Python interface

References
----------
    [1] Matsumoto, S. (2013). Weingarten calculus for matrix ensembles associated with compact
    symmetric spaces. arXiv preprint arXiv:1301.5401.
"""

from math import prod
from fractions import Fraction
from functools import lru_cache
from collections import Counter
from sympy import Symbol, Expr
from sympy.combinatorics import Permutation, SymmetricGroup
from sympy.core.numbers import Integer
from haarpy import (
    weingarten_orthogonal,
    weingarten_symplectic,
    coset_type,
    stabilizer_coset,
)
from ._utils import _simplify


[docs] @lru_cache def weingarten_circular_orthogonal( permutation: Permutation | tuple[int, ...], coe_dimension: Symbol, ) -> Expr: """Returns the circular orthogonal ensemble's Weingarten functions Parameters ---------- permutation : Permutation) : | tuple[int, ...] A permutation of the symmetric group :math:`S_{2p}` or its coset-type coe_dimension : Symbol The dimension of the circular orthogonal ensemble Returns ------- Expr The Weingarten function Examples -------- >>> from sympy import Symbol >>> from sympy.combinatorics import Permutation >>> from haarpy import weingarten_circular_orthogonal >>> d = Symbol("d") >>> weingarten_circular_orthogonal(Permutation(3)(0,1), 4) Fraction(3, 70) >>> weingarten_circular_orthogonal(Permutation(3)(0,1), d) (d + 2)/(d*(d + 1)*(d + 3)) >>> weingarten_circular_orthogonal((1,1), d) (d + 2)/(d*(d + 1)*(d + 3)) See Also -------- :func:`haarpy.symmetric.coset_type` Returns the coset-type of a given permutation of the symmetric group :func:`haarpy.orthogonal.weingarten_orthogonal` Returns the orthogonal Weingarten function """ return weingarten_orthogonal(permutation, coe_dimension + 1)
[docs] @lru_cache def weingarten_circular_symplectic(permutation: Permutation, cse_dimension: Symbol) -> Expr: """Returns the circular symplectic ensembles Weingarten functions Parameters ---------- permutation : Permutation A permutation of the symmetric group :math:`S_{2p} cse_dimension : int The dimension of the circular symplectic ensemble Returns ------- Expr The Weingarten function Examples -------- >>> from sympy import Symbol >>> from sympy.combinatorics import Permutation >>> from haarpy import weingarten_circular_symplectic >>> d = Symbol("d") >>> weingarten_circular_symplectic(Permutation(3)(0,1), 4) Fraction(-3, 140) >>> weingarten_circular_symplectic(Permutation(3)(0,1), d) (1 - d)/(d*(2*d - 3)*(2*d - 1)) See Also -------- :func:`haarpy.symplectic.weingarten_symplectic` Returns the symplectic Weingarten function """ symplectic_dimension = ( (2 * cse_dimension - 1) / 2 if isinstance(cse_dimension, Expr) else Fraction(2 * cse_dimension - 1, 2) ) return weingarten_symplectic(permutation, symplectic_dimension)
[docs] @lru_cache def haar_integral_circular_orthogonal( sequences: tuple[tuple[int, ...], ...], group_dimension: Symbol ) -> Expr: """Returns the integral over the circular orthogonal ensemble of a monomial sampled at random from the Haar measure Parameters ---------- sequences : tuple[tuple[int, ...], ...] Sequences of matrix elements group_dimension : Symbol The dimension of the circular orthogonal ensemble Returns ------- Expr The integral under the Haar measure Raises ------ ValueError If ``sequences`` do not contain precisely two sequences ValueError If the sequences are of odd length Examples -------- >>> from sympy import Symbol >>> from haarpy import haar_integral_circular_orthogonal >>> d = Symbol("d") >>> seq_i, seq_j = (0, 0, 1, 2), (1, 0, 0, 2) >>> haar_integral_circular_orthogonal((seq_i, seq_j), 7) Fraction(-1, 280) >>> haar_integral_circular_orthogonal((seq_i, seq_j), d) -2/(d*(d + 1)*(d + 3)) See Also -------- :func:`haarpy.symmetric.coset_type` Returns the coset-type of a given permutation of the symmetric group :func:`haarpy.symmetric.stabilizer_coset` Returns all permutations that sends the first sequences to the second. For a single input, the function returns the stabilizer group with respect to the sequence :func:`haarpy.circular_ensembles.weingarten_circular_orthogonal` Returns the circular orthogonal ensemble's Weingarten functions """ if len(sequences) != 2: raise ValueError("Wrong tuple format") seq_i, seq_j = sequences if len(seq_i) % 2 or len(seq_j) % 2: raise ValueError("Wrong tuple format") coset_mapping = Counter( coset_type(permutation) for permutation in stabilizer_coset(seq_i, seq_j) ) integral_gen = ( count * weingarten_circular_orthogonal(coset, group_dimension) for coset, count in coset_mapping.items() ) return sum(integral_gen) if isinstance(group_dimension, int) else _simplify(integral_gen)
[docs] @lru_cache def haar_integral_circular_symplectic( sequences: tuple[tuple[Expr, ...], ...], half_dimension: Expr ) -> Expr: """Returns the integral over the circular symplectic ensemble of a monomial sampled at random from the Haar measure Parameters ---------- sequences : tuple[tuple[Expr, ...], ...] Indices of matrix elements half_dimension : Expr Half the dimension of the unitary group Returns ------- Expr The integral under the Haar measure Raises ------ ValueError If parameter ``sequences`` do not contain precisely two sequences ValueError If either sequence is of odd length TypeError If parameter ``half_dimension`` is of type ``int`` while the sequences contain symbols TypeError If the parameter ``half_dimension`` is neither ``int`` nor ``Symbol`` ValueError If not all sequence indices are between ``0`` and ``2*half_dimension - 1`` TypeError If ``sequences`` contains something other than ``Expr`` or ``int`` TypeError If the symbolic sequences have the wrong format Examples -------- >>> from sympy import Symbol >>> from haarpy import haar_integral_circular_symplectic >>> d = Symbol("d") >>> seq_i_num, seq_j_num = (0, 3, 2, 1), (0, 1, 2, 3) >>> haar_integral_circular_symplectic((seq_i_num, seq_j_num), 2) Fraction(1, 6) >>> seq_i_symb, seq_j_symb = (0, d+1, d, 1), (0, 1, d, d + 1) >>> haar_integral_circular_symplectic((seq_i_symb, seq_j_symb), d) -1/(2*d*(2*d - 3)*(2*d - 1)) See Also -------- :func:`haarpy.circular_ensembles.weingarten_circular_symplectic` Returns the circular symplectic ensemble's Weingarten functions """ if len(sequences) != 2: raise ValueError("Wrong sequence format") seq_i, seq_j = sequences degree = len(seq_i) if degree % 2 or len(seq_j) % 2: raise ValueError("Wrong sequence format") if isinstance(half_dimension, int): if not all(isinstance(i, int) for i in seq_i + seq_j): raise TypeError if not all(0 <= i <= 2 * half_dimension - 1 for i in seq_i + seq_j): raise ValueError("The matrix indices are outside the dimension range") if degree != len(seq_j): return 0 coefficient = prod(-1 if i < half_dimension else 1 for i in (seq_i + seq_j)[::2]) shifted_i = [ ( i + half_dimension if i < half_dimension and index % 2 == 0 else i - half_dimension if index % 2 == 0 else i ) for index, i in enumerate(seq_i) ] shifted_j = [ ( i + half_dimension if i < half_dimension and index % 2 == 0 else i - half_dimension if index % 2 == 0 else i ) for index, i in enumerate(seq_j) ] elif isinstance(half_dimension, Symbol): if not all(isinstance(i, (int, Expr)) for i in seq_i + seq_j): raise TypeError if not all( ( len(xpr.as_ordered_terms()) == 2 and xpr.as_ordered_terms()[0] == half_dimension and isinstance(xpr.as_ordered_terms()[1], Integer) and xpr.as_ordered_terms()[1] > 0 ) or xpr == half_dimension for xpr in seq_i + seq_j if isinstance(xpr, Expr) ): raise TypeError if degree != len(seq_j): return 0 coefficient = prod(-1 if isinstance(i, int) else 1 for i in (seq_i + seq_j)[::2]) shifted_i = [ ( i + half_dimension if isinstance(i, int) and index % 2 == 0 else ( 0 if i == half_dimension and index % 2 == 0 else i.as_ordered_terms()[1] if index % 2 == 0 else i ) ) for index, i in enumerate(seq_i) ] shifted_j = [ ( i + half_dimension if isinstance(i, int) and index % 2 == 0 else ( 0 if i == half_dimension and index % 2 == 0 else i.as_ordered_terms()[1] if index % 2 == 0 else i ) ) for index, i in enumerate(seq_j) ] else: raise TypeError permutation_tuple = ( permutation for permutation in SymmetricGroup(degree).generate() if permutation(shifted_i) == shifted_j ) integral_gen = ( weingarten_circular_symplectic(permutation, half_dimension) for permutation in permutation_tuple ) return ( coefficient * sum(integral_gen) if isinstance(half_dimension, int) else _simplify(integral_gen, Fraction(coefficient, 1)) )