Skip to content

Commit 08818ad

Browse files
committed
Explicitly test matrices in test_cycle
1 parent ab98abe commit 08818ad

File tree

1 file changed

+75
-2
lines changed

1 file changed

+75
-2
lines changed

tests/statespace/models/structural/components/test_cycle.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import numpy as np
2+
import pytensor
23

34
from numpy.testing import assert_allclose
45
from pytensor import config
56

67
from pymc_extras.statespace.models import structural as st
8+
from pymc_extras.statespace.models.structural.utils import _frequency_transition_block
79
from tests.statespace.models.structural.conftest import _assert_basic_coords_correct
810
from tests.statespace.test_utilities import assert_pattern_repeats, simulate_from_numpy_model
911

@@ -31,7 +33,7 @@ def test_cycle_component_with_dampening(rng):
3133
params = {"cycle": np.array([10.0, 10.0], dtype=config.floatX), "cycle_dampening_factor": 0.75}
3234
x, y = simulate_from_numpy_model(cycle, rng, params, steps=100)
3335

34-
# Check that the cycle dampens to zero over time
36+
# check that cycle dampens to zero over time
3537
assert_allclose(y[-1], 0.0, atol=ATOL, rtol=RTOL)
3638

3739

@@ -75,6 +77,33 @@ def test_cycle_multivariate_deterministic(rng):
7577
assert np.std(y[:, 1]) > np.std(y[:, 0])
7678
assert np.std(y[:, 2]) > np.std(y[:, 0])
7779

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+
78107

79108
def test_cycle_multivariate_with_dampening(rng):
80109
"""Test multivariate cycle component with dampening."""
@@ -118,7 +147,7 @@ def test_cycle_multivariate_with_innovations_and_cycle_length(rng):
118147
"cycle": np.array([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0]], dtype=config.floatX),
119148
"cycle_length": 12.0,
120149
"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
122151
}
123152
x, y = simulate_from_numpy_model(cycle, rng, params)
124153

@@ -138,3 +167,47 @@ def test_cycle_multivariate_with_innovations_and_cycle_length(rng):
138167
# Check that each variable shows some variation (due to innovations)
139168
for i in range(3):
140169
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

Comments
 (0)