Skip to content

Commit a3de390

Browse files
author
Release Manager
committed
gh-40381: Implement random unitary matrices Add a new `random_unitary_matrix()` function that is called from `matrix.random(..., algorithm='unitary')`. URL: #40381 Reported by: Michael Orlitzky Reviewer(s): John Cremona, Michael Orlitzky
2 parents ad88b27 + 49ac0cd commit a3de390

File tree

2 files changed

+180
-5
lines changed

2 files changed

+180
-5
lines changed

src/doc/en/reference/references/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4488,6 +4488,10 @@ REFERENCES:
44884488
Mathematics. Springer-Verlag, New York, 1997. ISBN
44894489
0-387-98254-X
44904490
4491+
.. [LieOs1991] Hans Liebeck and Anthony Osborne.
4492+
The Generation of All Rational Orthogonal Matrices.
4493+
The American Mathematical Monthly, 98(2):131-133, 1991.
4494+
44914495
.. [Lim] \C. H. Lim,
44924496
*CRYPTON: A New 128-bit Block Cipher*; available at
44934497
https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=89a685057c2ce4f632b105dcaec264b9162a1800

src/sage/matrix/special.py

Lines changed: 176 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
:meth:`~sage.matrix.special.random_rref_matrix`
4141
:meth:`~sage.matrix.special.random_subspaces_matrix`
4242
:meth:`~sage.matrix.special.random_unimodular_matrix`
43+
:meth:`~sage.matrix.special.random_unitary_matrix`
4344
:meth:`~sage.matrix.special.toeplitz`
4445
:meth:`~sage.matrix.special.vandermonde`
4546
:meth:`~sage.matrix.special.vector_on_axis_rotation_matrix`
@@ -241,6 +242,9 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation
241242
242243
- ``'unimodular'`` -- creates a matrix of determinant 1
243244
245+
- ``'unitary'`` -- creates a (square) unitary matrix over a
246+
subfield of the complex numbers
247+
244248
- ``'diagonalizable'`` -- creates a diagonalizable matrix. if the
245249
base ring is ``QQ`` creates a diagonalizable matrix whose eigenvectors,
246250
if computed by hand, will have only integer entries. See the
@@ -303,7 +307,7 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation
303307
sage: expected(100)
304308
1/25250
305309
sage: add_samples(ZZ, 5, 5)
306-
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic):
310+
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): # long time
307311
....: add_samples(ZZ, 5, 5)
308312
309313
The ``distribution`` keyword set to ``uniform`` will limit values
@@ -313,7 +317,7 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation
313317
sage: total_count = 0
314318
sage: dic = defaultdict(Integer)
315319
sage: add_samples(ZZ, 5, 5, distribution='uniform')
316-
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic):
320+
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): # long time
317321
....: add_samples(ZZ, 5, 5, distribution='uniform')
318322
319323
The ``x`` and ``y`` keywords can be used to distribute entries uniformly.
@@ -324,14 +328,14 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation
324328
sage: total_count = 0
325329
sage: dic = defaultdict(Integer)
326330
sage: add_samples(ZZ, 4, 8, x=70, y=100)
327-
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic):
331+
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): # long time
328332
....: add_samples(ZZ, 4, 8, x=70, y=100)
329333
330334
sage: expected = lambda n : 1/10 if n in range(-5, 5) else 0
331335
sage: total_count = 0
332336
sage: dic = defaultdict(Integer)
333337
sage: add_samples(ZZ, 3, 7, x=-5, y=5)
334-
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic):
338+
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): # long time
335339
....: add_samples(ZZ, 3, 7, x=-5, y=5)
336340
337341
If only ``x`` is given, then it is used as the upper bound of a range
@@ -341,7 +345,7 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation
341345
sage: total_count = 0
342346
sage: dic = defaultdict(Integer)
343347
sage: add_samples(ZZ, 5, 5, x=25)
344-
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic):
348+
sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): # long time
345349
....: add_samples(ZZ, 5, 5, x=25)
346350
347351
To control the number of nonzero entries, use the ``density`` keyword
@@ -665,6 +669,8 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation
665669
return random_subspaces_matrix(parent, *args, **kwds)
666670
elif algorithm == 'unimodular':
667671
return random_unimodular_matrix(parent, *args, **kwds)
672+
elif algorithm == 'unitary':
673+
return random_unitary_matrix(parent, *args, **kwds)
668674
else:
669675
raise ValueError('random matrix algorithm "%s" is not recognized' % algorithm)
670676

@@ -3135,6 +3141,171 @@ def random_unimodular_matrix(parent, upper_bound=None, max_tries=100):
31353141
return random_matrix(ring, size,algorithm='echelonizable',rank=size, upper_bound=upper_bound, max_tries=max_tries)
31363142

31373143

