Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2""" 

3Impulse reponse-related code 

4""" 

5 

6import numpy as np 

7import numpy.linalg as la 

8import scipy.linalg as L 

9 

10 

11from statsmodels.tools.decorators import cache_readonly 

12import statsmodels.tsa.tsatools as tsa 

13import statsmodels.tsa.vector_ar.plotting as plotting 

14import statsmodels.tsa.vector_ar.util as util 

15 

16mat = np.array 

17 

18 

19class BaseIRAnalysis(object): 

20 """ 

21 Base class for plotting and computing IRF-related statistics, want to be 

22 able to handle known and estimated processes 

23 """ 

24 

25 def __init__(self, model, P=None, periods=10, order=None, svar=False, 

26 vecm=False): 

27 self.model = model 

28 self.periods = periods 

29 self.neqs, self.lags, self.T = model.neqs, model.k_ar, model.nobs 

30 

31 self.order = order 

32 

33 if P is None: 

34 sigma = model.sigma_u 

35 

36 # TODO, may be difficult at the moment 

37 # if order is not None: 

38 # indexer = [model.get_eq_index(name) for name in order] 

39 # sigma = sigma[:, indexer][indexer, :] 

40 

41 # if sigma.shape != model.sigma_u.shape: 

42 # raise ValueError('variable order is wrong length') 

43 

44 P = la.cholesky(sigma) 

45 

46 self.P = P 

47 

48 self.svar = svar 

49 

50 self.irfs = model.ma_rep(periods) 

51 if svar: 

52 self.svar_irfs = model.svar_ma_rep(periods, P=P) 

53 else: 

54 self.orth_irfs = model.orth_ma_rep(periods, P=P) 

55 

56 self.cum_effects = self.irfs.cumsum(axis=0) 

57 if svar: 

58 self.svar_cum_effects = self.svar_irfs.cumsum(axis=0) 

59 else: 

60 self.orth_cum_effects = self.orth_irfs.cumsum(axis=0) 

61 

62 # long-run effects may be infinite for VECMs. 

63 if not vecm: 

64 self.lr_effects = model.long_run_effects() 

65 if svar: 

66 self.svar_lr_effects = np.dot(model.long_run_effects(), P) 

67 else: 

68 self.orth_lr_effects = np.dot(model.long_run_effects(), P) 

69 

70 # auxiliary stuff 

71 if vecm: 

72 self._A = util.comp_matrix(model.var_rep) 

73 else: 

74 self._A = util.comp_matrix(model.coefs) 

75 

76 def _choose_irfs(self, orth=False, svar=False): 

77 if orth: 

78 return self.orth_irfs 

79 elif svar: 

80 return self.svar_irfs 

81 else: 

82 return self.irfs 

83 

84 def cov(self, *args, **kwargs): 

85 raise NotImplementedError 

86 

87 def cum_effect_cov(self, *args, **kwargs): 

88 raise NotImplementedError 

89 

90 def plot(self, orth=False, impulse=None, response=None, 

91 signif=0.05, plot_params=None, subplot_params=None, 

92 plot_stderr=True, stderr_type='asym', repl=1000, 

93 seed=None, component=None): 

94 """ 

95 Plot impulse responses 

96 

97 Parameters 

98 ---------- 

99 orth : bool, default False 

100 Compute orthogonalized impulse responses 

101 impulse : {str, int} 

102 variable providing the impulse 

103 response : {str, int} 

104 variable affected by the impulse 

105 signif : float (0 < signif < 1) 

106 Significance level for error bars, defaults to 95% CI 

107 subplot_params : dict 

108 To pass to subplot plotting funcions. Example: if fonts are too big, 

109 pass {'fontsize' : 8} or some number to your taste. 

110 plot_params : dict 

111 

112 plot_stderr: bool, default True 

113 Plot standard impulse response error bands 

114 stderr_type: str 

115 'asym': default, computes asymptotic standard errors 

116 'mc': monte carlo standard errors (use rpl) 

117 repl: int, default 1000 

118 Number of replications for Monte Carlo and Sims-Zha standard errors 

119 seed: int 

120 np.random.seed for Monte Carlo replications 

121 component: array or vector of principal component indices 

122 """ 

123 periods = self.periods 

124 model = self.model 

125 svar = self.svar 

126 

127 if orth and svar: 

128 raise ValueError("For SVAR system, set orth=False") 

