dfm-python 0.5.56__tar.gz → 0.5.58__tar.gz

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 (154) hide show
  1. {dfm_python-0.5.56 → dfm_python-0.5.58}/PKG-INFO +4 -5
  2. {dfm_python-0.5.56 → dfm_python-0.5.58}/README.md +3 -3
  3. {dfm_python-0.5.56 → dfm_python-0.5.58}/config/ddfm_macro.yaml +22 -21
  4. {dfm_python-0.5.56 → dfm_python-0.5.58}/config/dfm_macro.yaml +24 -22
  5. {dfm_python-0.5.56 → dfm_python-0.5.58}/pyproject.toml +2 -1
  6. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/__init__.py +0 -1
  7. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/__init__.py +4 -13
  8. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/adapter.py +68 -7
  9. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/constants.py +5 -21
  10. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/schema/__init__.py +2 -6
  11. dfm_python-0.5.58/src/dfm_python/config/schema/block.py +124 -0
  12. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/schema/model.py +31 -5
  13. dfm_python-0.5.58/src/dfm_python/config/schema/params.py +279 -0
  14. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/schema/results.py +8 -74
  15. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/config/types.py +3 -11
  16. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/dataset/ddfm_dataset.py +104 -16
  17. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/dataset/dfm_dataset.py +80 -14
  18. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/encoder/pca.py +59 -9
  19. dfm_python-0.5.58/src/dfm_python/functional/__init__.py +11 -0
  20. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/functional/dfm_block.py +85 -19
  21. dfm_python-0.5.58/src/dfm_python/functional/em.py +1356 -0
  22. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/logger/__init__.py +6 -12
  23. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/logger/inference_logger.py +3 -17
  24. dfm_python-0.5.58/src/dfm_python/logger/logger.py +271 -0
  25. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/logger/train_logger.py +26 -34
  26. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/models/__init__.py +2 -2
  27. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/models/base.py +8 -29
  28. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/models/ddfm.py +259 -30
  29. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/models/dfm.py +648 -236
  30. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/numeric/builder.py +1 -5
  31. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/numeric/estimator.py +217 -28
  32. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/numeric/stability.py +99 -151
  33. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/numeric/tent.py +75 -44
  34. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/numeric/validator.py +86 -2
  35. dfm_python-0.5.58/src/dfm_python/ssm/__init__.py +29 -0
  36. dfm_python-0.5.58/src/dfm_python/ssm/kalman.py +628 -0
  37. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/__init__.py +2 -5
  38. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/checkpoint.py +1 -1
  39. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/helper.py +64 -8
  40. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/misc.py +66 -19
  41. dfm_python-0.5.58/src/dfm_python/utils/validation.py +54 -0
  42. dfm_python-0.5.58/src/test/config/schema/test_model.py +67 -0
  43. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/config/schema/test_results.py +41 -58
  44. dfm_python-0.5.58/src/test/config/test_adapter.py +231 -0
  45. dfm_python-0.5.58/src/test/config/test_tent_weights_config.py +240 -0
  46. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/dataset/test_process.py +2 -2
  47. dfm_python-0.5.58/src/test/functional/test_dfm_block.py +140 -0
  48. dfm_python-0.5.58/src/test/functional/test_em.py +878 -0
  49. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/logger/test_inference_logger.py +4 -4
  50. dfm_python-0.5.58/src/test/logger/test_logger.py +146 -0
  51. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/logger/test_train_logger.py +4 -4
  52. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/models/test_ddfm.py +81 -6
  53. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/models/test_dfm.py +90 -2
  54. dfm_python-0.5.58/src/test/models/test_dfm_fixes.py +261 -0
  55. dfm_python-0.5.58/src/test/models/test_dfm_result_caching.py +309 -0
  56. dfm_python-0.5.58/src/test/models/test_dfm_save_load.py +261 -0
  57. dfm_python-0.5.58/src/test/models/test_dfm_save_load_integration.py +136 -0
  58. dfm_python-0.5.58/src/test/models/test_dfm_save_load_mock.py +340 -0
  59. dfm_python-0.5.58/src/test/models/test_dfm_training_pipeline_save.py +246 -0
  60. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/numeric/test_builder.py +5 -4
  61. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/numeric/test_estimator.py +4 -3
  62. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/numeric/test_stability.py +51 -50
  63. dfm_python-0.5.58/src/test/numeric/test_tent.py +113 -0
  64. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/ssm/test_kalman.py +40 -0
  65. dfm_python-0.5.58/src/test/ssm/test_kalman_progress.py +161 -0
  66. dfm_python-0.5.58/src/test/ssm/test_progress_bar_exact_scenario.py +226 -0
  67. dfm_python-0.5.58/src/test/ssm/test_progress_bar_spam_fix.py +297 -0
  68. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_helper.py +21 -0
  69. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_misc.py +51 -1
  70. {dfm_python-0.5.56 → dfm_python-0.5.58}/tutorial/tutorial_finance_dfm.py +1 -1
  71. {dfm_python-0.5.56 → dfm_python-0.5.58}/tutorial/tutorial_macro_dfm.py +1 -1
  72. {dfm_python-0.5.56 → dfm_python-0.5.58}/uv.lock +1 -1
  73. dfm_python-0.5.56/LICENSE +0 -21
  74. dfm_python-0.5.56/src/dfm_python/config/schema/block.py +0 -64
  75. dfm_python-0.5.56/src/dfm_python/config/schema/params.py +0 -94
  76. dfm_python-0.5.56/src/dfm_python/functional/__init__.py +0 -13
  77. dfm_python-0.5.56/src/dfm_python/functional/em.py +0 -977
  78. dfm_python-0.5.56/src/dfm_python/functional/irf.py +0 -305
  79. dfm_python-0.5.56/src/dfm_python/functional/krylov.py +0 -124
  80. dfm_python-0.5.56/src/dfm_python/logger/logger.py +0 -147
  81. dfm_python-0.5.56/src/dfm_python/ssm/__init__.py +0 -19
  82. dfm_python-0.5.56/src/dfm_python/ssm/arma.py +0 -0
  83. dfm_python-0.5.56/src/dfm_python/ssm/companion.py +0 -995
  84. dfm_python-0.5.56/src/dfm_python/ssm/kalman.py +0 -433
  85. dfm_python-0.5.56/src/dfm_python/ssm/structural.py +0 -298
  86. dfm_python-0.5.56/src/dfm_python/utils/validation.py +0 -121
  87. dfm_python-0.5.56/src/test/config/schema/test_model.py +0 -22
  88. dfm_python-0.5.56/src/test/config/test_adapter.py +0 -90
  89. dfm_python-0.5.56/src/test/functional/test_dfm_block.py +0 -222
  90. dfm_python-0.5.56/src/test/functional/test_em.py +0 -57
  91. dfm_python-0.5.56/src/test/logger/test_logger.py +0 -28
  92. dfm_python-0.5.56/src/test/numeric/test_tent.py +0 -69
  93. {dfm_python-0.5.56 → dfm_python-0.5.58}/.git +0 -0
  94. {dfm_python-0.5.56 → dfm_python-0.5.58}/.gitignore +0 -0
  95. {dfm_python-0.5.56 → dfm_python-0.5.58}/.python-version +0 -0
  96. {dfm_python-0.5.56 → dfm_python-0.5.58}/MANIFEST.in +0 -0
  97. {dfm_python-0.5.56 → dfm_python-0.5.58}/config/ddfm_exchange.yaml +0 -0
  98. {dfm_python-0.5.56 → dfm_python-0.5.58}/config/ddfm_finance.yaml +0 -0
  99. {dfm_python-0.5.56 → dfm_python-0.5.58}/config/dfm_finance.yaml +0 -0
  100. {dfm_python-0.5.56 → dfm_python-0.5.58}/data/exchange_rate.csv +0 -0
  101. {dfm_python-0.5.56 → dfm_python-0.5.58}/data/finance.csv +0 -0
  102. {dfm_python-0.5.56 → dfm_python-0.5.58}/data/macro.csv +0 -0
  103. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/dataset/__init__.py +0 -0
  104. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/dataset/time.py +0 -0
  105. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/decoder/__init__.py +0 -0
  106. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/decoder/base.py +0 -0
  107. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/decoder/linear.py +0 -0
  108. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/decoder/mlp.py +0 -0
  109. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/encoder/__init__.py +0 -0
  110. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/encoder/base.py +0 -0
  111. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/encoder/simple_autoencoder.py +0 -0
  112. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/numeric/statistic.py +0 -0
  113. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/errors.py +0 -0
  114. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/loss.py +0 -0
  115. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/dfm_python/utils/metric.py +0 -0
  116. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/__init__.py +0 -0
  117. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/config/__init__.py +0 -0
  118. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/config/schema/__init__.py +0 -0
  119. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/config/schema/test_block.py +0 -0
  120. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/config/test_constants.py +0 -0
  121. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/config/test_types.py +0 -0
  122. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/conftest.py +0 -0
  123. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/dataset/__init__.py +0 -0
  124. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/dataset/test_dataloader.py +0 -0
  125. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/dataset/test_dataset.py +0 -0
  126. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/decoder/__init__.py +0 -0
  127. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/decoder/test_linear.py +0 -0
  128. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/decoder/test_mlp.py +0 -0
  129. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/encoder/__init__.py +0 -0
  130. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/encoder/test_base.py +0 -0
  131. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/encoder/test_pca.py +0 -0
  132. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/encoder/test_simple_encoder.py +0 -0
  133. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/functional/__init__.py +0 -0
  134. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/logger/__init__.py +0 -0
  135. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/models/__init__.py +0 -0
  136. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/models/test_base.py +0 -0
  137. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/models/test_ddfm_batchnorm_placement.py +0 -0
  138. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/models/test_tensorflow_batchnorm_comparison.py +0 -0
  139. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/numeric/__init__.py +0 -0
  140. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/numeric/test_validator.py +0 -0
  141. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/ssm/__init__.py +0 -0
  142. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/ssm/test_arma.py +0 -0
  143. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/ssm/test_companion.py +0 -0
  144. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/ssm/test_structural.py +0 -0
  145. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/__init__.py +0 -0
  146. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_common.py +0 -0
  147. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_errors.py +0 -0
  148. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_metric.py +0 -0
  149. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_tensor_utils.py +0 -0
  150. {dfm_python-0.5.56 → dfm_python-0.5.58}/src/test/utils/test_validation.py +0 -0
  151. {dfm_python-0.5.56 → dfm_python-0.5.58}/tutorial/tutorial_exchange_ddfm.py +0 -0
  152. {dfm_python-0.5.56 → dfm_python-0.5.58}/tutorial/tutorial_finance_ddfm.py +0 -0
  153. {dfm_python-0.5.56 → dfm_python-0.5.58}/tutorial/tutorial_illness_ddfm.py +0 -0
  154. {dfm_python-0.5.56 → dfm_python-0.5.58}/tutorial/tutorial_macro_ddfm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.5.56
