random_psd_operator(
dim: int,
is_real: bool = False,
seed: int | None = None,
distribution: Literal["uniform", "wishart"] = "uniform",
scale: ndarray | None = None,
num_degrees: int | None = None,
) -> ndarray
Generate a random positive semidefinite operator.
A positive semidefinite operator is a Hermitian operator that has only real and non-negative eigenvalues.
This function generates a random PSD operator using one of two sampling strategies: "uniform"
constructs a Hermitian matrix via random sampling and eigendecomposition, while "wishart" samples
from the Wishart distribution parameterized by a scale matrix and degrees of freedom.
Parameters:
-
dim
(int)
–
The dimension of the operator.
-
is_real
(bool, default:
False
)
–
Boolean denoting whether the returned matrix will have all real entries or not.
Default is False.
-
seed
(int | None, default:
None
)
–
A seed used to instantiate numpy's random number generator.
-
distribution
(Literal['uniform', 'wishart'], default:
'uniform'
)
–
The sampling strategy to use. Either "uniform" (default) or "wishart".
The "uniform" strategy constructs a Hermitian matrix via random sampling and
eigendecomposition. The "wishart" strategy samples from the Wishart distribution,
which guarantees a PSD matrix by construction.
-
scale
(ndarray | None, default:
None
)
–
Scale matrix for the Wishart distribution. Must be a positive semidefinite matrix of
shape (dim, dim). Defaults to the identity matrix if not provided.
Only used when distribution="wishart".
-
num_degrees
(int | None, default:
None
)
–
Degrees of freedom for the Wishart distribution. Must be a positive integer.
Defaults to dim if not provided. Only used when distribution="wishart". Note that
when num_degrees < dim, the resulting matrix will be rank-deficient.
Returns:
-
ndarray
–
A dim x dim random positive semidefinite matrix.
Raises:
-
ValueError
–
If distribution is not "uniform" or "wishart".
-
ValueError
–
If scale does not have shape (dim, dim) or is not positive semidefinite.
-
ValueError
–
If num_degrees is not a positive integer.
Examples:
Using |toqito⟩, we may generate a random positive semidefinite matrix.
For \(\text{dim}=2\), this can be accomplished as follows.
from toqito.rand import random_psd_operator
complex_psd_mat = random_psd_operator(2)
print(complex_psd_mat)
[[0.50422707+3.85185989e-34j 0.27612674-3.60042839e-02j]
[0.27612674+3.60042839e-02j 0.95651264-1.38777878e-17j]]
We can confirm that this matrix indeed represents a valid positive semidefinite matrix by utilizing
the is_positive_semidefinite function from |toqito⟩.
from toqito.matrix_props import is_positive_semidefinite
print(is_positive_semidefinite(complex_psd_mat))
We can also generate random positive semidefinite matrices that are real-valued as follows.
real_psd_mat = random_psd_operator(2, is_real=True)
print(real_psd_mat)
[[0.49084937 0.17367228]
[0.17367228 0.50584256]]
Again, verifying that this is a valid positive semidefinite matrix can be done as follows.
print(is_positive_semidefinite(real_psd_mat))
It is also possible to add a seed for reproducibility.
seeded = random_psd_operator(2, is_real=True, seed=42)
print(seeded)
[[0.77395605 0.64873818]
[0.64873818 0.69736803]]
To generate a random PSD operator using the Wishart distribution, pass
distribution="wishart". Optional parameters scale (a PSD scale matrix) and
num_degrees (degrees of freedom) can be provided to fully parameterize the distribution.
Note that when num_degrees < dim, the resulting matrix is rank-deficient (singular) by
construction, though still valid PSD.
wishart_mat = random_psd_operator(3, distribution="wishart", num_degrees=5)
print(wishart_mat)
[[4.49370563+0.j 0.59365443-0.64911622j 2.29463187+2.16214379j]
[0.59365443+0.64911622j 1.30328043+0.j 1.25970688+0.72343984j]
[2.29463187-2.16214379j 1.25970688-0.72343984j 4.21221287+0.j ]]
Source code in toqito/rand/random_psd_operator.py
| def random_psd_operator(
dim: int,
is_real: bool = False,
seed: int | None = None,
distribution: Literal["uniform", "wishart"] = "uniform",
scale: np.ndarray | None = None,
num_degrees: int | None = None,
) -> np.ndarray:
r"""Generate a random positive semidefinite operator.
A positive semidefinite operator is a Hermitian operator that has only real and non-negative eigenvalues.
This function generates a random PSD operator using one of two sampling strategies: `"uniform"`
constructs a Hermitian matrix via random sampling and eigendecomposition, while `"wishart"` samples
from the Wishart distribution parameterized by a scale matrix and degrees of freedom.
Args:
dim: The dimension of the operator.
is_real: Boolean denoting whether the returned matrix will have all real entries or not.
Default is `False`.
seed: A seed used to instantiate numpy's random number generator.
distribution: The sampling strategy to use. Either `"uniform"` (default) or `"wishart"`.
The `"uniform"` strategy constructs a Hermitian matrix via random sampling and
eigendecomposition. The `"wishart"` strategy samples from the Wishart distribution,
which guarantees a PSD matrix by construction.
scale: Scale matrix for the Wishart distribution. Must be a positive semidefinite matrix of
shape `(dim, dim)`. Defaults to the identity matrix if not provided.
Only used when `distribution="wishart"`.
num_degrees: Degrees of freedom for the Wishart distribution. Must be a positive integer.
Defaults to `dim` if not provided. Only used when `distribution="wishart"`. Note that
when `num_degrees < dim`, the resulting matrix will be rank-deficient.
Returns:
A `dim` x `dim` random positive semidefinite matrix.
Raises:
ValueError: If `distribution` is not `"uniform"` or `"wishart"`.
ValueError: If `scale` does not have shape `(dim, dim)` or is not positive semidefinite.
ValueError: If `num_degrees` is not a positive integer.
Examples:
Using `|toqito⟩`, we may generate a random positive semidefinite matrix.
For \(\text{dim}=2\), this can be accomplished as follows.
```python exec="1" source="above" result="text" session="psd_operator"
from toqito.rand import random_psd_operator
complex_psd_mat = random_psd_operator(2)
print(complex_psd_mat)
```
We can confirm that this matrix indeed represents a valid positive semidefinite matrix by utilizing
the `is_positive_semidefinite` function from `|toqito⟩`.
```python exec="1" source="above" result="text" session="psd_operator"
from toqito.matrix_props import is_positive_semidefinite
print(is_positive_semidefinite(complex_psd_mat))
```
We can also generate random positive semidefinite matrices that are real-valued as follows.
```python exec="1" source="above" result="text" session="psd_operator"
real_psd_mat = random_psd_operator(2, is_real=True)
print(real_psd_mat)
```
Again, verifying that this is a valid positive semidefinite matrix can be done as follows.
```python exec="1" source="above" result="text" session="psd_operator"
print(is_positive_semidefinite(real_psd_mat))
```
It is also possible to add a seed for reproducibility.
```python exec="1" source="above" result="text" session="psd_operator"
seeded = random_psd_operator(2, is_real=True, seed=42)
print(seeded)
```
To generate a random PSD operator using the Wishart distribution, pass
`distribution="wishart"`. Optional parameters `scale` (a PSD scale matrix) and
`num_degrees` (degrees of freedom) can be provided to fully parameterize the distribution.
Note that when `num_degrees < dim`, the resulting matrix is rank-deficient (singular) by
construction, though still valid PSD.
```python exec="1" source="above" result="text" session="psd_operator"
wishart_mat = random_psd_operator(3, distribution="wishart", num_degrees=5)
print(wishart_mat)
```
"""
if distribution == "uniform":
if scale is not None or num_degrees is not None:
warnings.warn(
"scale and num_degrees are ignored when distribution='uniform'.",
UserWarning,
stacklevel=2,
)
gen = np.random.default_rng(seed=seed)
rand_mat = gen.random((dim, dim))
if not is_real:
rand_mat = rand_mat + 1j * gen.random((dim, dim))
rand_mat = (rand_mat.conj().T + rand_mat) / 2
eigenvals, eigenvecs = np.linalg.eigh(rand_mat)
q_mat, _ = np.linalg.qr(eigenvecs)
return q_mat @ np.diag(np.abs(eigenvals)) @ q_mat.conj().T
if distribution == "wishart":
if scale is None:
scale = np.eye(dim)
else:
scale = np.asarray(scale)
if scale.shape != (dim, dim):
raise ValueError(f"scale must be a {dim}x{dim} matrix, got {scale.shape}.")
if not is_positive_semidefinite(scale):
raise ValueError("scale must be a positive semidefinite matrix.")
if num_degrees is None:
num_degrees = dim
if not isinstance(num_degrees, (int, np.integer)) or num_degrees < 1:
raise ValueError("num_degrees must be a positive integer.")
if num_degrees < dim:
warnings.warn(
f"num_degrees={num_degrees} < dim={dim}: the resulting Wishart matrix will be rank-deficient.",
UserWarning,
stacklevel=2,
)
gen = np.random.default_rng(seed=seed)
if is_real:
x_mat = gen.multivariate_normal(np.zeros(dim), scale, size=num_degrees).T
else:
# Each component is drawn independently with covariance `scale`.
# Dividing by sqrt(2) ensures the resulting complex Wishart matrix
# x_mat @ x_mat.conj().T has the expected scale matrix `scale`
# rather than 2 * scale; see Goodman (1963), "Statistical analysis
# based on a certain multivariate complex Gaussian distribution."
x_mat = (
gen.multivariate_normal(np.zeros(dim), scale, size=num_degrees).T
+ 1j * gen.multivariate_normal(np.zeros(dim), scale, size=num_degrees).T
) / np.sqrt(2)
return x_mat @ x_mat.conj().T
raise ValueError("Invalid distribution. Supported options are 'uniform' and 'wishart'.")
|