|
12 | 12 | import numpy as np
|
13 | 13 | from numpy.random import multivariate_normal
|
14 | 14 | from scipy.linalg import solve
|
| 15 | +from .matrix_eqn import solve_discrete_lyapunov |
15 | 16 | from numba import jit
|
16 | 17 | from .util import check_random_state
|
17 | 18 |
|
@@ -281,59 +282,61 @@ def moment_sequence(self):
|
281 | 282 | mu_x = A.dot(mu_x)
|
282 | 283 | Sigma_x = A.dot(Sigma_x).dot(A.T) + C.dot(C.T)
|
283 | 284 |
|
284 |
| - def stationary_distributions(self, max_iter=200, tol=1e-5): |
| 285 | + def stationary_distributions(self): |
285 | 286 | r"""
|
286 | 287 | Compute the moments of the stationary distributions of :math:`x_t` and
|
287 |
| - :math:`y_t` if possible. Computation is by iteration, starting from |
288 |
| - the initial conditions self.mu_0 and self.Sigma_0 |
289 |
| -
|
290 |
| - Parameters |
291 |
| - ---------- |
292 |
| - max_iter : scalar(int), optional(default=200) |
293 |
| - The maximum number of iterations allowed |
294 |
| - tol : scalar(float), optional(default=1e-5) |
295 |
| - The tolerance level that one wishes to achieve |
| 288 | + :math:`y_t` if possible. Computation is by solving the discrete |
| 289 | + Lyapunov equation. |
296 | 290 |
|
297 | 291 | Returns
|
298 | 292 | -------
|
299 |
| - mu_x_star : array_like(float) |
| 293 | + mu_x : array_like(float) |
300 | 294 | An n x 1 array representing the stationary mean of :math:`x_t`
|
301 |
| - mu_y_star : array_like(float) |
| 295 | + mu_y : array_like(float) |
302 | 296 | An k x 1 array representing the stationary mean of :math:`y_t`
|
303 |
| - Sigma_x_star : array_like(float) |
| 297 | + Sigma_x : array_like(float) |
304 | 298 | An n x n array representing the stationary var-cov matrix
|
305 | 299 | of :math:`x_t`
|
306 |
| - Sigma_y_star : array_like(float) |
| 300 | + Sigma_y : array_like(float) |
307 | 301 | An k x k array representing the stationary var-cov matrix
|
308 | 302 | of :math:`y_t`
|
| 303 | + Sigma_yx : array_like(float) |
| 304 | + An k x n array representing the stationary cov matrix |
| 305 | + between :math:`y_t` and :math:`x_t`. |
309 | 306 |
|
310 | 307 | """
|
311 |
| - # == Initialize iteration == # |
312 |
| - m = self.moment_sequence() |
313 |
| - mu_x, mu_y, Sigma_x, Sigma_y = next(m) |
314 |
| - i = 0 |
315 |
| - error = tol + 1 |
| 308 | + self.__partition() |
| 309 | + num_const, sorted_idx = self.num_const, self.sorted_idx |
| 310 | + A21, A22 = self.A21, self.A22 |
| 311 | + CC2 = self.C2 @ self.C2.T |
| 312 | + n = self.n |
| 313 | + |
| 314 | + if num_const > 0: |
| 315 | + μ = solve(np.eye(n-num_const) - A22, A21) |
| 316 | + else: |
| 317 | + μ = solve(np.eye(n-num_const) - A22, np.zeros((n, 1))) |
| 318 | + Σ = solve_discrete_lyapunov(A22, CC2, method='bartels-stewart') |
316 | 319 |
|
317 |
| - # == Loop until convergence or failure == # |
318 |
| - while error > tol: |
| 320 | + mu_x = np.empty((n, 1)) |
| 321 | + mu_x[:num_const] = self.mu_0[sorted_idx[:num_const]] |
| 322 | + mu_x[num_const:] = μ |
319 | 323 |
|
320 |
| - if i > max_iter: |
321 |
| - fail_message = 'Convergence failed after {} iterations' |
322 |
| - raise ValueError(fail_message.format(max_iter)) |
| 324 | + Sigma_x = np.zeros((n, n)) |
| 325 | + Sigma_x[num_const:, num_const:] = Σ |
323 | 326 |
|
324 |
| - else: |
325 |
| - i += 1 |
326 |
| - mu_x1, mu_y1, Sigma_x1, Sigma_y1 = next(m) |
327 |
| - error_mu = np.max(np.abs(mu_x1 - mu_x)) |
328 |
| - error_Sigma = np.max(np.abs(Sigma_x1 - Sigma_x)) |
329 |
| - error = max(error_mu, error_Sigma) |
330 |
| - mu_x, Sigma_x = mu_x1, Sigma_x1 |
| 327 | + mu_x = self.P.T @ mu_x |
| 328 | + Sigma_x = self.P.T @ Sigma_x @ self.P |
331 | 329 |
|
332 |
| - # == Prepare return values == # |
333 |
| - mu_x_star, Sigma_x_star = mu_x, Sigma_x |
334 |
| - mu_y_star, Sigma_y_star = mu_y1, Sigma_y1 |
| 330 | + mu_y = self.G @ mu_x |
| 331 | + Sigma_y = self.G @ Sigma_x @ self.G.T |
| 332 | + if self.H is not None: |
| 333 | + Sigma_y += self.H @ self.H.T |
| 334 | + Sigma_yx = self.G @ Sigma_x |
| 335 | + |
| 336 | + self.mu_x, self.mu_y = mu_x, mu_y |
| 337 | + self.Sigma_x, self.Sigma_y, self.Sigma_yx = Sigma_x, Sigma_y, Sigma_yx |
335 | 338 |
|
336 |
| - return mu_x_star, mu_y_star, Sigma_x_star, Sigma_y_star |
| 339 | + return mu_x, mu_y, Sigma_x, Sigma_y, Sigma_yx |
337 | 340 |
|
338 | 341 | def geometric_sums(self, beta, x_t):
|
339 | 342 | r"""
|
@@ -406,3 +409,62 @@ def impulse_response(self, j=5):
|
406 | 409 | Apower = np.dot(Apower, A)
|
407 | 410 |
|
408 | 411 | return xcoef, ycoef
|
| 412 | + |
| 413 | + def __partition(self): |
| 414 | + r""" |
| 415 | + Reorder the states by shifting the constant terms to the |
| 416 | + top of the state vector. Then partition the linear state |
| 417 | + space model following Appendix C in RMT Ch2 such that the |
| 418 | + A22 matrix associated with non-constant states have eigenvalues |
| 419 | + all strictly smaller than 1. |
| 420 | +
|
| 421 | + .. math:: |
| 422 | + \left[\begin{array}{c} |
| 423 | + const\\ |
| 424 | + x_{2,t+1} |
| 425 | + \end{array}\right]=\left[\begin{array}{cc} |
| 426 | + I & 0\\ |
| 427 | + A_{21} & A_{22} |
| 428 | + \end{array}\right]\left[\begin{array}{c} |
| 429 | + 1\\ |
| 430 | + x_{2,t} |
| 431 | + \end{array}\right]+\left[\begin{array}{c} |
| 432 | + 0\\ |
| 433 | + C_{2} |
| 434 | + \end{array}\right]w_{t+1} |
| 435 | +
|
| 436 | + Returns |
| 437 | + ------- |
| 438 | + A22 : array_like(float) |
| 439 | + Lower right part of the reordered matrix A. |
| 440 | + A21 : array_like(float) |
| 441 | + Lower left part of the reordered matrix A. |
| 442 | + """ |
| 443 | + A, C = self.A, self.C |
| 444 | + n = self.n |
| 445 | + |
| 446 | + sorted_idx = [] |
| 447 | + A_diag = np.diag(A) |
| 448 | + num_const = 0 |
| 449 | + for idx in range(n): |
| 450 | + if (A_diag[idx] == 1) and (C[idx, :] == 0).all() \ |
| 451 | + and np.linalg.norm(A[idx, :]) == 1: |
| 452 | + sorted_idx.insert(0, idx) |
| 453 | + num_const += 1 |
| 454 | + else: |
| 455 | + sorted_idx.append(idx) |
| 456 | + self.num_const = num_const |
| 457 | + self.sorted_idx = sorted_idx |
| 458 | + |
| 459 | + P = np.zeros((n, n)) |
| 460 | + P[range(n), sorted_idx] = 1 |
| 461 | + |
| 462 | + sorted_A = P @ A @ P.T |
| 463 | + sorted_C = P @ C |
| 464 | + A21 = sorted_A[num_const:, :num_const] |
| 465 | + A22 = sorted_A[num_const:, num_const:] |
| 466 | + C2 = sorted_C[num_const:, :] |
| 467 | + |
| 468 | + self.P, self.A21, self.A22, self.C2 = P, A21, A22, C2 |
| 469 | + |
| 470 | + return A21, A22 |
0 commit comments