129 

130 irfs = self._choose_irfs(orth, svar) 

131 if orth: 

132 title = 'Impulse responses (orthogonalized)' 

133 elif svar: 

134 title = 'Impulse responses (structural)' 

135 else: 

136 title = 'Impulse responses' 

137 

138 if plot_stderr is False: 

139 stderr = None 

140 

141 elif stderr_type not in ['asym', 'mc', 'sz1', 'sz2','sz3']: 

142 raise ValueError("Error type must be either 'asym', 'mc','sz1','sz2', or 'sz3'") 

143 else: 

144 if stderr_type == 'asym': 

145 stderr = self.cov(orth=orth) 

146 if stderr_type == 'mc': 

147 stderr = self.errband_mc(orth=orth, svar=svar, 

148 repl=repl, signif=signif, 

149 seed=seed) 

150 if stderr_type == 'sz1': 

151 stderr = self.err_band_sz1(orth=orth, svar=svar, 

152 repl=repl, signif=signif, 

153 seed=seed, 

154 component=component) 

155 if stderr_type == 'sz2': 

156 stderr = self.err_band_sz2(orth=orth, svar=svar, 

157 repl=repl, signif=signif, 

158 seed=seed, 

159 component=component) 

160 if stderr_type == 'sz3': 

161 stderr = self.err_band_sz3(orth=orth, svar=svar, 

162 repl=repl, signif=signif, 

163 seed=seed, 

164 component=component) 

165 

166 fig = plotting.irf_grid_plot(irfs, stderr, impulse, response, 

167 self.model.names, title, signif=signif, 

168 subplot_params=subplot_params, 

169 plot_params=plot_params, 

170 stderr_type=stderr_type) 

171 return fig 

172 

173 def plot_cum_effects(self, orth=False, impulse=None, response=None, 

174 signif=0.05, plot_params=None, 

175 subplot_params=None, plot_stderr=True, 

176 stderr_type='asym', repl=1000, seed=None): 

177 """ 

178 Plot cumulative impulse response functions 

179 

180 Parameters 

181 ---------- 

182 orth : bool, default False 

183 Compute orthogonalized impulse responses 

184 impulse : {str, int} 

185 variable providing the impulse 

186 response : {str, int} 

187 variable affected by the impulse 

188 signif : float (0 < signif < 1) 

189 Significance level for error bars, defaults to 95% CI 

190 subplot_params : dict 

191 To pass to subplot plotting funcions. Example: if fonts are too big, 

192 pass {'fontsize' : 8} or some number to your taste. 

193 plot_params : dict 

194 

195 plot_stderr: bool, default True 

196 Plot standard impulse response error bands 

197 stderr_type: str 

198 'asym': default, computes asymptotic standard errors 

199 'mc': monte carlo standard errors (use rpl) 

200 repl: int, default 1000 

201 Number of replications for monte carlo standard errors 

202 seed: int 

203 np.random.seed for Monte Carlo replications 

204 """ 

205 

206 if orth: 

207 title = 'Cumulative responses responses (orthogonalized)' 

208 cum_effects = self.orth_cum_effects 

209 lr_effects = self.orth_lr_effects 

210 else: 

211 title = 'Cumulative responses' 

212 cum_effects = self.cum_effects 

213 lr_effects = self.lr_effects 

214 

215 if stderr_type not in ['asym', 'mc']: 

216 raise ValueError("`stderr_type` must be one of 'asym', 'mc'") 

217 else: 

218 if stderr_type == 'asym': 

219 stderr = self.cum_effect_cov(orth=orth) 

220 if stderr_type == 'mc': 

221 stderr = self.cum_errband_mc(orth=orth, repl=repl, 

222 signif=signif, seed=seed) 

223 if not plot_stderr: 

224 stderr = None 

225 

226 fig = plotting.irf_grid_plot(cum_effects, stderr, impulse, response, 

227 self.model.names, title, signif=signif, 

228 hlines=lr_effects, 

229 subplot_params=subplot_params, 

230 plot_params=plot_params, 

231 stderr_type=stderr_type) 

232 return fig 

233 

234 

235class IRAnalysis(BaseIRAnalysis): 

236 """ 

237 Impulse response analysis class. Computes impulse responses, asymptotic 

238 standard errors, and produces relevant plots 

239 

240 Parameters 

241 ---------- 

242 model : VAR instance 

243 

244 Notes 

245 ----- 

246 Using Lütkepohl (2005) notation 

247 """ 