3
+ Version: 0.5.58
4
4
  Summary: Dynamic Factor Model (DFM) estimation and forecasting in Python
5
5
  Project-URL: Homepage, https://github.com/yourusername/dfm-python
6
6
  Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
@@ -8,7 +8,6 @@ Project-URL: Repository, https://github.com/yourusername/dfm-python
8
8
  Project-URL: Issues, https://github.com/yourusername/dfm-python/issues
9
9
  Author: Minkey Chang
10
10
  License: MIT
11
- License-File: LICENSE
12
11
  Keywords: dfm,dynamic-factor-model,econometrics,forecasting,time-series
13
12
  Classifier: Development Status :: 4 - Beta
14
13
  Classifier: Intended Audience :: Science/Research
@@ -176,7 +175,7 @@ dfm-python/
176
175
  │ └── dfm_python/
177
176
  │ ├── models/ # Model implementations (DFM, DDFM, KDFM)
178
177
  │ ├── config/ # Configuration schemas
179
- │ ├── datamodule/ # PyTorch Lightning DataModules
178
+ │ ├── datamodule/ # Data loading and preprocessing modules
180
179
  │ ├── trainer/ # Training procedures
181
180
  │ ├── ssm/ # State-space model components
182
181
  │ ├── dataset/ # Data loading and preprocessing