3144+
@matrix_method
3145+
def random_unitary_matrix(parent):
3146+
r"""
3147+
Generate a random (square) unitary matrix of a given size
3148+
with entries in a subfield of the complex numbers.
3149+
3150+
INPUT:
3151+
3152+
- ``parent`` -- :class:`sage.matrix.matrix_space.MatrixSpace`; a
3153+
square matrix space over a subfield of the complex numbers
3154+
having characteristic zero.
3155+
3156+
OUTPUT:
3157+
3158+
A random unitary matrix in ``parent``. A :exc:`ValueError` is
3159+
raised if ``parent`` is not an appropriate matrix space (is not
3160+
square, or is not over a usable field).
3161+
3162+
ALGORITHM:
3163+
3164+
The method described by Liebeck and Osborne [LieOs1991]_ is used
3165+
almost verbatim.
3166+
3167+
.. WARNING:
3168+
3169+
Inexact rings may violate your expectations; in particular,
3170+
the rings ``RR`` and ``CC`` are accepted by this method but
3171+
the resulting matrix will usually fail the ``is_unitary()``
3172+
check due to numerical issues.
3173+
3174+
EXAMPLES:
3175+
3176+
This function is not in the global namespace and must be imported
3177+
before being used::
3178+
3179+
sage: from sage.matrix.constructor import random_unitary_matrix
3180+
3181+
Matrices with rational entries::
3182+
3183+
sage: n = ZZ.random_element(10)
3184+
sage: MS = MatrixSpace(QQ, n)
3185+
sage: U = random_unitary_matrix(MS)
3186+
sage: U.is_unitary() and U in MS
3187+
True
3188+
3189+
Matrices over a quadraric field::
3190+
3191+
sage: n = ZZ.random_element(10)
3192+
sage: K = QuadraticField(-1,'i')
3193+
sage: MS = MatrixSpace(K, n)
3194+
sage: U = random_unitary_matrix(MS)
3195+
sage: U.is_unitary() and U in MS
3196+
True
3197+
3198+
Matrices with entries in the algebraic real field (slow)::
3199+
3200+
sage: # long time
3201+
sage: n = ZZ.random_element(4)
3202+
sage: MS = MatrixSpace(AA, n)
3203+
sage: U = random_unitary_matrix(MS)
3204+
sage: U.is_unitary() and U in MS
3205+
True
3206+
3207+
Matrices with entries in the algebraic field (slower yet)::
3208+
3209+
sage: # long time
3210+
sage: n = ZZ.random_element(2)
3211+
sage: MS = MatrixSpace(QQbar, n)
3212+
sage: U = random_unitary_matrix(MS)
3213+
sage: U.is_unitary() and U in MS
3214+
True
3215+
3216+
Double-precision real/complex matrices can be constructed as
3217+
well::
3218+
3219+
sage: n = ZZ.random_element(10)
3220+
sage: MS = MatrixSpace(RDF, n)
3221+
sage: U = random_unitary_matrix(MS)
3222+
sage: U.is_unitary() and U in MS
3223+
True
3224+
sage: MS = MatrixSpace(CDF, n)
3225+
sage: U = random_unitary_matrix(MS)
3226+
sage: U.is_unitary() and U in MS
3227+
True
3228+
3229+
TESTS:
3230+
3231+
We raise a :exc:`ValueError` if the supplied matrix space is not
3232+
square::
3233+
3234+
sage: MS = MatrixSpace(QQ, 3, 4)
3235+
sage: random_unitary_matrix(MS)
3236+
Traceback (most recent call last):
3237+
...
3238+
ValueError: parent must be square
3239+
3240+
Likewise if its base ring is not a field::
3241+
3242+
sage: MS = MatrixSpace(ZZ, 3)
3243+
sage: random_unitary_matrix(MS)
3244+
Traceback (most recent call last):
3245+
...
3246+
ValueError: base ring of parent must be a field
3247+
3248+
Likewise if that field is not of characteristic zero::
3249+
3250+
sage: MS = MatrixSpace(GF(5), 3)
3251+
sage: random_unitary_matrix(MS)
3252+
Traceback (most recent call last):
3253+
...
3254+
ValueError: base ring of parent must have characteristic zero
3255+
3256+
Likewise if that field is not a subfield of the complex numbers::
3257+
3258+
sage: R = Qp(7)
3259+
sage: R.characteristic()
3260+
0
3261+
sage: MS = MatrixSpace(R, 3)
3262+
sage: random_unitary_matrix(MS)
3263+
Traceback (most recent call last):
3264+
...
3265+
ValueError: base ring of parent must be a subfield of the complex
3266+
numbers
3267+
"""
3268+
n = parent.nrows()
3269+
if n != parent.ncols():
3270+
raise ValueError("parent must be square")
3271+
3272+
F = parent.base_ring()
3273+
if not F.is_field():
3274+
raise ValueError("base ring of parent must be a field")
3275+
elif not F.characteristic().is_zero():
3276+
# This is probably covered by checking for subfields of
3277+
# RLF/CLF, but it's mentioned explicitly in the paper and my
3278+
# faith in the subfield check is not 100%, so we verify the
3279+
# characteristic separately.
3280+
raise ValueError("base ring of parent must have characteristic zero")
3281+
3282+
from sage.rings.real_lazy import RLF, CLF
3283+
if not (RLF.has_coerce_map_from(F) or
3284+
F.has_coerce_map_from(RLF) or
3285+
CLF.has_coerce_map_from(F) or
3286+
F.has_coerce_map_from(CLF)):
3287+
# The implementation of SR.random_element() currently just
3288+
# returns a random integer coerced into SR, so there is no
3289+
# benefit to allowing SR here when QQ is available.
3290+
raise ValueError("base ring of parent must be a subfield "
3291+
"of the complex numbers")
3292+
3293+
I = identity_matrix(F,n)
3294+
A = random_matrix(F,n)
3295+
S = A - A.conjugate_transpose()
3296+
U = (S-I).inverse()*(S+I)
3297+
3298+
# Scale the rows of U by plus/minus one with equal probability.
3299+
# This generates the equivalence class of U according to the
3300+
# Liebeck/Osborne paper.
3301+
from random import random
3302+
for i in range(n):
3303+
if random() < 0.5:
3304+
U.set_row_to_multiple_of_row(i, i, -1)
3305+
3306+
return U
3307+
3308+
31383309
@matrix_method
31393310
def random_diagonalizable_matrix(parent, eigenvalues=None, dimensions=None):
31403311
"""

0 commit comments

Comments
 (0)