pymc-extras 0.3.1__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymc_extras/distributions/__init__.py +5 -5
- pymc_extras/distributions/histogram_utils.py +1 -1
- pymc_extras/inference/__init__.py +1 -1
- pymc_extras/inference/laplace_approx/find_map.py +12 -5
- pymc_extras/inference/laplace_approx/idata.py +4 -3
- pymc_extras/inference/laplace_approx/laplace.py +6 -4
- pymc_extras/inference/pathfinder/pathfinder.py +1 -2
- pymc_extras/printing.py +1 -1
- pymc_extras/statespace/__init__.py +4 -4
- pymc_extras/statespace/core/__init__.py +1 -1
- pymc_extras/statespace/core/representation.py +8 -8
- pymc_extras/statespace/core/statespace.py +94 -23
- pymc_extras/statespace/filters/__init__.py +3 -3
- pymc_extras/statespace/filters/kalman_filter.py +16 -11
- pymc_extras/statespace/models/SARIMAX.py +138 -74
- pymc_extras/statespace/models/VARMAX.py +248 -57
- pymc_extras/statespace/models/__init__.py +2 -2
- pymc_extras/statespace/models/structural/__init__.py +21 -0
- pymc_extras/statespace/models/structural/components/__init__.py +0 -0
- pymc_extras/statespace/models/structural/components/autoregressive.py +213 -0
- pymc_extras/statespace/models/structural/components/cycle.py +325 -0
- pymc_extras/statespace/models/structural/components/level_trend.py +289 -0
- pymc_extras/statespace/models/structural/components/measurement_error.py +154 -0
- pymc_extras/statespace/models/structural/components/regression.py +257 -0
- pymc_extras/statespace/models/structural/components/seasonality.py +628 -0
- pymc_extras/statespace/models/structural/core.py +919 -0
- pymc_extras/statespace/models/structural/utils.py +16 -0
- pymc_extras/statespace/models/utilities.py +285 -0
- pymc_extras/statespace/utils/constants.py +21 -18
- pymc_extras/statespace/utils/data_tools.py +4 -3
- {pymc_extras-0.3.1.dist-info → pymc_extras-0.4.1.dist-info}/METADATA +5 -4
- {pymc_extras-0.3.1.dist-info → pymc_extras-0.4.1.dist-info}/RECORD +34 -25
- pymc_extras/statespace/models/structural.py +0 -1679
- {pymc_extras-0.3.1.dist-info → pymc_extras-0.4.1.dist-info}/WHEEL +0 -0
- {pymc_extras-0.3.1.dist-info → pymc_extras-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pymc_extras.statespace.models.structural.core import Component
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MeasurementError(Component):
|
|
7
|
+
r"""
|
|
8
|
+
Measurement error component for structural time series models.
|
|
9
|
+
|
|
10
|
+
This component adds observation noise to the model by introducing a variance parameter
|
|
11
|
+
that affects the observation covariance matrix H. Unlike other components, it has no
|
|
12
|
+
hidden states and should only be used in combination with other components.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
name : str, optional
|
|
17
|
+
Name of the measurement error component. Default is "MeasurementError".
|
|
18
|
+
observed_state_names : list[str] | None, optional
|
|
19
|
+
Names of the observed variables. If None, defaults to ["data"].
|
|
20
|
+
share_states: bool, default False
|
|
21
|
+
Whether latent states are shared across the observed states. If True, there will be only one set of latent
|
|
22
|
+
states, which are observed by all observed states. If False, each observed state has its own set of
|
|
23
|
+
latent states. This argument has no effect if `k_endog` is 1.
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
The measurement error component models observation noise as:
|
|
28
|
+
|
|
29
|
+
.. math::
|
|
30
|
+
|
|
31
|
+
y_t = \text{signal}_t + \varepsilon_t, \quad \varepsilon_t \sim N(0, \sigma^2)
|
|
32
|
+
|
|
33
|
+
Where :math:`\text{signal}_t` is the true signal from other components and
|
|
34
|
+
:math:`\sigma^2` is the measurement error variance.
|
|
35
|
+
|
|
36
|
+
This component:
|
|
37
|
+
- Has no hidden states (k_states = 0)
|
|
38
|
+
- Has no innovations (k_posdef = 0)
|
|
39
|
+
- Adds a single parameter: sigma_{name}
|
|
40
|
+
- Modifies the observation covariance matrix H
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
**Basic usage with trend component:**
|
|
45
|
+
|
|
46
|
+
.. code:: python
|
|
47
|
+
|
|
48
|
+
from pymc_extras.statespace import structural as st
|
|
49
|
+
import pymc as pm
|
|
50
|
+
import pytensor.tensor as pt
|
|
51
|
+
|
|
52
|
+
trend = st.LevelTrendComponent(order=2, innovations_order=1)
|
|
53
|
+
error = st.MeasurementError()
|
|
54
|
+
|
|
55
|
+
ss_mod = (trend + error).build()
|
|
56
|
+
|
|
57
|
+
# Use with PyMC
|
|
58
|
+
with pm.Model(coords=ss_mod.coords) as model:
|
|
59
|
+
P0 = pm.Deterministic('P0', pt.eye(ss_mod.k_states) * 10, dims=ss_mod.param_dims['P0'])
|
|
60
|
+
initial_trend = pm.Normal('initial_trend', sigma=10, dims=ss_mod.param_dims['initial_trend'])
|
|
61
|
+
sigma_obs = pm.Exponential('sigma_obs', 1, dims=ss_mod.param_dims['sigma_obs'])
|
|
62
|
+
|
|
63
|
+
ss_mod.build_statespace_graph(data)
|
|
64
|
+
idata = pm.sample()
|
|
65
|
+
|
|
66
|
+
**Multivariate measurement error:**
|
|
67
|
+
|
|
68
|
+
.. code:: python
|
|
69
|
+
|
|
70
|
+
# For multiple observed variables
|
|
71
|
+
# This creates separate measurement error variances for each variable
|
|
72
|
+
# sigma_obs_error will have shape (3,) for the three variables
|
|
73
|
+
error = st.MeasurementError(
|
|
74
|
+
name="obs_error",
|
|
75
|
+
observed_state_names=["gdp", "unemployment", "inflation"]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
**Complete model example:**
|
|
79
|
+
|
|
80
|
+
.. code:: python
|
|
81
|
+
|
|
82
|
+
trend = st.LevelTrendComponent(order=2, innovations_order=1)
|
|
83
|
+
seasonal = st.TimeSeasonality(season_length=12, innovations=True)
|
|
84
|
+
error = st.MeasurementError()
|
|
85
|
+
|
|
86
|
+
model = (trend + seasonal + error).build()
|
|
87
|
+
|
|
88
|
+
# The model now includes:
|
|
89
|
+
# - Trend parameters: level_trend, sigma_trend
|
|
90
|
+
# - Seasonal parameters: seasonal_coefs, sigma_seasonal
|
|
91
|
+
# - Measurement error parameter: sigma_obs
|
|
92
|
+
|
|
93
|
+
See Also
|
|
94
|
+
--------
|
|
95
|
+
Component : Base class for all structural components.
|
|
96
|
+
StructuralTimeSeries : Complete model class.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
name: str = "MeasurementError",
|
|
102
|
+
observed_state_names: list[str] | None = None,
|
|
103
|
+
share_states: bool = False,
|
|
104
|
+
):
|
|
105
|
+
if observed_state_names is None:
|
|
106
|
+
observed_state_names = ["data"]
|
|
107
|
+
|
|
108
|
+
self.share_states = share_states
|
|
109
|
+
|
|
110
|
+
k_endog = len(observed_state_names)
|
|
111
|
+
k_states = 0
|
|
112
|
+
k_posdef = 0
|
|
113
|
+
|
|
114
|
+
super().__init__(
|
|
115
|
+
name,
|
|
116
|
+
k_endog,
|
|
117
|
+
k_states,
|
|
118
|
+
k_posdef,
|
|
119
|
+
measurement_error=True,
|
|
120
|
+
combine_hidden_states=False,
|
|
121
|
+
observed_state_names=observed_state_names,
|
|
122
|
+
share_states=share_states,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def populate_component_properties(self):
|
|
126
|
+
k_endog = self.k_endog
|
|
127
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
128
|
+
|
|
129
|
+
self.param_names = [f"sigma_{self.name}"]
|
|
130
|
+
self.param_dims = {}
|
|
131
|
+
self.coords = {}
|
|
132
|
+
|
|
133
|
+
if k_endog_effective > 1:
|
|
134
|
+
self.param_dims[f"sigma_{self.name}"] = (f"endog_{self.name}",)
|
|
135
|
+
self.coords[f"endog_{self.name}"] = self.observed_state_names
|
|
136
|
+
|
|
137
|
+
self.param_info = {
|
|
138
|
+
f"sigma_{self.name}": {
|
|
139
|
+
"shape": (k_endog_effective,) if k_endog_effective > 1 else (),
|
|
140
|
+
"constraints": "Positive",
|
|
141
|
+
"dims": (f"endog_{self.name}",) if k_endog_effective > 1 else None,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def make_symbolic_graph(self) -> None:
|
|
146
|
+
k_endog = self.k_endog
|
|
147
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
148
|
+
|
|
149
|
+
sigma_shape = () if k_endog_effective == 1 else (k_endog_effective,)
|
|
150
|
+
error_sigma = self.make_and_register_variable(f"sigma_{self.name}", shape=sigma_shape)
|
|
151
|
+
|
|
152
|
+
diag_idx = np.diag_indices(self.k_endog)
|
|
153
|
+
idx = np.s_["obs_cov", diag_idx[0], diag_idx[1]]
|
|
154
|
+
self.ssm[idx] = error_sigma**2
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pytensor import tensor as pt
|
|
4
|
+
|
|
5
|
+
from pymc_extras.statespace.models.structural.core import Component
|
|
6
|
+
from pymc_extras.statespace.utils.constants import TIME_DIM
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RegressionComponent(Component):
|
|
10
|
+
r"""
|
|
11
|
+
Regression component for exogenous variables in a structural time series model
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
k_exog : int | None, default None
|
|
16
|
+
Number of exogenous variables to include in the regression. Must be specified if
|
|
17
|
+
state_names is not provided.
|
|
18
|
+
|
|
19
|
+
name : str | None, default "regression"
|
|
20
|
+
A name for this regression component. Used to label dimensions and coordinates.
|
|
21
|
+
|
|
22
|
+
state_names : list[str] | None, default None
|
|
23
|
+
List of strings for regression coefficient labels. If provided, must be of length
|
|
24
|
+
k_exog. If None and k_exog is provided, coefficients will be named
|
|
25
|
+
"{name}_1, {name}_2, ...".
|
|
26
|
+
|
|
27
|
+
observed_state_names : list[str] | None, default None
|
|
28
|
+
List of strings for observed state labels. If None, defaults to ["data"].
|
|
29
|
+
|
|
30
|
+
innovations : bool, default False
|
|
31
|
+
Whether to include stochastic innovations in the regression coefficients,
|
|
32
|
+
allowing them to vary over time. If True, coefficients follow a random walk.
|
|
33
|
+
|
|
34
|
+
share_states: bool, default False
|
|
35
|
+
Whether latent states are shared across the observed states. If True, there will be only one set of latent
|
|
36
|
+
states, which are observed by all observed states. If False, each observed state has its own set of
|
|
37
|
+
latent states.
|
|
38
|
+
|
|
39
|
+
Notes
|
|
40
|
+
-----
|
|
41
|
+
This component implements regression with exogenous variables in a structural time series
|
|
42
|
+
model. The regression component can be expressed as:
|
|
43
|
+
|
|
44
|
+
.. math::
|
|
45
|
+
y_t = \beta_t^T x_t + \epsilon_t
|
|
46
|
+
|
|
47
|
+
Where :math:`y_t` is the dependent variable, :math:`x_t` is the vector of exogenous
|
|
48
|
+
variables, :math:`\beta_t` is the vector of regression coefficients, and :math:`\epsilon_t`
|
|
49
|
+
is the error term.
|
|
50
|
+
|
|
51
|
+
When ``innovations=False`` (default), the coefficients are constant over time:
|
|
52
|
+
:math:`\beta_t = \beta_0` for all t.
|
|
53
|
+
|
|
54
|
+
When ``innovations=True``, the coefficients follow a random walk:
|
|
55
|
+
:math:`\beta_{t+1} = \beta_t + \eta_t`, where :math:`\eta_t \sim N(0, \Sigma_\beta)`.
|
|
56
|
+
|
|
57
|
+
The component supports both univariate and multivariate regression. In the multivariate
|
|
58
|
+
case, separate coefficients are estimated for each endogenous variable (i.e time series).
|
|
59
|
+
|
|
60
|
+
Examples
|
|
61
|
+
--------
|
|
62
|
+
Simple regression with constant coefficients:
|
|
63
|
+
|
|
64
|
+
.. code:: python
|
|
65
|
+
|
|
66
|
+
from pymc_extras.statespace import structural as st
|
|
67
|
+
import pymc as pm
|
|
68
|
+
import pytensor.tensor as pt
|
|
69
|
+
|
|
70
|
+
trend = st.LevelTrendComponent(order=1, innovations_order=1)
|
|
71
|
+
regression = st.RegressionComponent(k_exog=2, state_names=['intercept', 'slope'])
|
|
72
|
+
ss_mod = (trend + regression).build()
|
|
73
|
+
|
|
74
|
+
with pm.Model(coords=ss_mod.coords) as model:
|
|
75
|
+
# Prior for regression coefficients
|
|
76
|
+
betas = pm.Normal('betas', dims=ss_mod.param_dims['beta_regression'])
|
|
77
|
+
|
|
78
|
+
# Prior for trend innovations
|
|
79
|
+
sigma_trend = pm.Exponential('sigma_trend', 1)
|
|
80
|
+
|
|
81
|
+
ss_mod.build_statespace_graph(data)
|
|
82
|
+
idata = pm.sample()
|
|
83
|
+
|
|
84
|
+
Multivariate regression with time-varying coefficients:
|
|
85
|
+
- There are 2 exogenous variables (price and income effects)
|
|
86
|
+
- There are 2 endogenous variables (sales and revenue)
|
|
87
|
+
- The regression coefficients are allowed to vary over time (`innovations=True`)
|
|
88
|
+
|
|
89
|
+
.. code:: python
|
|
90
|
+
|
|
91
|
+
regression = st.RegressionComponent(
|
|
92
|
+
k_exog=2,
|
|
93
|
+
state_names=['price_effect', 'income_effect'],
|
|
94
|
+
observed_state_names=['sales', 'revenue'],
|
|
95
|
+
innovations=True
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
with pm.Model(coords=ss_mod.coords) as model:
|
|
99
|
+
betas = pm.Normal('betas', dims=ss_mod.param_dims['beta_regression'])
|
|
100
|
+
|
|
101
|
+
# Innovation variance for time-varying coefficients
|
|
102
|
+
sigma_beta = pm.Exponential('sigma_beta', 1, dims=ss_mod.param_dims['sigma_beta_regression'])
|
|
103
|
+
|
|
104
|
+
ss_mod.build_statespace_graph(data)
|
|
105
|
+
idata = pm.sample()
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
k_exog: int | None = None,
|
|
111
|
+
name: str | None = "regression",
|
|
112
|
+
state_names: list[str] | None = None,
|
|
113
|
+
observed_state_names: list[str] | None = None,
|
|
114
|
+
innovations=False,
|
|
115
|
+
share_states: bool = False,
|
|
116
|
+
):
|
|
117
|
+
self.share_states = share_states
|
|
118
|
+
|
|
119
|
+
if observed_state_names is None:
|
|
120
|
+
observed_state_names = ["data"]
|
|
121
|
+
|
|
122
|
+
self.innovations = innovations
|
|
123
|
+
k_exog = self._handle_input_data(k_exog, state_names, name)
|
|
124
|
+
|
|
125
|
+
k_states = k_exog
|
|
126
|
+
k_endog = len(observed_state_names)
|
|
127
|
+
k_posdef = k_exog
|
|
128
|
+
|
|
129
|
+
super().__init__(
|
|
130
|
+
name=name,
|
|
131
|
+
k_endog=k_endog,
|
|
132
|
+
k_states=k_states * k_endog if not share_states else k_states,
|
|
133
|
+
k_posdef=k_posdef * k_endog if not share_states else k_posdef,
|
|
134
|
+
state_names=self.state_names,
|
|
135
|
+
share_states=share_states,
|
|
136
|
+
observed_state_names=observed_state_names,
|
|
137
|
+
measurement_error=False,
|
|
138
|
+
combine_hidden_states=False,
|
|
139
|
+
exog_names=[f"data_{name}"],
|
|
140
|
+
obs_state_idxs=np.ones(k_states),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _get_state_names(k_exog: int | None, state_names: list[str] | None, name: str):
|
|
145
|
+
if k_exog is None and state_names is None:
|
|
146
|
+
raise ValueError("Must specify at least one of k_exog or state_names")
|
|
147
|
+
if state_names is not None and k_exog is not None:
|
|
148
|
+
if len(state_names) != k_exog:
|
|
149
|
+
raise ValueError(f"Expected {k_exog} state names, found {len(state_names)}")
|
|
150
|
+
elif k_exog is None:
|
|
151
|
+
k_exog = len(state_names)
|
|
152
|
+
else:
|
|
153
|
+
state_names = [f"{name}_{i + 1}" for i in range(k_exog)]
|
|
154
|
+
|
|
155
|
+
return k_exog, state_names
|
|
156
|
+
|
|
157
|
+
def _handle_input_data(self, k_exog: int, state_names: list[str] | None, name) -> int:
|
|
158
|
+
k_exog, state_names = self._get_state_names(k_exog, state_names, name)
|
|
159
|
+
self.state_names = state_names
|
|
160
|
+
|
|
161
|
+
return k_exog
|
|
162
|
+
|
|
163
|
+
def make_symbolic_graph(self) -> None:
|
|
164
|
+
k_endog = self.k_endog
|
|
165
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
166
|
+
|
|
167
|
+
k_states = self.k_states // k_endog_effective
|
|
168
|
+
|
|
169
|
+
betas = self.make_and_register_variable(
|
|
170
|
+
f"beta_{self.name}", shape=(k_endog, k_states) if k_endog_effective > 1 else (k_states,)
|
|
171
|
+
)
|
|
172
|
+
regression_data = self.make_and_register_data(f"data_{self.name}", shape=(None, k_states))
|
|
173
|
+
|
|
174
|
+
self.ssm["initial_state", :] = betas.ravel()
|
|
175
|
+
self.ssm["transition", :, :] = pt.eye(self.k_states)
|
|
176
|
+
self.ssm["selection", :, :] = pt.eye(self.k_states)
|
|
177
|
+
|
|
178
|
+
if self.share_states:
|
|
179
|
+
self.ssm["design"] = pt.specify_shape(
|
|
180
|
+
pt.join(1, *[pt.expand_dims(regression_data, 1) for _ in range(k_endog)]),
|
|
181
|
+
(None, k_endog, self.k_states),
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
Z = pt.linalg.block_diag(*[pt.expand_dims(regression_data, 1) for _ in range(k_endog)])
|
|
185
|
+
self.ssm["design"] = pt.specify_shape(
|
|
186
|
+
Z, (None, k_endog, regression_data.type.shape[1] * k_endog)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if self.innovations:
|
|
190
|
+
sigma_beta = self.make_and_register_variable(
|
|
191
|
+
f"sigma_beta_{self.name}",
|
|
192
|
+
(k_states,) if k_endog_effective == 1 else (k_endog, k_states),
|
|
193
|
+
)
|
|
194
|
+
row_idx, col_idx = np.diag_indices(self.k_states)
|
|
195
|
+
self.ssm["state_cov", row_idx, col_idx] = sigma_beta.ravel() ** 2
|
|
196
|
+
|
|
197
|
+
def populate_component_properties(self) -> None:
|
|
198
|
+
k_endog = self.k_endog
|
|
199
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
200
|
+
|
|
201
|
+
k_states = self.k_states // k_endog_effective
|
|
202
|
+
|
|
203
|
+
if self.share_states:
|
|
204
|
+
self.shock_names = [f"{state_name}_shared" for state_name in self.state_names]
|
|
205
|
+
else:
|
|
206
|
+
self.shock_names = self.state_names
|
|
207
|
+
|
|
208
|
+
self.param_names = [f"beta_{self.name}"]
|
|
209
|
+
self.data_names = [f"data_{self.name}"]
|
|
210
|
+
self.param_dims = {
|
|
211
|
+
f"beta_{self.name}": (f"endog_{self.name}", f"state_{self.name}")
|
|
212
|
+
if k_endog_effective > 1
|
|
213
|
+
else (f"state_{self.name}",)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
base_names = self.state_names
|
|
217
|
+
|
|
218
|
+
if self.share_states:
|
|
219
|
+
self.state_names = [f"{name}[{self.name}_shared]" for name in base_names]
|
|
220
|
+
else:
|
|
221
|
+
self.state_names = [
|
|
222
|
+
f"{name}[{obs_name}]"
|
|
223
|
+
for obs_name in self.observed_state_names
|
|
224
|
+
for name in base_names
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
self.param_info = {
|
|
228
|
+
f"beta_{self.name}": {
|
|
229
|
+
"shape": (k_endog_effective, k_states) if k_endog_effective > 1 else (k_states,),
|
|
230
|
+
"constraints": None,
|
|
231
|
+
"dims": (f"endog_{self.name}", f"state_{self.name}")
|
|
232
|
+
if k_endog_effective > 1
|
|
233
|
+
else (f"state_{self.name}",),
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
self.data_info = {
|
|
238
|
+
f"data_{self.name}": {
|
|
239
|
+
"shape": (None, k_states),
|
|
240
|
+
"dims": (TIME_DIM, f"state_{self.name}"),
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
self.coords = {
|
|
244
|
+
f"state_{self.name}": base_names,
|
|
245
|
+
f"endog_{self.name}": self.observed_state_names,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if self.innovations:
|
|
249
|
+
self.param_names += [f"sigma_beta_{self.name}"]
|
|
250
|
+
self.param_dims[f"sigma_beta_{self.name}"] = (f"state_{self.name}",)
|
|
251
|
+
self.param_info[f"sigma_beta_{self.name}"] = {
|
|
252
|
+
"shape": (k_states,),
|
|
253
|
+
"constraints": "Positive",
|
|
254
|
+
"dims": (f"state_{self.name}",)
|
|
255
|
+
if k_endog_effective == 1
|
|
256
|
+
else (f"endog_{self.name}", f"state_{self.name}"),
|
|
257
|
+
}
|