Skip to content

Commit 7d768fb

Browse files
committed
Update state space system methods
1 parent f8bc06e commit 7d768fb

File tree

1 file changed

+176
-87
lines changed

1 file changed

+176
-87
lines changed

src/utilities.jl

Lines changed: 176 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ function simulate(F::AbstractFactorProcess, S::Integer; rng::AbstractRNG = Xoshi
7373
else
7474
fs .= dynamics(F) * f[:, s - 1] + rand(rng, dist)
7575
end
76-
7776
end
7877

7978
return f
@@ -107,166 +106,256 @@ function simulate(ε::SpatialMovingAverage, S::Integer; rng::AbstractRNG = Xoshi
107106
end
108107

109108
"""
110-
collapse(model) -> (A_low, Z_basis)
109+
state_space_init(model) -> (a1, P1)
111110
112-
Low-dimensional collapsing matrices for the dynamic factor model `model`
113-
following the approach of Jungbacker and Koopman (2015).
111+
State space initial conditions of the state space form of the dynamic factor model `model`.
114112
"""
115-
function collapse(model::DynamicFactorModel)
116-
# active factors
117-
active = [!all(iszero, λ) for λ in eachcol(loadings(model))]
118-
119-
# collapsing matrix
120-
H = cov(model)
121-
if all(active)
122-
C = (H \ loadings(model))' * loadings(model)
123-
Z_basis = (C \ loadings(model)')'
124-
else
125-
Z_basis = loadings(model)[:, active]
126-
end
127-
A_low = (H \ Z_basis)'
113+
function state_space_init(model::DynamicFactorModel)
114+
T = eltype(data(model))
115+
R = nfactors(model)
116+
a1 = zeros(T, R)
117+
P1 = Matrix{T}(I, R, R)
128118

129-
return (A_low, Z_basis)
119+
return (a1, P1)
130120
end
131121

132122
"""
133-
state_space(model) -> (y_low, Z_low, d_low, H_low, a1, P1)
123+
collapse(model; objective = false) -> (y_low, d_low, Z_low, H_low[, M])
134124
135-
State space form of the collapsed dynamic factor model `model` following the
136-
approach of Jungbacker and Koopman (2015).
125+
Collapsed system components for the collapsed state space form of the dynamic factor model
126+
`model` following the approach of Jungbacker and Koopman (2015). Optional `objective`
127+
boolean indicating whether the collapsed system components are used for objective function
128+
(log-likelihood) computation, in which case additionally the annihilator matrix is returned.
137129
"""
138-
function state_space(model::DynamicFactorModel)
139-
R = size(process(model))
140-
Ty = eltype(data(model))
141-
142-
# active factors
143-
active = [!all(iszero, λ) for λ in eachcol(loadings(model))]
144-
145-
# collapsing matrix
146-
if any(active)
147-
(A_low, _) = collapse(model)
148-
else
130+
function collapse(model::DynamicFactorModel; objective::Bool = false)
131+
# linearly independent columns
132+
F = qr(loadings(model), ColumnNorm())
133+
ic = isapprox.(diag(F.R), 0.0, atol = 1e-8)
134+
135+
# collapsing matrices
136+
Z_basis = Z[:, .!ic]
137+
if all(ic)
149138
A_low = I
139+
else
140+
A_low = (cov(model) \ Z_basis)'
150141
end
151142

152-
# collapsing
153-
y_low = A_low * data(model)
143+
# collapsed system
144+
y_low = [A_low * yt for yt in eachcol(data(model))]
154145
Z_low = A_low * loadings(model)
155146
if mean(model) isa ZeroMean
156-
d_low = Zeros(mean(model).type, size(y_low))
147+
d_low = [Zeros(mean(model).type, nfactors(model)) for _ in 1:nobs(model)]
157148
elseif mean(model) isa Exogenous
158-
d_low = A_low * mean(mean(model))
149+
d_low = [A_low * μt for μt in eachcol(mean(mean(model)))]
159150
end
160151
H_low = A_low * cov(model) * A_low'
161152

162-
# initial conditions
163-
a1 = zeros(Ty, R)
164-
P1 = Matrix{Ty}(I, R, R)
153+
# annihilator matrix for log-likelihood
154+
if objective
155+
if all(ic)
156+
M = Zeros(eltype(data(model)), size(data(model), 1))
157+
else
158+
M = I - Z_basis * (H_low \ A_low)
159+
end
165160

166-
return (y_low, Z_low, d_low, H_low, a1, P1)
161+
return (y_low, d_low, Z_low, H_low, M)
162+
else
163+
return (y_low, d_low, Z_low, H_low)
164+
end
167165
end
168166

169167
"""
170-
_filter(y, Z, d, H, T, Q, a1, P1) -> (a, P, v, F, K)
168+
filter(model; predict = false) -> (a, P, v, F)
171169
172-
Kalman filter for the state space system `y`, `Z`, `d`, `H`, `T`, `Q`, `a1`,
173-
and `P1`.
170+
Collapsed Kalman filter for the dynamic factor model `model`. Returns the filtered state `a`
171+
, covariance `P`, forecast error `v`, and forecast error variance `F`. If `predict` is
172+
`true` the filter reports the one-step ahead out-of-sample prediction.
174173
"""
175-
function _filter(y, Z, d, H, T, Q, a1, P1)
174+
function filter(model::DynamicFactorModel; predict::Bool = false)
175+
# collapsed state space system
176+
(y, d, Z, H) = collapse(model)
177+
T = dynamics(model)
178+
Q = cov(process(model))
179+
(a1, P1) = state_space_init(model)
180+
176181
# initialize filter output
177-
n = size(y, 2)
178-
a = Vector{typeof(a1)}(undef, n)
179-
P = Vector{typeof(P1)}(undef, n)
180-
v = Vector{typeof(a1)}(undef, n)
181-
F = Vector{typeof(P1)}(undef, n)
182-
K = Vector{typeof(P1)}(undef, n)
182+
n = length(y) + (predict ? 1 : 0)
183+
a = similar(y, typeof(a1), n)
184+
P = similar(y, typeof(P1), n)
185+
v = similar(y)
186+
F = similar(y, typeof(P1))
187+
188+
# initialize storage
189+
ZtFinv = similar(P1)
190+
att = similar(a1)
191+
Ptt = similar(P1)
183192

184193
# initialize filter
185194
a[1] = a1
186195
P[1] = P1
187196

188197
# filter
189-
for (t, yt) in pairs(eachcol(y))
198+
for t in eachindex(y)
190199
# forecast error
191-
v[t] = yt - Z * a[t] - view(d, :, t)
200+
v[t] = y[t] - d[t] - Z * a[t]
192201
F[t] = Z * P[t] * Z' + H
193202

194-
# Kalman gain
195-
K[t] = T * P[t] * (F[t] \ Z)'
203+
# update
204+
ZtFinv .= (F[t] \ Z)'
205+
att .= a[t] + P[t] * ZtFinv * v[t]
206+
Ptt .= P[t] - P[t] * ZtFinv * Z * P[t]
196207

197208
# prediction
198-
if t < n
199-
a[t + 1] = T * a[t] + K[t] * v[t]
200-
P[t + 1] = T * P[t] * (T - K[t] * Z)' + Q
209+
if predict || t < length(y)
210+
a[t + 1] = T * att
211+
P[t + 1] = T * Ptt * T' + Q
212+
# enforce symmetry for numerical stability
213+
P[t + 1] = 0.5 * (P[t + 1] + P[t + 1]')
201214
end
202215
end
203216

204-
return (a, P, v, F, K)
217+
return (a, P, v, F)
205218
end
206219

207220
"""
208-
filter(model) -> (a, P, v, F, K)
221+
_filter_smoother(y, d, Z, H, T, Q, a1, P1) -> (a, P, v, ZtFinv)
209222
210-
Collapsed Kalman filter for the dynamic factor model `model`. Returns the
211-
filtered state `a`, covariance `P`, forecast error `v`, forecast error variance
212-
`F`, and Kalman gain `K`.
223+
Collapsed Kalman filter for the dynamic factor model used internally by the `smoother`
224+
routine to avoid duplicate expensive computation of state space system matrices.
213225
"""
214-
function filter(model::DynamicFactorModel)
215-
# collapsed state space system
216-
(y, Z, d, H, a1, P1) = state_space(model)
217-
T = dynamics(model)
218-
Q = cov(process(model))
226+
function _filter_smoother(y, d, Z, H, T, Q, a1, P1)
227+
# initialize filter output
228+
a = similar(y, typeof(a1))
229+
P = similar(y, typeof(P1))
230+
v = similar(y)
231+
ZtFinv = similar(y, typeof(P1))
232+
233+
# initialize storage
234+
F = similar(P1)
235+
att = similar(a1)
236+
Ptt = similar(P1)
237+
238+
# initialize filter
239+
a[1] = a1
240+
P[1] = P1
241+
242+
# filter
243+
for t in eachindex(y)
244+
# forecast error
245+
v[t] = y[t] - d[t] - Z * a[t]
246+
F .= Z * P[t] * Z' + H
247+
248+
# update
249+
ZtFinv[t] = (F \ Z)'
250+
att .= a[t] + P[t] * ZtFinv[t] * v[t]
251+
Ptt .= P[t] - P[t] * ZtFinv[t] * Z * P[t]
252+
253+
# prediction
254+
if t < length(y)
255+
a[t + 1] = T * att
256+
P[t + 1] = T * Ptt * T' + Q
257+
# enforce symmetry for numerical stability
258+
P[t + 1] = 0.5 * (P[t + 1] + P[t + 1]')
259+
end
260+
end
261+
262+
return (a, P, v, ZtFinv)
263+
end
264+
265+
"""
266+
_filter_likelihood(y, d, Z, H, T, Q, a1, P1) -> (v, F, fac)
267+
268+
Collapsed Kalman filter for the dynamic factor model used internally by the `loglikelihood`
269+
routine to avoid duplicate expensive computation of collapsing components and state space
270+
system matrices.
271+
"""
272+
function _filter_likelihood(y, d, Z, H, T, Q, a1, P1)
273+
# initialize filter output
274+
v = similar(y)
275+
F = similar(y, typeof(P1))
276+
277+
# initialize storage
278+
ZtFinv = similar(P1)
279+
att = similar(a1)
280+
Ptt = similar(P1)
281+
282+
# initialize filter
283+
a = copy(a1)
284+
P = copy(P1)
285+
286+
# filter
287+
for t in eachindex(y)
288+
# forecast error
289+
v[t] = y[t] - d[t] - Z * a
290+
F[t] = Z * P * Z' + H
219291

220-
return _filter(y, Z, d, H, T, Q, a1, P1)
292+
# update
293+
ZtFinv .= (F[t] \ Z)'
294+
att .= a + P * ZtFinv * v[t]
295+
Ptt .= P - P * ZtFinv * Z * P
296+
297+
# prediction
298+
if t < length(y)
299+
a .= T * att
300+
P = T * Ptt * T' + Q
301+
# enforce symmetry for numerical stability
302+
P = 0.5 * (P + P')
303+
end
304+
end
305+
306+
return (v, F)
221307
end
222308

223309
"""
224-
smoother(model) -> (α̂, V, Γ)
310+
smoother(model) -> (α, V, Γ)
225311
226-
Collapsed Kalman smoother for the dynamic factor model `model`. Returns the
227-
smoothed state `α̂`, covariance `V`, and autocovariance `Γ`.
312+
Collapsed Kalman smoother for the dynamic factor model `model`. Returns the smoothed state
313+
`, covariance `V`, and autocovariance `Γ`.
228314
"""
229-
function smoother(model::DynamicFactorModel)
315+
function smoother(model::DynamicTensorAutoregression)
230316
# collapsed state space system
231-
(y, Z, d, H, a1, P1) = state_space(model)
317+
(y, d, Z, H) = collapse(model)
232318
T = dynamics(model)
233319
Q = cov(process(model))
320+
(a1, P1) = state_space_init(model)
234321

235322
# filter
236-
(a, P, v, F, K) = _filter(y, Z, d, H, T, Q, a1, P1)
323+
(a, P, v, ZtFinv) = _filter_smoother(y, d, Z, H, T, Q, a1, P1)
237324

238325
# initialize smoother output
239-
α̂ = similar(a)
326+
α = similar(a)
240327
V = similar(P)
241-
Γ = similar(P, length(a) - 1)
328+
Γ = similar(P, length(y) - 1)
242329

243330
# initialize smoother
244-
r = zero(a1)
245-
N = zero(P1)
246-
L = similar(P1)
331+
r = zero(a[1])
332+
N = zero(P[1])
333+
L = similar(P[1])
247334

248335
# smoother
249-
for t in reverse(eachindex(a))
250-
L .= T - K[t] * Z
336+
for t in reverse(eachindex(y))
337+
L .= T - T * P[t] * ZtFinv[t] * Z
251338

252339
# backward recursion
253-
r .= (F[t] \ Z)' * v[t] + L' * r
254-
N .= (F[t] \ Z)' * Z + L' * N * L
340+
r .= ZtFinv[t] * v[t] + L' * r
341+
N .= ZtFinv[t] * Z[t] + L' * N * L
255342

256343
# smoothing
257-
α̂[t] = a[t] + P[t] * r
344+
α[t] = a[t] + P[t] * r
258345
V[t] = P[t] - P[t] * N * P[t]
259346
t > 1 && (Γ[t - 1] = I - P[t] * N)
260-
t < length(a) && (Γ[t] *= L * P[t])
347+
t < length(y) && (Γ[t] *= L * P[t])
261348
end
262349

263-
return (α̂, V, Γ)
350+
return (α, V, Γ)
264351
end
265352

266353
"""
267354
forecasts(μ, periods) -> forecasts
268355
269356
Forecast the mean model `μ` `periods` ahead.
270357
"""
271-
forecast::AbstractMeanSpecification, periods::Integer) = error("forecast for $(Base.typename(typeof(μ)).wrapper) not implemented.")
358+
function forecast::AbstractMeanSpecification, periods::Integer)
359+
error("forecast for $(Base.typename(typeof(μ)).wrapper) not implemented.")
360+
end
272361
forecast::ZeroMean, periods::Integer) = Zeros.type, μ.n, periods)

0 commit comments

Comments
 (0)