@@ -194,7 +193,7 @@ dfm-python/
194
193
  - **Structural identification**: Learnable structural shock identification (Cholesky, full, low-rank)
195
194
  - **Efficient computation**: Krylov FFT for O(T log T) forward pass
196
195
  - **Direct IRF computation**: Avoids numerical error accumulation
197
- - **Gradient descent training**: Uses PyTorch Lightning for training
196
+ - **Gradient descent training**: Uses PyTorch for training with custom logging
198
197
 
199
198
  ### DFM (Dynamic Factor Model)
200
199
 
@@ -207,7 +206,7 @@ dfm-python/
207
206
 
208
207
  - **Nonlinear encoder**: Neural network encoder for nonlinear factor extraction
209
208
  - **MCMC training**: Denoising training procedure
210
- - **PyTorch-based**: Full PyTorch implementation with Lightning support
209
+ - **PyTorch-based**: Full PyTorch implementation with custom logging system
211
210
 
212
211
  ## IRF Analysis: KDFM's PRIMARY CONTRIBUTION
213
212
 
@@ -118,7 +118,7 @@ dfm-python/
118
118
  │ └── dfm_python/
119
119
  │ ├── models/ # Model implementations (DFM, DDFM, KDFM)
120
120
  │ ├── config/ # Configuration schemas
