@@ -212,7 +212,7 @@ class TimeSeasonality(Component):
212
212
sigma_level_trend = pm.HalfNormal(
213
213
"sigma_level_trend", sigma=1e-6, dims=ss_mod.param_dims["sigma_level_trend"]
214
214
)
215
- coefs_annual = pm.Normal("coefs_annual ", sigma=1e-2, dims=ss_mod.param_dims["coefs_annual "])
215
+ params_annual = pm.Normal("params_annual ", sigma=1e-2, dims=ss_mod.param_dims["params_annual "])
216
216
217
217
ss_mod.build_statespace_graph(data)
218
218
idata = pm.sample(
@@ -298,10 +298,10 @@ def populate_component_properties(self):
298
298
for endog_name in self .observed_state_names
299
299
for state_name in self .provided_state_names
300
300
]
301
- self .param_names = [f"coefs_ { self .name } " ]
301
+ self .param_names = [f"params_ { self .name } " ]
302
302
303
303
self .param_info = {
304
- f"coefs_ { self .name } " : {
304
+ f"params_ { self .name } " : {
305
305
"shape" : (k_states ,) if k_endog == 1 else (k_endog , k_states ),
306
306
"constraints" : None ,
307
307
"dims" : (f"state_{ self .name } " ,)
@@ -311,7 +311,7 @@ def populate_component_properties(self):
311
311
}
312
312
313
313
self .param_dims = {
314
- f"coefs_ { self .name } " : (f"state_{ self .name } " ,)
314
+ f"params_ { self .name } " : (f"state_{ self .name } " ,)
315
315
if k_endog == 1
316
316
else (f"endog_{ self .name } " , f"state_{ self .name } " )
317
317
}
@@ -327,12 +327,14 @@ def populate_component_properties(self):
327
327
328
328
if self .innovations :
329
329
self .param_names += [f"sigma_{ self .name } " ]
330
+ self .shock_names = [f"{ self .name } [{ name } ]" for name in self .observed_state_names ]
330
331
self .param_info [f"sigma_{ self .name } " ] = {
331
- "shape" : (),
332
+ "shape" : () if k_endog == 1 else ( k_endog ,) ,
332
333
"constraints" : "Positive" ,
333
- "dims" : None ,
334
+ "dims" : None if k_endog == 1 else ( f"endog_ { self . name } " ,) ,
334
335
}
335
- self .shock_names = [f"{ self .name } [{ name } ]" for name in self .observed_state_names ]
336
+ if k_endog > 1 :
337
+ self .param_dims [f"sigma_{ self .name } " ] = (f"endog_{ self .name } " ,)
336
338
337
339
def make_symbolic_graph (self ) -> None :
338
340
k_states = self .k_states // self .k_endog
@@ -377,7 +379,7 @@ def make_symbolic_graph(self) -> None:
377
379
self .ssm ["design" , :, :] = pt .linalg .block_diag (* [Z for _ in range (k_endog )])
378
380
379
381
initial_states = self .make_and_register_variable (
380
- f"coefs_ { self .name } " ,
382
+ f"params_ { self .name } " ,
381
383
shape = (k_unique_states ,) if k_endog == 1 else (k_endog , k_unique_states ),
382
384
)
383
385
if k_endog == 1 :
@@ -506,7 +508,7 @@ def make_symbolic_graph(self) -> None:
506
508
self .ssm ["design" , :, :] = pt .linalg .block_diag (* [Z for _ in range (k_endog )])
507
509
508
510
init_state = self .make_and_register_variable (
509
- f"{ self .name } " , shape = (n_coefs ,) if k_endog == 1 else (k_endog , n_coefs )
511
+ f"params_ { self .name } " , shape = (n_coefs ,) if k_endog == 1 else (k_endog , n_coefs )
510
512
)
511
513
512
514
init_state_idx = np .concatenate (
@@ -535,19 +537,30 @@ def make_symbolic_graph(self) -> None:
535
537
def populate_component_properties (self ):
536
538
k_endog = self .k_endog
537
539
n_coefs = self .n_coefs
538
- k_states = self .k_states // k_endog
539
540
540
541
self .state_names = [
541
- f"{ f } _{ self . name } _{ i } [{ obs_state_name } ]"
542
+ f"{ f } _{ i } _{ self . name } [{ obs_state_name } ]"
542
543
for obs_state_name in self .observed_state_names
543
544
for i in range (self .n )
544
545
for f in ["Cos" , "Sin" ]
545
546
]
546
- self .param_names = [f"{ self .name } " ]
547
+ # determine which state names correspond to parameters
548
+ # all endog variables use same state structure, so we just need
549
+ # the first n_coefs state names (which may be less than total if saturated)
550
+ param_state_names = [f"{ f } _{ i } _{ self .name } " for i in range (self .n ) for f in ["Cos" , "Sin" ]][
551
+ :n_coefs
552
+ ]
553
+
554
+ self .param_names = [f"params_{ self .name } " ]
555
+
556
+ self .param_dims = {
557
+ f"params_{ self .name } " : (f"state_{ self .name } " ,)
558
+ if k_endog == 1
559
+ else (f"endog_{ self .name } " , f"state_{ self .name } " )
560
+ }
547
561
548
- self .param_dims = {self .name : (f"state_{ self .name } " ,)}
549
562
self .param_info = {
550
- f"{ self .name } " : {
563
+ f"params_ { self .name } " : {
551
564
"shape" : (n_coefs ,) if k_endog == 1 else (k_endog , n_coefs ),
552
565
"constraints" : None ,
553
566
"dims" : (f"state_{ self .name } " ,)
@@ -556,23 +569,22 @@ def populate_component_properties(self):
556
569
}
557
570
}
558
571
559
- # Regardless of whether the fourier basis are saturated, there will always be one symbolic state per basis.
560
- # That's why the self.states is just a simple loop over everything. But when saturated, one of those states
561
- # doesn't have an associated **parameter**, so the coords need to be adjusted to reflect this.
562
- init_state_idx = np .concatenate (
563
- [
564
- np .arange (k_states * i , (i + 1 ) * k_states , dtype = int )[:n_coefs ]
565
- for i in range (k_endog )
566
- ],
567
- axis = 0 ,
572
+ self .coords = (
573
+ {f"state_{ self .name } " : param_state_names }
574
+ if k_endog == 1
575
+ else {
576
+ f"endog_{ self .name } " : self .observed_state_names ,
577
+ f"state_{ self .name } " : param_state_names ,
578
+ }
568
579
)
569
- self .coords = {f"state_{ self .name } " : [self .state_names [i ] for i in init_state_idx ]}
570
580
571
581
if self .innovations :
572
- self .shock_names = self .state_names .copy ()
573
582
self .param_names += [f"sigma_{ self .name } " ]
583
+ self .shock_names = self .state_names .copy ()
574
584
self .param_info [f"sigma_{ self .name } " ] = {
575
- "shape" : () if k_endog == 1 else (k_endog , n_coefs ),
585
+ "shape" : () if k_endog == 1 else (k_endog ,),
576
586
"constraints" : "Positive" ,
577
587
"dims" : None if k_endog == 1 else (f"endog_{ self .name } " ,),
578
588
}
589
+ if k_endog > 1 :
590
+ self .param_dims [f"sigma_{ self .name } " ] = (f"endog_{ self .name } " ,)
0 commit comments