248 def __init__(self, model, P=None, periods=10, order=None, svar=False, 

249 vecm=False): 

250 BaseIRAnalysis.__init__(self, model, P=P, periods=periods, 

251 order=order, svar=svar, vecm=vecm) 

252 

253 if vecm: 

254 self.cov_a = model.cov_var_repr 

255 else: 

256 self.cov_a = model._cov_alpha 

257 self.cov_sig = model._cov_sigma 

258 

259 # memoize dict for G matrix function 

260 self._g_memo = {} 

261 

262 def cov(self, orth=False): 

263 """ 

264 Compute asymptotic standard errors for impulse response coefficients 

265 

266 Notes 

267 ----- 

268 Lütkepohl eq 3.7.5 

269 

270 Returns 

271 ------- 

272 """ 

273 if orth: 

274 return self._orth_cov() 

275 

276 covs = self._empty_covm(self.periods + 1) 

277 covs[0] = np.zeros((self.neqs ** 2, self.neqs ** 2)) 

278 for i in range(1, self.periods + 1): 

279 Gi = self.G[i - 1] 

280 covs[i] = Gi @ self.cov_a @ Gi.T 

281 

282 return covs 

283 

284 def errband_mc(self, orth=False, svar=False, repl=1000, 

285 signif=0.05, seed=None, burn=100): 

286 """ 

287 IRF Monte Carlo integrated error bands 

288 """ 

289 model = self.model 

290 periods = self.periods 

291 if svar: 

292 return model.sirf_errband_mc(orth=orth, repl=repl, steps=periods, 

293 signif=signif, seed=seed, 

294 burn=burn, cum=False) 

295 else: 

296 return model.irf_errband_mc(orth=orth, repl=repl, steps=periods, 

297 signif=signif, seed=seed, 

298 burn=burn, cum=False) 

299 

300 def err_band_sz1(self, orth=False, svar=False, repl=1000, 

301 signif=0.05, seed=None, burn=100, component=None): 

302 """ 

303 IRF Sims-Zha error band method 1. Assumes symmetric error bands around 

304 mean. 

305 

306 Parameters 

307 ---------- 

308 orth : bool, default False 

309 Compute orthogonalized impulse responses 

310 repl : int, default 1000 

311 Number of MC replications 

312 signif : float (0 < signif < 1) 

313 Significance level for error bars, defaults to 95% CI 

314 seed : int, default None 

315 np.random seed 

316 burn : int, default 100 

317 Number of initial simulated obs to discard 

318 component : neqs x neqs array, default to largest for each 

319 Index of column of eigenvector/value to use for each error band 

320 Note: period of impulse (t=0) is not included when computing 

321 principle component 

322 

323 References 

324 ---------- 

325 Sims, Christopher A., and Tao Zha. 1999. "Error Bands for Impulse 

326 Response". Econometrica 67: 1113-1155. 

327 """ 

328 

329 model = self.model 

330 periods = self.periods 

331 irfs = self._choose_irfs(orth, svar) 

332 neqs = self.neqs 

333 irf_resim = model.irf_resim(orth=orth, repl=repl, steps=periods, 

334 seed=seed, burn=burn) 

335 q = util.norm_signif_level(signif) 

336 

337 W, eigva, k =self._eigval_decomp_SZ(irf_resim) 

338 

339 if component is not None: 

340 if np.shape(component) != (neqs,neqs): 

341 raise ValueError("Component array must be " + str(neqs) + " x " + str(neqs)) 

342 if np.argmax(component) >= neqs*periods: 

343 raise ValueError("Atleast one of the components does not exist") 

344 else: 

345 k = component 

346 

347 # here take the kth column of W, which we determine by finding the largest eigenvalue of the covaraince matrix 

348 lower = np.copy(irfs) 

349 upper = np.copy(irfs) 

350 for i in range(neqs): 

351 for j in range(neqs): 

352 lower[1:,i,j] = irfs[1:,i,j] + W[i,j,:,k[i,j]]*q*np.sqrt(eigva[i,j,k[i,j]]) 

353 upper[1:,i,j] = irfs[1:,i,j] - W[i,j,:,k[i,j]]*q*np.sqrt(eigva[i,j,k[i,j]]) 

354 

355 return lower, upper 

356 

