/
random.py
154 lines (123 loc) · 5.1 KB
/
random.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Methods to create random operators.
"""
from __future__ import annotations
import numpy as np
from numpy.random import default_rng
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators import Operator, Stinespring
# pylint: disable=unused-import
from .dihedral.random import random_cnotdihedral
from .symplectic.random import random_clifford, random_pauli, random_pauli_list
DEFAULT_RNG = default_rng()
def random_unitary(dims: int | tuple, seed: int | np.random.Generator | None = None):
"""Return a random unitary Operator.
The operator is sampled from the unitary Haar measure.
Args:
dims (int or tuple): the input dimensions of the Operator.
seed (int or np.random.Generator): Optional. Set a fixed seed or
generator for RNG.
Returns:
Operator: a unitary operator.
"""
if seed is None:
random_state = DEFAULT_RNG
elif isinstance(seed, np.random.Generator):
random_state = seed
else:
random_state = default_rng(seed)
dim = np.prod(dims)
from scipy import stats
mat = stats.unitary_group.rvs(dim, random_state=random_state)
return Operator(mat, input_dims=dims, output_dims=dims)
def random_hermitian(
dims: int | tuple, traceless: bool = False, seed: int | np.random.Generator | None = None
):
"""Return a random hermitian Operator.
The operator is sampled from Gaussian Unitary Ensemble.
Args:
dims (int or tuple): the input dimension of the Operator.
traceless (bool): Optional. If True subtract diagonal entries to
return a traceless hermitian operator
(Default: False).
seed (int or np.random.Generator): Optional. Set a fixed seed or
generator for RNG.
Returns:
Operator: a Hermitian operator.
"""
if seed is None:
rng = DEFAULT_RNG
elif isinstance(seed, np.random.Generator):
rng = seed
else:
rng = default_rng(seed)
# Total dimension
dim = np.prod(dims)
from scipy import stats
if traceless:
mat = np.zeros((dim, dim), dtype=complex)
else:
# Generate diagonal part of matrix for Gaussian N(0, 1)
mat = np.diag(stats.norm.rvs(scale=1, size=dim, random_state=rng).astype(complex))
# Generate lower triangular values from Gaussian N(0, 0.5)
num_tril = (dim * (dim - 1)) // 2
real_tril = stats.norm.rvs(scale=0.5, size=num_tril, random_state=rng)
imag_tril = stats.norm.rvs(scale=0.5, size=num_tril, random_state=rng)
# Get lower triangular indices
rows, cols = np.tril_indices(dim, -1)
mat[(rows, cols)] = real_tril + 1j * imag_tril
mat[(cols, rows)] = real_tril - 1j * imag_tril
return Operator(mat, input_dims=dims, output_dims=dims)
def random_quantum_channel(
input_dims: int | tuple | None = None,
output_dims: int | tuple | None = None,
rank: int | None = None,
seed: int | np.random.Generator | None = None,
):
"""Return a random CPTP quantum channel.
This constructs the Stinespring operator for the quantum channel by
sampling a random isometry from the unitary Haar measure.
Args:
input_dims (int or tuple): the input dimension of the channel.
output_dims (int or tuple): the input dimension of the channel.
rank (int): Optional. The rank of the quantum channel Choi-matrix.
seed (int or np.random.Generator): Optional. Set a fixed seed or
generator for RNG.
Returns:
Stinespring: a quantum channel operator.
Raises:
QiskitError: if rank or dimensions are invalid.
"""
# Determine total input and output dimensions
if input_dims is None and output_dims is None:
raise QiskitError(
"No dimensions specified: input_dims and output_dims cannot both be None."
)
if input_dims is None:
input_dims = output_dims
elif output_dims is None:
output_dims = input_dims
d_in = np.prod(input_dims)
d_out = np.prod(output_dims)
# If rank is not specified set to the maximum rank for the
# Choi matrix (input_dim * output_dim)
if rank is None or rank > d_in * d_out:
rank = d_in * d_out
if rank < 1:
raise QiskitError(f"Rank {rank} must be greater than 0.")
from scipy import stats
# Generate a random unitary matrix
unitary = stats.unitary_group.rvs(max(rank * d_out, d_in), random_state=seed)
# Truncate columns to produce an isometry
return Stinespring(unitary[:, :d_in], input_dims=input_dims, output_dims=output_dims)