121
- │ ├── datamodule/ # PyTorch Lightning DataModules
121
+ │ ├── datamodule/ # Data loading and preprocessing modules
122
122
  │ ├── trainer/ # Training procedures
123
123
  │ ├── ssm/ # State-space model components
124
124
  │ ├── dataset/ # Data loading and preprocessing
@@ -136,7 +136,7 @@ dfm-python/
136
136
  - **Structural identification**: Learnable structural shock identification (Cholesky, full, low-rank)
137
137
  - **Efficient computation**: Krylov FFT for O(T log T) forward pass
138
138
  - **Direct IRF computation**: Avoids numerical error accumulation
139
- - **Gradient descent training**: Uses PyTorch Lightning for training
139
+ - **Gradient descent training**: Uses PyTorch for training with custom logging
140
140
 
141
141
  ### DFM (Dynamic Factor Model)
142
142
 
@@ -149,7 +149,7 @@ dfm-python/
149
149
 
150
150
  - **Nonlinear encoder**: Neural network encoder for nonlinear factor extraction
151
151
  - **MCMC training**: Denoising training procedure
152
- - **PyTorch-based**: Full PyTorch implementation with Lightning support
152
+ - **PyTorch-based**: Full PyTorch implementation with custom logging system
153
153
 
154
154
  ## IRF Analysis: KDFM's PRIMARY CONTRIBUTION
155
155
 
@@ -26,29 +26,30 @@ target_scaler: standard # Scaler for target series: 'standard', 'robust', 'minm
26
26
 
27
27
  # Frequency mapping for mixed-frequency macro data
28
28
  # Clock is weekly (w), so monthly series are slower frequency
29
- # All frequencies must be explicitly specified
29
+ # Grouped format: frequencies are specified as lists grouped by frequency code
30
30
  # Reduced set for faster testing (~50% reduction: 12 monthly series kept from original ~24)
31
31
  frequency:
32
32
  # Monthly series (slower than weekly clock) - typically end-of-month observations
33
33
  # Essential series for testing
