dfm-python 0.4.8__tar.gz → 0.4.9__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 (174) hide show
  1. {dfm_python-0.4.8 → dfm_python-0.4.9}/PKG-INFO +1 -1
  2. dfm_python-0.4.9/lightning_logs/dfm/version_0/metrics.csv +11 -0
  3. dfm_python-0.4.9/lightning_logs/dfm/version_1/metrics.csv +11 -0
  4. dfm_python-0.4.9/lightning_logs/dfm/version_2/metrics.csv +11 -0
  5. dfm_python-0.4.9/lightning_logs/dfm/version_3/metrics.csv +11 -0
  6. dfm_python-0.4.9/lightning_logs/dfm/version_4/metrics.csv +11 -0
  7. dfm_python-0.4.9/lightning_logs/dfm/version_5/metrics.csv +11 -0
  8. {dfm_python-0.4.8 → dfm_python-0.4.9}/pyproject.toml +1 -1
  9. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/__init__.py +1 -2
  10. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/logger/logger.py +22 -9
  11. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/models/__init__.py +2 -0
  12. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/models/base.py +461 -0
  13. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/models/ddfm.py +120 -974
  14. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/models/dfm.py +219 -23
  15. dfm_python-0.4.9/src/dfm_python/models/mcmc.py +518 -0
  16. dfm_python-0.4.9/src/dfm_python/models/utils.py +577 -0
  17. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/utils/__init__.py +9 -46
  18. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_models.py +24 -0
  19. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_nowcast.py +128 -6
  20. dfm_python-0.4.9/src/test/test_predict.py +310 -0
  21. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_sktime_integration.py +20 -8
  22. dfm_python-0.4.9/tutorial/tutorial_finance.py +246 -0
  23. dfm_python-0.4.9/tutorial/tutorial_macro.py +245 -0
  24. {dfm_python-0.4.8 → dfm_python-0.4.9}/.git +0 -0
  25. {dfm_python-0.4.8 → dfm_python-0.4.9}/.gitignore +0 -0
  26. {dfm_python-0.4.8 → dfm_python-0.4.9}/.python-version +0 -0
  27. {dfm_python-0.4.8 → dfm_python-0.4.9}/LICENSE +0 -0
  28. {dfm_python-0.4.8 → dfm_python-0.4.9}/MANIFEST.in +0 -0
  29. {dfm_python-0.4.8 → dfm_python-0.4.9}/README.md +0 -0
  30. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/experiment/test_finance.yaml +0 -0
  31. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/experiment/test_macro.yaml +0 -0
  32. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/model/ddfm.yaml +0 -0
  33. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/model/dfm.yaml +0 -0
  34. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KO3YEARC.yaml +0 -0
  35. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBFDSAAF.yaml +0 -0
  36. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBFINAAF.yaml +0 -0
  37. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBFXSAAF.yaml +0 -0
  38. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSESI_R.yaml +0 -0
  39. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIBCEQ.yaml +0 -0
  40. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIBCOQ.yaml +0 -0
  41. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIBNER.yaml +0 -0
  42. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIBNMR.yaml +0 -0
  43. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIEMAR.yaml +0 -0
  44. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIHAPR.yaml +0 -0
  45. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIIMER.yaml +0 -0
  46. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIIMPR.yaml +0 -0
  47. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBSIXMER.yaml +0 -0
  48. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOBUSINVR.yaml +0 -0
  49. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCALL_.yaml +0 -0
  50. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCNFBUSR.yaml +0 -0
  51. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCNFCONR.yaml +0 -0
  52. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCNGOV_D.yaml +0 -0
  53. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCNPER_D.yaml +0 -0
  54. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCOBUSIR.yaml +0 -0
  55. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCONPMTH.yaml +0 -0
  56. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCONPRCF.yaml +0 -0
  57. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCONSTRP.yaml +0 -0
  58. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCPCOREF.yaml +0 -0
  59. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCPEXFEF.yaml +0 -0
  60. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCREU__A.yaml +0 -0
  61. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSECDMR.yaml +0 -0
  62. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSECEMR.yaml +0 -0
  63. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSEMEMR.yaml +0 -0
  64. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSEOHDR.yaml +0 -0
  65. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSEOHSR.yaml +0 -0
  66. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSHHEMR.yaml +0 -0
  67. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSHHLMR.yaml +0 -0
  68. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSMTTOA.yaml +0 -0
  69. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSPHIMR.yaml +0 -0
  70. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCSPSPMR.yaml +0 -0
  71. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOCYLEADR.yaml +0 -0
  72. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KODBLNEQA.yaml +0 -0
  73. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KODBLNHSA.yaml +0 -0
  74. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEMPCONF.yaml +0 -0
  75. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEMPMANF.yaml +0 -0
  76. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEMPTOTO.yaml +0 -0
  77. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEMPWREP.yaml +0 -0
  78. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEQUIMAE.yaml +0 -0
  79. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEQUIPTE.yaml +0 -0
  80. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEQUITTE.yaml +0 -0
  81. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEXCHINA.yaml +0 -0
  82. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEXNGS_D.yaml +0 -0
  83. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEXPGDSA.yaml +0 -0
  84. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOEXPPRCF.yaml +0 -0
  85. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOGCFCNSD.yaml +0 -0
  86. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOGCFFNVD.yaml +0 -0
  87. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOGDP___D.yaml +0 -0
  88. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOGFCF__D.yaml +0 -0
  89. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOHWRWEMP.yaml +0 -0
  90. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIMNGS_D.yaml +0 -0
  91. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIMPCONA.yaml +0 -0
  92. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIMPGDDA.yaml +0 -0
  93. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIMPGDSA.yaml +0 -0
  94. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIMPPRCF.yaml +0 -0
  95. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIPALL_G.yaml +0 -0
  96. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOIPMIMAG.yaml +0 -0
  97. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOLRLTCO.yaml +0 -0
  98. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOLRLTHH.yaml +0 -0
  99. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMPCAPGG.yaml +0 -0
  100. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMPCONDG.yaml +0 -0
  101. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMPCONGG.yaml +0 -0
  102. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMPCONNG.yaml +0 -0
  103. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMPRI30G.yaml +0 -0
  104. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMSCAPGG.yaml +0 -0
  105. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOMSCONGG.yaml +0 -0
  106. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KONETTRDF.yaml +0 -0
  107. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KONEWCONA.yaml +0 -0
  108. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPICAPGG.yaml +0 -0
  109. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPICONGG.yaml +0 -0
  110. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPIMANUG.yaml +0 -0
  111. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPOPACTE.yaml +0 -0
  112. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPPSRM_F.yaml +0 -0
  113. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPROPRCF.yaml +0 -0
  114. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOPSMANUG.yaml +0 -0
  115. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KORCNST.yaml +0 -0
  116. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KORELEC.yaml +0 -0
  117. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KORFBEV.yaml +0 -0
  118. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KORMACH.yaml +0 -0
  119. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KORMCMP.yaml +0 -0
  120. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOROCOINF.yaml +0 -0
  121. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KORWHLS.yaml +0 -0
  122. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOSHRPRCF.yaml +0 -0
  123. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOSIAHOTE.yaml +0 -0
  124. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOSIAI__E.yaml +0 -0
  125. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOSIARRLE.yaml +0 -0
  126. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOSIAWRTE.yaml +0 -0
  127. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOSSBFMBE.yaml +0 -0
  128. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOUN_TOTQ.yaml +0 -0
  129. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOUS____.yaml +0 -0
  130. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOVALCONA.yaml +0 -0
  131. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOWRCCNSE.yaml +0 -0
  132. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOWRCDURE.yaml +0 -0
  133. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOWRCNODE.yaml +0 -0
  134. {dfm_python-0.4.8 → dfm_python-0.4.9}/config/series/KOWRCSEME.yaml +0 -0
  135. {dfm_python-0.4.8 → dfm_python-0.4.9}/data/finance.csv +0 -0
  136. {dfm_python-0.4.8 → dfm_python-0.4.9}/data/macro.csv +0 -0
  137. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/config/__init__.py +0 -0
  138. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/config/adapter.py +0 -0
  139. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/config/results.py +0 -0
  140. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/config/schema.py +0 -0
  141. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/config/utils.py +0 -0
  142. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/data/__init__.py +0 -0
  143. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/data/dataloader.py +0 -0
  144. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/data/dataset.py +0 -0
  145. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/data/utils.py +0 -0
  146. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/encoder/__init__.py +0 -0
  147. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/encoder/base.py +0 -0
  148. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/encoder/pca.py +0 -0
  149. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/encoder/vae.py +0 -0
  150. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/lightning/__init__.py +0 -0
  151. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/lightning/data_module.py +0 -0
  152. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/lightning/scaling.py +0 -0
  153. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/logger/__init__.py +0 -0
  154. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/logger/inference_logger.py +0 -0
  155. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/logger/train_logger.py +0 -0
  156. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/ssm/__init__.py +0 -0
  157. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/ssm/em.py +0 -0
  158. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/ssm/kalman.py +0 -0
  159. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/ssm/utils.py +0 -0
  160. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/trainer/__init__.py +0 -0
  161. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/trainer/ddfm.py +0 -0
  162. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/trainer/dfm.py +0 -0
  163. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/utils/data.py +0 -0
  164. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/utils/diagnostics.py +0 -0
  165. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/utils/helpers.py +0 -0
  166. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/utils/statespace.py +0 -0
  167. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/dfm_python/utils/time.py +0 -0
  168. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_config.py +0 -0
  169. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_helpers.py +0 -0
  170. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_logger.py +0 -0
  171. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_ssm.py +0 -0
  172. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_trainer.py +0 -0
  173. {dfm_python-0.4.8 → dfm_python-0.4.9}/src/test/test_utils.py +0 -0
  174. {dfm_python-0.4.8 → dfm_python-0.4.9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.4.8
3
+ Version: 0.4.9
4
4
  Summary: Dynamic Factor Model (DFM) estimation and nowcasting 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
@@ -0,0 +1,11 @@
1
+ em_iteration_epoch,epoch,loglik_epoch,step,train/em_iteration,train/loglik,train/loglik_change
2
+ 5.0,0,0.0,0,3.0,786.5249633789062,1626.0738525390625
3
+ 5.0,1,0.0,1,,,
4
+ 5.0,2,0.0,2,,,
5
+ 5.0,3,0.0,3,,,
6
+ 5.0,4,0.0,4,,,
7
+ 5.0,5,0.0,5,,,
8
+ 5.0,6,0.0,6,,,
9
+ 5.0,7,0.0,7,,,
10
+ 5.0,8,0.0,8,,,
11
+ 5.0,9,0.0,9,,,
@@ -0,0 +1,11 @@
1
+ em_iteration_epoch,epoch,loglik_epoch,step,train/em_iteration,train/loglik,train/loglik_change
2
+ 23.0,0,-4543.33642578125,0,12.0,-4647.8408203125,23.843765258789062
3
+ 23.0,1,-4543.33642578125,1,,,
4
+ 23.0,2,-4543.33642578125,2,,,
5
+ 23.0,3,-4543.33642578125,3,,,
6
+ 23.0,4,-4543.33642578125,4,,,
7
+ 23.0,5,-4543.33642578125,5,,,
8
+ 23.0,6,-4543.33642578125,6,,,
9
+ 23.0,7,-4543.33642578125,7,,,
10
+ 23.0,8,-4543.33642578125,8,,,
11
+ 23.0,9,-4543.33642578125,9,,,
@@ -0,0 +1,11 @@
1
+ em_iteration_epoch,epoch,loglik_epoch,step,train/em_iteration,train/loglik,train/loglik_change
2
+ 23.0,0,-4543.33642578125,0,12.0,-4647.8408203125,23.843765258789062
3
+ 23.0,1,-4543.33642578125,1,,,
4
+ 23.0,2,-4543.33642578125,2,,,
5
+ 23.0,3,-4543.33642578125,3,,,
6
+ 23.0,4,-4543.33642578125,4,,,
7
+ 23.0,5,-4543.33642578125,5,,,
8
+ 23.0,6,-4543.33642578125,6,,,
9
+ 23.0,7,-4543.33642578125,7,,,
10
+ 23.0,8,-4543.33642578125,8,,,
11
+ 23.0,9,-4543.33642578125,9,,,
@@ -0,0 +1,11 @@
1
+ em_iteration_epoch,epoch,loglik_epoch,step,train/em_iteration,train/loglik,train/loglik_change
2
+ 23.0,0,-4543.33642578125,0,12.0,-4647.8408203125,23.843765258789062
3
+ 23.0,1,-4543.33642578125,1,,,
4
+ 23.0,2,-4543.33642578125,2,,,
5
+ 23.0,3,-4543.33642578125,3,,,
6
+ 23.0,4,-4543.33642578125,4,,,
7
+ 23.0,5,-4543.33642578125,5,,,
8
+ 23.0,6,-4543.33642578125,6,,,
9
+ 23.0,7,-4543.33642578125,7,,,
10
+ 23.0,8,-4543.33642578125,8,,,
11
+ 23.0,9,-4543.33642578125,9,,,
@@ -0,0 +1,11 @@
1
+ em_iteration_epoch,epoch,loglik_epoch,step,train/em_iteration,train/loglik,train/loglik_change
2
+ 5.0,0,0.0,0,3.0,786.5249633789062,1626.0738525390625
3
+ 5.0,1,0.0,1,,,
4
+ 5.0,2,0.0,2,,,
5
+ 5.0,3,0.0,3,,,
6
+ 5.0,4,0.0,4,,,
7
+ 5.0,5,0.0,5,,,
8
+ 5.0,6,0.0,6,,,
9
+ 5.0,7,0.0,7,,,
10
+ 5.0,8,0.0,8,,,
11
+ 5.0,9,0.0,9,,,
@@ -0,0 +1,11 @@
1
+ em_iteration_epoch,epoch,loglik_epoch,step,train/em_iteration,train/loglik,train/loglik_change
2
+ 23.0,0,-4543.33642578125,0,12.0,-4647.8408203125,23.843765258789062
3
+ 23.0,1,-4543.33642578125,1,,,
4
+ 23.0,2,-4543.33642578125,2,,,
5
+ 23.0,3,-4543.33642578125,3,,,
6
+ 23.0,4,-4543.33642578125,4,,,
7
+ 23.0,5,-4543.33642578125,5,,,
8
+ 23.0,6,-4543.33642578125,6,,,
9
+ 23.0,7,-4543.33642578125,7,,,
10
+ 23.0,8,-4543.33642578125,8,,,
11
+ 23.0,9,-4543.33642578125,9,,,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dfm-python"
3
- version = "0.4.8"
3
+ version = "0.4.9"
4
4
  description = "Dynamic Factor Model (DFM) estimation and nowcasting in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -65,7 +65,7 @@ Note: DFMConfig and SeriesConfig are internal implementation details.
65
65
  For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
66
66
  """
67
67
 
68
- __version__ = "0.4.8"
68
+ __version__ = "0.4.9"
69
69
 
70
70
  # ============================================================================
71
71
  # PUBLIC API DEFINITION
@@ -80,7 +80,6 @@ __version__ = "0.4.8"
80
80
  # 3. Core utilities: TimeIndex, diagnostics
81
81
  # 4. Models: BaseFactorModel, DDFM (low-level)
82
82
  # 5. Data & Results: DFMResult
83
- # 6. Data & Results: DFMResult
84
83
  # ============================================================================
85
84
 
86
85
  # Configuration (from config/ subpackage)
@@ -37,20 +37,29 @@ def get_logger(name: str) -> logging.Logger:
37
37
  # Configure package logger if not already configured
38
38
  # Use a flag to avoid re-configuring if already done
39
39
  if not hasattr(package_logger, '_dfm_configured'):
40
- # Configure root logger for dfm_python package
41
- handler = logging.StreamHandler(sys.stdout)
42
- handler.setFormatter(
43
- logging.Formatter(
44
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
45
- datefmt='%Y-%m-%d %H:%M:%S'
40
+ # CRITICAL: Check if handlers already exist to prevent duplicates
41
+ # This can happen if configure_logging() was called before get_logger()
42
+ if not package_logger.handlers:
43
+ # Configure handler for dfm_python package logger
44
+ handler = logging.StreamHandler(sys.stdout)
45
+ handler.setFormatter(
46
+ logging.Formatter(
47
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
48
+ datefmt='%Y-%m-%d %H:%M:%S'
49
+ )
46
50
  )
47
- )
48
- package_logger.addHandler(handler)
51
+ package_logger.addHandler(handler)
52
+
49
53
  package_logger.setLevel(logging.INFO)
54
+ # CRITICAL: Disable propagation to root logger to prevent duplicate messages
55
+ # Root logger may have handlers from other code (e.g., basicConfig)
56
+ # By setting propagate=False, we ensure messages only go through our handler
57
+ package_logger.propagate = False
50
58
  # Mark as configured to avoid duplicate handlers
51
59
  package_logger._dfm_configured = True
52
60
 
53
- # Ensure child logger propagates to parent (default, but explicit for clarity)
61
+ # Child logger should propagate to package logger (not root)
62
+ # Since package logger has propagate=False, messages won't go to root
54
63
  logger.propagate = True
55
64
 
56
65
  return logger
@@ -110,6 +119,10 @@ def configure_logging(
110
119
  if hasattr(logger, '_dfm_configured'):
111
120
  delattr(logger, '_dfm_configured')
112
121
 
122
+ # CRITICAL: Disable propagation to root logger to prevent duplicate messages
123
+ # If root logger also has handlers, messages would be logged twice
124
+ logger.propagate = False
125
+
113
126
  # Add console handler
114
127
  console_handler = logging.StreamHandler(sys.stdout)
115
128
  console_handler.setLevel(level)
@@ -17,8 +17,10 @@ __all__ = [
17
17
 
18
18
  # DDFM implementation
19
19
  from .ddfm import DDFM, DDFMModel
20
+ from .mcmc import DDFMMCMCTrainer
20
21
  __all__.extend([
21
22
  'DDFM', # High-level API
22
23
  'DDFMModel', # Low-level implementation
24
+ 'DDFMMCMCTrainer', # MCMC training procedure
23
25
  ])
24
26
 
@@ -11,6 +11,7 @@ from abc import abstractmethod
11
11
  from typing import Optional, Union, Tuple, Any, Dict, TYPE_CHECKING
12
12
  from pathlib import Path
13
13
  import numpy as np
14
+ import torch
14
15
  import pytorch_lightning as pl
15
16
 
16
17
  if TYPE_CHECKING:
@@ -349,6 +350,394 @@ class BaseFactorModel(pl.LightningModule):
349
350
 
350
351
  return X_torch, Mx, Wx
351
352
 
353
+ def _compute_default_horizon(self) -> int:
354
+ """Compute default horizon based on clock frequency.
355
+
356
+ Returns
357
+ -------
358
+ int
359
+ Default horizon (1 year of periods based on clock frequency)
360
+ """
361
+ from ..config.utils import get_periods_per_year
362
+ from ..utils.helpers import get_clock_frequency
363
+ clock = get_clock_frequency(self.config, 'm')
364
+ return get_periods_per_year(clock)
365
+
366
+ def _validate_horizon(self, horizon: int) -> None:
367
+ """Validate horizon parameter.
368
+
369
+ Parameters
370
+ ----------
371
+ horizon : int
372
+ Horizon value to validate
373
+
374
+ Raises
375
+ ------
376
+ ValueError
377
+ If horizon is not a positive integer
378
+ """
379
+ if horizon <= 0:
380
+ raise ValueError(
381
+ self._format_error_message(
382
+ operation="prediction",
383
+ reason=f"horizon must be a positive integer, got {horizon}",
384
+ guidance="Please provide a positive integer value for the forecast horizon"
385
+ )
386
+ )
387
+
388
+ def _forecast_var_factors(
389
+ self,
390
+ Z_last: np.ndarray,
391
+ A: np.ndarray,
392
+ p: int,
393
+ horizon: int,
394
+ Z_prev: Optional[np.ndarray] = None
395
+ ) -> np.ndarray:
396
+ """Forecast factors using VAR dynamics.
397
+
398
+ Supports VAR(1) and VAR(2) factor dynamics.
399
+
400
+ Parameters
401
+ ----------
402
+ Z_last : np.ndarray
403
+ Last factor state (m,)
404
+ A : np.ndarray
405
+ Transition matrix. For VAR(1): (m x m), for VAR(2): (m x 2m)
406
+ p : int
407
+ VAR order (1 or 2)
408
+ horizon : int
409
+ Number of periods to forecast
410
+ Z_prev : np.ndarray, optional
411
+ Previous factor state for VAR(2) (m,). Required if p == 2.
412
+
413
+ Returns
414
+ -------
415
+ np.ndarray
416
+ Forecasted factors (horizon x m)
417
+ """
418
+ if p == 1:
419
+ # VAR(1): f_t = A @ f_{t-1}
420
+ Z_forecast = np.zeros((horizon, Z_last.shape[0]))
421
+ Z_forecast[0, :] = A @ Z_last
422
+ for h in range(1, horizon):
423
+ Z_forecast[h, :] = A @ Z_forecast[h - 1, :]
424
+ elif p == 2:
425
+ # VAR(2): f_t = A1 @ f_{t-1} + A2 @ f_{t-2}
426
+ if Z_prev is None:
427
+ # Fallback to VAR(1) if not enough history
428
+ A1 = A[:, :Z_last.shape[0]]
429
+ Z_forecast = np.zeros((horizon, Z_last.shape[0]))
430
+ Z_forecast[0, :] = A1 @ Z_last
431
+ for h in range(1, horizon):
432
+ Z_forecast[h, :] = A1 @ Z_forecast[h - 1, :]
433
+ else:
434
+ A1 = A[:, :Z_last.shape[0]]
435
+ A2 = A[:, Z_last.shape[0]:]
436
+ Z_forecast = np.zeros((horizon, Z_last.shape[0]))
437
+ Z_forecast[0, :] = A1 @ Z_last + A2 @ Z_prev
438
+ if horizon > 1:
439
+ Z_forecast[1, :] = A1 @ Z_forecast[0, :] + A2 @ Z_last
440
+ for h in range(2, horizon):
441
+ Z_forecast[h, :] = A1 @ Z_forecast[h - 1, :] + A2 @ Z_forecast[h - 2, :]
442
+ else:
443
+ raise ValueError(
444
+ self._format_error_message(
445
+ operation="prediction",
446
+ reason=f"unsupported VAR order {p}",
447
+ guidance="Only VAR(1) and VAR(2) are supported. Please use factor_order=1 or factor_order=2"
448
+ )
449
+ )
450
+ return Z_forecast
451
+
452
+ def _transform_factors_to_observations(
453
+ self,
454
+ Z_forecast: np.ndarray,
455
+ C: np.ndarray,
456
+ Wx: np.ndarray,
457
+ Mx: np.ndarray
458
+ ) -> np.ndarray:
459
+ """Transform forecasted factors to observed series.
460
+
461
+ Parameters
462
+ ----------
463
+ Z_forecast : np.ndarray
464
+ Forecasted factors (horizon x m)
465
+ C : np.ndarray
466
+ Loading matrix (N x m)
467
+ Wx : np.ndarray
468
+ Standard deviation values for unstandardization (N,)
469
+ Mx : np.ndarray
470
+ Mean values for unstandardization (N,)
471
+
472
+ Returns
473
+ -------
474
+ np.ndarray
475
+ Forecasted observations (horizon x N)
476
+ """
477
+ X_forecast_std = Z_forecast @ C.T
478
+ X_forecast = X_forecast_std * Wx + Mx
479
+ return X_forecast
480
+
481
+ def _update_factor_state_with_history(
482
+ self,
483
+ history: int,
484
+ result: 'BaseResult',
485
+ kalman_filter: Optional[Any] = None
486
+ ) -> Optional[np.ndarray]:
487
+ """Update factor state using recent N periods of data.
488
+
489
+ This method re-runs the Kalman filter (for DFM) or extracts factors
490
+ via encoder and runs Kalman filter (for DDFM) using only the most
491
+ recent N periods of data for efficiency.
492
+
493
+ Parameters
494
+ ----------
495
+ history : int
496
+ Number of recent periods to use for state update
497
+ result : BaseResult
498
+ Model result containing parameters (A, C, Q, R, Z_0, V_0)
499
+ kalman_filter : Any, optional
500
+ Kalman filter instance. If None, creates a new one.
501
+
502
+ Returns
503
+ -------
504
+ np.ndarray or None
505
+ Updated last factor state (m,), or None if update failed
506
+ """
507
+ # Get data from DataModule
508
+ if not hasattr(self, '_data_module') or self._data_module is None:
509
+ # Try to get from trainer
510
+ if hasattr(self, 'trainer') and self.trainer is not None:
511
+ if hasattr(self.trainer, 'datamodule') and self.trainer.datamodule is not None:
512
+ self._data_module = self.trainer.datamodule
513
+
514
+ if not hasattr(self, '_data_module') or self._data_module is None:
515
+ _logger.warning(
516
+ f"{self.__class__.__name__} predict(): DataModule not available for history={history}, "
517
+ f"using training state instead"
518
+ )
519
+ return None
520
+
521
+ try:
522
+ # Get processed data (T x N)
523
+ X_torch = self._data_module.get_processed_data()
524
+
525
+ # Convert to numpy if needed
526
+ if isinstance(X_torch, torch.Tensor):
527
+ X_data = X_torch.cpu().numpy()
528
+ else:
529
+ X_data = np.asarray(X_torch)
530
+
531
+ # Slice to recent N periods
532
+ if X_data.shape[0] > history:
533
+ X_recent = X_data[-history:, :]
534
+ _logger.debug(
535
+ f"{self.__class__.__name__} predict(): Using recent {history} periods "
536
+ f"(out of {X_data.shape[0]}) for state update"
537
+ )
538
+ else:
539
+ X_recent = X_data
540
+ _logger.debug(
541
+ f"{self.__class__.__name__} predict(): Using all {X_data.shape[0]} periods "
542
+ f"(less than history={history}) for state update"
543
+ )
544
+
545
+ # Standardize data using result's Mx and Wx
546
+ Mx = result.Mx
547
+ Wx = result.Wx
548
+ if Mx is not None and Wx is not None:
549
+ # Check if data is already standardized (mean ~0, std ~1)
550
+ if np.abs(np.nanmean(X_recent)) > 0.1 or np.nanstd(X_recent) > 2.0:
551
+ # Data not standardized, standardize it
552
+ X_recent_std = (X_recent - Mx) / np.where(Wx != 0, Wx, 1.0)
553
+ else:
554
+ # Data already standardized
555
+ X_recent_std = X_recent
556
+ else:
557
+ X_recent_std = X_recent
558
+
559
+ # Handle NaN values (missing data)
560
+ X_recent_std = np.where(np.isfinite(X_recent_std), X_recent_std, np.nan)
561
+
562
+ # Model-specific state update
563
+ # Use __class__.__name__ to avoid circular imports
564
+ # Check if model has encoder attribute (DDFM-specific)
565
+ if hasattr(self, 'encoder') and self.encoder is not None:
566
+ # DDFM: Extract factors via encoder, then use Kalman filter
567
+ return self._update_factor_state_ddfm(
568
+ X_recent_std, result, kalman_filter
569
+ )
570
+ else:
571
+ # DFM: Use Kalman filter directly on standardized data
572
+ return self._update_factor_state_dfm(
573
+ X_recent_std, result, kalman_filter
574
+ )
575
+ except Exception as e:
576
+ _logger.warning(
577
+ f"{self.__class__.__name__} predict(): Failed to update factor state with history={history}, "
578
+ f"using training state instead. Error: {type(e).__name__}: {str(e)}"
579
+ )
580
+ return None
581
+
582
+ def _update_factor_state_dfm(
583
+ self,
584
+ X_recent_std: np.ndarray,
585
+ result: 'BaseResult',
586
+ kalman_filter: Optional[Any] = None
587
+ ) -> np.ndarray:
588
+ """Update factor state for DFM using Kalman filter.
589
+
590
+ Parameters
591
+ ----------
592
+ X_recent_std : np.ndarray
593
+ Standardized recent data (T x N)
594
+ result : BaseResult
595
+ Model result containing parameters
596
+ kalman_filter : Any, optional
597
+ Kalman filter instance
598
+
599
+ Returns
600
+ -------
601
+ np.ndarray
602
+ Updated last factor state (m,)
603
+ """
604
+ import torch
605
+
606
+ # Convert to torch tensor: (N x T) format for Kalman filter
607
+ Y = torch.tensor(X_recent_std.T, dtype=torch.float32) # (N x T)
608
+
609
+ # Extract parameters
610
+ A = result.A
611
+ C = result.C
612
+ Q = result.Q
613
+ R = result.R
614
+ Z_0 = result.Z_0
615
+ V_0 = result.V_0
616
+
617
+ # Convert parameters to torch
618
+ A_torch = torch.tensor(A, dtype=torch.float32)
619
+ C_torch = torch.tensor(C, dtype=torch.float32)
620
+ Q_torch = torch.tensor(Q, dtype=torch.float32)
621
+ R_torch = torch.tensor(R, dtype=torch.float32)
622
+ Z_0_torch = torch.tensor(Z_0, dtype=torch.float32)
623
+ V_0_torch = torch.tensor(V_0, dtype=torch.float32)
624
+
625
+ # Re-run Kalman filter with recent data
626
+ if kalman_filter is None:
627
+ if hasattr(self, 'kalman') and self.kalman is not None:
628
+ kalman_filter = self.kalman
629
+ else:
630
+ from ..ssm.kalman import KalmanFilter
631
+ kalman_filter = KalmanFilter(
632
+ min_eigenval=1e-8,
633
+ inv_regularization=1e-6,
634
+ cholesky_regularization=1e-8
635
+ )
636
+
637
+ # Run Kalman smoother
638
+ zsmooth, Vsmooth, _, _ = kalman_filter(
639
+ Y, A_torch, C_torch, Q_torch, R_torch, Z_0_torch, V_0_torch
640
+ )
641
+
642
+ # zsmooth is (m x (T+1)), transpose to ((T+1) x m)
643
+ Zsmooth = zsmooth.T # ((T+1) x m)
644
+
645
+ # Get last factor state (skip initial state at index 0)
646
+ Z_last = Zsmooth[-1, :].cpu().numpy() # (m,)
647
+
648
+ return Z_last
649
+
650
+ def _update_factor_state_ddfm(
651
+ self,
652
+ X_recent_std: np.ndarray,
653
+ result: 'BaseResult',
654
+ kalman_filter: Optional[Any] = None
655
+ ) -> np.ndarray:
656
+ """Update factor state for DDFM using encoder and Kalman filter.
657
+
658
+ For DDFM, we first extract factors using the encoder, then run
659
+ Kalman filter on those factors to get smoothed state.
660
+
661
+ Parameters
662
+ ----------
663
+ X_recent_std : np.ndarray
664
+ Standardized recent data (T x N)
665
+ result : BaseResult
666
+ Model result containing parameters
667
+ kalman_filter : Any, optional
668
+ Kalman filter instance
669
+
670
+ Returns
671
+ -------
672
+ np.ndarray
673
+ Updated last factor state (m,)
674
+ """
675
+ import torch
676
+
677
+ # Extract factors using encoder
678
+ # Note: encoder is a DDFM-specific attribute, accessed via model instance
679
+ if not hasattr(self, 'encoder') or self.encoder is None:
680
+ _logger.warning(
681
+ f"{self.__class__.__name__} predict(): Encoder not available for history update, "
682
+ "using training state instead"
683
+ )
684
+ return None
685
+
686
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
687
+ self.encoder.eval()
688
+
689
+ # Convert to torch and extract factors
690
+ X_tensor = torch.tensor(X_recent_std, device=device, dtype=torch.float32)
691
+ with torch.no_grad():
692
+ factors_raw = self.encoder(X_tensor).cpu().numpy() # (T x m)
693
+
694
+ # Extract parameters for Kalman filter
695
+ A = result.A
696
+ Q = result.Q
697
+ Z_0 = result.Z_0
698
+ V_0 = result.V_0
699
+ p = result.p # VAR order
700
+
701
+ # For DDFM, the measurement equation is identity (factors are directly observed)
702
+ # So C = I (identity matrix), R = small noise
703
+ m = factors_raw.shape[1]
704
+ C = np.eye(m) # Identity matrix (factors are directly observed)
705
+ R = np.eye(m) * 1e-8 # Small noise for numerical stability
706
+
707
+ # Convert to torch tensor: (m x T) format for Kalman filter
708
+ # Note: For DDFM, we filter factors directly, so Y = factors.T
709
+ Y = torch.tensor(factors_raw.T, dtype=torch.float32) # (m x T)
710
+
711
+ # Convert parameters to torch
712
+ A_torch = torch.tensor(A, dtype=torch.float32)
713
+ C_torch = torch.tensor(C, dtype=torch.float32)
714
+ Q_torch = torch.tensor(Q, dtype=torch.float32)
715
+ R_torch = torch.tensor(R, dtype=torch.float32)
716
+ Z_0_torch = torch.tensor(Z_0, dtype=torch.float32)
717
+ V_0_torch = torch.tensor(V_0, dtype=torch.float32)
718
+
719
+ # Create or use Kalman filter
720
+ if kalman_filter is None:
721
+ from ..ssm.kalman import KalmanFilter
722
+ kalman_filter = KalmanFilter(
723
+ min_eigenval=1e-8,
724
+ inv_regularization=1e-6,
725
+ cholesky_regularization=1e-8
726
+ )
727
+
728
+ # Run Kalman smoother on factors
729
+ zsmooth, Vsmooth, _, _ = kalman_filter(
730
+ Y, A_torch, C_torch, Q_torch, R_torch, Z_0_torch, V_0_torch
731
+ )
732
+
733
+ # zsmooth is (m x (T+1)), transpose to ((T+1) x m)
734
+ Zsmooth = zsmooth.T # ((T+1) x m)
735
+
736
+ # Get last factor state (skip initial state at index 0)
737
+ Z_last = Zsmooth[-1, :].cpu().numpy() # (m,)
738
+
739
+ return Z_last
740
+
352
741
  @property
353
742
  def result(self) -> BaseResult:
354
743
  """Get last fit result.
@@ -366,6 +755,7 @@ class BaseFactorModel(pl.LightningModule):
366
755
  self,
367
756
  horizon: Optional[int] = None,
368
757
  *,
758
+ history: Optional[int] = None,
369
759
  return_series: bool = True,
370
760
  return_factors: bool = True
371
761
  ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
@@ -375,6 +765,11 @@ class BaseFactorModel(pl.LightningModule):
375
765
  ----------
376
766
  horizon : int, optional
377
767
  Number of periods ahead to forecast. If None, uses default based on clock frequency.
768
+ history : int, optional
769
+ Number of historical periods to use for Kalman filter update before prediction.
770
+ If None, uses full history (default). If specified (e.g., 60), uses only the most
771
+ recent N periods for efficiency. Initial state (Z_0, V_0) is always estimated from
772
+ full history (including any new data beyond training period).
378
773
  return_series : bool, default True
379
774
  Whether to return forecasted series.
380
775
  return_factors : bool, default True
@@ -387,6 +782,13 @@ class BaseFactorModel(pl.LightningModule):
387
782
  - If both return_series and return_factors are True: (X_forecast, Z_forecast)
388
783
  - If only return_series is True: X_forecast
389
784
  - If only return_factors is True: Z_forecast
785
+
786
+ Notes
787
+ -----
788
+ When history is specified, the method uses only the most recent N periods for
789
+ Kalman filter update, improving computational efficiency. The initial state
790
+ (Z_0, V_0) is always estimated from full history (including any new data beyond
791
+ training period), ensuring accuracy while maintaining efficiency.
390
792
  """
391
793
  pass
392
794
 
@@ -403,6 +805,65 @@ class BaseFactorModel(pl.LightningModule):
403
805
  """
404
806
  raise NotImplementedError("Subclasses must implement get_result()")
405
807
 
808
+ @property
809
+ def nowcast(self) -> 'Nowcast':
810
+ """Get nowcast manager for generating nowcasts.
811
+
812
+ Returns
813
+ -------
814
+ Nowcast
815
+ Nowcast manager object that provides methods for:
816
+ - Generating nowcasts (forecasts for current period)
817
+ - Computing revision impacts
818
+ - Analyzing data releases
819
+ - News decomposition
820
+
821
+ Raises
822
+ ------
823
+ ValueError
824
+ If model has not been trained yet. Call trainer.fit(model, data_module) first.
825
+
826
+ Examples
827
+ --------
828
+ >>> model = DFM()
829
+ >>> model.load_config('config.yaml')
830
+ >>> trainer.fit(model, data_module)
831
+ >>> nowcast = model.nowcast
832
+ >>> value = nowcast('gdp', view_date='2024-01-15', target_period='2024Q1')
833
+ """
834
+ self._check_trained()
835
+
836
+ # Import here to avoid circular dependency
837
+ try:
838
+ from ..nowcast import Nowcast
839
+ except ImportError:
840
+ # Fallback: try importing from src.nowcasting if dfm_python.nowcast doesn't exist
841
+ try:
842
+ import sys
843
+ from pathlib import Path
844
+ # Add src to path if not already there
845
+ src_path = Path(__file__).parent.parent.parent.parent / 'src'
846
+ if str(src_path) not in sys.path:
847
+ sys.path.insert(0, str(src_path))
848
+ from nowcasting import Nowcast
849
+ except ImportError:
850
+ error_msg = self._format_error_message(
851
+ operation="nowcast access",
852
+ reason="Nowcast class not available",
853
+ guidance="Please ensure nowcasting module is available"
854
+ )
855
+ raise ValueError(error_msg)
856
+
857
+ # Get DataModule from model
858
+ data_module = getattr(self, '_data_module', None)
859
+ if data_module is None:
860
+ # Try to get from trainer if available
861
+ if hasattr(self, 'trainer') and self.trainer is not None:
862
+ if hasattr(self.trainer, 'datamodule') and self.trainer.datamodule is not None:
863
+ data_module = self.trainer.datamodule
864
+
865
+ return Nowcast(self, data_module=data_module)
866
+
406
867
  def _load_config_common(
407
868
  self,
408
869
  source: Optional[Union[str, Path, Dict[str, Any], DFMConfig, ConfigSource]] = None,