drvarma 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drvarma/__init__.py +19 -0
- drvarma/_as311.py +531 -0
- drvarma/_ascii.py +261 -0
- drvarma/_build_cffi.py +83 -0
- drvarma/_engine.py +82 -0
- drvarma/_pyfug.py +100 -0
- drvarma/_qnewt.py +358 -0
- drvarma/cli.py +117 -0
- drvarma/datasets.py +142 -0
- drvarma/deseason.py +147 -0
- drvarma/diagnostics.py +221 -0
- drvarma/elfvarma_py.py +217 -0
- drvarma/estimate_py.py +386 -0
- drvarma/forecast.py +167 -0
- drvarma/inp.py +82 -0
- drvarma/irf.py +46 -0
- drvarma/model.py +187 -0
- drvarma/plots.py +378 -0
- drvarma/report.py +741 -0
- drvarma/report_forecast.py +483 -0
- drvarma/series.py +53 -0
- drvarma/transform.py +109 -0
- drvarma/volatility.py +163 -0
- drvarma-0.1.0.dist-info/METADATA +151 -0
- drvarma-0.1.0.dist-info/RECORD +29 -0
- drvarma-0.1.0.dist-info/WHEEL +5 -0
- drvarma-0.1.0.dist-info/entry_points.txt +2 -0
- drvarma-0.1.0.dist-info/licenses/COPYING +339 -0
- drvarma-0.1.0.dist-info/top_level.txt +1 -0
drvarma/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""drvarma — multivariate VARMA modelling (Python port).
|
|
2
|
+
|
|
3
|
+
Free software under the GNU General Public License v2 or later (see COPYING).
|
|
4
|
+
Python port of the drvarma C engine; see docs/MIGRATION_PLAN.md.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from .series import MultiSeries
|
|
10
|
+
from .inp import load, save, InpSpec
|
|
11
|
+
from .model import Model
|
|
12
|
+
from . import (transform, forecast, diagnostics, irf, deseason, datasets,
|
|
13
|
+
report, report_forecast, elfvarma_py, estimate_py, plots,
|
|
14
|
+
volatility)
|
|
15
|
+
|
|
16
|
+
__all__ = ["MultiSeries", "load", "save", "InpSpec", "Model",
|
|
17
|
+
"transform", "forecast", "diagnostics", "irf", "deseason",
|
|
18
|
+
"datasets", "report", "report_forecast", "elfvarma_py",
|
|
19
|
+
"estimate_py", "plots", "volatility", "__version__"]
|
drvarma/_as311.py
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"""Faithful Python port of Mauricio's AS 311 exact VARMA log-likelihood.
|
|
2
|
+
|
|
3
|
+
Direct translation of ``csrc/internal/elfvarma.c`` (functions ``elf``, ``cgamma``,
|
|
4
|
+
``cxi``, ``cres``, ``chekma``) by J.A. Mauricio — *not* a Kalman/state-space
|
|
5
|
+
reimplementation. Mauricio (1995) JASA 90, 282-291; (1997) Applied Statistics
|
|
6
|
+
46, 157-171 [AS 311].
|
|
7
|
+
|
|
8
|
+
The C is 1-indexed (Numerical-Recipes style); to keep the index arithmetic an
|
|
9
|
+
exact transcription, the arrays here are also 1-indexed — allocated with a
|
|
10
|
+
leading unused slot and addressed from 1. Linear-algebra primitives (Cholesky,
|
|
11
|
+
forward substitution, quadratic forms) use NumPy, matching the C ``choldcp`` /
|
|
12
|
+
``cholfor`` / ``cholbak`` which compute the same factorisations.
|
|
13
|
+
|
|
14
|
+
License: GPL-2.0-or-later
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
_LOG2PI = 1.837877066
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# --------------------------------------------------------------------------- #
|
|
23
|
+
# 1-indexed helpers #
|
|
24
|
+
# --------------------------------------------------------------------------- #
|
|
25
|
+
|
|
26
|
+
def _m1(r, c):
|
|
27
|
+
return np.zeros((r + 1, c + 1))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _to_1based_cube(arr, p, m):
|
|
31
|
+
"""(p, m, m) 0-indexed -> (p+1, m+1, m+1) 1-indexed cube."""
|
|
32
|
+
out = np.zeros((p + 1, m + 1, m + 1))
|
|
33
|
+
for k in range(p):
|
|
34
|
+
out[k + 1, 1:, 1:] = arr[k]
|
|
35
|
+
return out
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _chol_lower(A_1, n):
|
|
39
|
+
"""Cholesky lower factor of a 1-indexed symmetric PD matrix A_1 (n x n).
|
|
40
|
+
|
|
41
|
+
Returns (L_1, detfac, ifault): L_1 is 1-indexed lower-triangular with
|
|
42
|
+
A = L L'; detfac = det(A); ifault=1 if not positive definite.
|
|
43
|
+
"""
|
|
44
|
+
A = A_1[1:n + 1, 1:n + 1]
|
|
45
|
+
A = 0.5 * (A + A.T)
|
|
46
|
+
try:
|
|
47
|
+
L = np.linalg.cholesky(A)
|
|
48
|
+
except np.linalg.LinAlgError:
|
|
49
|
+
return None, 0.0, 1
|
|
50
|
+
L1 = np.zeros((n + 1, n + 1))
|
|
51
|
+
L1[1:n + 1, 1:n + 1] = L
|
|
52
|
+
detfac = float(np.prod(np.diag(L)) ** 2)
|
|
53
|
+
return L1, detfac, 0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# --------------------------------------------------------------------------- #
|
|
57
|
+
# chekma : MA invertibility via companion eigenvalues #
|
|
58
|
+
# --------------------------------------------------------------------------- #
|
|
59
|
+
|
|
60
|
+
def chekma(m, q, Theta):
|
|
61
|
+
"""Return ifault=1 if the MA operator is (near) non-invertible.
|
|
62
|
+
|
|
63
|
+
Theta is 1-indexed (q+1, m+1, m+1); mirrors elfvarma.c:chekma.
|
|
64
|
+
"""
|
|
65
|
+
if q == 0:
|
|
66
|
+
return 0
|
|
67
|
+
n = m * q
|
|
68
|
+
A = np.zeros((n, n))
|
|
69
|
+
for k in range(1, q + 1):
|
|
70
|
+
for i in range(1, m + 1):
|
|
71
|
+
for j in range(1, m + 1):
|
|
72
|
+
A[i - 1, j - 1 + (k - 1) * m] = Theta[k][i][j]
|
|
73
|
+
for k in range(1, q):
|
|
74
|
+
for j in range(1, m + 1):
|
|
75
|
+
A[j - 1 + k * m, j - 1 + (k - 1) * m] = 1.0
|
|
76
|
+
wmod = np.abs(np.linalg.eigvals(A))
|
|
77
|
+
return 1 if np.any(wmod >= 1.00005) else 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# --------------------------------------------------------------------------- #
|
|
81
|
+
# cgamma : autocovariances Gamma(k) and cross-covariances Gamma_wa(k) #
|
|
82
|
+
# --------------------------------------------------------------------------- #
|
|
83
|
+
|
|
84
|
+
def cgamma(m, p, q, Phi, Theta, Qq):
|
|
85
|
+
"""Port of elfvarma.c:cgamma.
|
|
86
|
+
|
|
87
|
+
Returns (gamma, gamwa, ifault):
|
|
88
|
+
gamma : 1-indexed packed vector, length big = m(m+1)/2 + m^2 (p-1)
|
|
89
|
+
gamwa : dict {k: (m+1,m+1) 1-indexed} for k = -q+1 .. 0
|
|
90
|
+
ifault: 1 if the Yule-Walker system is singular (AR unit root)
|
|
91
|
+
"""
|
|
92
|
+
gamwa = {k: _m1(m, m) for k in range(-q + 1, 1)}
|
|
93
|
+
|
|
94
|
+
# [1]: cross-covariance matrices
|
|
95
|
+
if q > 0:
|
|
96
|
+
for i in range(1, m + 1):
|
|
97
|
+
for j in range(1, m + 1):
|
|
98
|
+
gamwa[0][i][j] = Qq[i][j]
|
|
99
|
+
for k in range(1, q): # k = 1 .. q-1
|
|
100
|
+
for i in range(1, m + 1):
|
|
101
|
+
for j in range(1, m + 1):
|
|
102
|
+
s = 0.0
|
|
103
|
+
for h in range(1, m + 1):
|
|
104
|
+
s -= Theta[k][i][h] * Qq[h][j]
|
|
105
|
+
for l in range(1, k + 1):
|
|
106
|
+
if l <= p:
|
|
107
|
+
for h in range(1, m + 1):
|
|
108
|
+
s += Phi[l][i][h] * gamwa[l - k][h][j]
|
|
109
|
+
gamwa[-k][i][j] = s
|
|
110
|
+
|
|
111
|
+
big = m * (m + 1) // 2 + m * m * (p - 1)
|
|
112
|
+
if p == 0:
|
|
113
|
+
return np.zeros(0), gamwa, 0
|
|
114
|
+
|
|
115
|
+
# [2]: w(0)
|
|
116
|
+
wzero = _m1(m, m)
|
|
117
|
+
mzero = _m1(m, m)
|
|
118
|
+
for i in range(1, p + 1):
|
|
119
|
+
for j in range(i, q + 1):
|
|
120
|
+
for ii in range(1, m + 1):
|
|
121
|
+
for jj in range(1, m + 1):
|
|
122
|
+
s = 0.0
|
|
123
|
+
for k in range(1, m + 1):
|
|
124
|
+
s += Phi[i][ii][k] * gamwa[i - j][k][jj]
|
|
125
|
+
mzero[ii][jj] = s
|
|
126
|
+
for ii in range(1, m + 1):
|
|
127
|
+
for jj in range(1, m + 1):
|
|
128
|
+
s = 0.0
|
|
129
|
+
for k in range(1, m + 1):
|
|
130
|
+
s += mzero[ii][k] * Theta[j][jj][k]
|
|
131
|
+
wzero[ii][jj] += s
|
|
132
|
+
for i in range(1, m + 1):
|
|
133
|
+
for j in range(i, m + 1):
|
|
134
|
+
wzero[i][j] = Qq[i][j] - wzero[i][j] - wzero[j][i]
|
|
135
|
+
for j in range(1, q + 1):
|
|
136
|
+
for ii in range(1, m + 1):
|
|
137
|
+
for jj in range(1, m + 1):
|
|
138
|
+
s = 0.0
|
|
139
|
+
for k in range(1, m + 1):
|
|
140
|
+
s += Theta[j][ii][k] * Qq[k][jj]
|
|
141
|
+
mzero[ii][jj] = s
|
|
142
|
+
for ii in range(1, m + 1):
|
|
143
|
+
for jj in range(ii, m + 1):
|
|
144
|
+
s = 0.0
|
|
145
|
+
for k in range(1, m + 1):
|
|
146
|
+
s += mzero[ii][k] * Theta[j][jj][k]
|
|
147
|
+
wzero[ii][jj] += s
|
|
148
|
+
|
|
149
|
+
# [3]: linear system
|
|
150
|
+
mat = _m1(big, big)
|
|
151
|
+
rhs = np.zeros(big + 1)
|
|
152
|
+
|
|
153
|
+
# [3.1] first m(m+1)/2 rows
|
|
154
|
+
for j in range(1, m + 1):
|
|
155
|
+
for i in range(1, j + 1):
|
|
156
|
+
row = j * (j - 1) // 2 + i
|
|
157
|
+
for l in range(1, m + 1):
|
|
158
|
+
for k in range(1, l + 1):
|
|
159
|
+
col = l * (l - 1) // 2 + k
|
|
160
|
+
s = 0.0
|
|
161
|
+
if k == l:
|
|
162
|
+
for r in range(1, p + 1):
|
|
163
|
+
s -= Phi[r][i][k] * Phi[r][j][l]
|
|
164
|
+
else:
|
|
165
|
+
for r in range(1, p + 1):
|
|
166
|
+
s -= (Phi[r][i][k] * Phi[r][j][l]
|
|
167
|
+
+ Phi[r][i][l] * Phi[r][j][k])
|
|
168
|
+
mat[row][col] = s
|
|
169
|
+
for sv in range(1, p):
|
|
170
|
+
for l in range(1, m + 1):
|
|
171
|
+
for k in range(1, m + 1):
|
|
172
|
+
col = m * (m + 1) // 2 + m * m * (sv - 1) + m * (l - 1) + k
|
|
173
|
+
s = 0.0
|
|
174
|
+
for r in range(1, p - sv + 1):
|
|
175
|
+
s -= (Phi[r + sv][i][k] * Phi[r][j][l]
|
|
176
|
+
+ Phi[r + sv][j][k] * Phi[r][i][l])
|
|
177
|
+
mat[row][col] = s
|
|
178
|
+
mat[row][row] += 1.0
|
|
179
|
+
rhs[row] = wzero[i][j]
|
|
180
|
+
|
|
181
|
+
# [3.2] remaining m^2 (p-1) rows
|
|
182
|
+
for sv in range(1, p):
|
|
183
|
+
for i in range(1, m + 1):
|
|
184
|
+
for j in range(1, m + 1):
|
|
185
|
+
row = m * (m + 1) // 2 + m * m * (sv - 1) + m * (i - 1) + j
|
|
186
|
+
for l in range(1, m + 1):
|
|
187
|
+
if l <= j:
|
|
188
|
+
col = j * (j - 1) // 2 + l
|
|
189
|
+
else:
|
|
190
|
+
col = l * (l - 1) // 2 + j
|
|
191
|
+
mat[row][col] = -Phi[sv][i][l]
|
|
192
|
+
for r in range(1, p):
|
|
193
|
+
for l in range(1, m + 1):
|
|
194
|
+
col = m * (m + 1) // 2 + m * m * (r - 1) + m * (j - 1) + l
|
|
195
|
+
if r + sv <= p:
|
|
196
|
+
mat[row][col] = -Phi[r + sv][i][l]
|
|
197
|
+
if sv > r:
|
|
198
|
+
col2 = m * (m + 1) // 2 + m * m * (r - 1) + m * (l - 1) + j
|
|
199
|
+
mat[row][col2] -= Phi[sv - r][i][l]
|
|
200
|
+
mat[row][row] += 1.0
|
|
201
|
+
val = 0.0
|
|
202
|
+
for h in range(sv, q + 1):
|
|
203
|
+
for k in range(1, m + 1):
|
|
204
|
+
val -= gamwa[sv - h][j][k] * Theta[h][i][k]
|
|
205
|
+
rhs[row] = val
|
|
206
|
+
|
|
207
|
+
# [4]: solve
|
|
208
|
+
try:
|
|
209
|
+
sol = np.linalg.solve(mat[1:big + 1, 1:big + 1], rhs[1:big + 1])
|
|
210
|
+
except np.linalg.LinAlgError:
|
|
211
|
+
return np.zeros(big + 1), gamwa, 1
|
|
212
|
+
gamma = np.zeros(big + 1)
|
|
213
|
+
gamma[1:big + 1] = sol
|
|
214
|
+
return gamma, gamwa, 0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# --------------------------------------------------------------------------- #
|
|
218
|
+
# cxi : Green's-function matrix sequence Xi_k, premultiplied by q1inv #
|
|
219
|
+
# --------------------------------------------------------------------------- #
|
|
220
|
+
|
|
221
|
+
def cxi(m, n, q, Theta, Q1inv, xitol):
|
|
222
|
+
"""Port of elfvarma.c:cxi. Returns (nlim, rxi) with rxi[k] (k=0..n-1)
|
|
223
|
+
a (m+1,m+1) 1-indexed matrix; rxi premultiplied by the lower-triangular q1inv.
|
|
224
|
+
"""
|
|
225
|
+
rxi = np.zeros((n, m + 1, m + 1))
|
|
226
|
+
for i in range(1, m + 1):
|
|
227
|
+
rxi[0][i][i] = 1.0
|
|
228
|
+
|
|
229
|
+
r = 0
|
|
230
|
+
delta = False
|
|
231
|
+
while True:
|
|
232
|
+
r += 1
|
|
233
|
+
for jx in range(1, q + 1):
|
|
234
|
+
if r >= jx:
|
|
235
|
+
for ii in range(1, m + 1):
|
|
236
|
+
for jj in range(1, m + 1):
|
|
237
|
+
s1 = 0.0
|
|
238
|
+
for h in range(1, m + 1):
|
|
239
|
+
s1 += Theta[jx][ii][h] * rxi[r - jx][h][jj]
|
|
240
|
+
rxi[r][ii][jj] += s1
|
|
241
|
+
s2 = float(np.sum(np.abs(rxi[r])))
|
|
242
|
+
if s2 < xitol:
|
|
243
|
+
nq = 1
|
|
244
|
+
delta = True
|
|
245
|
+
while (nq <= q) and (r < n - 1) and delta:
|
|
246
|
+
nq += 1
|
|
247
|
+
r += 1
|
|
248
|
+
for jx in range(1, q + 1):
|
|
249
|
+
if r >= jx:
|
|
250
|
+
for ii in range(1, m + 1):
|
|
251
|
+
for jj in range(1, m + 1):
|
|
252
|
+
s1 = 0.0
|
|
253
|
+
for h in range(1, m + 1):
|
|
254
|
+
s1 += Theta[jx][ii][h] * rxi[r - jx][h][jj]
|
|
255
|
+
rxi[r][ii][jj] += s1
|
|
256
|
+
s2 = float(np.sum(np.abs(rxi[r])))
|
|
257
|
+
if s2 > xitol:
|
|
258
|
+
delta = False
|
|
259
|
+
if delta:
|
|
260
|
+
r -= nq
|
|
261
|
+
if delta or r >= n - 1:
|
|
262
|
+
break
|
|
263
|
+
nlim = r
|
|
264
|
+
|
|
265
|
+
# [2]: premultiply each rxi[k] by q1inv (lower triangular)
|
|
266
|
+
for k in range(0, nlim + 1):
|
|
267
|
+
mtmp = _m1(m, m)
|
|
268
|
+
for i in range(1, m + 1):
|
|
269
|
+
for jx in range(1, m + 1):
|
|
270
|
+
s1 = 0.0
|
|
271
|
+
for h in range(1, i + 1):
|
|
272
|
+
s1 += Q1inv[i][h] * rxi[k][h][jx]
|
|
273
|
+
mtmp[i][jx] = s1
|
|
274
|
+
rxi[k][1:, 1:] = mtmp[1:, 1:]
|
|
275
|
+
return nlim, rxi
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# --------------------------------------------------------------------------- #
|
|
279
|
+
# cres : exact residuals #
|
|
280
|
+
# --------------------------------------------------------------------------- #
|
|
281
|
+
|
|
282
|
+
def cres(m, n, g, nlim, rxi, Q1, M, L, lam, res):
|
|
283
|
+
"""Port of elfvarma.c:cres. Overwrites res (1-indexed (n+1, m+1))."""
|
|
284
|
+
mg = m * g
|
|
285
|
+
# [1] solve L' c = lambda (L lower 1-indexed) -> overwrite lam[1..mg]
|
|
286
|
+
Lmat = L[1:mg + 1, 1:mg + 1]
|
|
287
|
+
from scipy.linalg import solve_triangular
|
|
288
|
+
c = solve_triangular(Lmat, lam[1:mg + 1], lower=True, trans='T')
|
|
289
|
+
lam[1:mg + 1] = c
|
|
290
|
+
# [2] d = M c (M lower 1-indexed)
|
|
291
|
+
for i in range(mg, 0, -1):
|
|
292
|
+
s = 0.0
|
|
293
|
+
for j in range(1, i + 1):
|
|
294
|
+
s += M[i][j] * lam[j]
|
|
295
|
+
lam[i] = s
|
|
296
|
+
# [3] residual correction
|
|
297
|
+
for i in range(1, n + 1):
|
|
298
|
+
for j in range(1, i + 1):
|
|
299
|
+
if (i - j <= nlim) and (j <= g):
|
|
300
|
+
for jj in range(1, m + 1):
|
|
301
|
+
s = 0.0
|
|
302
|
+
for h in range(1, m + 1):
|
|
303
|
+
s += rxi[i - j][jj][h] * lam[h + (j - 1) * m]
|
|
304
|
+
res[i][jj] -= s
|
|
305
|
+
for j in range(1, n + 1):
|
|
306
|
+
for i in range(m, 0, -1):
|
|
307
|
+
s = 0.0
|
|
308
|
+
for h in range(1, i + 1):
|
|
309
|
+
s += Q1[i][h] * res[j][h]
|
|
310
|
+
res[j][i] = s
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# --------------------------------------------------------------------------- #
|
|
314
|
+
# elf : the exact log-likelihood #
|
|
315
|
+
# --------------------------------------------------------------------------- #
|
|
316
|
+
|
|
317
|
+
def elf(m, n, p, q, Mu, Phi, Theta, Qq, W, sigma2, xitol, atf):
|
|
318
|
+
"""Port of elfvarma.c:elf.
|
|
319
|
+
|
|
320
|
+
All matrix arguments are 1-indexed (Mu (m+1,), Phi/Theta cubes, Qq (m+1,m+1),
|
|
321
|
+
W (n+1, m+1)). Returns (logelf, f1, f2, a, ifault) where a is the 1-indexed
|
|
322
|
+
residual matrix (n+1, m+1) (filled only if atf=True).
|
|
323
|
+
"""
|
|
324
|
+
g = max(p, q)
|
|
325
|
+
a = np.zeros((n + 1, m + 1))
|
|
326
|
+
|
|
327
|
+
# [0]/[1]: q1 = chol(Qq), q1inv, detq
|
|
328
|
+
Q1full = _m1(m, m)
|
|
329
|
+
for i in range(1, m + 1):
|
|
330
|
+
for j in range(i, m + 1):
|
|
331
|
+
Q1full[i][j] = Qq[i][j]
|
|
332
|
+
Q1full[j][i] = Qq[i][j]
|
|
333
|
+
Q1, detq, ifault = _chol_lower(Q1full, m)
|
|
334
|
+
if ifault:
|
|
335
|
+
return 0.0, 0.0, 0.0, a, 1
|
|
336
|
+
from scipy.linalg import solve_triangular
|
|
337
|
+
Lq = Q1[1:m + 1, 1:m + 1]
|
|
338
|
+
Q1inv_full = solve_triangular(Lq, np.eye(m), lower=True) # inv of lower chol
|
|
339
|
+
Q1inv = _m1(m, m)
|
|
340
|
+
Q1inv[1:m + 1, 1:m + 1] = Q1inv_full
|
|
341
|
+
|
|
342
|
+
# [2]: autocovariances
|
|
343
|
+
gamma = np.zeros(0)
|
|
344
|
+
gamwa = {}
|
|
345
|
+
if p > 0:
|
|
346
|
+
gamma, gamwa, ifg = cgamma(m, p, q, Phi, Theta, Qq)
|
|
347
|
+
if ifg:
|
|
348
|
+
return 0.0, 0.0, 0.0, a, 2
|
|
349
|
+
|
|
350
|
+
half = m * (m + 1) // 2
|
|
351
|
+
|
|
352
|
+
def gval(k, ii, kk):
|
|
353
|
+
"""gamma packing access used in [3.1] (see elfvarma.c)."""
|
|
354
|
+
if k > 0:
|
|
355
|
+
jl = half + m * m * (k - 1) + m * (kk - 1) + ii
|
|
356
|
+
elif k < 0:
|
|
357
|
+
jl = half - m * m * (k + 1) + m * (ii - 1) + kk
|
|
358
|
+
else:
|
|
359
|
+
if kk >= ii:
|
|
360
|
+
jl = kk * (kk - 1) // 2 + ii
|
|
361
|
+
else:
|
|
362
|
+
jl = ii * (ii - 1) // 2 + kk
|
|
363
|
+
return gamma[jl]
|
|
364
|
+
|
|
365
|
+
# [3]: M = chol(v1 omega v1')
|
|
366
|
+
mg = m * g
|
|
367
|
+
mtmp0 = _m1(mg, mg)
|
|
368
|
+
|
|
369
|
+
# [3.1]: omega * v1' -> mtmp1 ((p+q)m x g m)
|
|
370
|
+
mtmp1 = _m1(m * (p + q), mg)
|
|
371
|
+
for i in range(1, p + 1):
|
|
372
|
+
for j in range(1, g + 1):
|
|
373
|
+
for k in range(j - i, p - i + 1):
|
|
374
|
+
for ii in range(1, m + 1):
|
|
375
|
+
for jj in range(1, m + 1):
|
|
376
|
+
s1 = 0.0
|
|
377
|
+
for kk in range(1, m + 1):
|
|
378
|
+
s1 += gval(k, ii, kk) * Phi[p - k - i + j][jj][kk]
|
|
379
|
+
mtmp1[ii + (i - 1) * m][jj + (j - 1) * m] += s1
|
|
380
|
+
for k in range(j - i, q - i + 1):
|
|
381
|
+
if p + k <= q:
|
|
382
|
+
for ii in range(1, m + 1):
|
|
383
|
+
for jj in range(1, m + 1):
|
|
384
|
+
s1 = 0.0
|
|
385
|
+
for kk in range(1, m + 1):
|
|
386
|
+
s1 += gamwa[-q + p + k][ii][kk] * Theta[q - k - i + j][jj][kk]
|
|
387
|
+
mtmp1[ii + (i - 1) * m][jj + (j - 1) * m] -= s1
|
|
388
|
+
for i in range(p + 1, p + q + 1):
|
|
389
|
+
for j in range(1, g + 1):
|
|
390
|
+
for k in range(p + j - i, p + p - i + 1):
|
|
391
|
+
if p - k <= q:
|
|
392
|
+
for ii in range(1, m + 1):
|
|
393
|
+
for jj in range(1, m + 1):
|
|
394
|
+
s1 = 0.0
|
|
395
|
+
for kk in range(1, m + 1):
|
|
396
|
+
s1 += gamwa[-q + p - k][kk][ii] * Phi[p + p - k - i + j][jj][kk]
|
|
397
|
+
mtmp1[ii + (i - 1) * m][jj + (j - 1) * m] += s1
|
|
398
|
+
if p - i + j <= 0:
|
|
399
|
+
for ii in range(1, m + 1):
|
|
400
|
+
for jj in range(1, m + 1):
|
|
401
|
+
s1 = 0.0
|
|
402
|
+
for kk in range(1, m + 1):
|
|
403
|
+
s1 += Qq[ii][kk] * Theta[q + p - i + j][jj][kk]
|
|
404
|
+
mtmp1[ii + (i - 1) * m][jj + (j - 1) * m] -= s1
|
|
405
|
+
|
|
406
|
+
# [3.2]: v1 omega v1' -> mtmp0 (upper triangle)
|
|
407
|
+
for i in range(1, g + 1):
|
|
408
|
+
for j in range(i, g + 1):
|
|
409
|
+
for k in range(0, p - i + 1):
|
|
410
|
+
for ii in range(1, m + 1):
|
|
411
|
+
jl = ii if i == j else 1
|
|
412
|
+
for jj in range(jl, m + 1):
|
|
413
|
+
s1 = 0.0
|
|
414
|
+
for kk in range(1, m + 1):
|
|
415
|
+
s1 += Phi[p - k][ii][kk] * mtmp1[kk + (k + i - 1) * m][jj + (j - 1) * m]
|
|
416
|
+
mtmp0[ii + (i - 1) * m][jj + (j - 1) * m] += s1
|
|
417
|
+
for k in range(0, q - i + 1):
|
|
418
|
+
for ii in range(1, m + 1):
|
|
419
|
+
jl = ii if i == j else 1
|
|
420
|
+
for jj in range(jl, m + 1):
|
|
421
|
+
s1 = 0.0
|
|
422
|
+
for kk in range(1, m + 1):
|
|
423
|
+
s1 += Theta[q - k][ii][kk] * mtmp1[kk + (k + p + i - 1) * m][jj + (j - 1) * m]
|
|
424
|
+
mtmp0[ii + (i - 1) * m][jj + (j - 1) * m] -= s1
|
|
425
|
+
|
|
426
|
+
# symmetrise mtmp0 (it was filled on/above the block-diagonal upper triangle)
|
|
427
|
+
Vfull = mtmp0[1:mg + 1, 1:mg + 1].copy()
|
|
428
|
+
Vfull = np.triu(Vfull) + np.triu(Vfull, 1).T
|
|
429
|
+
mtmp0[1:mg + 1, 1:mg + 1] = Vfull
|
|
430
|
+
|
|
431
|
+
# [3.3]: M = chol(mtmp0)
|
|
432
|
+
M, _detM, ifault = _chol_lower(mtmp0, mg)
|
|
433
|
+
if ifault:
|
|
434
|
+
return 0.0, 0.0, 0.0, a, 3
|
|
435
|
+
|
|
436
|
+
# [4.1]: MA invertibility
|
|
437
|
+
if q > 0 and chekma(m, q, Theta):
|
|
438
|
+
return 0.0, 0.0, 0.0, a, 4
|
|
439
|
+
|
|
440
|
+
# [4.2]: rxi
|
|
441
|
+
nlim, rxi = cxi(m, n, q, Theta, Q1inv, xitol)
|
|
442
|
+
|
|
443
|
+
# [5]: eta -> a (conditional residuals, then premultiply by q1inv).
|
|
444
|
+
# Vectorised equivalent of the C double loop: AR part by shifted matmuls,
|
|
445
|
+
# MA part by the (inherently sequential) feedback recursion.
|
|
446
|
+
x0 = W[1:n + 1, 1:m + 1] - Mu[1:m + 1] # (n, m), 0-indexed
|
|
447
|
+
phi0 = Phi[1:p + 1, 1:m + 1, 1:m + 1] if p else np.zeros((0, m, m))
|
|
448
|
+
theta0 = Theta[1:q + 1, 1:m + 1, 1:m + 1] if q else np.zeros((0, m, m))
|
|
449
|
+
b = x0.copy()
|
|
450
|
+
for jx in range(1, p + 1):
|
|
451
|
+
b[jx:] -= x0[:n - jx] @ phi0[jx - 1].T # - Phi_j x_{t-j}
|
|
452
|
+
a0 = b
|
|
453
|
+
if q:
|
|
454
|
+
a0 = b.copy()
|
|
455
|
+
for t in range(n):
|
|
456
|
+
acc = b[t]
|
|
457
|
+
for jx in range(1, q + 1):
|
|
458
|
+
if t - jx >= 0:
|
|
459
|
+
acc = acc + theta0[jx - 1] @ a0[t - jx]
|
|
460
|
+
a0[t] = acc
|
|
461
|
+
a0 = a0 @ Q1inv[1:m + 1, 1:m + 1].T # [5.2] premultiply by q1inv
|
|
462
|
+
a[1:n + 1, 1:m + 1] = a0
|
|
463
|
+
|
|
464
|
+
# [6]: M' h -> vechh. h_j = sum_i Xi_i' a_{i+j} (i = 0..min(nlim, n-j)).
|
|
465
|
+
vechh = np.zeros(mg + 1)
|
|
466
|
+
R = rxi[:nlim + 1, 1:m + 1, 1:m + 1] # (nlim+1, m, m)
|
|
467
|
+
a0 = a[1:n + 1, 1:m + 1] # (n, m), 0-indexed rows
|
|
468
|
+
for j in range(1, g + 1):
|
|
469
|
+
kmax = min(nlim, n - j)
|
|
470
|
+
if kmax >= 0:
|
|
471
|
+
# sum_i Xi_i^T a_{i+j} ; a_{i+j} (1-indexed) = a0[i+j-1]
|
|
472
|
+
Hj = np.einsum('iab,ia->b', R[:kmax + 1], a0[j - 1:j + kmax])
|
|
473
|
+
vechh[1 + (j - 1) * m:j * m + 1] = Hj
|
|
474
|
+
# premultiply by M' : vechh = M^T vechh
|
|
475
|
+
Mmat = M[1:mg + 1, 1:mg + 1]
|
|
476
|
+
vechh[1:mg + 1] = Mmat.T @ vechh[1:mg + 1]
|
|
477
|
+
|
|
478
|
+
Msave = M.copy() if atf else None
|
|
479
|
+
|
|
480
|
+
# [7]: H'H -> mtmp2. First block-column (i,1) = sum_k Xi_k' Xi_{k+i-1};
|
|
481
|
+
# remaining lower blocks by the recursion (i,j) = (i-1,j-1) - corr.
|
|
482
|
+
rx = rxi[:, 1:m + 1, 1:m + 1] # (n, m, m), 0-indexed
|
|
483
|
+
blk = np.zeros((g + 1, g + 1, m, m)) # blk[i][j] = HtH block (i,j)
|
|
484
|
+
for i in range(1, g + 1):
|
|
485
|
+
kmax = min(n - i, nlim - i + 1)
|
|
486
|
+
if kmax >= 0:
|
|
487
|
+
blk[i][1] = np.einsum('kab,kac->bc', rx[:kmax + 1], rx[i - 1:i + kmax])
|
|
488
|
+
for i in range(2, g + 1):
|
|
489
|
+
for j in range(2, i + 1):
|
|
490
|
+
corr = np.zeros((m, m))
|
|
491
|
+
if (n - i + 1 <= nlim) and (n - j + 1 <= nlim):
|
|
492
|
+
corr = rx[n - i + 1].T @ rx[n - j + 1]
|
|
493
|
+
blk[i][j] = blk[i - 1][j - 1] - corr
|
|
494
|
+
HtH = np.zeros((mg, mg))
|
|
495
|
+
for i in range(1, g + 1):
|
|
496
|
+
for j in range(1, i + 1):
|
|
497
|
+
HtH[(i - 1) * m:i * m, (j - 1) * m:j * m] = blk[i][j]
|
|
498
|
+
HtH = np.tril(HtH) + np.tril(HtH, -1).T # symmetrise from lower
|
|
499
|
+
|
|
500
|
+
# [8]: I + M'H'HM and its Cholesky L
|
|
501
|
+
ImMtHtHM = np.eye(mg) + (Mmat.T @ HtH) @ Mmat
|
|
502
|
+
try:
|
|
503
|
+
Lom = np.linalg.cholesky(0.5 * (ImMtHtHM + ImMtHtHM.T))
|
|
504
|
+
except np.linalg.LinAlgError:
|
|
505
|
+
return 0.0, 0.0, 0.0, a, 5
|
|
506
|
+
detom = float(np.prod(np.diag(Lom)) ** 2)
|
|
507
|
+
|
|
508
|
+
# [9]: lambda via forward substitution L lambda = vechh
|
|
509
|
+
lam_vec = solve_triangular(Lom, vechh[1:mg + 1], lower=True)
|
|
510
|
+
vechh[1:mg + 1] = lam_vec
|
|
511
|
+
|
|
512
|
+
# [10]: quadratic form f1
|
|
513
|
+
s1 = float(np.sum(a[1:n + 1, 1:m + 1] ** 2))
|
|
514
|
+
s2 = float(np.sum(vechh[1:mg + 1] ** 2))
|
|
515
|
+
f1 = s1 - s2
|
|
516
|
+
|
|
517
|
+
# [11]: determinant factor
|
|
518
|
+
f2 = detq * np.exp(np.log(detom) / n)
|
|
519
|
+
|
|
520
|
+
# [12]: log-likelihood
|
|
521
|
+
logelf = -0.5 * (n * m * (_LOG2PI + np.log(sigma2)) + n * np.log(detq)
|
|
522
|
+
+ np.log(detom) + f1 / sigma2)
|
|
523
|
+
|
|
524
|
+
# [13]: residuals
|
|
525
|
+
if atf:
|
|
526
|
+
# rebuild L as 1-indexed for cres
|
|
527
|
+
L1 = _m1(mg, mg)
|
|
528
|
+
L1[1:mg + 1, 1:mg + 1] = Lom
|
|
529
|
+
cres(m, n, g, nlim, rxi, Q1, Msave, L1, vechh, a)
|
|
530
|
+
|
|
531
|
+
return float(logelf), float(f1), float(f2), a, 0
|