pymc-extras 0.7.0__py3-none-any.whl → 0.8.0__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.
Files changed (25) hide show
  1. pymc_extras/inference/laplace_approx/laplace.py +2 -2
  2. pymc_extras/inference/pathfinder/pathfinder.py +1 -1
  3. pymc_extras/prior.py +3 -3
  4. pymc_extras/statespace/core/properties.py +276 -0
  5. pymc_extras/statespace/core/statespace.py +180 -44
  6. pymc_extras/statespace/filters/distributions.py +12 -29
  7. pymc_extras/statespace/filters/kalman_filter.py +1 -1
  8. pymc_extras/statespace/models/DFM.py +179 -168
  9. pymc_extras/statespace/models/ETS.py +177 -151
  10. pymc_extras/statespace/models/SARIMAX.py +149 -152
  11. pymc_extras/statespace/models/VARMAX.py +134 -145
  12. pymc_extras/statespace/models/__init__.py +8 -1
  13. pymc_extras/statespace/models/structural/__init__.py +30 -8
  14. pymc_extras/statespace/models/structural/components/autoregressive.py +87 -45
  15. pymc_extras/statespace/models/structural/components/cycle.py +119 -80
  16. pymc_extras/statespace/models/structural/components/level_trend.py +95 -42
  17. pymc_extras/statespace/models/structural/components/measurement_error.py +27 -17
  18. pymc_extras/statespace/models/structural/components/regression.py +105 -68
  19. pymc_extras/statespace/models/structural/components/seasonality.py +138 -100
  20. pymc_extras/statespace/models/structural/core.py +397 -286
  21. pymc_extras/statespace/models/utilities.py +5 -20
  22. {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/METADATA +3 -3
  23. {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/RECORD +25 -24
  24. {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/WHEEL +0 -0
  25. {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,21 @@
1
+ import warnings
2
+
1
3
  import numpy as np
2
4
 
3
5
  from pytensor import tensor as pt
4
- from pytensor.tensor.slinalg import block_diag
5
-
6
+ from pytensor.tensor.linalg import block_diag
7
+
8
+ from pymc_extras.statespace.core.properties import (
9
+ Coord,
10
+ Parameter,
11
+ Shock,
12
+ State,
13
+ )
6
14
  from pymc_extras.statespace.models.structural.core import Component
7
15
  from pymc_extras.statespace.models.structural.utils import _frequency_transition_block
8
16
 
9
17
 
10
- class CycleComponent(Component):
18
+ class Cycle(Component):
11
19
  r"""
12
20
  A component for modeling longer-term cyclical effects
13
21
 
@@ -64,7 +72,7 @@ class CycleComponent(Component):
64
72
  effects, such as business cycles, and that the seasonal component be used for shorter term effects, such as
65
73
  weekly or monthly seasonality.
66
74
 
67
- Unlike a FrequencySeasonality component, the length of a CycleComponent can be estimated.
75
+ Unlike a FrequencySeasonality component, the length of a Cycle can be estimated.
68
76
 
69
77
  **Multivariate Support:**
70
78
  For multivariate time series with k endogenous variables, the component creates:
@@ -89,8 +97,8 @@ class CycleComponent(Component):
89
97
  data = np.random.normal(size=(100, 1))
90
98
 
91
99
  # Build the structural model
92
- grw = st.LevelTrendComponent(order=1, innovations_order=1)
93
- cycle = st.CycleComponent(
100
+ grw = st.LevelTrend(order=1, innovations_order=1)
101
+ cycle = st.Cycle(
94
102
  "business_cycle", cycle_length=12, estimate_cycle_length=False, innovations=True, dampen=True
95
103
  )
96
104
  ss_mod = (grw + cycle).build()
@@ -117,7 +125,7 @@ class CycleComponent(Component):
117
125
  .. code:: python
118
126
 
119
127
  # Multivariate cycle component
120
- cycle = st.CycleComponent(
128
+ cycle = st.Cycle(
121
129
  name='business_cycle',
122
130
  cycle_length=12,
123
131
  estimate_cycle_length=False,
@@ -188,18 +196,111 @@ class CycleComponent(Component):
188
196
  obs_state_idx = np.zeros(k_states)
189
197
  obs_state_idx[slice(0, k_states, 2)] = 1
190
198
 
199
+ state_names = [f"{f}_{name}" for f in ["Cos", "Sin"]]
200
+
191
201
  super().__init__(
192
202
  name=name,
193
203
  k_endog=k_endog,
194
204
  k_states=k_states,
195
205
  k_posdef=k_posdef,
206
+ base_state_names=state_names,
196
207
  measurement_error=False,
197
208
  combine_hidden_states=True,
198
209
  obs_state_idxs=obs_state_idx,
199
- observed_state_names=observed_state_names,
210
+ base_observed_state_names=observed_state_names,
200
211
  share_states=share_states,
201
212
  )
202
213
 
214
+ def set_states(self) -> State | tuple[State, ...] | None:
215
+ k_endog_effective = 1 if self.share_states else self.k_endog
216
+
217
+ base_names = self.base_state_names
218
+ observed_state_names = self.base_observed_state_names
219
+
220
+ if self.share_states:
221
+ state_names = [f"{name}[shared]" for name in base_names]
222
+ else:
223
+ state_names = [
224
+ f"{name}[{var_name}]" if k_endog_effective > 1 else name
225
+ for var_name in observed_state_names
226
+ for name in base_names
227
+ ]
228
+ hidden_states = [State(name=name, observed=False, shared=True) for name in state_names]
229
+ observed_states = [
230
+ State(name=name, observed=True, shared=False) for name in observed_state_names
231
+ ]
232
+ return *hidden_states, *observed_states
233
+
234
+ def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
235
+ k_endog = self.k_endog
236
+ k_endog_effective = 1 if self.share_states else k_endog
237
+
238
+ cycle_param = Parameter(
239
+ name=f"params_{self.name}",
240
+ shape=(2,) if k_endog_effective == 1 else (k_endog_effective, 2),
241
+ dims=(f"state_{self.name}",)
242
+ if k_endog_effective == 1
243
+ else (f"endog_{self.name}", f"state_{self.name}"),
244
+ constraints=None,
245
+ )
246
+
247
+ params_container = [cycle_param]
248
+
249
+ if self.estimate_cycle_length:
250
+ length_param = Parameter(
251
+ name=f"length_{self.name}",
252
+ shape=() if k_endog_effective == 1 else (k_endog_effective,),
253
+ dims=None if k_endog_effective == 1 else (f"endog_{self.name}",),
254
+ constraints="Positive, non-zero",
255
+ )
256
+ params_container.append(length_param)
257
+
258
+ if self.dampen:
259
+ dampen_param = Parameter(
260
+ name=f"dampening_factor_{self.name}",
261
+ shape=() if k_endog_effective == 1 else (k_endog_effective,),
262
+ dims=None if k_endog_effective == 1 else (f"endog_{self.name}",),
263
+ constraints="0 < x ≤ 1",
264
+ )
265
+ params_container.append(dampen_param)
266
+
267
+ if self.innovations:
268
+ sigma_param = Parameter(
269
+ name=f"sigma_{self.name}",
270
+ shape=() if k_endog_effective == 1 else (k_endog_effective,),
271
+ dims=None if k_endog_effective == 1 else (f"endog_{self.name}",),
272
+ constraints="Positive",
273
+ )
274
+ params_container.append(sigma_param)
275
+
276
+ return tuple(params_container)
277
+
278
+ def set_shocks(self) -> Shock | tuple[Shock, ...] | None:
279
+ if self.innovations:
280
+ return tuple(Shock(name=name) for name in self.state_names)
281
+ return None
282
+
283
+ def set_coords(self) -> Coord | tuple[Coord, ...] | None:
284
+ k_endog = self.k_endog
285
+ k_endog_effective = 1 if self.share_states else k_endog
286
+ base_names = tuple(f"{f}_{self.name}" for f in ["Cos", "Sin"])
287
+ observed_state_names = self.observed_state_names
288
+
289
+ state_coords = Coord(
290
+ dimension=f"state_{self.name}",
291
+ labels=base_names
292
+ if k_endog_effective == 1
293
+ else (f"Cos_{self.name}", f"Sin_{self.name}"),
294
+ )
295
+
296
+ coord_container = [state_coords]
297
+
298
+ if k_endog_effective != 1:
299
+ endog_coords = Coord(dimension=f"endog_{self.name}", labels=observed_state_names)
300
+ coord_container.append(endog_coords)
301
+
302
+ return tuple(coord_container)
303
+
203
304
  def make_symbolic_graph(self) -> None:
204
305
  k_endog = self.k_endog
205
306
  k_endog_effective = 1 if self.share_states else k_endog
@@ -250,76 +351,14 @@ class CycleComponent(Component):
250
351
  # explicitly set state cov to 0 when no innovations
251
352
  self.ssm["state_cov", :, :] = pt.zeros((self.k_posdef, self.k_posdef))
252
353
 
253
- def populate_component_properties(self):
254
- k_endog = self.k_endog
255
- k_endog_effective = 1 if self.share_states else k_endog
256
-
257
- base_names = [f"{f}_{self.name}" for f in ["Cos", "Sin"]]
258
354
 
259
- if self.share_states:
260
- self.state_names = [f"{name}[shared]" for name in base_names]
261
- else:
262
- self.state_names = [
263
- f"{name}[{var_name}]" if k_endog_effective > 1 else name
264
- for var_name in self.observed_state_names
265
- for name in base_names
266
- ]
267
-
268
- self.param_names = [f"params_{self.name}"]
269
-
270
- if k_endog_effective == 1:
271
- self.param_dims = {f"params_{self.name}": (f"state_{self.name}",)}
272
- self.coords = {f"state_{self.name}": base_names}
273
- self.param_info = {
274
- f"params_{self.name}": {
275
- "shape": (2,),
276
- "constraints": None,
277
- "dims": (f"state_{self.name}",),
278
- }
279
- }
280
- else:
281
- self.param_dims = {f"params_{self.name}": (f"endog_{self.name}", f"state_{self.name}")}
282
- self.coords = {
283
- f"state_{self.name}": [f"Cos_{self.name}", f"Sin_{self.name}"],
284
- f"endog_{self.name}": self.observed_state_names,
285
- }
286
- self.param_info = {
287
- f"params_{self.name}": {
288
- "shape": (k_endog_effective, 2),
289
- "constraints": None,
290
- "dims": (f"endog_{self.name}", f"state_{self.name}"),
291
- }
292
- }
293
-
294
- if self.estimate_cycle_length:
295
- self.param_names += [f"length_{self.name}"]
296
- self.param_info[f"length_{self.name}"] = {
297
- "shape": () if k_endog_effective == 1 else (k_endog_effective,),
298
- "constraints": "Positive, non-zero",
299
- "dims": None if k_endog_effective == 1 else (f"endog_{self.name}",),
300
- }
301
-
302
- if self.dampen:
303
- self.param_names += [f"dampening_factor_{self.name}"]
304
- self.param_info[f"dampening_factor_{self.name}"] = {
305
- "shape": () if k_endog_effective == 1 else (k_endog_effective,),
306
- "constraints": "0 < x ≤ 1",
307
- "dims": None if k_endog_effective == 1 else (f"endog_{self.name}",),
308
- }
309
-
310
- if self.innovations:
311
- self.param_names += [f"sigma_{self.name}"]
312
- if k_endog_effective == 1:
313
- self.param_info[f"sigma_{self.name}"] = {
314
- "shape": (),
315
- "constraints": "Positive",
316
- "dims": None,
317
- }
318
- else:
319
- self.param_dims[f"sigma_{self.name}"] = (f"endog_{self.name}",)
320
- self.param_info[f"sigma_{self.name}"] = {
321
- "shape": (k_endog_effective,),
322
- "constraints": "Positive",
323
- "dims": (f"endog_{self.name}",),
324
- }
325
- self.shock_names = self.state_names.copy()
355
+ def __getattr__(name: str):
356
+ if name == "CycleComponent":
357
+ warnings.warn(
358
+ "CycleComponent is deprecated and will be removed in a future release. "
359
+ "Use Cycle instead.",
360
+ FutureWarning,
361
+ stacklevel=2,
362
+ )
363
+ return Cycle
364
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1,12 +1,20 @@
1
+ import warnings
2
+
1
3
  import numpy as np
2
4
  import pytensor.tensor as pt
3
5
 
6
+ from pymc_extras.statespace.core.properties import (
7
+ Coord,
8
+ Parameter,
9
+ Shock,
10
+ State,
11
+ )
4
12
  from pymc_extras.statespace.models.structural.core import Component
5
13
  from pymc_extras.statespace.models.structural.utils import order_to_mask
6
14
  from pymc_extras.statespace.utils.constants import POSITION_DERIVATIVE_NAMES
7
15
 
8
16
 
9
- class LevelTrendComponent(Component):
17
+ class LevelTrend(Component):
10
18
  r"""
11
19
  Level and trend component of a structural time series model
12
20
 
@@ -145,6 +153,7 @@ class LevelTrendComponent(Component):
145
153
  f"lower order model, explicitly omit the zeros."
146
154
  )
147
155
  k_states = max_state
156
+ state_names = POSITION_DERIVATIVE_NAMES[:max_state]
148
157
 
149
158
  if isinstance(innovations_order, int):
150
159
  n = innovations_order
@@ -164,7 +173,8 @@ class LevelTrendComponent(Component):
164
173
  k_endog=k_endog,
165
174
  k_states=k_states * k_endog if not share_states else k_states,
166
175
  k_posdef=k_posdef * k_endog if not share_states else k_posdef,
167
- observed_state_names=observed_state_names,
176
+ base_state_names=state_names,
177
+ base_observed_state_names=observed_state_names,
168
178
  measurement_error=False,
169
179
  combine_hidden_states=False,
170
180
  obs_state_idxs=np.tile(
@@ -173,71 +183,102 @@ class LevelTrendComponent(Component):
173
183
  share_states=share_states,
174
184
  )
175
185
 
176
- def populate_component_properties(self):
177
- k_endog = self.k_endog
178
- k_endog_effective = 1 if self.share_states else k_endog
179
-
180
- k_states = self.k_states // k_endog_effective
181
- k_posdef = self.k_posdef // k_endog_effective
182
-
183
- name_slice = POSITION_DERIVATIVE_NAMES[:k_states]
184
- self.param_names = [f"initial_{self.name}"]
185
- base_names = [name for name, mask in zip(name_slice, self._order_mask) if mask]
186
+ def set_states(self) -> State | tuple[State, ...] | None:
187
+ observed_state_names = self.base_observed_state_names
186
188
 
187
189
  if self.share_states:
188
- self.state_names = [f"{name}[{self.name}_shared]" for name in base_names]
190
+ state_names = [f"{name}[{self.name}_shared]" for name in self.base_state_names]
189
191
  else:
190
- self.state_names = [
192
+ state_names = [
191
193
  f"{name}[{obs_name}]"
192
- for obs_name in self.observed_state_names
193
- for name in base_names
194
+ for obs_name in observed_state_names
195
+ for name in self.base_state_names
194
196
  ]
195
197
 
196
- self.param_dims = {f"initial_{self.name}": (f"state_{self.name}",)}
197
- self.coords = {f"state_{self.name}": base_names}
198
+ hidden_states = [State(name=name, observed=False, shared=True) for name in state_names]
199
+ observed_states = [
200
+ State(name=name, observed=True, shared=False) for name in observed_state_names
201
+ ]
202
+ return *hidden_states, *observed_states
198
203
 
199
- if k_endog > 1:
200
- self.coords[f"endog_{self.name}"] = self.observed_state_names
204
+ def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
205
+ k_endog = self.k_endog
206
+ k_endog_effective = 1 if self.share_states else k_endog
201
207
 
202
- if k_endog_effective > 1:
203
- self.param_dims[f"state_{self.name}"] = (
204
- f"endog_{self.name}",
205
- f"state_{self.name}",
206
- )
207
- self.param_dims = {f"initial_{self.name}": (f"endog_{self.name}", f"state_{self.name}")}
208
+ k_states = self.k_states // k_endog_effective
209
+ k_posdef = self.k_posdef // k_endog_effective
208
210
 
209
- shape = (k_endog_effective, k_states) if k_endog_effective > 1 else (k_states,)
210
- self.param_info = {f"initial_{self.name}": {"shape": shape, "constraints": None}}
211
+ initial_param = Parameter(
212
+ name=f"initial_{self.name}",
213
+ shape=(k_endog_effective, k_states) if k_endog_effective > 1 else (k_states,),
214
+ dims=(f"endog_{self.name}", f"state_{self.name}")
215
+ if k_endog_effective > 1
216
+ else (f"state_{self.name}",),
217
+ constraints=None,
218
+ )
211
219
 
212
220
  if self.k_posdef > 0:
213
- self.param_names += [f"sigma_{self.name}"]
221
+ sigma_param = Parameter(
222
+ name=f"sigma_{self.name}",
223
+ shape=(k_posdef,) if k_endog_effective == 1 else (k_endog_effective, k_posdef),
224
+ dims=(f"shock_{self.name}",)
225
+ if k_endog_effective == 1
226
+ else (f"endog_{self.name}", f"shock_{self.name}"),
227
+ constraints="Positive",
228
+ )
229
+ return initial_param, sigma_param
230
+ else:
231
+ return (initial_param,)
214
232
 
233
+ def set_shocks(self) -> Shock | tuple[Shock, ...] | None:
234
+ k_endog = self.k_endog
235
+ k_endog_effective = 1 if self.share_states else k_endog
236
+ k_states = self.k_states // k_endog_effective
237
+ name_slice = POSITION_DERIVATIVE_NAMES[:k_states]
238
+
239
+ if self.k_posdef > 0:
215
240
  base_shock_names = [
216
241
  name for name, mask in zip(name_slice, self.innovations_order) if mask
217
242
  ]
218
243
 
219
244
  if self.share_states:
220
- self.shock_names = [f"{name}[{self.name}_shared]" for name in base_shock_names]
245
+ shock_names = [f"{name}[{self.name}_shared]" for name in base_shock_names]
221
246
  else:
222
- self.shock_names = [
247
+ shock_names = [
223
248
  f"{name}[{obs_name}]"
224
249
  for obs_name in self.observed_state_names
225
250
  for name in base_shock_names
226
251
  ]
227
252
 
228
- self.param_dims[f"sigma_{self.name}"] = (
229
- (f"shock_{self.name}",)
230
- if k_endog_effective == 1
231
- else (f"endog_{self.name}", f"shock_{self.name}")
253
+ return tuple(Shock(name=name) for name in shock_names)
254
+ return None
255
+
256
+ def set_coords(self) -> Coord | tuple[Coord, ...] | None:
257
+ k_endog = self.k_endog
258
+ k_endog_effective = 1 if self.share_states else k_endog
259
+ k_states = self.k_states // k_endog_effective
260
+ name_slice = POSITION_DERIVATIVE_NAMES[:k_states]
261
+
262
+ base_names = [name for name, mask in zip(name_slice, self._order_mask) if mask]
263
+
264
+ base_shock_names = [name for name, mask in zip(name_slice, self.innovations_order) if mask]
265
+
266
+ state_coord = Coord(dimension=f"state_{self.name}", labels=tuple(base_names))
267
+
268
+ coords_container = [state_coord]
269
+
270
+ if k_endog > 1:
271
+ endog_coord = Coord(
272
+ dimension=f"endog_{self.name}",
273
+ labels=self.observed_state_names,
232
274
  )
233
- self.coords[f"shock_{self.name}"] = base_shock_names
234
- self.param_info[f"sigma_{self.name}"] = {
235
- "shape": (k_posdef,) if k_endog_effective == 1 else (k_endog_effective, k_posdef),
236
- "constraints": "Positive",
237
- }
275
+ coords_container.append(endog_coord)
238
276
 
239
- for name in self.param_names:
240
- self.param_info[name]["dims"] = self.param_dims[name]
277
+ if self.k_posdef > 0:
278
+ shock_coord = Coord(dimension=f"shock_{self.name}", labels=tuple(base_shock_names))
279
+ coords_container.append(shock_coord)
280
+
281
+ return tuple(coords_container)
241
282
 
242
283
  def make_symbolic_graph(self) -> None:
243
284
  k_endog = self.k_endog
@@ -287,3 +328,15 @@ class LevelTrendComponent(Component):
287
328
  diag_idx = np.diag_indices(k_posdef * k_endog_effective)
288
329
  idx = np.s_["state_cov", diag_idx[0], diag_idx[1]]
289
330
  self.ssm[idx] = (sigma_trend**2).ravel()
331
+
332
+
333
+ def __getattr__(name: str):
334
+ if name == "LevelTrendComponent":
335
+ warnings.warn(
336
+ "LevelTrendComponent is deprecated and will be removed in a future release. "
337
+ "Use LevelTrend instead.",
338
+ FutureWarning,
339
+ stacklevel=2,
340
+ )
341
+ return LevelTrend
342
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1,5 +1,9 @@
1
1
  import numpy as np
2
2
 
3
+ from pymc_extras.statespace.core.properties import (
4
+ Coord,
5
+ Parameter,
6
+ )
3
7
  from pymc_extras.statespace.models.structural.core import Component
4
8
 
5
9
 
@@ -49,7 +53,7 @@ class MeasurementError(Component):
49
53
  import pymc as pm
50
54
  import pytensor.tensor as pt
51
55
 
52
- trend = st.LevelTrendComponent(order=2, innovations_order=1)
56
+ trend = st.LevelTrend(order=2, innovations_order=1)
53
57
  error = st.MeasurementError()
54
58
 
55
59
  ss_mod = (trend + error).build()
@@ -79,7 +83,7 @@ class MeasurementError(Component):
79
83
 
80
84
  .. code:: python
81
85
 
82
- trend = st.LevelTrendComponent(order=2, innovations_order=1)
86
+ trend = st.LevelTrend(order=2, innovations_order=1)
83
87
  seasonal = st.TimeSeasonality(season_length=12, innovations=True)
84
88
  error = st.MeasurementError()
85
89
 
@@ -118,29 +122,35 @@ class MeasurementError(Component):
118
122
  k_posdef,
119
123
  measurement_error=True,
120
124
  combine_hidden_states=False,
121
- observed_state_names=observed_state_names,
125
+ base_observed_state_names=observed_state_names,
122
126
  share_states=share_states,
123
127
  )
124
128
 
125
- def populate_component_properties(self):
129
+ def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
126
130
  k_endog = self.k_endog
127
131
  k_endog_effective = 1 if self.share_states else k_endog
128
132
 
129
- self.param_names = [f"sigma_{self.name}"]
130
- self.param_dims = {}
131
- self.coords = {}
133
+ sigma_param = Parameter(
134
+ name=f"sigma_{self.name}",
135
+ shape=(k_endog_effective,) if k_endog_effective > 1 else (),
136
+ dims=(f"endog_{self.name}",) if k_endog_effective > 1 else None,
137
+ constraints="Positive",
138
+ )
139
+
140
+ return (sigma_param,)
141
+
142
+ def set_coords(self) -> Coord | tuple[Coord, ...] | None:
143
+ k_endog = self.k_endog
144
+ k_endog_effective = 1 if self.share_states else k_endog
145
+ observed_state_names = self.base_observed_state_names
146
+
147
+ coords_container = []
132
148
 
133
149
  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
- }
150
+ endog_coord = Coord(dimension=f"endog_{self.name}", labels=observed_state_names)
151
+ coords_container.append(endog_coord)
152
+
153
+ return tuple(coords_container) if coords_container else None
144
154
 
145
155
  def make_symbolic_graph(self) -> None:
146
156
  k_endog = self.k_endog