357 def err_band_sz2(self, orth=False, svar=False, repl=1000, signif=0.05, 

358 seed=None, burn=100, component=None): 

359 """ 

360 IRF Sims-Zha error band method 2. 

361 

362 This method Does not assume symmetric error bands around mean. 

363 

364 Parameters 

365 ---------- 

366 orth : bool, default False 

367 Compute orthogonalized impulse responses 

368 repl : int, default 1000 

369 Number of MC replications 

370 signif : float (0 < signif < 1) 

371 Significance level for error bars, defaults to 95% CI 

372 seed : int, default None 

373 np.random seed 

374 burn : int, default 100 

375 Number of initial simulated obs to discard 

376 component : neqs x neqs array, default to largest for each 

377 Index of column of eigenvector/value to use for each error band 

378 Note: period of impulse (t=0) is not included when computing 

379 principle component 

380 

381 References 

382 ---------- 

383 Sims, Christopher A., and Tao Zha. 1999. "Error Bands for Impulse 

384 Response". Econometrica 67: 1113-1155. 

385 """ 

386 model = self.model 

387 periods = self.periods 

388 irfs = self._choose_irfs(orth, svar) 

389 neqs = self.neqs 

390 irf_resim = model.irf_resim(orth=orth, repl=repl, T=periods, seed=seed, 

391 burn=100) 

392 

393 W, eigva, k = self._eigval_decomp_SZ(irf_resim) 

394 

395 if component is not None: 

396 if np.shape(component) != (neqs,neqs): 

397 raise ValueError("Component array must be " + str(neqs) + " x " + str(neqs)) 

398 if np.argmax(component) >= neqs*periods: 

399 raise ValueError("Atleast one of the components does not exist") 

400 else: 

401 k = component 

402 

403 gamma = np.zeros((repl, periods+1, neqs, neqs)) 

404 for p in range(repl): 

405 for i in range(neqs): 

406 for j in range(neqs): 

407 gamma[p,1:,i,j] = W[i,j,k[i,j],:] * irf_resim[p,1:,i,j] 

408 

409 gamma_sort = np.sort(gamma, axis=0) #sort to get quantiles 

410 indx = round(signif/2*repl)-1,round((1-signif/2)*repl)-1 

411 

412 lower = np.copy(irfs) 

413 upper = np.copy(irfs) 

414 for i in range(neqs): 

415 for j in range(neqs): 

416 lower[:,i,j] = irfs[:,i,j] + gamma_sort[indx[0],:,i,j] 

417 upper[:,i,j] = irfs[:,i,j] + gamma_sort[indx[1],:,i,j] 

418 

419 return lower, upper 

420 

421 def err_band_sz3(self, orth=False, svar=False, repl=1000, signif=0.05, 

422 seed=None, burn=100, component=None): 

423 """ 

424 IRF Sims-Zha error band method 3. Does not assume symmetric error bands around mean. 

425 

426 Parameters 

427 ---------- 

428 orth : bool, default False 

429 Compute orthogonalized impulse responses 

430 repl : int, default 1000 

431 Number of MC replications 

432 signif : float (0 < signif < 1) 

433 Significance level for error bars, defaults to 95% CI 

434 seed : int, default None 

435 np.random seed 

436 burn : int, default 100 

437 Number of initial simulated obs to discard 

438 component : vector length neqs, default to largest for each 

439 Index of column of eigenvector/value to use for each error band 

440 Note: period of impulse (t=0) is not included when computing 

441 principle component 

442 

443 References 

444 ---------- 

445 Sims, Christopher A., and Tao Zha. 1999. "Error Bands for Impulse 

446 Response". Econometrica 67: 1113-1155. 

447 """ 

448 

449 model = self.model 

450 periods = self.periods 

451 irfs = self._choose_irfs(orth, svar) 

452 neqs = self.neqs 

453 irf_resim = model.irf_resim(orth=orth, repl=repl, T=periods, seed=seed, 

454 burn=100) 

455 stack = np.zeros((neqs, repl, periods*neqs)) 

456 

457 #stack left to right, up and down 

458 

459 for p in range(repl): 

460 for i in range(neqs): 

461 stack[i, p,:] = np.ravel(irf_resim[p,1:,:,i].T) 

462 

463 stack_cov=np.zeros((neqs, periods*neqs, periods*neqs)) 

464 W = np.zeros((neqs, periods*neqs, periods*neqs)) 

