dfm-python 0.5.58__py3-none-any.whl → 0.5.59__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dfm_python/functional/em.py +1 -1
- dfm_python/models/dfm.py +64 -9
- dfm_python/ssm/kalman.py +3 -3
- {dfm_python-0.5.58.dist-info → dfm_python-0.5.59.dist-info}/METADATA +2 -1
- {dfm_python-0.5.58.dist-info → dfm_python-0.5.59.dist-info}/RECORD +6 -6
- {dfm_python-0.5.58.dist-info → dfm_python-0.5.59.dist-info}/WHEEL +0 -0
dfm_python/functional/em.py
CHANGED
|
@@ -1233,7 +1233,7 @@ def em_step(
|
|
|
1233
1233
|
_logger.info(f" E-step: Running Kalman filter + smoother (T={T}, N={N}, m={m}, ~{ops_estimate:.1f}B ops)...")
|
|
1234
1234
|
_logger.info(f" E-step: Filter is fast (~1s), but smooth may take 5-7 minutes (bottleneck)")
|
|
1235
1235
|
EZ, V_smooth, VVsmooth, loglik = kalman_filter.filter_and_smooth(X_masked)
|
|
1236
|
-
|
|
1236
|
+
|
|
1237
1237
|
# Keep float64 for numerical stability (prevents V_smooth accumulation overflow)
|
|
1238
1238
|
# Do NOT convert to float32 - maintain float64 precision throughout M-step
|
|
1239
1239
|
# Float64 is critical for large state spaces (m=183) and long time series (T=2135)
|
dfm_python/models/dfm.py
CHANGED
|
@@ -1319,7 +1319,33 @@ class DFM(BaseFactorModel):
|
|
|
1319
1319
|
|
|
1320
1320
|
# Run filter and smooth on new data
|
|
1321
1321
|
y_masked = np.ma.masked_invalid(data_new)
|
|
1322
|
-
|
|
1322
|
+
|
|
1323
|
+
# ------------------------------------------------------------------
|
|
1324
|
+
# IMPORTANT (pykalman quirk):
|
|
1325
|
+
# pykalman.KalmanFilter._parse_observations() transposes when input has
|
|
1326
|
+
# shape (1, n_dim_obs), interpreting it as (n_dim_obs, 1). For our
|
|
1327
|
+
# update() case, (1, N) means 1 timestep with N series. This transpose
|
|
1328
|
+
# causes n_dim_obs=1 and breaks CholeskyKalmanFilter internals.
|
|
1329
|
+
#
|
|
1330
|
+
# Workaround: for a single new timestep, use filter_update() directly
|
|
1331
|
+
# with a 1D observation vector (n_dim_obs,), avoiding _parse_observations.
|
|
1332
|
+
# ------------------------------------------------------------------
|
|
1333
|
+
if hasattr(data_new, "shape") and len(data_new.shape) == 2 and int(data_new.shape[0]) == 1:
|
|
1334
|
+
pk = getattr(kalman_new, "_pykalman", None)
|
|
1335
|
+
if pk is None:
|
|
1336
|
+
raise ModelNotTrainedError("Kalman filter not initialized for update()")
|
|
1337
|
+
|
|
1338
|
+
# 1D observation vector for this timestep
|
|
1339
|
+
obs = np.ma.masked_invalid(data_new[0])
|
|
1340
|
+
next_mean, _next_cov = pk.filter_update(
|
|
1341
|
+
filtered_state_mean=Z_last,
|
|
1342
|
+
filtered_state_covariance=V_last,
|
|
1343
|
+
observation=obs,
|
|
1344
|
+
)
|
|
1345
|
+
Z_new = np.asarray(next_mean, dtype=DEFAULT_DTYPE)[np.newaxis, :]
|
|
1346
|
+
V_smooth_new = None
|
|
1347
|
+
else:
|
|
1348
|
+
Z_new, V_smooth_new, _, _ = kalman_new.filter_and_smooth(y_masked)
|
|
1323
1349
|
|
|
1324
1350
|
# Update model state: append new factors and data
|
|
1325
1351
|
# Concatenate new factors to existing result.Z
|
|
@@ -1509,6 +1535,18 @@ class DFM(BaseFactorModel):
|
|
|
1509
1535
|
except (ModelNotInitializedError, AttributeError):
|
|
1510
1536
|
pass
|
|
1511
1537
|
target_series, target_indices = resolve_target_series(dataset, series_ids, result, self.__class__.__name__)
|
|
1538
|
+
|
|
1539
|
+
# Fallback: If no Dataset is attached, use config.target_series.
|
|
1540
|
+
# This is needed for checkpoints used in external pipelines where dataset.pkl
|
|
1541
|
+
# is missing or not attached to the model instance.
|
|
1542
|
+
if (target_series is None or len(target_series) == 0 or target_indices is None) and getattr(self, "_config", None) is not None:
|
|
1543
|
+
cfg_ts = getattr(self._config, "target_series", None)
|
|
1544
|
+
if cfg_ts is not None:
|
|
1545
|
+
cfg_ts_list = cfg_ts if isinstance(cfg_ts, list) else [cfg_ts]
|
|
1546
|
+
cfg_indices = [series_ids.index(t) for t in cfg_ts_list if series_ids is not None and t in series_ids]
|
|
1547
|
+
|
|
1548
|
+
if cfg_indices:
|
|
1549
|
+
target_series, target_indices = cfg_ts_list, cfg_indices
|
|
1512
1550
|
|
|
1513
1551
|
if target_series is None or len(target_series) == 0:
|
|
1514
1552
|
raise ValueError(
|
|
@@ -1747,19 +1785,36 @@ class DFM(BaseFactorModel):
|
|
|
1747
1785
|
raise FileNotFoundError(f"Checkpoint file not found: {path}")
|
|
1748
1786
|
|
|
1749
1787
|
# Try loading with error handling for corrupted files
|
|
1788
|
+
# NOTE: Our training pipeline typically saves with joblib (for speed/compression),
|
|
1789
|
+
# but older checkpoints may be plain pickle. Support both.
|
|
1790
|
+
checkpoint = None
|
|
1750
1791
|
try:
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
except
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
)
|
|
1792
|
+
import joblib # local import to avoid hard dependency at import-time
|
|
1793
|
+
checkpoint = joblib.load(path)
|
|
1794
|
+
except Exception:
|
|
1795
|
+
# Fallback to plain pickle
|
|
1796
|
+
try:
|
|
1797
|
+
with open(path, 'rb') as f:
|
|
1798
|
+
checkpoint = pickle.load(f)
|
|
1799
|
+
except (pickle.UnpicklingError, EOFError, ValueError) as e:
|
|
1800
|
+
raise RuntimeError(
|
|
1801
|
+
f"Failed to load checkpoint from {path}. "
|
|
1802
|
+
f"The file may be corrupted or saved in an unsupported format. Error: {e}. "
|
|
1803
|
+
f"Try re-training the model to generate a new checkpoint."
|
|
1804
|
+
) from e
|
|
1759
1805
|
|
|
1760
1806
|
# Use checkpoint config if not provided
|
|
1761
1807
|
if config is None:
|
|
1762
1808
|
config = checkpoint.get('config')
|
|
1809
|
+
|
|
1810
|
+
# If target_series was stored separately in the checkpoint (common in our pipeline),
|
|
1811
|
+
# inject it into config so predict() can resolve targets without a Dataset attached.
|
|
1812
|
+
try:
|
|
1813
|
+
ckpt_target_series = checkpoint.get('target_series')
|
|
1814
|
+
if config is not None and getattr(config, 'target_series', None) is None and ckpt_target_series is not None:
|
|
1815
|
+
config.target_series = ckpt_target_series if isinstance(ckpt_target_series, list) else [ckpt_target_series]
|
|
1816
|
+
except Exception:
|
|
1817
|
+
pass
|
|
1763
1818
|
|
|
1764
1819
|
# Get model state (new format preferred, fallback to old format)
|
|
1765
1820
|
model_state = checkpoint.get('model_state')
|
dfm_python/ssm/kalman.py
CHANGED
|
@@ -204,7 +204,7 @@ class DFMKalmanFilter:
|
|
|
204
204
|
"DFMKalmanFilter parameters not initialized. "
|
|
205
205
|
"Call update_parameters() first."
|
|
206
206
|
)
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
# Get filtered states first (needed for smoother)
|
|
209
209
|
transition_offsets = getattr(self._pykalman, 'transition_offsets', None)
|
|
210
210
|
observation_offsets = getattr(self._pykalman, 'observation_offsets', None)
|
|
@@ -241,7 +241,7 @@ class DFMKalmanFilter:
|
|
|
241
241
|
|
|
242
242
|
# Run filter
|
|
243
243
|
predicted_state_means, predicted_state_covariances, _, filtered_state_means, filtered_state_covariances = run_filter()
|
|
244
|
-
|
|
244
|
+
|
|
245
245
|
filter_time = time_module.time() - filter_start
|
|
246
246
|
_logger.info(f" Filter: Completed in {filter_time:.2f}s ({filter_time/T*1000:.2f}ms/timestep)")
|
|
247
247
|
|
|
@@ -266,7 +266,7 @@ class DFMKalmanFilter:
|
|
|
266
266
|
# This is much cheaper than full eigendecomposition (O(m²) vs O(m³))
|
|
267
267
|
from ..config.constants import MIN_EIGENVALUE
|
|
268
268
|
regularization = max(1e-6, MIN_EIGENVALUE * 100) # 1e-4 for stability
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
_logger.info(f" Smooth: Stabilizing {len(predicted_state_covariances)} covariance matrices "
|
|
271
271
|
f"(regularization={regularization:.2e})")
|
|
272
272
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.59
|
|
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
|
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: build>=1.4.0
|
|
21
22
|
Requires-Dist: einops>=0.8.1
|
|
22
23
|
Requires-Dist: hydra-core>=1.3.2
|
|
23
24
|
Requires-Dist: matplotlib>=3.5.0
|
|
@@ -22,7 +22,7 @@ dfm_python/encoder/pca.py,sha256=C1U0bGOVdr-HQaJYD57MyA7RLaEr70LTXWgmo6P-sUU,105
|
|
|
22
22
|
dfm_python/encoder/simple_autoencoder.py,sha256=9y5C6b3MEgNO3oZ0DjA2aX_AnBcX6eqG_9zFJPgHAjk,10717
|
|
23
23
|
dfm_python/functional/__init__.py,sha256=J9EYUqQnstmY0Wq-g8v3Z3We01gOW99x6M9MxPcSp4w,329
|
|
24
24
|
dfm_python/functional/dfm_block.py,sha256=lifzLA0fHudj-tnqCK4kMy09zVUtlJMPb2uw635yH4c,21006
|
|
25
|
-
dfm_python/functional/em.py,sha256=
|
|
25
|
+
dfm_python/functional/em.py,sha256=4U6ftX8PWnMdc_ODYuPSaVVD6rqY2ykkV5zBBdO9zU8,64164
|
|
26
26
|
dfm_python/logger/__init__.py,sha256=ZsUPp5Gd_FTWvgtBweOMafxc14hZRSOdfG8yaYskRgI,1622
|
|
27
27
|
dfm_python/logger/inference_logger.py,sha256=t6K1Di-QkqIvcbSUGW1sCeWD84ZTgoC27oB2FWaY1SI,10314
|
|
28
28
|
dfm_python/logger/logger.py,sha256=drTD5o4VPb34YKjux7MaEnI_k5a93AOlhQciMZVTkAE,10379
|
|
@@ -30,7 +30,7 @@ dfm_python/logger/train_logger.py,sha256=BGtlJt-FP3WC4u_oGpfNrIYctIXamkB1gVyMhhn
|
|
|
30
30
|
dfm_python/models/__init__.py,sha256=NHv79jHILrnJG4Auuo3fspi1FtLLedWfLS9GK9WZx6A,552
|
|
31
31
|
dfm_python/models/base.py,sha256=igYjuGpcEsaDbsRbz3_8mvYkYuMKGZYdMqVJodylg0E,11024
|
|
32
32
|
dfm_python/models/ddfm.py,sha256=AjjcCdyFo4LJSfDZjMVd_FNa0ggmI6VnvhZhkFb_tLU,50700
|
|
33
|
-
dfm_python/models/dfm.py,sha256=
|
|
33
|
+
dfm_python/models/dfm.py,sha256=36_Z6WabQC48SaCEBu7jSyU8IMSORCv1UanlsVUACVM,84407
|
|
34
34
|
dfm_python/numeric/builder.py,sha256=zTaZFNqR0B7rb5hPkJs4I7beUYPMu44K9AaTIJniD8s,5852
|
|
35
35
|
dfm_python/numeric/estimator.py,sha256=OPe7tAiTn0Jj_bL1ywOe47vJ37oeSMC2AtAX2FxLe5s,47534
|
|
36
36
|
dfm_python/numeric/stability.py,sha256=PIR2XvxHGQ4uD1QuIkGGKuNmkfAasQdhoZlH2BWt56U,43922
|
|
@@ -38,7 +38,7 @@ dfm_python/numeric/statistic.py,sha256=Y9rrv18OKBiholy-R5Bz9tzGCkaziMn0Jk4Ca_KS0
|
|
|
38
38
|
dfm_python/numeric/tent.py,sha256=tsmmYfj3UbV8caVUpeP-7dzIvslmTVl6p-l5X5--AqU,12229
|
|
39
39
|
dfm_python/numeric/validator.py,sha256=FPgVC19PsWRgx40S9tDG0x0ccgiAUhnMFyf-IcfxOtA,39927
|
|
40
40
|
dfm_python/ssm/__init__.py,sha256=AmmiJPlAy1bctM9AVso7eWKdPMrUbCfAZQHgqZv-KLY,784
|
|
41
|
-
dfm_python/ssm/kalman.py,sha256=
|
|
41
|
+
dfm_python/ssm/kalman.py,sha256=uwkMzUDqQOrhTszxDXalQP3xl0856o00Uizeiry6_2U,31739
|
|
42
42
|
dfm_python/utils/__init__.py,sha256=WLczOBMPw-_VIvuzoCGHouf-S5dcjhrUnnaTDfNouzQ,5382
|
|
43
43
|
dfm_python/utils/checkpoint.py,sha256=npdYsNg74KE7wUtU25Wuqs846oXfwYxUJcEzKnh-k6o,11201
|
|
44
44
|
dfm_python/utils/errors.py,sha256=BKMjYldXjdFg-O_pIBo1-WITInNXvNrHk-6IyFHMf-k,1861
|
|
@@ -47,6 +47,6 @@ dfm_python/utils/loss.py,sha256=1KcYwFJmY9l-Uh6Sk-dFpJ2kgMfm0u41Gmgu-5NzaAs,3878
|
|
|
47
47
|
dfm_python/utils/metric.py,sha256=AC_TnJ3eIXiF_maNNexSHzXpLv5Hrf1W88X5pJx9C-Q,15327
|
|
48
48
|
dfm_python/utils/misc.py,sha256=NprRqONFlqYE_LbIbPE6G0CDbYNGXKikaoH80SpVY4E,9596
|
|
49
49
|
dfm_python/utils/validation.py,sha256=WGosO4AY7HnvUNpzAptzRWHXeu75Uzv-qiZjHSFgn8M,1437
|
|
50
|
-
dfm_python-0.5.
|
|
51
|
-
dfm_python-0.5.
|
|
52
|
-
dfm_python-0.5.
|
|
50
|
+
dfm_python-0.5.59.dist-info/METADATA,sha256=wtA3kxn3Smr8abJUD8bi_sxxuBRlnOp9EJY_MrXIvVk,13131
|
|
51
|
+
dfm_python-0.5.59.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
52
|
+
dfm_python-0.5.59.dist-info/RECORD,,
|
|
File without changes
|