34
- KOEMPTOTO: m # Total employment (monthly)
35
- KOHWRWEMP: m # Hours worked (monthly)
36
- KOIMPCONA: m # Investment, Construction (monthly)
37
- KOWRCCNSE: m # Consumption (monthly)
38
- KOCONPRCF: m # Production (monthly)
39
- KOCPCOREF: m # Consumer prices (monthly)
40
- KOCPEXFEF: m # Export prices (monthly)
41
- KOCREU..A: m # Credit (monthly)
42
- KOLRLTHH: m # Interest rates (monthly)
43
- KOBSIHAPR: m # Business survey (monthly)
44
- KOCNFCONR: m # Confidence (monthly)
45
- # Target series
46
- KOEQUIPTE: m # Investment, Equipment (monthly, target series)
34
+ m:
35
+ - KOEMPTOTO # Total employment (monthly)
36
+ - KOHWRWEMP # Hours worked (monthly)
37
+ - KOIMPCONA # Investment, Construction (monthly)
38
+ - KOWRCCNSE # Consumption (monthly)
39
+ - KOCONPRCF # Production (monthly)
40
+ - KOCPCOREF # Consumer prices (monthly)
41
+ - KOCPEXFEF # Export prices (monthly)
42
+ - KOCREU..A # Credit (monthly)
43
+ - KOLRLTHH # Interest rates (monthly)
44
+ - KOBSIHAPR # Business survey (monthly)
45
+ - KOCNFCONR # Confidence (monthly)
46
+ - KOEQUIPTE # Investment, Equipment (monthly, target series)
47
47
  # Weekly series (same as clock frequency)
48
- KORFBEV: w # Retail, Food & Beverages (weekly)
49
- KORWHLS: w # Retail, Wholesale (weekly)
50
- KORMCMP: w # Retail, Miscellaneous & Comparison (weekly)
51
- KORELEC: w # Retail, Electronics (weekly)
52
- KORCNST: w # Retail, Construction (weekly)
53
- KORMACH: w # Retail, Machinery (weekly)
54
- KOROCOINF: w # Retail, Other Consumer & Information (weekly)
48
+ w:
49
+ - KORFBEV # Retail, Food & Beverages (weekly)
50
+ - KORWHLS # Retail, Wholesale (weekly)
51
+ - KORMCMP # Retail, Miscellaneous & Comparison (weekly)
52
+ - KORELEC # Retail, Electronics (weekly)
53
+ - KORCNST # Retail, Construction (weekly)
54
+ - KORMACH # Retail, Machinery (weekly)
55
+ - KOROCOINF # Retail, Other Consumer & Information (weekly)
@@ -55,30 +55,32 @@ blocks:
55
55
 
56
56
  # Frequency mapping for mixed-frequency macro data
57
57
  # Clock is weekly (w), so monthly series are slower frequency
58
- # All frequencies must be explicitly specified
58
+ # Grouped format: frequencies are specified as lists grouped by frequency code
59
59
  # Reduced set for faster testing (~50% reduction: 18 monthly series kept from original ~36)
60
60
  frequency:
61
61
  # Monthly series (slower than weekly clock) - typically end-of-month observations
62
62
  # Block_Real series
