55from numpy .testing import assert_allclose
66from pytensor import config
77from pytensor import tensor as pt
8+ from pytensor .graph .basic import explicit_graph_inputs
89
910from pymc_extras .statespace .models import structural as st
1011from tests .statespace .models .structural .conftest import _assert_basic_coords_correct
@@ -25,7 +26,7 @@ def test_exogenous_component(rng):
2526 # Check that the generated data is just a linear regression
2627 assert_allclose (y , data @ params ["beta_exog" ], atol = ATOL , rtol = RTOL )
2728
28- mod .build (verbose = False )
29+ mod = mod .build (verbose = False )
2930 _assert_basic_coords_correct (mod )
3031 assert mod .coords ["exog_state" ] == ["feature_1" , "feature_2" ]
3132
@@ -42,6 +43,73 @@ def test_adding_exogenous_component(rng):
4243 assert_allclose (mod .ssm ["design" , 5 , 0 , :2 ].eval ({"data_exog" : data }), data [5 ])
4344
4445
46+ def test_regression_with_multiple_observed_states (rng ):
47+ from scipy .linalg import block_diag
48+
49+ data = rng .normal (size = (100 , 2 )).astype (config .floatX )
50+ mod = st .RegressionComponent (
51+ state_names = ["feature_1" , "feature_2" ],
52+ name = "exog" ,
53+ observed_state_names = ["data_1" , "data_2" ],
54+ )
55+
56+ params = {"beta_exog" : np .array ([[1.0 , 2.0 ], [3.0 , 4.0 ]], dtype = config .floatX )}
57+ exog_data = {"data_exog" : data }
58+ x , y = simulate_from_numpy_model (mod , rng , params , exog_data )
59+
60+ assert x .shape == (100 , 4 ) # 2 features, 2 states
61+ assert y .shape == (100 , 2 )
62+
63+ # Check that the generated data are two independent linear regressions
64+ assert_allclose (y [:, 0 ], data @ params ["beta_exog" ][0 ], atol = ATOL , rtol = RTOL )
65+ assert_allclose (y [:, 1 ], data @ params ["beta_exog" ][1 ], atol = ATOL , rtol = RTOL )
66+
67+ mod = mod .build (verbose = False )
68+ assert mod .coords ["exog_state" ] == [
69+ "feature_1[data_1]" ,
70+ "feature_2[data_1]" ,
71+ "feature_1[data_2]" ,
72+ "feature_2[data_2]" ,
73+ ]
74+
75+ Z = mod .ssm ["design" ].eval ({"data_exog" : data })
76+ vec_block_diag = np .vectorize (block_diag , signature = "(n,m),(o,p)->(q,r)" )
77+ assert Z .shape == (100 , 2 , 4 )
78+ assert np .allclose (Z , vec_block_diag (data [:, None , :], data [:, None , :]))
79+
80+
81+ def test_add_regression_components_with_multiple_observed_states (rng ):
82+ from scipy .linalg import block_diag
83+
84+ data_1 = rng .normal (size = (100 , 2 )).astype (config .floatX )
85+ data_2 = rng .normal (size = (100 , 1 )).astype (config .floatX )
86+
87+ reg1 = st .RegressionComponent (
88+ state_names = ["a" , "b" ], name = "exog1" , observed_state_names = ["data_1" , "data_2" ]
89+ )
90+ reg2 = st .RegressionComponent (state_names = ["c" ], name = "exog2" , observed_state_names = ["data_3" ])
91+
92+ mod = (reg1 + reg2 ).build (verbose = False )
93+ assert mod .coords ["exog1_state" ] == ["a[data_1]" , "b[data_1]" , "a[data_2]" , "b[data_2]" ]
94+ assert mod .coords ["exog2_state" ] == ["c[data_3]" ]
95+
96+ Z = mod .ssm ["design" ].eval ({"data_exog1" : data_1 , "data_exog2" : data_2 })
97+ vec_block_diag = np .vectorize (block_diag , signature = "(n,m),(o,p)->(q,r)" )
98+ assert Z .shape == (100 , 3 , 5 )
99+ assert np .allclose (
100+ Z ,
101+ vec_block_diag (vec_block_diag (data_1 [:, None , :], data_1 [:, None , :]), data_2 [:, None , :]),
102+ )
103+
104+ x0 = mod .ssm ["initial_state" ].eval (
105+ {
106+ "beta_exog1" : np .array ([[1.0 , 2.0 ], [3.0 , 4.0 ]], dtype = config .floatX ),
107+ "beta_exog2" : np .array ([5.0 ], dtype = config .floatX ),
108+ }
109+ )
110+ np .testing .assert_allclose (x0 , np .array ([1.0 , 2.0 , 3.0 , 4.0 , 5.0 ], dtype = config .floatX ))
111+
112+
45113def test_filter_scans_time_varying_design_matrix (rng ):
46114 time_idx = pd .date_range (start = "2000-01-01" , freq = "D" , periods = 100 )
47115 data = pd .DataFrame (rng .normal (size = (100 , 2 )), columns = ["a" , "b" ], index = time_idx )
0 commit comments