dfm-python 0.5.53__py3-none-any.whl → 0.5.54__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 (45) hide show
  1. dfm_python/__init__.py +3 -3
  2. dfm_python/config/adapter.py +17 -1
  3. dfm_python/config/constants.py +97 -11
  4. dfm_python/config/schema/model.py +125 -59
  5. dfm_python/config/schema/results.py +101 -1
  6. dfm_python/config/types.py +19 -4
  7. dfm_python/datamodule/base.py +61 -55
  8. dfm_python/datamodule/ddfm_dm.py +34 -49
  9. dfm_python/datamodule/dfm_dm.py +55 -28
  10. dfm_python/datamodule/kdfm_dm.py +9 -8
  11. dfm_python/dataset/process.py +63 -64
  12. dfm_python/decoder/mlp.py +12 -5
  13. dfm_python/encoder/pca.py +15 -17
  14. dfm_python/encoder/simple_encoder.py +80 -37
  15. dfm_python/functional/dfm_block.py +9 -8
  16. dfm_python/functional/em.py +211 -78
  17. dfm_python/functional/irf.py +25 -17
  18. dfm_python/models/base.py +144 -385
  19. dfm_python/models/ddfm.py +273 -270
  20. dfm_python/models/dfm.py +291 -196
  21. dfm_python/models/kdfm.py +344 -224
  22. dfm_python/numeric/builder.py +38 -82
  23. dfm_python/numeric/estimator.py +73 -299
  24. dfm_python/numeric/stability.py +128 -72
  25. dfm_python/numeric/tent.py +8 -3
  26. dfm_python/numeric/validator.py +81 -34
  27. dfm_python/ssm/companion.py +37 -24
  28. dfm_python/ssm/kalman.py +11 -9
  29. dfm_python/ssm/structural.py +1 -1
  30. dfm_python/trainer/__init__.py +12 -9
  31. dfm_python/trainer/ddfm.py +77 -67
  32. dfm_python/trainer/dfm.py +4 -3
  33. dfm_python/trainer/kdfm.py +5 -4
  34. dfm_python/utils/__init__.py +3 -4
  35. dfm_python/utils/common.py +96 -10
  36. dfm_python/utils/helper.py +32 -12
  37. dfm_python/utils/metric.py +14 -8
  38. dfm_python/utils/misc.py +6 -43
  39. dfm_python/utils/tensor_utils.py +14 -7
  40. dfm_python/utils/validation.py +32 -3
  41. {dfm_python-0.5.53.dist-info → dfm_python-0.5.54.dist-info}/METADATA +1 -1
  42. dfm_python-0.5.54.dist-info/RECORD +66 -0
  43. dfm_python-0.5.53.dist-info/RECORD +0 -66
  44. {dfm_python-0.5.53.dist-info → dfm_python-0.5.54.dist-info}/WHEEL +0 -0
  45. {dfm_python-0.5.53.dist-info → dfm_python-0.5.54.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py CHANGED
@@ -40,7 +40,7 @@ Example (Standard Lightning Pattern):
40
40
  >>> model.load_config('config/default.yaml')
41
41
  >>>
42
42
  >>> # Step 4: Create trainer and fit (standard Lightning pattern)
43
- >>> trainer = DFMTrainer(max_epochs=100)
43
+ >>> trainer = DFMTrainer(max_epochs=100) # DEFAULT_MAX_EPOCHS
44
44
  >>> trainer.fit(model, dm)
45
45
  >>>
46
46
  >>> # Step 5: Predict
@@ -55,7 +55,7 @@ Example (Standard Lightning Pattern):
55
55
  >>> ddfm_model = DDFM(encoder_layers=[64, 32], num_factors=2)
56
56
  >>> ddfm_model.load_config('config/default.yaml')
57
57
  >>>
58
- >>> trainer_ddfm = DDFMTrainer(max_epochs=100)
58
+ >>> trainer_ddfm = DDFMTrainer(max_epochs=100) # DEFAULT_MAX_EPOCHS
59
59
  >>> trainer_ddfm.fit(ddfm_model, dm_ddfm)
60
60
  >>> Xf, Zf = ddfm_model.predict(horizon=6)
61
61
 
@@ -65,7 +65,7 @@ Note: DFMConfig uses frequency dict to specify series (column names -> frequenci
65
65
  For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
66
66
  """
67
67
 
68
- __version__ = "0.5.53"
68
+ __version__ = "0.5.54"
69
69
 
70
70
  # ============================================================================
71
71
  # PUBLIC API DEFINITION
@@ -28,7 +28,7 @@ from .constants import DEFAULT_CLOCK_FREQUENCY
28
28
  # Error helper to reduce repetitive imports
29
29
  def _get_config_error():
30
30
  """Get ConfigurationError (lazy import to avoid circular dependencies)."""
31
- from ...utils.errors import ConfigurationError
31
+ from dfm_python.utils.errors import ConfigurationError
32
32
  return ConfigurationError
33
33
 
34
34
 
@@ -58,6 +58,22 @@ def _raise_config_error(message: str, details: Optional[str] = None) -> NoReturn
58
58
  # Helper Functions for Config Parsing
59
59
  # ============================================================================
60
60
 
61
+ def _is_dict_like(obj: Any) -> bool:
62
+ """Check if object is dict-like (dict, OrderedDict, etc.).
63
+
64
+ Parameters
65
+ ----------
66
+ obj : Any
67
+ Object to check
68
+
69
+ Returns
70
+ -------
71
+ bool
72
+ True if object is dict-like, False otherwise
73
+ """
74
+ return isinstance(obj, dict) or hasattr(obj, 'keys') and hasattr(obj, '__getitem__')
75
+
76
+
61
77
  def _convert_series_to_frequency_dict(
62
78
  series_data: Any,
63
79
  default_clock: str = DEFAULT_CLOCK_FREQUENCY
@@ -28,10 +28,15 @@ 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
  MIN_FACTOR_VARIANCE = 1e-10 # Minimum variance for factors
30
30
  MIN_STD = 1e-8 # Minimum standard deviation
31
+ DEFAULT_VARIANCE_FALLBACK = 1.0 # Default variance fallback value for numerical stability
31
32
 
32
33
  # Maximum eigenvalues
33
34
  MAX_EIGENVALUE = 1e6 # Maximum eigenvalue cap
34
35
 
36
+ # Eigenvalue stability thresholds
37
+ DEFAULT_EIGENVALUE_MAX_MAGNITUDE = 1.0 # Default maximum eigenvalue magnitude for stability checks
38
+ DEFAULT_EIGENVALUE_WARN_THRESHOLD = 0.99 # Default warning threshold for near-unstable eigenvalues
39
+
35
40
  # Matrix cleaning defaults
36
41
  DEFAULT_CLEAN_NAN = 0.0 # Default value for NaN replacement in clean_matrix
37
42
  DEFAULT_CLEAN_INF = MAX_EIGENVALUE # Default value for Inf replacement in clean_matrix (uses MAX_EIGENVALUE)
@@ -39,6 +44,12 @@ DEFAULT_CLEAN_INF = MAX_EIGENVALUE # Default value for Inf replacement in clean
39
44
  # Identity matrix defaults
40
45
  DEFAULT_IDENTITY_SCALE = 1.0 # Default scale for create_scaled_identity(n, 1.0)
41
46
  DEFAULT_ZERO_VALUE = 0.0 # Default zero value for explicit zero assignments
47
+ DEFAULT_XAVIER_GAIN = 1.0 # Default gain for Xavier initialization
48
+ DEFAULT_OUTPUT_LAYER_GAIN = 0.1 # Default gain for output layer Xavier initialization (smaller for stability)
49
+
50
+ # Companion matrix initialization defaults
51
+ DEFAULT_INIT_SCALE = 0.01 # Default initialization scale for companion matrix B and C matrices
52
+ DEFAULT_KERNEL_INIT_SCALE = 0.1 # Default initialization scale for companion matrix coefficient matrices
42
53
 
43
54
  # Regularization scales
44
55
  DEFAULT_REGULARIZATION_SCALE = 1e-5 # Default ridge regularization scale
@@ -57,7 +68,6 @@ DEFAULT_MAX_ITER = 100 # Default maximum EM iterations (general)
57
68
  DEFAULT_EM_MAX_ITER = 5000 # Default maximum EM iterations (DFM-specific)
58
69
  DEFAULT_MAX_EPOCHS = 100 # Default maximum training epochs
59
70
  DEFAULT_MAX_MCMC_ITER = 200 # Default maximum MCMC iterations
60
- DEFAULT_EPOCHS_PER_ITER = 10 # Default epochs per MCMC iteration
61
71
 
62
72
  # Batch size defaults
63
73
  DEFAULT_BATCH_SIZE = 32 # Default batch size for neural networks
@@ -78,6 +88,10 @@ DEFAULT_LR_DECAY_RATE = 0.96 # Default exponential decay rate for learning rate
78
88
 
79
89
  # Loss function defaults
80
90
  DEFAULT_HUBER_DELTA = 1.0 # Default delta parameter for Huber loss
91
+ HUBER_QUADRATIC_COEFF = 0.5 # Quadratic coefficient for Huber loss (0.5 * a^2 term)
92
+
93
+ # Matrix computation defaults
94
+ SYMMETRY_AVERAGE_FACTOR = 0.5 # Averaging factor for symmetric matrix computation (0.5 * (M + M.T))
81
95
 
82
96
  # Data clipping defaults
83
97
  DEFAULT_DDFM_CLIP_RANGE_DEEP = 8.0 # Clipping range for deep networks (>2 layers)
@@ -86,11 +100,15 @@ DEFAULT_DDFM_CLIP_RANGE_SHALLOW = 10.0 # Clipping range for shallow networks (<
86
100
  # Numerical stability for division
87
101
  DEFAULT_EPSILON = 1e-8 # Default epsilon for division operations to prevent division by zero
88
102
 
103
+ # Random seed defaults
104
+ DEFAULT_SEED = 3 # Default random seed for reproducibility
105
+
89
106
  # Structural identification defaults
90
107
  DEFAULT_STRUCTURAL_REG_WEIGHT = 0.1 # Default weight for structural regularization loss
91
108
  DEFAULT_STRUCTURAL_INIT_SCALE = 0.1 # Default initialization scale for structural matrices
92
109
  DEFAULT_STRUCTURAL_DIAG_SCALE = 1.0 # Default diagonal scale for structural matrices (FIXED Iteration 7: was 0.1, caused near-singular S)
93
110
  DEFAULT_CHOLESKY_EPS = 1e-6 # Default epsilon for Cholesky decomposition stability
111
+ CHOLESKY_LOG_DET_FACTOR = 2.0 # Factor for log determinant computation from Cholesky decomposition (log det = 2.0 * sum(log(diag(L))))
94
112
 
95
113
  # ============================================================================
96
114
  # Network Architecture Defaults
@@ -112,17 +130,27 @@ DEFAULT_START_DATE = datetime(2000, 1, 1)
112
130
 
113
131
  # Default window size for DDFM
114
132
  DEFAULT_WINDOW_SIZE = 100
133
+ DEFAULT_TENT_KERNEL_SIZE = 5 # Default tent kernel size for slower-frequency series aggregation
115
134
  # Note: DEFAULT_BATCH_SIZE is defined above (line 52) as 32 for general neural networks
116
135
  # Use DEFAULT_DDFM_BATCH_SIZE (100) for DDFM-specific batch size
117
136
 
118
137
  # Warning/display limits
119
138
  MAX_WARNING_ITEMS = 5 # Maximum number of items to show in warning messages
139
+ MAX_ERROR_ITEMS = 20 # Maximum number of items to show in error message details
120
140
 
121
141
  # Minimum observations
122
142
  DEFAULT_MIN_OBS = 5 # Default minimum observations for estimation
123
143
  DEFAULT_MIN_OBS_IDIO = 5 # Default minimum observations for idio estimation
124
144
  DEFAULT_MIN_OBS_VAR = 7 # Minimum observations for VAR estimation (order + 5)
125
145
 
146
+ # Dimension validation bounds
147
+ MIN_TIME_STEPS = 1 # Minimum number of time steps (T) required for data
148
+ MIN_VARIABLES = 1 # Minimum number of variables (N) required for data
149
+ MIN_DDFM_TIME_STEPS = 2 # Minimum number of time steps (T) required for DDFM training (DDFM-specific, different from general MIN_TIME_STEPS=1)
150
+ MIN_DDFM_DATASET_SIZE_WARNING = 10 # Minimum dataset size (T) below which DDFM denoising training warns about potential instability
151
+ MIN_ITER_FOR_DELTA_COMPUTATION = 1 # Minimum iteration count required before computing delta (MSE change) in DDFM denoising training
152
+ MIN_EPS_SHAPE_FOR_IDIO = 1 # Minimum eps shape dimension required for idiosyncratic component processing in DDFM
153
+
126
154
  # Idiosyncratic component defaults
127
155
  DEFAULT_IDIO_STD = 0.1 # Default idiosyncratic standard deviation (when estimation fails)
128
156
  DEFAULT_IDIO_RHO0 = 0.1 # Default initial AR coefficient for idiosyncratic components
@@ -130,9 +158,7 @@ DEFAULT_AR_COEF = 0.5 # Default AR coefficient for initialization (conservative
130
158
  DEFAULT_PROCESS_NOISE = 0.1 # Default process noise for initialization
131
159
  DEFAULT_TRANSITION_COEF = 0.9 # Default transition coefficient for DFM initialization
132
160
 
133
- # Standardization defaults
134
- DEFAULT_WX_VALUE = 1.0 # Default standard deviation when Wx is missing (unit scale)
135
- DEFAULT_MX_VALUE = 0.0 # Default mean when Mx is missing (zero-centered)
161
+ # Standardization defaults removed - now using sklearn scalers directly
136
162
 
137
163
  # VAR stability and clipping
138
164
  VAR_STABILITY_THRESHOLD = 0.99 # Maximum eigenvalue for VAR stability
@@ -153,18 +179,20 @@ DEFAULT_DAMPING_FACTOR = 0.5 # Default damping factor for parameter updates (us
153
179
 
154
180
  # Numerical thresholds
155
181
  MIN_CONDITION_NUMBER = 1e-12 # Minimum value for condition number calculations
182
+ MAX_CONDITION_NUMBER = 1e8 # Maximum condition number threshold for regularization (ill-conditioned matrix threshold)
156
183
 
157
184
  # ============================================================================
158
185
  # Display and Logging Defaults
159
186
  # ============================================================================
160
187
 
161
188
  DEFAULT_DISP = 10 # Default display interval for progress
189
+ DEFAULT_LOG_INTERVAL = 10 # Default logging interval divisor (log every num_epochs // DEFAULT_LOG_INTERVAL epochs)
190
+ DEFAULT_PROGRESS_LOG_INTERVAL = 5 # Default progress logging interval for EM algorithm
162
191
 
163
192
  # ============================================================================
164
193
  # Precision Defaults
165
194
  # ============================================================================
166
195
 
167
- DEFAULT_PRECISION = 32 # Default training precision
168
196
  DEFAULT_DTYPE = np.float32 # Default numpy dtype for arrays
169
197
 
170
198
  # PyTorch dtype (matches DEFAULT_DTYPE)
@@ -180,6 +208,34 @@ except ImportError:
180
208
 
181
209
  DEFAULT_IRF_HORIZON = 20 # Default horizon for IRF computation
182
210
 
211
+ # ============================================================================
212
+ # Forecast Defaults
213
+ # ============================================================================
214
+
215
+ DEFAULT_FORECAST_HORIZON = 6 # Default horizon for forecast computation
216
+
217
+ # ============================================================================
218
+ # Error Handling Constants
219
+ # ============================================================================
220
+
221
+ # Common exception types for computation error handling
222
+ # Used to consolidate duplicate exception handling patterns across models
223
+ COMPUTATION_ERROR_TYPES = (RuntimeError, ValueError, TypeError, AttributeError, KeyError)
224
+
225
+ # ============================================================================
226
+ # KDFM Defaults
227
+ # ============================================================================
228
+
229
+ DEFAULT_KDFM_AR_ORDER = 1 # Default AR order (VAR lag order p) for KDFM
230
+ DEFAULT_KDFM_MA_ORDER = 0 # Default MA order (MA lag order q) for KDFM (0 = pure VAR)
231
+
232
+ # ============================================================================
233
+ # Tutorial Defaults
234
+ # ============================================================================
235
+
236
+ TUTORIAL_MAX_PERIODS = 100 # Default maximum periods for tutorial data (reduced for faster execution)
237
+ TUTORIAL_MAX_EPOCHS = 10 # Default maximum epochs for tutorial training (reduced for faster execution)
238
+
183
239
  # ============================================================================
184
240
  # Matrix Type Constants
185
241
  # ============================================================================
@@ -248,6 +304,8 @@ TENT_WEIGHTS_LOOKUP: Dict[Tuple[str, str], np.ndarray] = {
248
304
  ('a', 'm'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: annual -> monthly
249
305
  ('m', 'w'): np.array([1, 2, 3, 2, 1]), # 5 periods: monthly -> weekly
250
306
  ('q', 'w'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: quarterly -> weekly
307
+ ('sa', 'w'): np.array([1, 2, 3, 4, 3, 2, 1]), # 7 periods: semi-annual -> weekly
308
+ ('a', 'w'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: annual -> weekly
251
309
  ('sa', 'q'): np.array([1, 2, 1]), # 3 periods: semi-annual -> quarterly
252
310
  ('a', 'q'): np.array([1, 2, 3, 2, 1]), # 5 periods: annual -> quarterly
253
311
  ('a', 'sa'): np.array([1, 2, 1]), # 3 periods: annual -> semi-annual
@@ -271,19 +329,26 @@ __all__ = [
271
329
  'MIN_FACTOR_VARIANCE',
272
330
  'MIN_STD',
273
331
  'MAX_EIGENVALUE',
332
+ 'DEFAULT_EIGENVALUE_MAX_MAGNITUDE',
333
+ 'DEFAULT_EIGENVALUE_WARN_THRESHOLD',
274
334
  'DEFAULT_CLEAN_NAN',
275
335
  'DEFAULT_CLEAN_INF',
276
336
  'DEFAULT_IDENTITY_SCALE',
277
337
  'DEFAULT_ZERO_VALUE',
338
+ 'DEFAULT_XAVIER_GAIN',
339
+ 'DEFAULT_OUTPUT_LAYER_GAIN',
340
+ 'DEFAULT_INIT_SCALE',
341
+ 'DEFAULT_KERNEL_INIT_SCALE',
278
342
  'DEFAULT_REGULARIZATION_SCALE',
279
343
  'DEFAULT_REGULARIZATION',
280
344
  'DEFAULT_CLIP_THRESHOLD',
281
345
  'DEFAULT_DATA_CLIP_THRESHOLD',
346
+ 'MIN_CONDITION_NUMBER',
347
+ 'MAX_CONDITION_NUMBER',
282
348
  # Training
283
349
  'DEFAULT_MAX_ITER',
284
350
  'DEFAULT_MAX_EPOCHS',
285
351
  'DEFAULT_MAX_MCMC_ITER',
286
- 'DEFAULT_EPOCHS_PER_ITER',
287
352
  'DEFAULT_BATCH_SIZE', # General neural network default (32)
288
353
  'DEFAULT_DDFM_BATCH_SIZE', # DDFM-specific default (100)
289
354
  'DEFAULT_LEARNING_RATE',
@@ -292,9 +357,11 @@ __all__ = [
292
357
  'DEFAULT_WEIGHT_DECAY',
293
358
  'DEFAULT_LR_DECAY_RATE',
294
359
  'DEFAULT_HUBER_DELTA',
360
+ 'HUBER_QUADRATIC_COEFF',
295
361
  'DEFAULT_DDFM_CLIP_RANGE_DEEP',
296
362
  'DEFAULT_DDFM_CLIP_RANGE_SHALLOW',
297
363
  'DEFAULT_EPSILON',
364
+ 'DEFAULT_SEED',
298
365
  # Structural identification
299
366
  'DEFAULT_STRUCTURAL_REG_WEIGHT',
300
367
  'DEFAULT_STRUCTURAL_INIT_SCALE',
@@ -311,6 +378,13 @@ __all__ = [
311
378
  'DEFAULT_MIN_OBS',
312
379
  'DEFAULT_MIN_OBS_IDIO',
313
380
  'DEFAULT_MIN_OBS_VAR',
381
+ # Dimension validation
382
+ 'MIN_TIME_STEPS',
383
+ 'MIN_VARIABLES',
384
+ 'MIN_DDFM_TIME_STEPS',
385
+ 'MIN_DDFM_DATASET_SIZE_WARNING',
386
+ 'MIN_ITER_FOR_DELTA_COMPUTATION',
387
+ 'MIN_EPS_SHAPE_FOR_IDIO',
314
388
  'DEFAULT_IDIO_STD',
315
389
  'DEFAULT_IDIO_RHO0',
316
390
  'DEFAULT_AR_COEF',
@@ -325,19 +399,28 @@ __all__ = [
325
399
  'DEFAULT_SLOWER_FREQ_VARIANCE_DENOMINATOR',
326
400
  'DEFAULT_EXTREME_FORECAST_THRESHOLD',
327
401
  'DEFAULT_MAX_VARIANCE',
328
- # Standardization
329
- 'DEFAULT_WX_VALUE',
330
- 'DEFAULT_MX_VALUE',
331
402
  # Correlation and validation
332
403
  'PERFECT_CORR_THRESHOLD',
333
404
  'HIGH_CORR_THRESHOLD',
334
405
  'DEFAULT_DAMPING_FACTOR',
335
406
  # Display
336
407
  'DEFAULT_DISP',
337
- # Precision
338
- 'DEFAULT_PRECISION',
408
+ 'DEFAULT_LOG_INTERVAL',
409
+ 'DEFAULT_PROGRESS_LOG_INTERVAL',
410
+ 'DEFAULT_TENT_KERNEL_SIZE',
411
+ 'MAX_ERROR_ITEMS',
339
412
  # IRF
340
413
  'DEFAULT_IRF_HORIZON',
414
+ # Forecast
415
+ 'DEFAULT_FORECAST_HORIZON',
416
+ # Error handling
417
+ 'COMPUTATION_ERROR_TYPES',
418
+ # KDFM defaults
419
+ 'DEFAULT_KDFM_AR_ORDER',
420
+ 'DEFAULT_KDFM_MA_ORDER',
421
+ # Tutorial defaults
422
+ 'TUTORIAL_MAX_PERIODS',
423
+ 'TUTORIAL_MAX_EPOCHS',
341
424
  # Matrix types
342
425
  'MATRIX_TYPE_GENERAL',
343
426
  'MATRIX_TYPE_COVARIANCE',
@@ -345,6 +428,9 @@ __all__ = [
345
428
  'MATRIX_TYPE_LOADING',
346
429
  # Log-determinant
347
430
  'MAX_LOG_DETERMINANT',
431
+ 'CHOLESKY_LOG_DET_FACTOR',
432
+ # Matrix computation
433
+ 'SYMMETRY_AVERAGE_FACTOR',
348
434
  # Default frequency
349
435
  'DEFAULT_CLOCK_FREQUENCY',
350
436
  'DEFAULT_HIERARCHY_VALUE',
@@ -68,6 +68,10 @@ from ..constants import (
68
68
  DEFAULT_NAN_METHOD,
69
69
  DEFAULT_NAN_K,
70
70
  DEFAULT_CLOCK_FREQUENCY,
71
+ DEFAULT_KDFM_AR_ORDER,
72
+ DEFAULT_KDFM_MA_ORDER,
73
+ FREQUENCY_HIERARCHY,
74
+ DEFAULT_HIERARCHY_VALUE,
71
75
  )
72
76
 
73
77
 
@@ -124,7 +128,7 @@ class BaseModelConfig:
124
128
  # Shared Data Handling Parameters
125
129
  # ========================================================================
126
130
  clock: str = 'm' # Required: Base frequency for latent factors (global clock): 'd', 'w', 'm', 'q', 'sa', 'a' (defaults to 'm' for monthly)
127
- target_scaler: Optional[ScalerType] = None # Sklearn scaler instance (StandardScaler, RobustScaler, etc.) for target series only. Pass scaler object directly, not string. Feature series are assumed to be manually preprocessed.
131
+ target_scaler: Optional[ScalerType] = None # Fitted sklearn scaler instance (StandardScaler, RobustScaler, etc.) for target series only. Must be a fitted scaler object (call .fit() on target data first). Pass scaler object directly, not string. Feature series are assumed to be manually preprocessed. If None, target series are assumed to be already in the desired scale.
128
132
  # Note: nan_method and nan_k are internal constants (DEFAULT_NAN_METHOD, DEFAULT_NAN_K) used during initialization only
129
133
 
130
134
  def __post_init__(self):
@@ -254,47 +258,38 @@ class DFMConfig(BaseModelConfig):
254
258
  # ========================================================================
255
259
  # EM Algorithm Parameters (HOW - controls the algorithm)
256
260
  # ========================================================================
257
- ar_lag: int = 1 # Number of lags in AR transition equation (lookback window). Must be 1 or 2 (maximum supported order is VAR(2))
261
+ # Note: ar_lag removed - factors always use AR(1) dynamics (simplified)
258
262
  threshold: float = DEFAULT_EM_THRESHOLD # EM convergence threshold
259
263
  max_iter: int = DEFAULT_EM_MAX_ITER # Maximum EM iterations
260
264
 
261
265
  # ========================================================================
262
266
  # Numerical Stability Parameters (transparent and configurable)
263
267
  # ========================================================================
264
- # AR Coefficient Clipping
265
- clip_ar_coefficients: bool = True # Enable AR coefficient clipping for stationarity
266
- ar_clip_min: float = AR_CLIP_MIN # Minimum AR coefficient (must be > -1 for stationarity)
267
- ar_clip_max: float = AR_CLIP_MAX # Maximum AR coefficient (must be < 1 for stationarity)
268
- warn_on_ar_clip: bool = True # Warn when AR coefficients are clipped (indicates near-unit root)
268
+ # AR Coefficient Clipping: If provided (not None), automatically enables clipping and always warns
269
+ ar_clip: Optional[Dict[str, float]] = None # {"min": float, "max": float} - AR coefficient clipping bounds. If None, no clipping. If provided, clipping enabled and warnings always shown.
269
270
 
270
- # Data Value Clipping
271
- clip_data_values: bool = True # Enable clipping of extreme data values
272
- data_clip_threshold: float = 100.0 # Clip values beyond this many standard deviations
273
- warn_on_data_clip: bool = True # Warn when data values are clipped (indicates outliers)
271
+ # Data Value Clipping: If provided (not None), automatically enables clipping and always warns
272
+ data_clip: Optional[float] = None # Clip values beyond this many standard deviations. If None, no clipping. If provided, clipping enabled and warnings always shown.
274
273
 
275
- # Regularization
276
- use_regularization: bool = True # Enable regularization for numerical stability
277
- regularization_scale: float = DEFAULT_REGULARIZATION_SCALE # Scale factor for ridge regularization (relative to trace, default 1e-5)
278
- min_eigenvalue: float = 1e-8 # Minimum eigenvalue for positive definite matrices
279
- max_eigenvalue: float = 1e6 # Maximum eigenvalue cap to prevent explosion
280
- warn_on_regularization: bool = True # Warn when regularization is applied
281
-
282
- # Damped Updates
283
- use_damped_updates: bool = True # Enable damped updates when likelihood decreases
284
- damping_factor: float = 0.8 # Damping factor (0.8 = 80% new, 20% old)
285
- warn_on_damped_update: bool = True # Warn when damped updates are used
286
-
287
- # Idiosyncratic Component Augmentation
288
- augment_idio: bool = True # Enable state augmentation with idiosyncratic components (default: True)
289
- augment_idio_slow: bool = True # Enable tent-length chains for slower-frequency series (default: True)
274
+ # Regularization: If provided (not None), automatically enables regularization and always warns
275
+ regularization: Optional[Dict[str, float]] = None # {"scale": float, "min_eigenvalue": float, "max_eigenvalue": float} - Regularization parameters. If None, no regularization. If provided, regularization enabled and warnings always shown.
276
+
277
+ # Damped Updates: If provided (not None), automatically enables damping and always warns
278
+ damping_factor: Optional[float] = None # Damping factor (0.8 = 80% new, 20% old). If None, no damping. If provided, damping enabled and warnings always shown.
279
+
280
+ # Idiosyncratic Component Parameters (auto-detected from frequencies)
290
281
  idio_rho0: float = DEFAULT_IDIO_RHO0 # Initial AR coefficient for idiosyncratic components (default: 0.1)
291
- idio_min_var: float = 1e-8 # Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
282
+ idio_min_var: float = MIN_DIAGONAL_VARIANCE # Minimum variance for idiosyncratic innovation covariance (defaults to MIN_DIAGONAL_VARIANCE)
283
+ # Note: augment_idio and augment_idio_slow are auto-detected from frequency configuration
284
+ # - If all series use clock frequency: augment_idio=False, augment_idio_slow=False
285
+ # - If mixed frequencies detected: augment_idio=True, augment_idio_slow=True (auto-enabled)
292
286
 
293
287
  def __post_init__(self):
294
288
  """Validate blocks structure and derive block properties."""
295
289
  super().__post_init__()
296
290
 
297
291
  from ...config.adapter import _raise_config_error
292
+ from ..constants import FREQUENCY_HIERARCHY, DEFAULT_HIERARCHY_VALUE
298
293
 
299
294
  if not self.blocks:
300
295
  _raise_config_error("DFM configuration must contain at least one block.")
@@ -327,6 +322,67 @@ class DFMConfig(BaseModelConfig):
327
322
  from ...config.adapter import _raise_config_error
328
323
  if any(f < 1 for f in self.factors_per_block):
329
324
  _raise_config_error("factors_per_block must contain positive integers >= 1")
325
+
326
+ # Auto-detect mixed frequencies and set augment_idio/augment_idio_slow
327
+ # If frequency dict exists, check if all frequencies match clock
328
+ if self.frequency is not None and len(self.frequency) > 0:
329
+ frequencies = list(self.frequency.values())
330
+ clock_hierarchy = FREQUENCY_HIERARCHY.get(self.clock, DEFAULT_HIERARCHY_VALUE)
331
+ is_mixed_freq = any(
332
+ FREQUENCY_HIERARCHY.get(freq, DEFAULT_HIERARCHY_VALUE) != clock_hierarchy
333
+ for freq in frequencies
334
+ )
335
+ # Auto-set augment_idio and augment_idio_slow based on frequency detection
336
+ # Store as internal attributes (not in __init__ signature)
337
+ object.__setattr__(self, '_augment_idio', is_mixed_freq)
338
+ object.__setattr__(self, '_augment_idio_slow', is_mixed_freq)
339
+ else:
340
+ # No frequency info yet - will be auto-detected when data is loaded
341
+ object.__setattr__(self, '_augment_idio', False)
342
+ object.__setattr__(self, '_augment_idio_slow', False)
343
+
344
+ @property
345
+ def augment_idio(self) -> bool:
346
+ """Auto-detected: True if mixed frequencies detected, False if single frequency."""
347
+ return getattr(self, '_augment_idio', False)
348
+
349
+ @property
350
+ def augment_idio_slow(self) -> bool:
351
+ """Auto-detected: True if mixed frequencies detected, False if single frequency."""
352
+ return getattr(self, '_augment_idio_slow', False)
353
+
354
+ def _update_idio_flags_from_frequencies(self, frequencies: List[str]) -> None:
355
+ """Update augment_idio flags based on detected frequencies (called when data is loaded)."""
356
+ from ..constants import FREQUENCY_HIERARCHY, DEFAULT_HIERARCHY_VALUE
357
+ clock_hierarchy = FREQUENCY_HIERARCHY.get(self.clock, DEFAULT_HIERARCHY_VALUE)
358
+ is_mixed_freq = any(
359
+ FREQUENCY_HIERARCHY.get(freq, DEFAULT_HIERARCHY_VALUE) != clock_hierarchy
360
+ for freq in frequencies
361
+ )
362
+ object.__setattr__(self, '_augment_idio', is_mixed_freq)
363
+ object.__setattr__(self, '_augment_idio_slow', is_mixed_freq)
364
+
365
+ def to_em_config(self) -> 'EMConfig':
366
+ """Create EMConfig from DFMConfig consolidated parameters."""
367
+ from ...functional.em import EMConfig
368
+ from ..constants import DEFAULT_REGULARIZATION, VAR_STABILITY_THRESHOLD
369
+
370
+ # Extract regularization parameters directly from dict
371
+ if self.regularization is not None and isinstance(self.regularization, dict):
372
+ reg_scale = self.regularization.get('scale', DEFAULT_REGULARIZATION_SCALE)
373
+ min_eigenval = self.regularization.get('min_eigenvalue', MIN_EIGENVALUE)
374
+ max_eigenval = self.regularization.get('max_eigenvalue', VAR_STABILITY_THRESHOLD)
375
+ else:
376
+ reg_scale = DEFAULT_REGULARIZATION
377
+ min_eigenval = MIN_EIGENVALUE
378
+ max_eigenval = VAR_STABILITY_THRESHOLD
379
+
380
+ return EMConfig(
381
+ regularization=reg_scale,
382
+ min_norm=min_eigenval,
383
+ max_eigenval=max_eigenval,
384
+ # Other parameters use defaults from EMConfig
385
+ )
330
386
 
331
387
  def get_blocks_array(self, columns: Optional[List[str]] = None) -> np.ndarray:
332
388
  """Get blocks as numpy array (N x B) where N is number of series and B is number of blocks.
@@ -357,38 +413,43 @@ class DFMConfig(BaseModelConfig):
357
413
  return self._cached_blocks
358
414
 
359
415
  @classmethod
360
- def _extract_dfm_params(cls, data: Dict[str, Any]) -> Dict[str, Any]:
361
- """Extract DFM-specific parameters from config dict."""
362
- base_params = cls._extract_base(data)
363
- dfm_params = cls._extract_params(data, {
364
- 'ar_lag': 1,
365
- 'threshold': DEFAULT_EM_THRESHOLD,
366
- 'max_iter': DEFAULT_EM_MAX_ITER,
367
- 'clip_ar_coefficients': True,
368
- 'ar_clip_min': -0.99,
369
- 'ar_clip_max': 0.99,
370
- 'use_regularization': True,
371
- 'regularization_scale': 1e-5,
372
- 'min_eigenvalue': MIN_EIGENVALUE,
373
- 'max_eigenvalue': MAX_EIGENVALUE,
374
- 'augment_idio': True,
375
- 'augment_idio_slow': True,
376
- 'idio_rho0': 0.1,
377
- 'idio_min_var': MIN_DIAGONAL_VARIANCE,
378
- })
379
- base_params.update(dfm_params)
380
- return base_params
416
+ def _extract_dfm_params(cls, data: Dict[str, Any], base_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
417
+ """Extract DFM-specific parameters from config dict.
418
+
419
+ Parameters
420
+ ----------
421
+ data : Dict[str, Any]
422
+ Config dictionary
423
+ base_params : Dict[str, Any], optional
424
+ Pre-extracted base parameters. If provided, avoids duplicate extraction.
425
+ If None, extracts base params internally.
426
+ """
427
+ if base_params is None:
428
+ base_params = cls._extract_base(data)
429
+
430
+ # Extract consolidated parameters (new format only)
431
+ dfm_params = {
432
+ 'threshold': data.get('threshold', DEFAULT_EM_THRESHOLD),
433
+ 'max_iter': data.get('max_iter', DEFAULT_EM_MAX_ITER),
434
+ 'idio_rho0': data.get('idio_rho0', DEFAULT_IDIO_RHO0),
435
+ 'idio_min_var': data.get('idio_min_var', MIN_DIAGONAL_VARIANCE),
436
+ 'ar_clip': data.get('ar_clip', None),
437
+ 'data_clip': data.get('data_clip', None),
438
+ 'regularization': data.get('regularization', None),
439
+ 'damping_factor': data.get('damping_factor', None),
440
+ }
441
+
442
+ result = base_params.copy()
443
+ result.update(dfm_params)
444
+ return result
381
445
 
382
446
  @classmethod
383
447
  def from_dict(cls, data: Dict[str, Any]) -> Union['DFMConfig', 'DDFMConfig']:
384
448
  """Create DFMConfig or DDFMConfig from dictionary.
385
449
 
386
- Handles multiple formats:
387
- 1. New format: {'frequency': {'column_name': 'frequency'}, ...}
388
- 2. Legacy format (Hydra): {'series': {'series_id': {...}}, 'blocks': {...}}
389
- 3. Legacy format (list): {'series': [{'series_id': ..., 'frequency': ...}], ...}
450
+ Expected format: {'frequency': {'column_name': 'frequency'}, 'blocks': {...}, ...}
390
451
 
391
- Also accepts estimation parameters: ar_lag, threshold, max_iter, etc.
452
+ Also accepts estimation parameters: threshold, max_iter, etc.
392
453
  """
393
454
  from ...config.adapter import detect_config_type, MODEL_TYPE_DDFM, _normalize_blocks_dict
394
455
  from ...utils.errors import ConfigurationError
@@ -411,7 +472,9 @@ class DFMConfig(BaseModelConfig):
411
472
  _raise_config_error(f"blocks must be a dict, got {type(blocks_dict)}")
412
473
 
413
474
  blocks_dict_normalized = _normalize_blocks_dict(blocks_dict)
414
- return DFMConfig(blocks=blocks_dict_normalized, **base_params, **DFMConfig._extract_dfm_params(data))
475
+ # Pass base_params to _extract_dfm_params to avoid duplicate extraction
476
+ dfm_params = DFMConfig._extract_dfm_params(data, base_params=base_params)
477
+ return DFMConfig(blocks=blocks_dict_normalized, **dfm_params)
415
478
 
416
479
 
417
480
  @dataclass
@@ -438,7 +501,7 @@ class DDFMConfig(BaseModelConfig):
438
501
  learning_rate: float = 0.001 # Learning rate for Adam optimizer (default: 0.001)
439
502
  epochs: int = 100 # Number of training epochs (default: 100)
440
503
  batch_size: int = 100 # Batch size for training (default: 100 to match original DDFM)
441
- factor_order: int = 1 # VAR lag order for factor dynamics. Must be 1 or 2 (maximum supported order is VAR(2), default: 1)
504
+ # Note: factor_order removed - factors always use AR(1) dynamics (simplified)
442
505
  use_idiosyncratic: bool = True # Model idio components with AR(1) dynamics (default: True)
443
506
  min_obs_idio: int = 5 # Minimum observations for idio AR(1) estimation (default: 5)
444
507
 
@@ -465,7 +528,6 @@ class DDFMConfig(BaseModelConfig):
465
528
  'learning_rate': DEFAULT_LEARNING_RATE,
466
529
  'epochs': DEFAULT_MAX_EPOCHS,
467
530
  'batch_size': DEFAULT_DDFM_BATCH_SIZE,
468
- 'factor_order': 1,
469
531
  'use_idiosyncratic': True,
470
532
  'min_obs_idio': DEFAULT_MIN_OBS_IDIO,
471
533
  'max_iter': DEFAULT_MAX_MCMC_ITER,
@@ -482,7 +544,11 @@ class DDFMConfig(BaseModelConfig):
482
544
  result = DFMConfig.from_dict(data)
483
545
  if isinstance(result, DDFMConfig):
484
546
  return result
485
- raise ValueError("Expected DDFMConfig but got DFMConfig")
547
+ from ..utils.errors import ConfigurationError
548
+ raise ConfigurationError(
549
+ "Expected DDFMConfig but got DFMConfig",
550
+ details=f"Result type: {type(result).__name__}, expected: DDFMConfig"
551
+ )
486
552
 
487
553
 
488
554
  @dataclass
@@ -493,8 +559,8 @@ class KDFMConfig(BaseModelConfig):
493
559
  It inherits from BaseModelConfig and adds KDFM-specific parameters.
494
560
  """
495
561
  # VARMA parameters
496
- ar_order: int = 1 # VAR order p
497
- ma_order: int = 0 # MA order q (0 = pure VAR)
562
+ ar_order: int = DEFAULT_KDFM_AR_ORDER # VAR order p
563
+ ma_order: int = DEFAULT_KDFM_MA_ORDER # MA order q (0 = pure VAR)
498
564
 
499
565
  # Structural identification
500
566
  structural_method: str = 'cholesky' # 'cholesky', 'full', 'low_rank'