dfm-python 0.5.54__tar.gz → 0.5.55__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 (212) hide show
  1. {dfm_python-0.5.54 → dfm_python-0.5.55}/PKG-INFO +1 -1
  2. {dfm_python-0.5.54 → dfm_python-0.5.55}/pyproject.toml +1 -1
  3. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/__init__.py +1 -1
  4. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/model.py +46 -6
  5. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/em.py +25 -6
  6. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/base.py +103 -226
  7. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/ddfm.py +120 -71
  8. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/dfm.py +219 -98
  9. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/kdfm.py +139 -26
  10. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/estimator.py +100 -0
  11. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/validator.py +175 -0
  12. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/__init__.py +18 -14
  13. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/dfm.py +0 -2
  14. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/kdfm.py +2 -3
  15. dfm_python-0.5.55/src/dfm_python/utils/__init__.py +221 -0
  16. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/common.py +109 -4
  17. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/misc.py +98 -34
  18. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_base.py +28 -27
  19. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_ddfm.py +1 -1
  20. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_kdfm.py +1 -1
  21. dfm_python-0.5.54/src/dfm_python/utils/__init__.py +0 -301
  22. {dfm_python-0.5.54 → dfm_python-0.5.55}/.git +0 -0
  23. {dfm_python-0.5.54 → dfm_python-0.5.55}/.gitignore +0 -0
  24. {dfm_python-0.5.54 → dfm_python-0.5.55}/.python-version +0 -0
  25. {dfm_python-0.5.54 → dfm_python-0.5.55}/LICENSE +0 -0
  26. {dfm_python-0.5.54 → dfm_python-0.5.55}/MANIFEST.in +0 -0
  27. {dfm_python-0.5.54 → dfm_python-0.5.55}/README.md +0 -0
  28. {dfm_python-0.5.54 → dfm_python-0.5.55}/config/ddfm_finance.yaml +0 -0
  29. {dfm_python-0.5.54 → dfm_python-0.5.55}/config/ddfm_macro.yaml +0 -0
  30. {dfm_python-0.5.54 → dfm_python-0.5.55}/config/dfm_finance.yaml +0 -0
  31. {dfm_python-0.5.54 → dfm_python-0.5.55}/config/dfm_macro.yaml +0 -0
  32. {dfm_python-0.5.54 → dfm_python-0.5.55}/data/finance.csv +0 -0
  33. {dfm_python-0.5.54 → dfm_python-0.5.55}/data/macro.csv +0 -0
  34. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_10/checkpoints/ddfm-epoch=00-train_loss=0.6495.ckpt +0 -0
  35. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_10/metrics.csv +0 -0
  36. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_11/checkpoints/ddfm-epoch=00-train_loss=0.8428.ckpt +0 -0
  37. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_11/metrics.csv +0 -0
  38. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_12/checkpoints/ddfm-epoch=00-train_loss=0.8959.ckpt +0 -0
  39. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_12/metrics.csv +0 -0
  40. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_13/checkpoints/ddfm-epoch=00-train_loss=0.7644.ckpt +0 -0
  41. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_13/metrics.csv +0 -0
  42. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_14/checkpoints/ddfm-epoch=00-train_loss=0.7935.ckpt +0 -0
  43. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_14/metrics.csv +0 -0
  44. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_15/checkpoints/ddfm-epoch=00-train_loss=0.6240.ckpt +0 -0
  45. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_15/metrics.csv +0 -0
  46. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_16/checkpoints/ddfm-epoch=00-train_loss=0.9689.ckpt +0 -0
  47. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_16/metrics.csv +0 -0
  48. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_17/checkpoints/ddfm-epoch=00-train_loss=0.9336.ckpt +0 -0
  49. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_17/metrics.csv +0 -0
  50. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_18/checkpoints/ddfm-epoch=00-train_loss=0.8481.ckpt +0 -0
  51. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_18/metrics.csv +0 -0
  52. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_19/checkpoints/ddfm-epoch=00-train_loss=0.6991.ckpt +0 -0
  53. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_19/metrics.csv +0 -0
  54. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_20/checkpoints/ddfm-epoch=00-train_loss=0.9289.ckpt +0 -0
  55. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_20/metrics.csv +0 -0
  56. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_21/checkpoints/ddfm-epoch=00-train_loss=0.8253.ckpt +0 -0
  57. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_21/metrics.csv +0 -0
  58. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_22/checkpoints/ddfm-epoch=00-train_loss=0.8933.ckpt +0 -0
  59. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_22/metrics.csv +0 -0
  60. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_5/checkpoints/ddfm-epoch=00-train_loss=0.8292.ckpt +0 -0
  61. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_5/metrics.csv +0 -0
  62. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_7/checkpoints/ddfm-epoch=00-train_loss=0.8216.ckpt +0 -0
  63. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_7/metrics.csv +0 -0
  64. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_8/checkpoints/ddfm-epoch=00-train_loss=0.6088.ckpt +0 -0
  65. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_8/metrics.csv +0 -0
  66. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_9/checkpoints/ddfm-epoch=00-train_loss=0.6595.ckpt +0 -0
  67. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_9/metrics.csv +0 -0
  68. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_0/checkpoints/kdfm-epoch=01-train_loss=9750156656246784.0000.ckpt +0 -0
  69. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_0/metrics.csv +0 -0
  70. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_1/checkpoints/kdfm-epoch=00-train_loss=inf.ckpt +0 -0
  71. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_1/metrics.csv +0 -0
  72. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_10/checkpoints/kdfm-epoch=01-train_loss=0.2948.ckpt +0 -0
  73. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_10/metrics.csv +0 -0
  74. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_11/checkpoints/kdfm-epoch=01-train_loss=0.2337.ckpt +0 -0
  75. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_11/metrics.csv +0 -0
  76. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_12/checkpoints/kdfm-epoch=01-train_loss=0.2372.ckpt +0 -0
  77. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_12/metrics.csv +0 -0
  78. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_13/checkpoints/kdfm-epoch=01-train_loss=0.3323.ckpt +0 -0
  79. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_13/metrics.csv +0 -0
  80. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_14/checkpoints/kdfm-epoch=01-train_loss=0.0895.ckpt +0 -0
  81. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_14/metrics.csv +0 -0
  82. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_2/checkpoints/kdfm-epoch=00-train_loss=11076925440424673280.0000.ckpt +0 -0
  83. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_2/metrics.csv +0 -0
  84. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_3/checkpoints/kdfm-epoch=01-train_loss=3466010845203398656.0000.ckpt +0 -0
  85. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_3/metrics.csv +0 -0
  86. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_4/checkpoints/kdfm-epoch=01-train_loss=89213903044608.0000.ckpt +0 -0
  87. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_4/metrics.csv +0 -0
  88. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_5/checkpoints/kdfm-epoch=01-train_loss=1842788085222342656.0000.ckpt +0 -0
  89. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_5/metrics.csv +0 -0
  90. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_6/checkpoints/kdfm-epoch=00-train_loss=28594968117244854272.0000.ckpt +0 -0
  91. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_6/metrics.csv +0 -0
  92. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_7/checkpoints/kdfm-epoch=00-train_loss=inf.ckpt +0 -0
  93. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_7/metrics.csv +0 -0
  94. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_8/checkpoints/kdfm-epoch=01-train_loss=457264.4062.ckpt +0 -0
  95. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_8/metrics.csv +0 -0
  96. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_9/checkpoints/kdfm-epoch=01-train_loss=0.3095.ckpt +0 -0
  97. {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_9/metrics.csv +0 -0
  98. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/__init__.py +0 -0
  99. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/adapter.py +0 -0
  100. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/constants.py +0 -0
  101. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/__init__.py +0 -0
  102. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/block.py +0 -0
  103. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/results.py +0 -0
  104. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/types.py +0 -0
  105. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/__init__.py +0 -0
  106. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/base.py +0 -0
  107. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/ddfm_dm.py +0 -0
  108. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/dfm_dm.py +0 -0
  109. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/kdfm_dm.py +0 -0
  110. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/__init__.py +0 -0
  111. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/dataloader.py +0 -0
  112. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/dataset.py +0 -0
  113. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/process.py +0 -0
  114. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/decoder/__init__.py +0 -0
  115. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/decoder/linear.py +0 -0
  116. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/decoder/mlp.py +0 -0
  117. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/__init__.py +0 -0
  118. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/base.py +0 -0
  119. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/pca.py +0 -0
  120. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/simple_encoder.py +0 -0
  121. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/variational_encoder.py +0 -0
  122. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/__init__.py +0 -0
  123. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/dfm_block.py +0 -0
  124. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/irf.py +0 -0
  125. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/krylov.py +0 -0
  126. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/__init__.py +0 -0
  127. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/inference_logger.py +0 -0
  128. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/logger.py +0 -0
  129. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/train_logger.py +0 -0
  130. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/__init__.py +0 -0
  131. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/builder.py +0 -0
  132. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/stability.py +0 -0
  133. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/tent.py +0 -0
  134. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/__init__.py +0 -0
  135. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/arma.py +0 -0
  136. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/companion.py +0 -0
  137. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/kalman.py +0 -0
  138. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/structural.py +0 -0
  139. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/ddfm.py +0 -0
  140. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/errors.py +0 -0
  141. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/helper.py +0 -0
  142. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/metric.py +0 -0
  143. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/tensor_utils.py +0 -0
  144. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/validation.py +0 -0
  145. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/__init__.py +0 -0
  146. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/__init__.py +0 -0
  147. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/__init__.py +0 -0
  148. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/test_block.py +0 -0
  149. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/test_model.py +0 -0
  150. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/test_results.py +0 -0
  151. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/test_adapter.py +0 -0
  152. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/test_constants.py +0 -0
  153. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/test_types.py +0 -0
  154. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/conftest.py +0 -0
  155. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/__init__.py +0 -0
  156. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_base.py +0 -0
  157. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_ddfm_dm.py +0 -0
  158. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_dfm_dm.py +0 -0
  159. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_kdfm_dm.py +0 -0
  160. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/__init__.py +0 -0
  161. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/test_dataloader.py +0 -0
  162. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/test_dataset.py +0 -0
  163. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/test_process.py +0 -0
  164. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/decoder/__init__.py +0 -0
  165. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/decoder/test_linear.py +0 -0
  166. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/decoder/test_mlp.py +0 -0
  167. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/__init__.py +0 -0
  168. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_base.py +0 -0
  169. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_pca.py +0 -0
  170. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_simple_encoder.py +0 -0
  171. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_variational_encoder.py +0 -0
  172. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/__init__.py +0 -0
  173. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_dfm_block.py +0 -0
  174. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_em.py +0 -0
  175. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_irf.py +0 -0
  176. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_krylov.py +0 -0
  177. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/__init__.py +0 -0
  178. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/test_inference_logger.py +0 -0
  179. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/test_logger.py +0 -0
  180. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/test_train_logger.py +0 -0
  181. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/__init__.py +0 -0
  182. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_dfm.py +0 -0
  183. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/__init__.py +0 -0
  184. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_builder.py +0 -0
  185. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_estimator.py +0 -0
  186. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_stability.py +0 -0
  187. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_tent.py +0 -0
  188. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_validator.py +0 -0
  189. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/__init__.py +0 -0
  190. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_arma.py +0 -0
  191. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_companion.py +0 -0
  192. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_kalman.py +0 -0
  193. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_structural.py +0 -0
  194. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/__init__.py +0 -0
  195. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/test_ddfm.py +0 -0
  196. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/test_dfm.py +0 -0
  197. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/test_kdfm.py +0 -0
  198. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/__init__.py +0 -0
  199. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_common.py +0 -0
  200. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_errors.py +0 -0
  201. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_helper.py +0 -0
  202. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_metric.py +0 -0
  203. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_misc.py +0 -0
  204. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_tensor_utils.py +0 -0
  205. {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_validation.py +0 -0
  206. {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_finance_ddfm.py +0 -0
  207. {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_finance_dfm.py +0 -0
  208. {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_finance_kdfm.py +0 -0
  209. {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_macro_ddfm.py +0 -0
  210. {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_macro_dfm.py +0 -0
  211. {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_macro_kdfm.py +0 -0
  212. {dfm_python-0.5.54 → dfm_python-0.5.55}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.5.54
3
+ Version: 0.5.55
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dfm-python"
3
- version = "0.5.54"
3
+ version = "0.5.55"
4
4
  description = "Dynamic Factor Model (DFM) estimation and forecasting in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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.54"
68
+ __version__ = "0.5.55"
69
69
 
70
70
  # ============================================================================
71
71
  # PUBLIC API DEFINITION
@@ -444,14 +444,14 @@ class DFMConfig(BaseModelConfig):
444
444
  return result
445
445
 
446
446
  @classmethod
447
- def from_dict(cls, data: Dict[str, Any]) -> Union['DFMConfig', 'DDFMConfig']:
448
- """Create DFMConfig or DDFMConfig from dictionary.
447
+ def from_dict(cls, data: Dict[str, Any]) -> Union['DFMConfig', 'DDFMConfig', 'KDFMConfig']:
448
+ """Create DFMConfig, DDFMConfig, or KDFMConfig from dictionary.
449
449
 
450
450
  Expected format: {'frequency': {'column_name': 'frequency'}, 'blocks': {...}, ...}
451
451
 
452
452
  Also accepts estimation parameters: threshold, max_iter, etc.
453
453
  """
454
- from ...config.adapter import detect_config_type, MODEL_TYPE_DDFM, _normalize_blocks_dict
454
+ from ...config.adapter import detect_config_type, MODEL_TYPE_DDFM, MODEL_TYPE_KDFM, _normalize_blocks_dict
455
455
  from ...utils.errors import ConfigurationError
456
456
 
457
457
  # Extract base params (handles frequency conversion from series if needed)
@@ -463,6 +463,9 @@ class DFMConfig(BaseModelConfig):
463
463
  if config_type == MODEL_TYPE_DDFM:
464
464
  return DDFMConfig(**base_params, **DDFMConfig._extract_ddfm(data))
465
465
 
466
+ if config_type == MODEL_TYPE_KDFM:
467
+ return KDFMConfig(**base_params, **KDFMConfig._extract_kdfm(data))
468
+
466
469
  # Handle blocks for DFM
467
470
  from ...config.adapter import _raise_config_error, _is_dict_like
468
471
  blocks_dict = data.get('blocks', {})
@@ -519,7 +522,7 @@ class DDFMConfig(BaseModelConfig):
519
522
  @classmethod
520
523
  def _extract_ddfm(cls, data: Dict[str, Any]) -> Dict[str, Any]:
521
524
  """Extract DDFM-specific parameters from config dict."""
522
- base_params = cls._extract_base(data)
525
+ # Don't extract base params here - they're already in base_params from from_dict
523
526
  ddfm_params = cls._extract_params(data, {
524
527
  'encoder_layers': None,
525
528
  'num_factors': None,
@@ -535,8 +538,7 @@ class DDFMConfig(BaseModelConfig):
535
538
  'disp': DEFAULT_DISP,
536
539
  'seed': None,
537
540
  })
538
- base_params.update(ddfm_params)
539
- return base_params
541
+ return ddfm_params
540
542
 
541
543
  @classmethod
542
544
  def from_dict(cls, data: Dict[str, Any]) -> 'DDFMConfig':
@@ -557,6 +559,8 @@ class KDFMConfig(BaseModelConfig):
557
559
 
558
560
  This dataclass contains all configuration parameters for the KDFM model.
559
561
  It inherits from BaseModelConfig and adds KDFM-specific parameters.
562
+
563
+ Note: KDFM does not use blocks structure (unlike DFM). Only frequency dict is needed.
560
564
  """
561
565
  # VARMA parameters
562
566
  ar_order: int = DEFAULT_KDFM_AR_ORDER # VAR order p
@@ -577,6 +581,42 @@ class KDFMConfig(BaseModelConfig):
577
581
  structural_reg_weight: float = DEFAULT_STRUCTURAL_REG_WEIGHT # Weight for structural loss
578
582
  use_regularization: bool = True
579
583
  regularization_scale: float = DEFAULT_REGULARIZATION_SCALE
584
+
585
+ # ========================================================================
586
+ # Factory Methods
587
+ # ========================================================================
588
+
589
+ @classmethod
590
+ def _extract_kdfm(cls, data: Dict[str, Any]) -> Dict[str, Any]:
591
+ """Extract KDFM-specific parameters from config dict."""
592
+ # Don't extract base params here - they're already in base_params from from_dict
593
+ kdfm_params = cls._extract_params(data, {
594
+ 'ar_order': DEFAULT_KDFM_AR_ORDER,
595
+ 'ma_order': DEFAULT_KDFM_MA_ORDER,
596
+ 'structural_method': 'cholesky',
597
+ 'structural_rank': None,
598
+ 'learning_rate': DEFAULT_LEARNING_RATE,
599
+ 'max_epochs': DEFAULT_MAX_EPOCHS,
600
+ 'batch_size': DEFAULT_BATCH_SIZE,
601
+ 'weight_decay': DEFAULT_REGULARIZATION_SCALE,
602
+ 'grad_clip_val': DEFAULT_GRAD_CLIP_VAL,
603
+ 'structural_reg_weight': DEFAULT_STRUCTURAL_REG_WEIGHT,
604
+ 'use_regularization': True,
605
+ 'regularization_scale': DEFAULT_REGULARIZATION_SCALE,
606
+ })
607
+ return kdfm_params
608
+
609
+ @classmethod
610
+ def from_dict(cls, data: Dict[str, Any]) -> 'KDFMConfig':
611
+ """Create KDFMConfig from dictionary (delegates to DFMConfig.from_dict for type detection)."""
612
+ result = DFMConfig.from_dict(data)
613
+ if isinstance(result, KDFMConfig):
614
+ return result
615
+ from ..utils.errors import ConfigurationError
616
+ raise ConfigurationError(
617
+ f"Expected KDFMConfig but got {type(result).__name__}",
618
+ details=f"Result type: {type(result).__name__}, expected: KDFMConfig"
619
+ )
580
620
 
581
621
 
582
622
  # ============================================================================
@@ -545,6 +545,9 @@ def _update_observation_matrix_blocked(
545
545
 
546
546
  # Update clock-frequency variables
547
547
  for t in range(T):
548
+ # Safety check: ensure t is within valid range for nanY
549
+ if t < 0 or t >= nanY.shape[0]:
550
+ continue
548
551
  # Selection matrix for non-missing values
549
552
  # Final defensive check: ensure idx_iM indices are valid for nanY
550
553
  # Filter out any indices that are out of bounds
@@ -559,15 +562,21 @@ def _update_observation_matrix_blocked(
559
562
  idx_iM_valid = idx_iM_valid[(idx_iM_valid >= 0) & (idx_iM_valid < nanY.shape[1])]
560
563
  if len(idx_iM_valid) == 0:
561
564
  continue
565
+ # nanY has shape (T, N), so index as nanY[t, idx_iM_valid] not nanY[idx_iM_valid, t]
562
566
  try:
563
- Wt = np.diag(~nanY[idx_iM_valid, t])
567
+ Wt = np.diag(~nanY[t, idx_iM_valid])
564
568
  except IndexError:
565
569
  # If this still fails, there's a deeper issue - log and skip
566
- _logger.warning(f"IndexError in Wt computation: idx_iM_valid={idx_iM_valid}, nanY.shape={nanY.shape}")
570
+ _logger.warning(f"IndexError in Wt computation: idx_iM_valid={idx_iM_valid}, nanY.shape={nanY.shape}, t={t}")
567
571
  continue
568
572
 
569
573
  # E[f_t*f_t' | Omega_T]
574
+ # EZ has shape (T+1, m), so EZ[t+1] corresponds to time t (t+1 must be <= T)
575
+ if t + 1 >= EZ.shape[0]:
576
+ continue
570
577
  Z_t = EZ[t+1, bl_idxM_i] # (rs,)
578
+ if t + 1 >= V_smooth.shape[0]:
579
+ continue
571
580
  V_t = V_smooth[t+1, bl_idxM_i, :][:, bl_idxM_i] # (rs, rs)
572
581
  EZZ_t = np.outer(Z_t, Z_t) + V_t
573
582
 
@@ -578,7 +587,7 @@ def _update_observation_matrix_blocked(
578
587
  nom += np.outer(y_t, Z_t)
579
588
 
580
589
  # Subtract idiosyncratic component contribution
581
- if len(i_idio_ii) > 0:
590
+ if len(i_idio_ii) > 0 and t + 1 < EZ.shape[0] and t + 1 < V_smooth.shape[0]:
582
591
  idio_idx = (rp1 + i_idio_ii - 1).astype(int) # Convert to 0-based, ensure integer type
583
592
  Z_idio_t = EZ[t+1, idio_idx] # (n_idio,)
584
593
  V_idio_t = V_smooth[t+1, idio_idx, :][:, bl_idxM_i] # (n_idio, rs)
@@ -596,7 +605,10 @@ def _update_observation_matrix_blocked(
596
605
  fallback_func=lambda: None
597
606
  )
598
607
  if loadings is not None:
599
- C_new[idx_iM, bl_idxM_i] = loadings
608
+ # Use broadcasting for advanced indexing assignment
609
+ # idx_iM[:, None] creates shape (n_i, 1) and bl_idxM_i creates shape (rs,)
610
+ # This broadcasts to (n_i, rs) matching loadings.shape
611
+ C_new[idx_iM[:, None], bl_idxM_i] = loadings
600
612
 
601
613
  # Update slower-frequency variables
602
614
  # Filter to slower-frequency series and validate indices
@@ -668,17 +680,24 @@ def _update_observation_matrix_blocked(
668
680
  nom = np.zeros(rps)
669
681
 
670
682
  for t in range(T):
683
+ # Safety check: ensure t is within valid range for nanY
684
+ if t < 0 or t >= nanY.shape[0]:
685
+ continue
671
686
  # Selection matrix
672
687
  # Final safety check: ensure j is valid for nanY
688
+ # nanY has shape (T, N), so j is column index (series), t is row index (time)
673
689
  if j < 0 or j >= nanY.shape[1]:
674
690
  continue
675
691
  try:
676
- Wt = np.diag([~nanY[j, t]])
692
+ Wt = np.diag([~nanY[t, j]])
677
693
  except IndexError:
678
694
  # Skip this iteration if j is out of bounds
679
695
  continue
680
696
 
681
697
  # E[f_t*f_t' | Omega_T]
698
+ # EZ has shape (T+1, m), so EZ[t+1] corresponds to time t (t+1 must be <= T)
699
+ if t + 1 >= EZ.shape[0] or t + 1 >= V_smooth.shape[0]:
700
+ continue
682
701
  Z_t = EZ[t+1, bl_idxQ_i] # (rps,)
683
702
  V_t = V_smooth[t+1, bl_idxQ_i, :][:, bl_idxQ_i] # (rps, rps)
684
703
  EZZ_t = np.outer(Z_t, Z_t) + V_t
@@ -690,7 +709,7 @@ def _update_observation_matrix_blocked(
690
709
  nom += y_t * Z_t
691
710
 
692
711
  # Subtract idiosyncratic component contribution
693
- if tent_weights is not None and len(i_idio_jQ) == len(tent_weights):
712
+ if tent_weights is not None and len(i_idio_jQ) == len(tent_weights) and t + 1 < EZ.shape[0] and t + 1 < V_smooth.shape[0]:
694
713
  Z_idio_t = EZ[t+1, i_idio_jQ] # (tent_kernel_size,)
695
714
  V_idio_t = V_smooth[t+1, i_idio_jQ, :][:, bl_idxQ_i] # (tent_kernel_size, rps)
696
715
  nom -= Wt[0, 0] * (tent_weights @ (np.outer(Z_idio_t, Z_t) + V_idio_t))
@@ -61,6 +61,14 @@ from typing import Optional, Union, Tuple, Any, Dict, List, TYPE_CHECKING
61
61
  from pathlib import Path
62
62
  import numpy as np
63
63
 
64
+ if TYPE_CHECKING:
65
+ from torch import Tensor
66
+ else:
67
+ try:
68
+ from torch import Tensor
69
+ except ImportError:
70
+ Tensor = Any
71
+
64
72
  if TYPE_CHECKING:
65
73
  from ..datamodule import KDFMDataModule, DFMDataModule, DDFMDataModule
66
74
 
@@ -71,7 +79,7 @@ from ..config import (
71
79
  from ..config.constants import DEFAULT_BLOCK_NAME, DEFAULT_DTYPE, DEFAULT_FORECAST_HORIZON, MAX_WARNING_ITEMS, MAX_ERROR_ITEMS, DEFAULT_CLOCK_FREQUENCY
72
80
  from ..logger import get_logger
73
81
  from ..utils.errors import ConfigurationError, ModelNotTrainedError, ModelNotInitializedError
74
- from ..utils.validation import check_has_attr
82
+ from ..utils.validation import check_has_attr, check_condition
75
83
 
76
84
  _logger = get_logger(__name__)
77
85
 
@@ -99,6 +107,7 @@ class BaseFactorModel(ABC):
99
107
  self._result: Optional[BaseResult] = None
100
108
  self.training_state: Optional[Any] = None
101
109
  self._data_module: Optional[Any] = None
110
+ self.data_processed: Optional[np.ndarray] = None # Store processed training data for shape validation
102
111
 
103
112
  @property
104
113
  def config(self) -> DFMConfig:
@@ -128,7 +137,7 @@ class BaseFactorModel(ABC):
128
137
 
129
138
  Raises
130
139
  ------
131
- ValueError
140
+ ModelNotTrainedError
132
141
  If model has not been trained yet
133
142
  """
134
143
  if self._result is None:
@@ -147,6 +156,30 @@ class BaseFactorModel(ABC):
147
156
  details="Please call fit() or train the model before accessing results"
148
157
  )
149
158
 
159
+ def _ensure_result(self) -> BaseResult:
160
+ """Ensure result exists, computing it if necessary.
161
+
162
+ This helper method ensures that self._result is available. If it's None,
163
+ it attempts to compute it from training_state. This is a common pattern
164
+ used in update(), predict(), and result property.
165
+
166
+ Returns
167
+ -------
168
+ BaseResult
169
+ The model result (computed if necessary)
170
+
171
+ Raises
172
+ ------
173
+ ModelNotTrainedError
174
+ If model has not been trained yet
175
+ """
176
+ if self._result is None:
177
+ self._check_trained() # This will try to compute result if training_state exists
178
+ if self._result is None:
179
+ # If still None after _check_trained, explicitly compute
180
+ self._result = self.get_result()
181
+ return self._result
182
+
150
183
  def _create_temp_config(self, block_name: Optional[str] = None) -> DFMConfig:
151
184
  """Create a temporary configuration for model initialization.
152
185
 
@@ -263,30 +296,6 @@ class BaseFactorModel(ABC):
263
296
  )
264
297
  return data_module
265
298
 
266
- @staticmethod
267
- def _validate_ndarray(arr: Any, name: str, expected_ndim: int) -> None:
268
- """Validate a numpy array has expected number of dimensions.
269
-
270
- Parameters
271
- ----------
272
- arr : Any
273
- Array to validate
274
- name : str
275
- Name of the array for error messages
276
- expected_ndim : int
277
- Expected number of dimensions
278
-
279
- Raises
280
- ------
281
- DataValidationError
282
- If array is not a numpy array or has wrong number of dimensions
283
- """
284
- from ..utils.errors import DataValidationError
285
- if not isinstance(arr, np.ndarray) or arr.ndim != expected_ndim:
286
- raise DataValidationError(
287
- f"{name} must be {expected_ndim}D numpy array, got shape {arr.shape if isinstance(arr, np.ndarray) else 'not array'}"
288
- )
289
-
290
299
  def _forecast_var_factors(
291
300
  self,
292
301
  Z_last: np.ndarray,
@@ -297,142 +306,30 @@ class BaseFactorModel(ABC):
297
306
  ) -> np.ndarray:
298
307
  """Forecast factors using AR(1) dynamics.
299
308
 
300
- Uses iterative matrix multiplication for efficient computation.
301
-
302
- **AR(1) Dynamics**: f_t = A @ f_{t-1}
303
-
304
- This method is used by both KDFM and DDFM for factor forecasting, ensuring
305
- consistent AR dynamics computation across models.
309
+ This is a convenience wrapper around forecast_ar1_factors() from numeric.estimator.
310
+ The `p` and `Z_prev` parameters are kept for backward compatibility but are ignored
311
+ (AR(1) dynamics only).
306
312
 
307
313
  Parameters
308
314
  ----------
309
315
  Z_last : np.ndarray
310
- Last factor state of shape (m,) where m is number of factors.
311
- Must be 1D array with m >= 1.
316
+ Last factor state of shape (m,)
312
317
  A : np.ndarray
313
- Transition matrix of shape (m, m) for AR(1).
314
- Must be 2D array with compatible dimensions.
318
+ Transition matrix of shape (m, m)
315
319
  p : int, default 1
316
- AR order (always 1).
320
+ AR order (ignored, always uses AR(1))
317
321
  horizon : int, default 1
318
- Number of periods to forecast. Must be >= 1.
322
+ Number of periods to forecast
319
323
  Z_prev : np.ndarray, optional
320
- Ignored (unused parameter).
324
+ Ignored (unused parameter)
321
325
 
322
326
  Returns
323
327
  -------
324
328
  np.ndarray
325
- Forecasted factors of shape (horizon, m) where:
326
- - horizon: Number of forecast periods
327
- - m: Number of factors (matches Z_last.shape[0])
328
-
329
- Raises
330
- ------
331
- DataValidationError
332
- If input shapes are incompatible or invalid
333
- NumericalError
334
- If matrix operations produce NaN/Inf values
335
- If forecast computation produces NaN/Inf values
336
-
337
- Examples
338
- --------
339
- >>> # AR(1) forecast
340
- >>> from dfm_python.numeric.stability import create_scaled_identity
341
- >>> Z_last = np.array([1.0, 2.0, 3.0]) # 3 factors
342
- >>> A = create_scaled_identity(3, 0.9) # Use helper function for scaled identity
343
- >>> Z_forecast = model._forecast_var_factors(Z_last, A, p=1, horizon=5)
344
- >>> assert Z_forecast.shape == (5, 3)
329
+ Forecasted factors of shape (horizon, m)
345
330
  """
346
- from ..utils.errors import DataValidationError, NumericalError
347
- from ..numeric.validator import validate_no_nan_inf
348
-
349
- # Validate inputs
350
- self._validate_ndarray(Z_last, "Z_last", 1)
351
- self._validate_ndarray(A, "A", 2)
352
-
353
- m = Z_last.shape[0]
354
- if m < 1:
355
- raise DataValidationError(f"Z_last must have at least 1 factor, got m={m}")
356
-
357
- if horizon < 1:
358
- raise DataValidationError(f"horizon must be >= 1, got {horizon}")
359
-
360
- validate_no_nan_inf(Z_last, name="Z_last")
361
- validate_no_nan_inf(A, name="transition matrix A")
362
-
363
- # Helper function to validate array shape consistently
364
- def _validate_shape(
365
- array: np.ndarray,
366
- expected_shape: Tuple[int, ...],
367
- name: str
368
- ) -> None:
369
- """Validate array has expected shape.
370
-
371
- Args:
372
- array: Array to validate
373
- expected_shape: Expected shape tuple
374
- name: Name of array for error message
375
- """
376
- if array.shape != expected_shape:
377
- raise DataValidationError(
378
- f"{name} must have shape {expected_shape}, got {array.shape}"
379
- )
380
-
381
- if Z_prev is not None:
382
- validate_no_nan_inf(Z_prev, name="Z_prev")
383
- _validate_shape(Z_prev, (m,), "Z_prev")
384
-
385
- # Helper function to handle forecast computation errors consistently
386
- def _handle_forecast_error(
387
- error: Exception,
388
- error_message: str,
389
- error_details: str,
390
- exceptions_to_re_raise: Tuple[Any, ...]
391
- ) -> None:
392
- """Handle forecast computation errors consistently.
393
-
394
- Args:
395
- error: The exception that occurred
396
- error_message: Main error message
397
- error_details: Detailed diagnostic information
398
- exceptions_to_re_raise: Tuple of exception types to re-raise without wrapping
399
- """
400
- if isinstance(error, exceptions_to_re_raise):
401
- raise
402
- raise NumericalError(
403
- error_message,
404
- details=error_details
405
- ) from error
406
-
407
- try:
408
- # AR(1) dynamics only (simplified)
409
- # Validate AR(1) shape
410
- _validate_shape(A, (m, m), "A (transition matrix)")
411
-
412
- # AR(1): f_t = A @ f_{t-1}
413
- Z_forecast = np.zeros((horizon, m), dtype=DEFAULT_DTYPE)
414
- Z_forecast[0, :] = A @ Z_last
415
- for h in range(1, horizon):
416
- Z_forecast[h, :] = A @ Z_forecast[h - 1, :]
417
-
418
- # Validate output
419
- validate_no_nan_inf(Z_forecast, name="forecasted factors")
420
- _validate_shape(Z_forecast, (horizon, m), "Forecast output")
421
-
422
- return Z_forecast
423
-
424
- except (RuntimeError, ValueError, TypeError, AttributeError, KeyError, IndexError) as e:
425
- # Specific exceptions for forecast computation failures
426
- _handle_forecast_error(
427
- error=e,
428
- error_message=f"Forecast computation failed: {e}",
429
- error_details=(
430
- f"Z_last shape: {Z_last.shape}, A shape: {A.shape}, "
431
- f"p={p}, horizon={horizon}. Check: (1) Matrix dimensions, "
432
- f"(2) Numerical stability, (3) Input validity."
433
- ),
434
- exceptions_to_re_raise=(ConfigurationError, DataValidationError)
435
- )
331
+ from ..numeric.estimator import forecast_ar1_factors
332
+ return forecast_ar1_factors(Z_last, A, horizon, dtype=DEFAULT_DTYPE)
436
333
 
437
334
  # Legacy method removed: _transform_factors_to_observations()
438
335
  # This method used Mx/Wx arrays which are no longer used.
@@ -445,8 +342,8 @@ class BaseFactorModel(ABC):
445
342
  ) -> Tuple[Optional[List[str]], Optional[List[int]]]:
446
343
  """Resolve target series from DataModule.
447
344
 
448
- This helper method resolves target series from the DataModule's target_series attribute.
449
- Target series should be set during DataModule initialization.
345
+ This is a convenience wrapper around resolve_target_series() from utils.misc.
346
+ It gets the DataModule and series_ids, then calls the utility function.
450
347
 
451
348
  Parameters
452
349
  ----------
@@ -467,17 +364,14 @@ class BaseFactorModel(ABC):
467
364
  DataError
468
365
  If target series are not found in available series
469
366
  """
470
- from ..utils.errors import DataError
367
+ from ..utils.misc import resolve_target_series
471
368
 
472
- # Get target series from DataModule
473
- target_series = None
369
+ # Get DataModule (may raise ModelNotInitializedError)
370
+ data_module = None
474
371
  try:
475
372
  data_module = self._get_datamodule()
476
- target_series = getattr(data_module, 'target_series', None)
477
- if target_series is not None and len(target_series) > 0:
478
- target_series = target_series if isinstance(target_series, list) else [target_series]
479
373
  except (ModelNotInitializedError, AttributeError):
480
- target_series = None
374
+ pass
481
375
 
482
376
  # Get series_ids for validation
483
377
  if series_ids is None:
@@ -486,31 +380,19 @@ class BaseFactorModel(ABC):
486
380
  if series_ids is None and self._config is not None:
487
381
  series_ids = self._config.get_series_ids()
488
382
 
489
- # Resolve indices if we have both target_series and series_ids
490
- target_indices = None
491
- if target_series is not None and series_ids is not None:
492
- target_indices = []
493
- for tgt_id in target_series:
494
- if tgt_id in series_ids:
495
- target_indices.append(series_ids.index(tgt_id))
496
- else:
497
- _logger.warning(
498
- f"{self.__class__.__name__} prediction: target series '{tgt_id}' not found in series_ids. "
499
- f"Available: {series_ids[:MAX_WARNING_ITEMS]}{'...' if len(series_ids) > MAX_WARNING_ITEMS else ''}. "
500
- f"Skipping this target series."
501
- )
502
-
503
- if len(target_indices) == 0:
504
- raise DataError(
505
- f"{self.__class__.__name__} prediction failed: none of the specified target series found",
506
- details=f"Target: {target_series}, Available: {series_ids[:MAX_ERROR_ITEMS]}{'...' if len(series_ids) > MAX_ERROR_ITEMS else ''}"
507
- )
508
-
509
- return target_series, target_indices
383
+ # Call utility function
384
+ return resolve_target_series(
385
+ datamodule=data_module,
386
+ series_ids=series_ids,
387
+ result=result,
388
+ model_name=self.__class__.__name__
389
+ )
510
390
 
511
391
  def _compute_default_horizon(self, default: Optional[int] = None) -> int:
512
392
  """Compute default forecast horizon from clock frequency.
513
393
 
394
+ This is a convenience wrapper around compute_default_horizon() from utils.misc.
395
+
514
396
  Parameters
515
397
  ----------
516
398
  default : int, optional
@@ -522,53 +404,8 @@ class BaseFactorModel(ABC):
522
404
  int
523
405
  Default horizon in periods (typically 1 year worth of periods)
524
406
  """
525
- if default is None:
526
- default = DEFAULT_FORECAST_HORIZON
527
-
528
- try:
529
- from ..config import get_periods_per_year
530
- from ..utils.misc import get_clock_frequency
531
-
532
- if self._config is not None:
533
- clock = get_clock_frequency(self._config)
534
- return get_periods_per_year(clock)
535
- except (AttributeError, ImportError, ValueError):
536
- _logger.debug(f"Could not determine horizon from clock frequency, using default={default}")
537
-
538
- return default
539
-
540
-
541
- @staticmethod
542
- def _create_default_standardization_arrays(
543
- n_series: int,
544
- dtype: Optional[type] = None
545
- ) -> Tuple[np.ndarray, np.ndarray]:
546
- """Create default standardization arrays (for internal use only).
547
-
548
- Creates arrays representing standardized data (mean=0, std=1).
549
- Used internally when data is already standardized and no scaler is provided.
550
-
551
- Parameters
552
- ----------
553
- n_series : int
554
- Number of series
555
- dtype : type, optional
556
- Data type for arrays. If None, uses DEFAULT_DTYPE.
557
-
558
- Returns
559
- -------
560
- Tuple[np.ndarray, np.ndarray]
561
- (mean_array, std_array) both of shape (n_series,)
562
- mean_array: zeros (standardized data has zero mean)
563
- std_array: ones (standardized data has unit variance)
564
- """
565
- if dtype is None:
566
- dtype = DEFAULT_DTYPE
567
-
568
- mean_array = np.zeros(n_series, dtype=dtype)
569
- std_array = np.ones(n_series, dtype=dtype)
570
-
571
- return mean_array, std_array
407
+ from ..utils.misc import compute_default_horizon
408
+ return compute_default_horizon(self._config, default=default)
572
409
 
573
410
  # Legacy method removed: _standardize_data()
574
411
  # This method used Mx/Wx arrays which are no longer used.
@@ -582,6 +419,46 @@ class BaseFactorModel(ABC):
582
419
  self._data_module = None
583
420
  return self
584
421
 
422
+
423
+ @abstractmethod
424
+ def update(self, data: Union[np.ndarray, Any]) -> None:
425
+ """Update model state with new observations.
426
+
427
+ This method updates the model's internal state (factors) with new observations,
428
+ but keeps model parameters fixed. The implementation differs by model type:
429
+ - DFM: Uses Kalman filtering/smoothing
430
+ - DDFM: Uses neural network forward pass
431
+ - KDFM: Uses companion matrix forward pass
432
+
433
+ **Data Shape**: The input data must be 2D with shape (T_new x N) where:
434
+ - T_new: Number of new time steps (can be any positive integer)
435
+ - N: Number of series (must match training data)
436
+
437
+ **Supported Types**:
438
+ - numpy.ndarray: (T_new x N) array
439
+ - pandas.DataFrame: DataFrame with N columns, T_new rows
440
+ - polars.DataFrame: DataFrame with N columns, T_new rows
441
+
442
+ **Important**: Data must be preprocessed by the user (same preprocessing as training).
443
+ Only target scaler is handled internally if needed.
444
+
445
+ Parameters
446
+ ----------
447
+ data : np.ndarray, pandas.DataFrame, or polars.DataFrame
448
+ New preprocessed observations with shape (T_new x N) where:
449
+ - T_new: Number of new time steps (any positive integer)
450
+ - N: Number of series (must match training data)
451
+ Data must be preprocessed by user (same preprocessing as training).
452
+
453
+ Raises
454
+ ------
455
+ ModelNotTrainedError
456
+ If model has not been trained yet
457
+ DataValidationError
458
+ If data shape doesn't match training data (N must match)
459
+ """
460
+ raise NotImplementedError("Subclasses must implement update()")
461
+
585
462
  @abstractmethod
586
463
  def get_result(self) -> BaseResult:
587
464
  """Extract result from trained model.