465 eigva = np.zeros((neqs, periods*neqs)) 

466 k = np.zeros((neqs)) 

467 

468 if component is not None: 

469 if np.size(component) != (neqs): 

470 raise ValueError("Component array must be of length " + str(neqs)) 

471 if np.argmax(component) >= neqs*periods: 

472 raise ValueError("Atleast one of the components does not exist") 

473 else: 

474 k = component 

475 

476 #compute for eigen decomp for each stack 

477 for i in range(neqs): 

478 stack_cov[i] = np.cov(stack[i],rowvar=0) 

479 W[i], eigva[i], k[i] = util.eigval_decomp(stack_cov[i]) 

480 

481 gamma = np.zeros((repl, periods+1, neqs, neqs)) 

482 for p in range(repl): 

483 c = 0 

484 for j in range(neqs): 

485 for i in range(neqs): 

486 gamma[p,1:,i,j] = W[j,k[j],i*periods:(i+1)*periods] * irf_resim[p,1:,i,j] 

487 if i == neqs-1: 

488 gamma[p,1:,i,j] = W[j,k[j],i*periods:] * irf_resim[p,1:,i,j] 

489 

490 gamma_sort = np.sort(gamma, axis=0) #sort to get quantiles 

491 indx = round(signif/2*repl)-1,round((1-signif/2)*repl)-1 

492 

493 lower = np.copy(irfs) 

494 upper = np.copy(irfs) 

495 for i in range(neqs): 

496 for j in range(neqs): 

497 lower[:,i,j] = irfs[:,i,j] + gamma_sort[indx[0],:,i,j] 

498 upper[:,i,j] = irfs[:,i,j] + gamma_sort[indx[1],:,i,j] 

499 

500 return lower, upper 

501 

502 def _eigval_decomp_SZ(self, irf_resim): 

503 """ 

504 Returns 

505 ------- 

506 W: array of eigenvectors 

507 eigva: list of eigenvalues 

508 k: matrix indicating column # of largest eigenvalue for each c_i,j 

509 """ 

510 neqs = self.neqs 

511 periods = self.periods 

512 

513 cov_hold = np.zeros((neqs, neqs, periods, periods)) 

514 for i in range(neqs): 

515 for j in range(neqs): 

516 cov_hold[i,j,:,:] = np.cov(irf_resim[:,1:,i,j],rowvar=0) 

517 

518 W = np.zeros((neqs, neqs, periods, periods)) 

519 eigva = np.zeros((neqs, neqs, periods, 1)) 

520 k = np.zeros((neqs, neqs)) 

521 

522 for i in range(neqs): 

523 for j in range(neqs): 

524 W[i,j,:,:], eigva[i,j,:,0], k[i,j] = util.eigval_decomp(cov_hold[i,j,:,:]) 

525 return W, eigva, k 

526 

527 @cache_readonly 

528 def G(self): 

529 # Gi matrices as defined on p. 111 

530 

531 K = self.neqs 

532 

533 # nlags = self.model.p 

534 # J = np.hstack((np.eye(K),) + (np.zeros((K, K)),) * (nlags - 1)) 

535 

536 def _make_g(i): 

537 # p. 111 Lutkepohl 

538 G = 0. 

539 for m in range(i): 

540 # be a bit cute to go faster 

541 idx = i - 1 - m 

542 if idx in self._g_memo: 

543 apow = self._g_memo[idx] 

544 else: 

545 apow = la.matrix_power(self._A.T, idx) 

546 # apow = np.dot(J, apow) 

547 apow = apow[:K] 

548 self._g_memo[idx] = apow 

549 

550 # take first K rows 

551 piece = np.kron(apow, self.irfs[m]) 

552 G = G + piece 

553 

554 return G 

555 

556 return [_make_g(i) for i in range(1, self.periods + 1)] 

557 

558 def _orth_cov(self): 

559 # Lutkepohl 3.7.8 

560 

561 Ik = np.eye(self.neqs) 

562 PIk = np.kron(self.P.T, Ik) 

563 H = self.H 

564 

565 covs = self._empty_covm(self.periods + 1) 

566 for i in range(self.periods + 1): 

567 if i == 0: 

568 apiece = 0 

569 else: 

570 Ci = np.dot(PIk, self.G[i-1]) 

571 apiece = Ci @ self.cov_a @ Ci.T 

