1
1
import numpy as np
2
+ import pytensor
2
3
3
4
from numpy .testing import assert_allclose
4
5
from pytensor import config
5
6
6
7
from pymc_extras .statespace .models import structural as st
8
+ from pymc_extras .statespace .models .structural .utils import _frequency_transition_block
7
9
from tests .statespace .models .structural .conftest import _assert_basic_coords_correct
8
10
from tests .statespace .test_utilities import assert_pattern_repeats , simulate_from_numpy_model
9
11
@@ -31,7 +33,7 @@ def test_cycle_component_with_dampening(rng):
31
33
params = {"cycle" : np .array ([10.0 , 10.0 ], dtype = config .floatX ), "cycle_dampening_factor" : 0.75 }
32
34
x , y = simulate_from_numpy_model (cycle , rng , params , steps = 100 )
33
35
34
- # Check that the cycle dampens to zero over time
36
+ # check that cycle dampens to zero over time
35
37
assert_allclose (y [- 1 ], 0.0 , atol = ATOL , rtol = RTOL )
36
38
37
39
@@ -75,6 +77,33 @@ def test_cycle_multivariate_deterministic(rng):
75
77
assert np .std (y [:, 1 ]) > np .std (y [:, 0 ])
76
78
assert np .std (y [:, 2 ]) > np .std (y [:, 0 ])
77
79
80
+ # check design, transition, selection matrices
81
+ Z , T , R = pytensor .function (
82
+ [],
83
+ [cycle .ssm ["design" ], cycle .ssm ["transition" ], cycle .ssm ["selection" ]],
84
+ mode = "FAST_COMPILE" ,
85
+ )()
86
+
87
+ # each block is [1, 0] for design
88
+ expected_Z = np .zeros ((3 , 6 ))
89
+ expected_Z [0 , 0 ] = 1.0
90
+ expected_Z [1 , 2 ] = 1.0
91
+ expected_Z [2 , 4 ] = 1.0
92
+ np .testing .assert_allclose (Z , expected_Z )
93
+
94
+ # each block is 2x2 frequency transition matrix for given cycle length (12 here)
95
+ block = _frequency_transition_block (12 , 1 ).eval ()
96
+ expected_T = np .zeros ((6 , 6 ))
97
+ for i in range (3 ):
98
+ expected_T [2 * i : 2 * i + 2 , 2 * i : 2 * i + 2 ] = block
99
+ np .testing .assert_allclose (T , expected_T )
100
+
101
+ # each block is 2x2 identity for selection
102
+ expected_R = np .zeros ((6 , 6 ))
103
+ for i in range (3 ):
104
+ expected_R [2 * i : 2 * i + 2 , 2 * i : 2 * i + 2 ] = np .eye (2 )
105
+ np .testing .assert_allclose (R , expected_R )
106
+
78
107
79
108
def test_cycle_multivariate_with_dampening (rng ):
80
109
"""Test multivariate cycle component with dampening."""
@@ -118,7 +147,7 @@ def test_cycle_multivariate_with_innovations_and_cycle_length(rng):
118
147
"cycle" : np .array ([[1.0 , 1.0 ], [2.0 , 2.0 ], [3.0 , 3.0 ]], dtype = config .floatX ),
119
148
"cycle_length" : 12.0 ,
120
149
"cycle_dampening_factor" : 0.95 ,
121
- "sigma_cycle" : np .array ([0.5 , 1.0 , 1.5 ]), # Different innovation variances per variable
150
+ "sigma_cycle" : np .array ([0.5 , 1.0 , 1.5 ]), # different innov variances per var
122
151
}
123
152
x , y = simulate_from_numpy_model (cycle , rng , params )
124
153
@@ -138,3 +167,47 @@ def test_cycle_multivariate_with_innovations_and_cycle_length(rng):
138
167
# Check that each variable shows some variation (due to innovations)
139
168
for i in range (3 ):
140
169
assert np .std (y [:, i ]) > 0
170
+
171
+ # check design, transition, selection & state_cov matrices
172
+ Z , T , R , Q = pytensor .function (
173
+ [
174
+ cycle ._name_to_variable ["cycle_length" ],
175
+ cycle ._name_to_variable ["cycle_dampening_factor" ],
176
+ cycle ._name_to_variable ["sigma_cycle" ],
177
+ ],
178
+ [
179
+ cycle .ssm ["design" ],
180
+ cycle .ssm ["transition" ],
181
+ cycle .ssm ["selection" ],
182
+ cycle .ssm ["state_cov" ],
183
+ ],
184
+ mode = "FAST_COMPILE" ,
185
+ )(params ["cycle_length" ], params ["cycle_dampening_factor" ], params ["sigma_cycle" ])
186
+
187
+ # each block is [1, 0] for design
188
+ expected_Z = np .zeros ((3 , 6 ))
189
+ expected_Z [0 , 0 ] = 1.0
190
+ expected_Z [1 , 2 ] = 1.0
191
+ expected_Z [2 , 4 ] = 1.0
192
+ np .testing .assert_allclose (Z , expected_Z )
193
+
194
+ # each block is 2x2 frequency transition matrix for given cycle length (12 here),
195
+ # scaled by dampening factor
196
+ block = _frequency_transition_block (12 , 1 ).eval () * params ["cycle_dampening_factor" ]
197
+ expected_T = np .zeros ((6 , 6 ))
198
+ for i in range (3 ):
199
+ expected_T [2 * i : 2 * i + 2 , 2 * i : 2 * i + 2 ] = block
200
+ np .testing .assert_allclose (T , expected_T )
201
+
202
+ # each block is 2x2 identity for selection
203
+ expected_R = np .zeros ((6 , 6 ))
204
+ for i in range (3 ):
205
+ expected_R [2 * i : 2 * i + 2 , 2 * i : 2 * i + 2 ] = np .eye (2 )
206
+ np .testing .assert_allclose (R , expected_R )
207
+
208
+ # each block is sigma^2 * I_2 for state_cov
209
+ sigmas = params ["sigma_cycle" ]
210
+ expected_Q = np .zeros ((6 , 6 ))
211
+ for i in range (3 ):
212
+ expected_Q [2 * i : 2 * i + 2 , 2 * i : 2 * i + 2 ] = np .eye (2 ) * sigmas [i ] ** 2
213
+ np .testing .assert_allclose (Q , expected_Q )
0 commit comments