63
- KOEMPTOTO: m # Total employment (monthly)
64
- KOHWRWEMP: m # Hours worked (monthly)
65
- KOIMPCONA: m # Investment, Construction (monthly)
66
- KOWRCCNSE: m # Consumption (monthly)
67
- KOCONPRCF: m # Production (monthly)
68
- # Block_Prices_Financial series
69
- KOCPCOREF: m # Consumer prices (monthly)
70
- KOCPEXFEF: m # Export prices (monthly)
71
- KOCREU..A: m # Credit (monthly)
72
- KOLRLTHH: m # Interest rates (monthly)
73
- KOBSIHAPR: m # Business survey (monthly)
74
- KOCNFCONR: m # Confidence (monthly)
75
- # Target series
76
- KOEQUIPTE: m # Investment, Equipment (monthly, target series)
63
+ m:
64
+ - KOEMPTOTO # Total employment (monthly)
65
+ - KOHWRWEMP # Hours worked (monthly)
66
+ - KOIMPCONA # Investment, Construction (monthly)
67
+ - KOWRCCNSE # Consumption (monthly)
68
+ - KOCONPRCF # Production (monthly)
69
+ # Block_Prices_Financial series
70
+ - KOCPCOREF # Consumer prices (monthly)
71
+ - KOCPEXFEF # Export prices (monthly)
72
+ - KOCREU..A # Credit (monthly)
73
+ - KOLRLTHH # Interest rates (monthly)
74
+ - KOBSIHAPR # Business survey (monthly)
75
+ - KOCNFCONR # Confidence (monthly)
76
+ # Target series
77
+ - KOEQUIPTE # Investment, Equipment (monthly, target series)
77
78
  # Weekly series (same as clock frequency)
78
- KORFBEV: w # Retail, Food & Beverages (weekly)
79
- KORWHLS: w # Retail, Wholesale (weekly)
80
- KORMCMP: w # Retail, Miscellaneous & Comparison (weekly)
81
- KORELEC: w # Retail, Electronics (weekly)
82
- KORCNST: w # Retail, Construction (weekly)
83
- KORMACH: w # Retail, Machinery (weekly)
84
- KOROCOINF: w # Retail, Other Consumer & Information (weekly)
79
+ w:
80
+ - KORFBEV # Retail, Food & Beverages (weekly)
81
+ - KORWHLS # Retail, Wholesale (weekly)
82
+ - KORMCMP # Retail, Miscellaneous & Comparison (weekly)
83
+ - KORELEC # Retail, Electronics (weekly)
84
+ - KORCNST # Retail, Construction (weekly)
85
+ - KORMACH # Retail, Machinery (weekly)
86
+ - KOROCOINF # Retail, Other Consumer & Information (weekly)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dfm-python"
3
- version = "0.5.56"
3
+ version = "0.5.58"
4
4
  description = "Dynamic Factor Model (DFM) estimation and forecasting in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -83,6 +83,7 @@ packages = [
83
83
  exclude = [
84
84
  "test_venv",
85
85
  "test_venv/**",
86
+ "LICENSE",
86
87
  ]
87
88
 
88
89
  [dependency-groups]
@@ -80,7 +80,6 @@ __version__ = "0.5.56"
80
80
 
81
81
  # Configuration (from config/ subpackage)
82
82
  from .config import (
83
- # DEFAULT_BLOCK_NAME, # Removed to avoid circular import - import directly from functional.dfm_block
84
83
  ConfigSource, YamlSource,
85
84
  make_config_source,
86
85
  )
@@ -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,
@@ -89,9 +86,8 @@ except ImportError:
89
86
  compute_idio_lengths = None
90
87
 
91
88
  try:
92
- from ..numeric.tent import get_tent_weights, get_agg_structure, group_by_freq
89
+ from ..numeric.tent import get_agg_structure, group_by_freq
93
90
  except ImportError:
94
- get_tent_weights = None
95
91
  get_agg_structure = None
96
92
  group_by_freq = None
97
93
  from .constants import FREQUENCY_HIERARCHY, PERIODS_PER_YEAR