572 

573 Cibar = np.dot(np.kron(Ik, self.irfs[i]), H) 

574 bpiece = (Cibar @ self.cov_sig @ Cibar.T) / self.T 

575 

576 # Lutkepohl typo, cov_sig correct 

577 covs[i] = apiece + bpiece 

578 

579 return covs 

580 

581 def cum_effect_cov(self, orth=False): 

582 """ 

583 Compute asymptotic standard errors for cumulative impulse response 

584 coefficients 

585 

586 Parameters 

587 ---------- 

588 orth : bool 

589 

590 Notes 

591 ----- 

592 eq. 3.7.7 (non-orth), 3.7.10 (orth) 

593 

594 Returns 

595 ------- 

596 """ 

597 Ik = np.eye(self.neqs) 

598 PIk = np.kron(self.P.T, Ik) 

599 

600 F = 0. 

601 covs = self._empty_covm(self.periods + 1) 

602 for i in range(self.periods + 1): 

603 if i > 0: 

604 F = F + self.G[i - 1] 

605 

606 if orth: 

607 if i == 0: 

608 apiece = 0 

609 else: 

610 Bn = np.dot(PIk, F) 

611 apiece = Bn @ self.cov_a @ Bn.T 

612 

613 Bnbar = np.dot(np.kron(Ik, self.cum_effects[i]), self.H) 

614 bpiece = (Bnbar @ self.cov_sig @ Bnbar.T) / self.T 

615 

616 covs[i] = apiece + bpiece 

617 else: 

618 if i == 0: 

619 covs[i] = np.zeros((self.neqs**2, self.neqs**2)) 

620 continue 

621 

622 covs[i] = F @ self.cov_a @ F.T 

623 

624 return covs 

625 

626 def cum_errband_mc(self, orth=False, repl=1000, 

627 signif=0.05, seed=None, burn=100): 

628 """ 

629 IRF Monte Carlo integrated error bands of cumulative effect 

630 """ 

631 model = self.model 

632 periods = self.periods 

633 return model.irf_errband_mc(orth=orth, repl=repl, 

634 T=periods, signif=signif, seed=seed, burn=burn, cum=True) 

635 

636 def lr_effect_cov(self, orth=False): 

637 """ 

638 Returns 

639 ------- 

640 """ 

641 lre = self.lr_effects 

642 Finfty = np.kron(np.tile(lre.T, self.lags), lre) 

643 Ik = np.eye(self.neqs) 

644 

645 if orth: 

646 Binf = np.dot(np.kron(self.P.T, np.eye(self.neqs)), Finfty) 

647 Binfbar = np.dot(np.kron(Ik, lre), self.H) 

648 

649 return (Binf @ self.cov_a @ Binf.T + 

650 Binfbar @ self.cov_sig @ Binfbar.T) 

651 else: 

652 return Finfty @ self.cov_a @ Finfty.T 

653 

654 def stderr(self, orth=False): 

655 return np.array([tsa.unvec(np.sqrt(np.diag(c))) 

656 for c in self.cov(orth=orth)]) 

657 

658 def cum_effect_stderr(self, orth=False): 

659 return np.array([tsa.unvec(np.sqrt(np.diag(c))) 

660 for c in self.cum_effect_cov(orth=orth)]) 

661 

662 def lr_effect_stderr(self, orth=False): 

663 cov = self.lr_effect_cov(orth=orth) 

664 return tsa.unvec(np.sqrt(np.diag(cov))) 

665 

666 def _empty_covm(self, periods): 

667 return np.zeros((periods, self.neqs ** 2, self.neqs ** 2), 

668 dtype=float) 

669 

670 @cache_readonly 

671 def H(self): 

672 k = self.neqs 

673 Lk = tsa.elimination_matrix(k) 

674 Kkk = tsa.commutation_matrix(k, k) 

675 Ik = np.eye(k) 

676 

677 # B = Lk @ (np.eye(k**2) + commutation_matrix(k, k)) @ \ 

678 # np.kron(self.P, np.eye(k)) @ Lk.T 

679 # return Lk.T @ L.inv(B) 

680 

681 B = Lk @ (np.kron(Ik, self.P) @ Kkk + np.kron(self.P, Ik)) @ Lk.T 

682 

683 return np.dot(Lk.T, L.inv(B)) 

684 

685 def fevd_table(self): 

686 raise NotImplementedError