dfm-python 0.5.56__py3-none-any.whl → 0.5.57__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 (41) hide show
  1. dfm_python/config/__init__.py +3 -10
  2. dfm_python/config/adapter.py +63 -7
  3. dfm_python/config/constants.py +5 -4
  4. dfm_python/config/schema/__init__.py +2 -6
  5. dfm_python/config/schema/block.py +61 -3
  6. dfm_python/config/schema/model.py +11 -2
  7. dfm_python/config/schema/params.py +207 -22
  8. dfm_python/config/schema/results.py +8 -74
  9. dfm_python/config/types.py +3 -11
  10. dfm_python/dataset/ddfm_dataset.py +35 -13
  11. dfm_python/dataset/dfm_dataset.py +9 -1
  12. dfm_python/functional/__init__.py +7 -9
  13. dfm_python/functional/dfm_block.py +65 -14
  14. dfm_python/functional/em.py +580 -223
  15. dfm_python/logger/__init__.py +6 -12
  16. dfm_python/logger/inference_logger.py +3 -17
  17. dfm_python/logger/logger.py +129 -5
  18. dfm_python/logger/train_logger.py +26 -34
  19. dfm_python/models/__init__.py +2 -2
  20. dfm_python/models/base.py +8 -29
  21. dfm_python/models/ddfm.py +103 -3
  22. dfm_python/models/dfm.py +486 -219
  23. dfm_python/numeric/estimator.py +146 -26
  24. dfm_python/numeric/stability.py +55 -151
  25. dfm_python/numeric/tent.py +16 -6
  26. dfm_python/numeric/validator.py +1 -2
  27. dfm_python/ssm/__init__.py +13 -3
  28. dfm_python/ssm/kalman.py +272 -141
  29. dfm_python/utils/checkpoint.py +1 -1
  30. dfm_python/utils/misc.py +4 -10
  31. dfm_python/utils/validation.py +4 -71
  32. {dfm_python-0.5.56.dist-info → dfm_python-0.5.57.dist-info}/METADATA +4 -5
  33. dfm_python-0.5.57.dist-info/RECORD +52 -0
  34. dfm_python/functional/irf.py +0 -305
  35. dfm_python/functional/krylov.py +0 -124
  36. dfm_python/ssm/arma.py +0 -0
  37. dfm_python/ssm/companion.py +0 -995
  38. dfm_python/ssm/structural.py +0 -298
  39. dfm_python-0.5.56.dist-info/RECORD +0 -58
  40. dfm_python-0.5.56.dist-info/licenses/LICENSE +0 -21
  41. {dfm_python-0.5.56.dist-info → dfm_python-0.5.57.dist-info}/WHEEL +0 -0
@@ -10,7 +10,7 @@ Note: Series are specified via frequency dict mapping column names to frequencie
10
10
  from .schema import (
11
11
  BaseModelConfig, DFMConfig, DDFMConfig,
12
12
  BaseResult, DFMResult, DDFMResult,
13
- DFMFitParams, DDFMFitParams, FitParams, # FitParams is backward compatibility alias
13
+ DFMStateSpaceParams, DDFMStateSpaceParams,
14
14
  )
15
15
  # DEFAULT_BLOCK_NAME is imported lazily where needed to avoid circular imports
