Coverage for src / cvx / risk / rand / rand_cov.py: 100%
6 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 03:39 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 03:39 +0000
1"""Random covariance matrix generation utilities.
3This module provides functions for generating random positive semi-definite
4covariance matrices. These are useful for testing and simulation purposes.
6Example:
7 Generate a random covariance matrix:
9 >>> import numpy as np
10 >>> from cvx.risk.rand import rand_cov
11 >>> # Generate a 5x5 random covariance matrix
12 >>> cov = rand_cov(5, seed=42)
13 >>> cov.shape
14 (5, 5)
15 >>> # Verify it's symmetric
16 >>> bool(np.allclose(cov, cov.T))
17 True
18 >>> # Verify it's positive semi-definite
19 >>> bool(np.all(np.linalg.eigvals(cov) >= -1e-10))
20 True
22"""
24# Copyright 2023 Stanford University Convex Optimization Group
25#
26# Licensed under the Apache License, Version 2.0 (the "License");
27# you may not use this file except in compliance with the License.
28# You may obtain a copy of the License at
29#
30# http://www.apache.org/licenses/LICENSE-2.0
31#
32# Unless required by applicable law or agreed to in writing, software
33# distributed under the License is distributed on an "AS IS" BASIS,
34# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35# See the License for the specific language governing permissions and
36# limitations under the License.
37from __future__ import annotations
39import numpy as np
42def rand_cov(n: int, seed: int | None = None) -> np.ndarray:
43 """Construct a random positive semi-definite covariance matrix of size n x n.
45 The matrix is constructed as A^T @ A where A is a random n x n matrix with
46 elements drawn from a standard normal distribution. This ensures the result
47 is symmetric and positive semi-definite.
49 Args:
50 n: Size of the covariance matrix (n x n).
51 seed: Random seed for reproducibility. If None, uses the current
52 random state.
54 Returns:
55 A random positive semi-definite n x n covariance matrix.
57 Example:
58 Generate a reproducible random covariance matrix:
60 >>> import numpy as np
61 >>> from cvx.risk.rand import rand_cov
62 >>> cov1 = rand_cov(3, seed=42)
63 >>> cov2 = rand_cov(3, seed=42)
64 >>> np.allclose(cov1, cov2)
65 True
67 Use in portfolio optimization:
69 >>> from cvx.risk.sample import SampleCovariance
70 >>> import cvxpy as cp
71 >>> model = SampleCovariance(num=4)
72 >>> cov = rand_cov(4, seed=42)
73 >>> model.update(
74 ... cov=cov,
75 ... lower_assets=np.zeros(4),
76 ... upper_assets=np.ones(4)
77 ... )
78 >>> weights = cp.Variable(4)
79 >>> risk = model.estimate(weights)
80 >>> isinstance(risk, cp.Expression)
81 True
83 Verify positive definiteness via Cholesky decomposition:
85 >>> cov = rand_cov(5, seed=123)
86 >>> # If Cholesky succeeds without error, matrix is positive definite
87 >>> L = np.linalg.cholesky(cov)
88 >>> bool(np.allclose(L @ L.T, cov))
89 True
91 Eigenvalue verification:
93 >>> cov = rand_cov(3, seed=99)
94 >>> eigenvalues = np.linalg.eigvalsh(cov)
95 >>> # All eigenvalues should be positive for PD matrix
96 >>> bool(np.all(eigenvalues > 0))
97 True
99 Different seeds produce different matrices:
101 >>> cov1 = rand_cov(3, seed=1)
102 >>> cov2 = rand_cov(3, seed=2)
103 >>> bool(not np.allclose(cov1, cov2))
104 True
106 Without seed, consecutive calls may differ (random state):
108 >>> # These may or may not be equal depending on random state
109 >>> cov_a = rand_cov(2, seed=None)
110 >>> cov_b = rand_cov(2, seed=None)
111 >>> cov_a.shape == cov_b.shape == (2, 2)
112 True
114 Monte Carlo simulation example:
116 >>> from cvx.risk.portfolio import minrisk_problem
117 >>> results = []
118 >>> for i in range(5):
119 ... cov = rand_cov(3, seed=i)
120 ... model = SampleCovariance(num=3)
121 ... model.update(
122 ... cov=cov,
123 ... lower_assets=np.zeros(3),
124 ... upper_assets=np.ones(3)
125 ... )
126 ... weights = cp.Variable(3)
127 ... prob = minrisk_problem(model, weights)
128 ... _ = prob.solve(solver="CLARABEL")
129 ... results.append(prob.value)
130 >>> len(results)
131 5
132 >>> all(r > 0 for r in results) # All risks are positive
133 True
135 Note:
136 The generated matrix is guaranteed to be positive semi-definite because
137 it is constructed as A^T @ A. In practice, it will typically be positive
138 definite (all eigenvalues strictly positive) unless n is very large.
140 """
141 rng = np.random.default_rng(seed)
142 a = rng.standard_normal((n, n))
143 return np.transpose(a) @ a