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.
- {dfm_python-0.5.54 → dfm_python-0.5.55}/PKG-INFO +1 -1
- {dfm_python-0.5.54 → dfm_python-0.5.55}/pyproject.toml +1 -1
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/__init__.py +1 -1
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/model.py +46 -6
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/em.py +25 -6
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/base.py +103 -226
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/ddfm.py +120 -71
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/dfm.py +219 -98
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/kdfm.py +139 -26
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/estimator.py +100 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/validator.py +175 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/__init__.py +18 -14
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/dfm.py +0 -2
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/kdfm.py +2 -3
- dfm_python-0.5.55/src/dfm_python/utils/__init__.py +221 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/common.py +109 -4
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/misc.py +98 -34
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_base.py +28 -27
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_ddfm.py +1 -1
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_kdfm.py +1 -1
- dfm_python-0.5.54/src/dfm_python/utils/__init__.py +0 -301
- {dfm_python-0.5.54 → dfm_python-0.5.55}/.git +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/.gitignore +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/.python-version +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/LICENSE +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/MANIFEST.in +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/README.md +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/config/ddfm_finance.yaml +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/config/ddfm_macro.yaml +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/config/dfm_finance.yaml +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/config/dfm_macro.yaml +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/data/finance.csv +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/data/macro.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_10/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_11/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_12/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_13/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_14/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_15/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_16/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_17/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_18/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_19/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_20/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_21/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_22/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_5/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_7/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_8/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/ddfm/version_9/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_0/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_1/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_10/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_11/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_12/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_13/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_14/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_2/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_3/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_4/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_5/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_6/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_7/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_8/metrics.csv +0 -0
- {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
- {dfm_python-0.5.54 → dfm_python-0.5.55}/lightning_logs/trainer/version_9/metrics.csv +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/adapter.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/constants.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/block.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/schema/results.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/config/types.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/base.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/ddfm_dm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/dfm_dm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/datamodule/kdfm_dm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/dataloader.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/dataset.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/dataset/process.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/decoder/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/decoder/linear.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/decoder/mlp.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/base.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/pca.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/simple_encoder.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/encoder/variational_encoder.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/dfm_block.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/irf.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/functional/krylov.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/inference_logger.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/logger.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/logger/train_logger.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/models/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/builder.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/stability.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/numeric/tent.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/arma.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/companion.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/kalman.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/ssm/structural.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/trainer/ddfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/errors.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/helper.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/metric.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/tensor_utils.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/dfm_python/utils/validation.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/test_block.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/test_model.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/schema/test_results.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/test_adapter.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/test_constants.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/config/test_types.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/conftest.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_base.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_ddfm_dm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_dfm_dm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/datamodule/test_kdfm_dm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/test_dataloader.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/test_dataset.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/dataset/test_process.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/decoder/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/decoder/test_linear.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/decoder/test_mlp.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_base.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_pca.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_simple_encoder.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/encoder/test_variational_encoder.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_dfm_block.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_em.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_irf.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/functional/test_krylov.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/test_inference_logger.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/test_logger.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/logger/test_train_logger.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/models/test_dfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_builder.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_estimator.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_stability.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_tent.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/numeric/test_validator.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_arma.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_companion.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_kalman.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/ssm/test_structural.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/test_ddfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/test_dfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/trainer/test_kdfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/__init__.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_common.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_errors.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_helper.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_metric.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_misc.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_tensor_utils.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/src/test/utils/test_validation.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_finance_ddfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_finance_dfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_finance_kdfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_macro_ddfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_macro_dfm.py +0 -0
- {dfm_python-0.5.54 → dfm_python-0.5.55}/tutorial/tutorial_macro_kdfm.py +0 -0
- {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.
|
|
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
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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,)
|
|
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)
|
|
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
|
|
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)
|
|
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 ..
|
|
347
|
-
|
|
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
|
|
449
|
-
|
|
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.
|
|
367
|
+
from ..utils.misc import resolve_target_series
|
|
471
368
|
|
|
472
|
-
# Get
|
|
473
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
526
|
-
|
|
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.
|