16
16
  from .constants import (
@@ -46,8 +46,6 @@ from .types import (
46
46
  ForecastResult,
47
47
  CoefficientMatrix,
48
48
  CovarianceMatrix,
49
- IRFArray,
50
- CompanionMatrix,
51
49
  SeriesID,
52
50
  Frequency,
53
51
  DatasetName,
@@ -58,7 +56,6 @@ from .types import (
58
56
  ResultDict,
59
57
  ForecastDict,
60
58
  MetricsDict,
61
- IRFResultDict,
62
59
  CheckpointDict,
63
60
  ConfigDict,
64
61
  Device,
@@ -107,9 +104,8 @@ __all__ = [
107
104
  # 'DEFAULT_BLOCK_NAME', # Removed to avoid circular import - import directly from functional.dfm_block
108
105
  # Model-specific configs (from schema.py)
109
106
  'DFMConfig', 'DDFMConfig',
110
- # Parameter overrides
111
- 'DFMFitParams', 'DDFMFitParams',
112
- 'FitParams', # Backward compatibility alias
107
+ # State-space parameters
108
+ 'DFMStateSpaceParams', 'DDFMStateSpaceParams',
113
109
  # Model-specific results (from results.py)
114
110
  'DFMResult', 'DDFMResult',
115
111
  # Utilities
@@ -139,8 +135,6 @@ __all__ = [
139
135
  'ForecastResult',
140
136
  'CoefficientMatrix',
141
137
  'CovarianceMatrix',
142
- 'IRFArray',
143
- 'CompanionMatrix',
144
138
  'SeriesID',
145
139
  'Frequency',
146
140
  'DatasetName',
@@ -151,7 +145,6 @@ __all__ = [
151
145
  'ResultDict',
152
146
  'ForecastDict',
153
147
  'MetricsDict',
154
- 'IRFResultDict',
155
148
  'CheckpointDict',
156
149
  'ConfigDict',
157
150
  'Device',
@@ -118,7 +118,12 @@ def _extract_frequency_dict(
118
118
  data: Dict[str, Any],
119
119
  clock: str = DEFAULT_CLOCK_FREQUENCY
120
120
  ) -> Optional[Dict[str, str]]:
121
- """Extract frequency dict from data, handling both new API and legacy formats.
121
+ """Extract frequency dict from data, handling multiple formats.
122
+
123
+ Supports:
124
+ 1. Grouped format: {'w': [series1, series2, ...], 'm': [series3, ...]}
125
+ 2. Individual format: {'series1': 'w', 'series2': 'm', ...}
126
+ 3. Legacy format: {'series': [{'series_id': ..., 'frequency': ...}, ...]}
122
127
 
123
128
  Parameters
124
129
  ----------
@@ -132,12 +137,63 @@ def _extract_frequency_dict(
132
137
  Optional[Dict[str, str]]
133
138
  Frequency dict mapping column names to frequencies, or None if not found
134
139
  """
135
- if 'frequency' in data:
136
- # New API: frequency dict directly
137
- return data['frequency']
138
- elif 'series' in data:
139
- # Legacy: convert series list/dict to frequency dict
140
- return _convert_series_to_frequency_dict(data['series'], clock)
140
+ if 'frequency' not in data:
141
+ # Legacy: check for 'series' key
142
+ if 'series' in data:
143
+ return _convert_series_to_frequency_dict(data['series'], clock)
144
+ return None
145
+
146
+ freq_data = data['frequency']
147
+
148
+ # Check if it's in grouped format: {'w': [...], 'm': [...]}
149
+ if _is_dict_like(freq_data):
150
+ from .constants import VALID_FREQUENCIES
151
+
152
+ # Check if all keys are valid frequency codes (grouped format)
153
+ keys_are_frequencies = all(
154
+ isinstance(k, str) and k in VALID_FREQUENCIES
155
+ for k in freq_data.keys()
156
+ )
157
+
158
+ # Check if values are lists (grouped format)
159
+ values_are_lists = all(
160
+ isinstance(v, (list, tuple)) for v in freq_data.values()
161
+ )
162
+
163
+ if keys_are_frequencies and values_are_lists:
164
+ # Grouped format: convert to individual format
165
+ result = {}
166
+ for freq, series_list in freq_data.items():
167
+ if not isinstance(series_list, (list, tuple)):
168
+ _raise_config_error(
169
+ f"Frequency '{freq}' must map to a list of series names, got {type(series_list)}"
170
+ )
171
+ for series_name in series_list:
172
+ if not isinstance(series_name, str):
173
+ _raise_config_error(
174
+ f"Series names must be strings, got {type(series_name)} in frequency '{freq}'"
175
+ )
176
+ if series_name in result:
177
+ _raise_config_error(
178
+ f"Series '{series_name}' appears in multiple frequency groups"
179
+ )
180
+ result[series_name] = freq
181
+ return result
182
+ else:
183
+ # Individual format: already in correct format, validate structure
184
+ result = {}
185
+ for series_name, freq in freq_data.items():
186
+ if not isinstance(series_name, str):
187
+ _raise_config_error(
188
+ f"Frequency dict keys must be strings (series names), got {type(series_name)}"
189
+ )
190
+ if not isinstance(freq, str):
191
+ _raise_config_error(
192
+ f"Frequency dict values must be strings (frequency codes), got {type(freq)} for series '{series_name}'"
193
+ )
194
+ result[series_name] = freq
195
+ return result
196
+
141
197
  return None
142
198
 
143
199
 
@@ -23,7 +23,7 @@ DEFAULT_MIN_DELTA = 1e-6 # Minimum change for improvement
23
23
  # ============================================================================
24
24
 
25
25
  # Minimum eigenvalues and variances
26
- MIN_EIGENVALUE = 1e-8 # Minimum eigenvalue for positive definite matrices
26
+ MIN_EIGENVALUE = 1e-6 # Minimum eigenvalue for positive definite matrices (increased from 1e-8 for better conditioning)
27
27
  MIN_DIAGONAL_VARIANCE = 1e-8 # Minimum variance for diagonal elements
28
28
  MIN_OBSERVATION_NOISE = 1e-4 # Minimum observation noise for measurement error (used in EM updates)
29
29
  DEFAULT_DDFM_OBSERVATION_NOISE = 1e-15 # Default observation noise for DDFM state-space (matches original TensorFlow)
@@ -33,7 +33,7 @@ MIN_STD_FOR_SCALE_CHECK = 1e-10 # Minimum standard deviation for scale ratio co
33
33
  DEFAULT_VARIANCE_FALLBACK = 1.0 # Default variance fallback value for numerical stability
34
34
 
35
35
  # Maximum eigenvalues
36
- MAX_EIGENVALUE = 1e6 # Maximum eigenvalue cap
36
+ MAX_EIGENVALUE = 1e3 # Maximum eigenvalue cap (reduced to 1e3 to improve condition number: 1e3/1e-6 = 1e9)
37
37
 
38
38
  # Eigenvalue stability thresholds
39
39
  DEFAULT_EIGENVALUE_MAX_MAGNITUDE = 1.0 # Default maximum eigenvalue magnitude for stability checks
@@ -217,6 +217,7 @@ DEFAULT_START_DATE = datetime(2000, 1, 1)
217
217
  # Default window size for DDFM
218
218
  DEFAULT_WINDOW_SIZE = 100
219
219
  DEFAULT_TENT_KERNEL_SIZE = 5 # Default tent kernel size for slower-frequency series aggregation
220
+ DEFAULT_TENT_KERNEL_REGULARIZATION_MULTIPLIER = 100.0 # Regularization multiplier for tent kernel series (handles ill-conditioning)
220
221
  # Note: DEFAULT_BATCH_SIZE is defined above (line 88) as 32 for general neural networks
221
222
  # Use DEFAULT_DDFM_WINDOW_SIZE (100) for DDFM-specific window size (time-step batch size)
222
223
 
@@ -284,7 +285,7 @@ DEFAULT_PROGRESS_LOG_INTERVAL = 5 # Default progress logging interval for EM al
284
285
  # Precision Defaults
285
286
  # ============================================================================
286
287
 
287
- DEFAULT_DTYPE = np.float32 # Default numpy dtype for arrays
288
+ DEFAULT_DTYPE = np.float64 # Default numpy dtype for arrays (float64 for better numerical stability)
288
289
 
289
290
  # PyTorch dtype (matches DEFAULT_DTYPE)
290
291
  try:
@@ -518,7 +519,6 @@ __all__ = [
518
519
  'MIN_VARIABLES',
519
520
  'MIN_SHAPE_FOR_AR2',
520
521
  'MIN_DDFM_TIME_STEPS',
521
- 'MIN_SHAPE_FOR_AR2',
522
522
  'MIN_DDFM_DATASET_SIZE_WARNING',
523
523
  'MIN_ITER_FOR_DELTA_COMPUTATION',
524
524
  'DEFAULT_MIN_ITER_FOR_CONVERGENCE_CHECK',
@@ -546,6 +546,7 @@ __all__ = [
546
546
  'DEFAULT_LOG_INTERVAL',
547
547
  'DEFAULT_PROGRESS_LOG_INTERVAL',
548
548
  'DEFAULT_TENT_KERNEL_SIZE',
549
+ 'DEFAULT_TENT_KERNEL_REGULARIZATION_MULTIPLIER',
549
550
  'MAX_ERROR_ITEMS',
550
551
  # IRF
551
552
  'DEFAULT_IRF_HORIZON',
@@ -9,15 +9,11 @@ Note: Series are specified via frequency dict mapping column names to frequencie
9
9
 
10
10
  from .model import BaseModelConfig, DFMConfig, DDFMConfig
11
11
  from .results import BaseResult, DFMResult, DDFMResult
12
- from .params import DFMFitParams, DDFMFitParams
13
-
14
- # Backward compatibility alias
15
- FitParams = DFMFitParams
12
+ from .params import DFMStateSpaceParams, DDFMStateSpaceParams
16
13
 
17
14
  __all__ = [
18
15
  'BaseModelConfig', 'DFMConfig', 'DDFMConfig',
19
16
  'BaseResult', 'DFMResult', 'DDFMResult',
20
- 'DFMFitParams', 'DDFMFitParams',
21
- 'FitParams', # Backward compatibility alias
17
+ 'DFMStateSpaceParams', 'DDFMStateSpaceParams',
22
18
  ]
23
19
 
@@ -5,8 +5,8 @@ parameters in EM algorithm, replacing long conditional checks with a single
5
5
  optional parameter.
6
6
  """
7
7
 
8
- from dataclasses import dataclass
9
- from typing import Optional, Dict
8
+ from dataclasses import dataclass, field
9
+ from typing import Optional, Dict, List
10
10
  import numpy as np
11
11
 
12
12
 
@@ -26,7 +26,10 @@ class BlockStructure:
26
26
  p : int
27
27
  VAR lag order
28
28
  p_plus_one : int
29
- p + 1 (state dimension per factor)
29
+ State dimension per factor. This is equal to max_lag_size = max(p + 1, tent_kernel_size).
30
+ The name "p_plus_one" is used for backward compatibility with the EM algorithm interface.
31
+ In the model code, this is called "max_lag_size" to more clearly indicate it accounts
32
+ for tent kernel size when tent_kernel_size > p + 1.
30
33
  n_clock_freq : int
31
34
  Number of clock-frequency series
32
35
  idio_indicator : np.ndarray
@@ -39,6 +42,30 @@ class BlockStructure:
39
42
  Number of slower-frequency series
40
43
  tent_weights_dict : dict, optional
41
44
  Dictionary mapping frequency pairs to tent weights
45
+
46
+ Cached indices (computed once, reused across EM iterations):
47
+ _cached_unique_blocks : List[np.ndarray], optional
48
+ Unique block patterns (cached)
49
+ _cached_unique_indices : List[int], optional
50
+ Indices of unique block patterns (cached)
51
+ _cached_bl_idxM : List[np.ndarray], optional
52
+ Clock-frequency factor loadings indices per unique block pattern (cached)
53
+ _cached_bl_idxQ : List[np.ndarray], optional
54
+ Slower-frequency factor loadings indices per unique block pattern (cached)
55
+ _cached_R_con : np.ndarray, optional
56
+ Block diagonal constraint matrix (cached)
57
+ _cached_q_con : np.ndarray, optional
58
+ Constraint vector (cached)
59
+ _cached_total_factor_dim : int, optional
60
+ Total factor state dimension (cached)
61
+ _cached_idio_indicator_M : np.ndarray, optional
62
+ Clock-frequency idiosyncratic component indicator (cached)
63
+ _cached_n_idio_M : int, optional
64
+ Number of clock-frequency idiosyncratic components (cached)
65
+ _cached_c_idio_indicator : np.ndarray, optional
66
+ Cumulative sum of idiosyncratic component indicator (cached)
67
+ _cached_rp1 : int, optional
68
+ Start of idiosyncratic components in state space (cached)
42
69
  """
43
70
  blocks: np.ndarray
44
71
  r: np.ndarray
@@ -51,6 +78,19 @@ class BlockStructure:
51
78
  n_slower_freq: Optional[int] = None
52
79
  tent_weights_dict: Optional[Dict[str, np.ndarray]] = None
53
80
 
81
+ # Cached indices (computed once, reused across EM iterations)
82
+ _cached_unique_blocks: Optional[List[np.ndarray]] = field(default=None, init=False, repr=False)
83
+ _cached_unique_indices: Optional[List[int]] = field(default=None, init=False, repr=False)
84
+ _cached_bl_idxM: Optional[List[np.ndarray]] = field(default=None, init=False, repr=False)
85
+ _cached_bl_idxQ: Optional[List[np.ndarray]] = field(default=None, init=False, repr=False)
86
+ _cached_R_con: Optional[np.ndarray] = field(default=None, init=False, repr=False)
87
+ _cached_q_con: Optional[np.ndarray] = field(default=None, init=False, repr=False)
88
+ _cached_total_factor_dim: Optional[int] = field(default=None, init=False, repr=False)
89
+ _cached_idio_indicator_M: Optional[np.ndarray] = field(default=None, init=False, repr=False)
90
+ _cached_n_idio_M: Optional[int] = field(default=None, init=False, repr=False)
91
+ _cached_c_idio_indicator: Optional[np.ndarray] = field(default=None, init=False, repr=False)
92
+ _cached_rp1: Optional[int] = field(default=None, init=False, repr=False)
93
+
54
94
  def is_valid(self) -> bool:
55
95
  """Check if block structure is valid (all required fields are not None)."""
56
96
  return (
@@ -61,4 +101,22 @@ class BlockStructure:
61
101
  and self.n_clock_freq is not None
62
102
  and self.idio_indicator is not None
63
103
  )
104
+
105
+ def has_cached_indices(self) -> bool:
106
+ """Check if block structure indices are cached."""
107
+ return self._cached_unique_blocks is not None
108
+
109
+ def clear_cache(self) -> None:
110
+ """Clear all cached indices to ensure clean state for new training runs."""
111
+ self._cached_unique_blocks = None
112
+ self._cached_unique_indices = None
113
+ self._cached_bl_idxM = None
114
+ self._cached_bl_idxQ = None
115
+ self._cached_R_con = None
116
+ self._cached_q_con = None
117
+ self._cached_total_factor_dim = None
118
+ self._cached_idio_indicator_M = None
119
+ self._cached_n_idio_M = None
120
+ self._cached_c_idio_indicator = None
121
+ self._cached_rp1 = None
64
122
 
@@ -91,7 +91,9 @@ class BaseModelConfig:
91
91
  - Data preprocessing (missing data handling)
92
92
 
93
93
  Series Configuration:
94
- - Provide `frequency` dict: {'column_name': 'frequency_code'} to specify per-series frequencies
94
+ - Provide `frequency` dict in one of two formats:
95
+ 1. Grouped format: {'w': [series1, series2, ...], 'm': [series3, ...]} (recommended for large configs)
96
+ 2. Individual format: {'series1': 'w', 'series2': 'm', ...} (backward compatible)
95
97
  - If `frequency` is None, all columns will use `clock` frequency
96
98
  - If a column is missing from `frequency` dict, it will use `clock` frequency
97
99
  - When data is loaded, missing columns in `frequency` dict are automatically added with `clock` frequency
@@ -103,7 +105,14 @@ class BaseModelConfig:
103
105
 
104
106
  Examples
105
107
  --------
106
- >>> # With explicit frequency mapping
108
+ >>> # With grouped frequency mapping (recommended for large configs)
109
+ >>> config = DFMConfig(
110
+ ... frequency={'q': ['gdp'], 'm': ['unemployment', 'interest_rate']},
111
+ ... clock='m',
112
+ ... blocks={...}
113
+ ... )
114
+ >>>
115
+ >>> # With individual frequency mapping (backward compatible)
107
116
  >>> config = DFMConfig(
108
117
  ... frequency={'gdp': 'q', 'unemployment': 'm', 'interest_rate': 'm'},
109
118
  ... clock='m',
@@ -1,37 +1,199 @@
1
1
  """Model parameters for DFM and DDFM.
2
2
 
3
- This module defines dataclasses for storing fit-time parameters and state-space parameters.
3
+ This module defines dataclasses for storing state-space parameters and model structure.
4
4
  """
5
5
 
6
6
  from dataclasses import dataclass
7
7
  import numpy as np
8
- from typing import Optional
8
+ from typing import Optional, Any
9
9
 
10
10
 
11
11
  @dataclass
12
- class DFMFitParams:
13
- """Fit-time parameter overrides for DFM estimation.
12
+ class DFMStateSpaceParams:
13
+ """State-space model parameters for DFM (fitted during model training).
14
14
 
15
- This dataclass allows overriding configuration parameters at fit time
16
- without modifying the configuration object itself.
15
+ These parameters are computed during DFM.fit() and represent the fitted
16
+ state-space model structure. Contains only the state-space parameters,
17
+ not training metadata (loglik, num_iter, converged) which are stored
18
+ separately in checkpoint dicts.
17
19
 
18
- Based on DFM.fit() usage, these parameters are passed to the EM algorithm.
20
+ Parameters
21
+ ----------
22
+ A : np.ndarray
23
+ Transition matrix (m x m) - VAR dynamics for factors
24
+ C : np.ndarray
25
+ Observation matrix (N x m) - factor loadings
26
+ Q : np.ndarray
27
+ Process noise covariance (m x m) - innovation covariance
28
+ R : np.ndarray
29
+ Observation noise covariance (N x N) - typically diagonal
30
+ Z_0 : np.ndarray
31
+ Initial state mean (m,) - initial factor values
32
+ V_0 : np.ndarray
33
+ Initial state covariance (m x m) - initial uncertainty
34
+ """
35
+ A: np.ndarray # Transition matrix (m x m)
36
+ C: np.ndarray # Observation matrix (N x m)
37
+ Q: np.ndarray # Process noise covariance (m x m)
38
+ R: np.ndarray # Observation noise covariance (N x N)
39
+ Z_0: np.ndarray # Initial state mean (m,)
40
+ V_0: np.ndarray # Initial state covariance (m x m)
41
+
42
+ @classmethod
43
+ def from_model(cls, model: Any) -> 'DFMStateSpaceParams':
44
+ """Create DFMStateSpaceParams from DFM model instance.
45
+
46
+ Parameters
47
+ ----------
48
+ model : DFM
49
+ DFM model instance with fitted parameters
50
+
51
+ Returns
52
+ -------
53
+ DFMStateSpaceParams
54
+ State-space parameters dataclass
55
+ """
56
+ # Get parameters from model (stored after fit)
57
+ # First check if training_state is already DFMStateSpaceParams (new format)
58
+ if hasattr(model, 'training_state') and model.training_state is not None:
59
+ if isinstance(model.training_state, cls):
60
+ # Already a DFMStateSpaceParams instance
61
+ return model.training_state
62
+ # Backward compatibility: If old format training_state (has A, C, Q, R, Z_0, V_0 attributes)
63
+ if hasattr(model.training_state, 'A'):
64
+ return cls(
65
+ A=model.training_state.A,
66
+ C=model.training_state.C,
67
+ Q=model.training_state.Q,
68
+ R=model.training_state.R,
69
+ Z_0=model.training_state.Z_0,
70
+ V_0=model.training_state.V_0
71
+ )
72
+
73
+ # Get from model attributes (after _update_parameters is called)
74
+ return cls(
75
+ A=getattr(model, 'A', None),
76
+ C=getattr(model, 'C', None),
77
+ Q=getattr(model, 'Q', None),
78
+ R=getattr(model, 'R', None),
79
+ Z_0=getattr(model, 'Z_0', None),
80
+ V_0=getattr(model, 'V_0', None)
81
+ )
82
+
83
+ def apply_to_model(self, model: Any) -> None:
84
+ """Apply state-space parameters to DFM model instance.
85
+
86
+ Parameters
87
+ ----------
88
+ model : DFM
89
+ DFM model instance to update
90
+ """
91
+ model._update_parameters(
92
+ self.A, self.C, self.Q, self.R, self.Z_0, self.V_0
93
+ )
94
+
95
+
96
+ @dataclass
97
+ class DFMModelState:
98
+ """DFM model structure and mixed-frequency parameters.
99
+
100
+ Consolidates model structure and mixed-frequency state for checkpointing.
101
+ This allows save/load methods to be simplified by using a single dataclass.
19
102
 
20
103
  Attributes
21
104
  ----------
22
- max_iter : int, optional
23
- Maximum EM iterations (overrides config.max_iter)
24
- Used in: kalman_filter.em(max_iter=...)
25
- threshold : float, optional
26
- Convergence threshold (overrides config.threshold)
27
- Used in: kalman_filter.em(threshold=...)
28
- regularization_scale : float, optional
29
- Regularization scale for matrix operations (overrides EM config)
30
- Note: This affects EMConfig.regularization and EMConfig.matrix_regularization
105
+ num_factors : int
106
+ Number of factors
107
+ r : np.ndarray
108
+ Number of factors per block
109
+ p : int
110
+ AR lag order
111
+ blocks : np.ndarray
112
+ Block structure array
113
+ mixed_freq : bool, optional
114
+ Whether mixed-frequency data is used
115
+ constraint_matrix : np.ndarray, optional
116
+ Constraint matrix for tent kernel aggregation
117
+ constraint_vector : np.ndarray, optional
118
+ Constraint vector for tent kernel aggregation
119
+ n_slower_freq : int
120
+ Number of slower-frequency series
121
+ n_clock_freq : int, optional
122
+ Number of clock-frequency series
123
+ tent_weights_dict : dict, optional
124
+ Dictionary of tent weights by frequency
125
+ frequencies : np.ndarray, optional
126
+ Frequency array for each series
127
+ idio_indicator : np.ndarray, optional
128
+ Indicator for idiosyncratic components
129
+ max_lag_size : int, optional
130
+ Maximum lag size for state dimension
31
131
  """
32
- max_iter: Optional[int] = None
33
- threshold: Optional[float] = None
34
- regularization_scale: Optional[float] = None
132
+ num_factors: int
133
+ r: np.ndarray
134
+ p: int
135
+ blocks: np.ndarray
136
+ mixed_freq: Optional[bool] = None
137
+ constraint_matrix: Optional[np.ndarray] = None
138
+ constraint_vector: Optional[np.ndarray] = None
139
+ n_slower_freq: int = 0
140
+ n_clock_freq: Optional[int] = None
141
+ tent_weights_dict: Optional[dict] = None
142
+ frequencies: Optional[np.ndarray] = None
143
+ idio_indicator: Optional[np.ndarray] = None
144
+ max_lag_size: Optional[int] = None
145
+
146
+ @classmethod
147
+ def from_model(cls, model: Any) -> 'DFMModelState':
148
+ """Create DFMModelState from DFM model instance.
149
+
150
+ Parameters
151
+ ----------
152
+ model : DFM
153
+ DFM model instance
154
+
155
+ Returns
156
+ -------
157
+ DFMModelState
158
+ Model state dataclass
159
+ """
160
+ return cls(
161
+ num_factors=model.num_factors,
162
+ r=model.r,
163
+ p=model.p,
164
+ blocks=model.blocks,
165
+ mixed_freq=getattr(model, '_mixed_freq', None),
166
+ constraint_matrix=getattr(model, '_constraint_matrix', None),
167
+ constraint_vector=getattr(model, '_constraint_vector', None),
168
+ n_slower_freq=getattr(model, '_n_slower_freq', 0),
169
+ n_clock_freq=getattr(model, '_n_clock_freq', None),
170
+ tent_weights_dict=getattr(model, '_tent_weights_dict', None),
171
+ frequencies=getattr(model, '_frequencies', None),
172
+ idio_indicator=getattr(model, '_idio_indicator', None),
173
+ max_lag_size=getattr(model, '_max_lag_size', None)
174
+ )
175
+
176
+ def apply_to_model(self, model: Any) -> None:
177
+ """Apply this state to DFM model instance.
178
+
179
+ Parameters
180
+ ----------
181
+ model : DFM
182
+ DFM model instance to update
183
+ """
184
+ model.num_factors = self.num_factors
185
+ model.r = self.r
186
+ model.p = self.p
187
+ model.blocks = self.blocks
188
+ model._mixed_freq = self.mixed_freq
189
+ model._constraint_matrix = self.constraint_matrix
190
+ model._constraint_vector = self.constraint_vector
191
+ model._n_slower_freq = self.n_slower_freq
192
+ model._n_clock_freq = self.n_clock_freq
193
+ model._tent_weights_dict = self.tent_weights_dict
194
+ model._frequencies = self.frequencies
195
+ model._idio_indicator = self.idio_indicator
196
+ model._max_lag_size = self.max_lag_size
35
197
 
36
198
  @dataclass
37
199
  class DDFMTrainingState:
@@ -62,13 +224,36 @@ class DDFMTrainingState:
62
224
  eps: Optional[np.ndarray] = None
63
225
  factors: Optional[np.ndarray] = None
64
226
  last_neurons: Optional[np.ndarray] = None
227
+
228
+ def sync_from_model(self, model: Any) -> 'DDFMTrainingState':
229
+ """Update this dataclass from model instance attributes.
230
+
231
+ Parameters
232
+ ----------
233
+ model : DDFM
234
+ DDFM model instance
235
+
236
+ Returns
237
+ -------
238
+ DDFMTrainingState
239
+ Self (for chaining)
240
+ """
241
+ if hasattr(model, '_has_factors') and model._has_factors:
242
+ self.factors = getattr(model, 'factors', None)
243
+ self.eps = getattr(model, 'eps', None)
244
+ self.last_neurons = getattr(model, 'last_neurons', None)
245
+ self.num_iter = getattr(model, '_num_iter', 0)
246
+ self.loss_now = getattr(model, 'loss_now', None)
247
+ self.converged = getattr(model, '_converged', False)
248
+ return self
65
249
 
66
250
  @dataclass
67
- class DDFMFitParams:
68
- """State-space model parameters for DDFM (created during fit).
251
+ class DDFMStateSpaceParams:
252
+ """State-space model parameters for DDFM (fitted during model training).
69
253
 
70
254
  These parameters are computed during DDFM.build_state_space() and represent
71
- the fitted state-space model structure. Naming follows original paper conventions.
255
+ the fitted state-space model structure. Similar to DFMStateSpaceParams, this contains
256
+ the fitted state-space parameters but for DDFM. Naming follows original paper conventions.
72
257
 
73
258
  Parameters
74
259
  ----------