@@ -107,9 +103,8 @@ __all__ = [
107
103
  # 'DEFAULT_BLOCK_NAME', # Removed to avoid circular import - import directly from functional.dfm_block
108
104
  # Model-specific configs (from schema.py)
109
105
  'DFMConfig', 'DDFMConfig',
110
- # Parameter overrides
111
- 'DFMFitParams', 'DDFMFitParams',
112
- 'FitParams', # Backward compatibility alias
106
+ # State-space parameters
107
+ 'DFMStateSpaceParams', 'DDFMStateSpaceParams',
113
108
  # Model-specific results (from results.py)
114
109
  'DFMResult', 'DDFMResult',
115
110
  # Utilities
@@ -123,7 +118,6 @@ __all__ = [
123
118
  'PERIODS_PER_YEAR',
124
119
  'get_periods_per_year',
125
120
  'compute_idio_lengths',
126
- 'get_tent_weights',
127
121
  'get_agg_structure',
128
122
  'group_by_freq',
129
123
  # Type definitions (from types.py)
@@ -139,8 +133,6 @@ __all__ = [
139
133
  'ForecastResult',
140
134
  'CoefficientMatrix',
141
135
  'CovarianceMatrix',
142
- 'IRFArray',
143
- 'CompanionMatrix',
144
136
  'SeriesID',
145
137
  'Frequency',
146
138
  'DatasetName',
@@ -151,7 +143,6 @@ __all__ = [
151
143
  'ResultDict',
152
144
  'ForecastDict',
153
145
  'MetricsDict',
154
- 'IRFResultDict',
155
146
  'CheckpointDict',
156
147
  'ConfigDict',
157
148
  '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
 
@@ -496,6 +552,11 @@ def detect_config_type(data: Dict[str, Any]) -> str:
496
552
  if model_type == MODEL_TYPE_DFM:
497
553
  return MODEL_TYPE_DFM
498
554
 
555
+ # Priority check: blocks parameter indicates DFM (highest priority)
556
+ # DFM is the only model type that uses blocks structure
557
+ if 'blocks' in data and data.get('blocks'):
558
+ return MODEL_TYPE_DFM
559
+
499
560
  # Check for model-specific parameters
500
561
  keys = set(data.keys())
501
562
  if keys & _KDFM_PARAMS:
@@ -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:
@@ -386,22 +387,6 @@ FREQUENCY_HIERARCHY: Dict[str, int] = {
386
387
  # For frequency gaps larger than this, the missing data approach is used instead
387
388
  MAX_TENT_SIZE: int = 12
388
389
 
389
- # Deterministic tent weights lookup for supported frequency pairs
390
- # Format: (slower_freq, faster_freq) -> tent_weights_array
391
- # These weights define how slower-frequency series aggregate clock-frequency factors
392
- TENT_WEIGHTS_LOOKUP: Dict[Tuple[str, str], np.ndarray] = {
393
- ('q', 'm'): np.array([1, 2, 3, 2, 1]), # 5 periods: quarterly -> monthly
394
- ('sa', 'm'): np.array([1, 2, 3, 4, 3, 2, 1]), # 7 periods: semi-annual -> monthly
395
- ('a', 'm'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: annual -> monthly
396
- ('m', 'w'): np.array([1, 2, 3, 2, 1]), # 5 periods: monthly -> weekly
397
- ('q', 'w'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: quarterly -> weekly
398
- ('sa', 'w'): np.array([1, 2, 3, 4, 3, 2, 1]), # 7 periods: semi-annual -> weekly
399
- ('a', 'w'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: annual -> weekly
400
- ('sa', 'q'): np.array([1, 2, 1]), # 3 periods: semi-annual -> quarterly
401
- ('a', 'q'): np.array([1, 2, 3, 2, 1]), # 5 periods: annual -> quarterly
402
- ('a', 'sa'): np.array([1, 2, 1]), # 3 periods: annual -> semi-annual
403
- }
404
-
405
390
  # ============================================================================
406
391
  # Export all constants
407
392
  # ============================================================================
@@ -518,7 +503,6 @@ __all__ = [
518
503
  'MIN_VARIABLES',
519
504
  'MIN_SHAPE_FOR_AR2',
520
505
  'MIN_DDFM_TIME_STEPS',
521
- 'MIN_SHAPE_FOR_AR2',
522
506
  'MIN_DDFM_DATASET_SIZE_WARNING',
523
507
  'MIN_ITER_FOR_DELTA_COMPUTATION',
524
508
  'DEFAULT_MIN_ITER_FOR_CONVERGENCE_CHECK',
@@ -546,6 +530,7 @@ __all__ = [
546
530
  'DEFAULT_LOG_INTERVAL',
547
531
  'DEFAULT_PROGRESS_LOG_INTERVAL',
548
532
  'DEFAULT_TENT_KERNEL_SIZE',
533
+ 'DEFAULT_TENT_KERNEL_REGULARIZATION_MULTIPLIER',
549
534
  'MAX_ERROR_ITEMS',
550
535
  # IRF
551
536
  'DEFAULT_IRF_HORIZON',
@@ -581,6 +566,5 @@ __all__ = [
581
566
  # Frequency hierarchy and tent kernels
582
567
  'FREQUENCY_HIERARCHY',
583
568
  'MAX_TENT_SIZE',
584
- 'TENT_WEIGHTS_LOOKUP',
585
569
  ]
586
570
 
@@ -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
 
@@ -0,0 +1,124 @@
1
+ """Block structure configuration for DFM.
2
+
3
+ This module defines the BlockStructure dataclass used to group block-related
4
+ parameters in EM algorithm, replacing long conditional checks with a single
5
+ optional parameter.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Optional, Dict, List
10
+ import numpy as np
11
+
12
+
13
+ @dataclass
14
+ class BlockStructure:
15
+ """Block structure configuration for EM algorithm.
16
+
17
+ Groups all block-related parameters together to simplify function signatures
18
+ and replace long conditional checks.
19
+
20
+ Attributes
21
+ ----------
22
+ blocks : np.ndarray
23
+ Block structure array (N x n_blocks)
24
+ r : np.ndarray
25
+ Number of factors per block (n_blocks,)
26
+ p : int
27
+ VAR lag order
28
+ p_plus_one : int
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.
33
+ n_clock_freq : int
34
+ Number of clock-frequency series
35
+ idio_indicator : np.ndarray
36
+ Idiosyncratic component indicator (N,)
37
+ R_mat : np.ndarray, optional
38
+ Tent kernel constraint matrix for mixed-frequency data
39
+ q : np.ndarray, optional
40
+ Tent kernel constraint vector for mixed-frequency data
41
+ n_slower_freq : int, optional
42
+ Number of slower-frequency series
43
+ tent_weights_dict : dict, optional
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)
69
+ """
70
+ blocks: np.ndarray
71
+ r: np.ndarray
72
+ p: int
73
+ p_plus_one: int
74
+ n_clock_freq: int
75
+ idio_indicator: np.ndarray
76
+ R_mat: Optional[np.ndarray] = None
77
+ q: Optional[np.ndarray] = None
78
+ n_slower_freq: Optional[int] = None
79
+ tent_weights_dict: Optional[Dict[str, np.ndarray]] = None
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
+ _cached_N: Optional[int] = field(default=None, init=False, repr=False) # N value used for caching
94
+
95
+ def is_valid(self) -> bool:
96
+ """Check if block structure is valid (all required fields are not None)."""
97
+ return (
98
+ self.blocks is not None
99
+ and self.r is not None
100
+ and self.p is not None
101
+ and self.p_plus_one is not None
102
+ and self.n_clock_freq is not None
103
+ and self.idio_indicator is not None
104
+ )
105
+
106
+ def has_cached_indices(self) -> bool:
107
+ """Check if block structure indices are cached."""
108
+ return self._cached_unique_blocks is not None
109
+
110
+ def clear_cache(self) -> None:
111
+ """Clear all cached indices to ensure clean state for new training runs."""
112
+ self._cached_unique_blocks = None
113
+ self._cached_unique_indices = None
114
+ self._cached_bl_idxM = None
115
+ self._cached_bl_idxQ = None
116
+ self._cached_R_con = None
117
+ self._cached_q_con = None
118
+ self._cached_total_factor_dim = None
119
+ self._cached_idio_indicator_M = None
120
+ self._cached_n_idio_M = None
121
+ self._cached_c_idio_indicator = None
122
+ self._cached_rp1 = None
123
+ self._cached_N = None
124
+