dfm-python 0.5.56__py3-none-any.whl → 0.5.58__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/__init__.py +0 -1
- dfm_python/config/__init__.py +4 -13
- dfm_python/config/adapter.py +68 -7
- dfm_python/config/constants.py +5 -21
- dfm_python/config/schema/__init__.py +2 -6
- dfm_python/config/schema/block.py +63 -3
- dfm_python/config/schema/model.py +31 -5
- dfm_python/config/schema/params.py +207 -22
- dfm_python/config/schema/results.py +8 -74
- dfm_python/config/types.py +3 -11
- dfm_python/dataset/ddfm_dataset.py +104 -16
- dfm_python/dataset/dfm_dataset.py +80 -14
- dfm_python/encoder/pca.py +59 -9
- dfm_python/functional/__init__.py +7 -9
- dfm_python/functional/dfm_block.py +85 -19
- dfm_python/functional/em.py +635 -256
- dfm_python/logger/__init__.py +6 -12
- dfm_python/logger/inference_logger.py +3 -17
- dfm_python/logger/logger.py +129 -5
- dfm_python/logger/train_logger.py +26 -34
- dfm_python/models/__init__.py +2 -2
- dfm_python/models/base.py +8 -29
- dfm_python/models/ddfm.py +259 -30
- dfm_python/models/dfm.py +648 -236
- dfm_python/numeric/builder.py +1 -5
- dfm_python/numeric/estimator.py +217 -28
- dfm_python/numeric/stability.py +99 -151
- dfm_python/numeric/tent.py +75 -44
- dfm_python/numeric/validator.py +86 -2
- dfm_python/ssm/__init__.py +13 -3
- dfm_python/ssm/kalman.py +342 -147
- dfm_python/utils/__init__.py +2 -5
- dfm_python/utils/checkpoint.py +1 -1
- dfm_python/utils/helper.py +64 -8
- dfm_python/utils/misc.py +66 -19
- dfm_python/utils/validation.py +4 -71
- {dfm_python-0.5.56.dist-info → dfm_python-0.5.58.dist-info}/METADATA +4 -5
- dfm_python-0.5.58.dist-info/RECORD +52 -0
- dfm_python/functional/irf.py +0 -305
- dfm_python/functional/krylov.py +0 -124
- dfm_python/ssm/arma.py +0 -0
- dfm_python/ssm/companion.py +0 -995
- dfm_python/ssm/structural.py +0 -298
- dfm_python-0.5.56.dist-info/RECORD +0 -58
- dfm_python-0.5.56.dist-info/licenses/LICENSE +0 -21
- {dfm_python-0.5.56.dist-info → dfm_python-0.5.58.dist-info}/WHEEL +0 -0
dfm_python/__init__.py
CHANGED
dfm_python/config/__init__.py
CHANGED
|
@@ -10,7 +10,7 @@ Note: Series are specified via frequency dict mapping column names to frequencie
|
|
|
10
10
|
from .schema import (
|
|
11
11
|
BaseModelConfig, DFMConfig, DDFMConfig,
|
|
12
12
|
BaseResult, DFMResult, DDFMResult,
|
|
13
|
-
|
|
13
|
+
DFMStateSpaceParams, DDFMStateSpaceParams,
|
|
14
14
|
)
|
|
15
15
|
# DEFAULT_BLOCK_NAME is imported lazily where needed to avoid circular imports
|
|
16
16
|
from .constants import (
|
|
@@ -46,8 +46,6 @@ from .types import (
|
|
|
46
46
|
ForecastResult,
|
|
47
47
|
CoefficientMatrix,
|
|
48
48
|
CovarianceMatrix,
|
|
49
|
-
IRFArray,
|
|
50
|
-
CompanionMatrix,
|
|
51
49
|
SeriesID,
|
|
52
50
|
Frequency,
|
|
53
51
|
DatasetName,
|
|
@@ -58,7 +56,6 @@ from .types import (
|
|
|
58
56
|
ResultDict,
|
|
59
57
|
ForecastDict,
|
|
60
58
|
MetricsDict,
|
|
61
|
-
IRFResultDict,
|
|
62
59
|
CheckpointDict,
|
|
63
60
|
ConfigDict,
|
|
64
61
|
Device,
|
|
@@ -89,9 +86,8 @@ except ImportError:
|
|
|
89
86
|
compute_idio_lengths = None
|
|
90
87
|
|
|
91
88
|
try:
|
|
92
|
-
from ..numeric.tent import
|
|
89
|
+
from ..numeric.tent import get_agg_structure, group_by_freq
|
|
93
90
|
except ImportError:
|
|
94
|
-
get_tent_weights = None
|
|
95
91
|
get_agg_structure = None
|
|
96
92
|
group_by_freq = None
|
|
97
93
|
from .constants import FREQUENCY_HIERARCHY, PERIODS_PER_YEAR
|
|
@@ -107,9 +103,8 @@ __all__ = [
|
|
|
107
103
|
# 'DEFAULT_BLOCK_NAME', # Removed to avoid circular import - import directly from functional.dfm_block
|
|
108
104
|
# Model-specific configs (from schema.py)
|
|
109
105
|
'DFMConfig', 'DDFMConfig',
|
|
110
|
-
#
|
|
111
|
-
'
|
|
112
|
-
'FitParams', # Backward compatibility alias
|
|
106
|
+
# State-space parameters
|
|
107
|
+
'DFMStateSpaceParams', 'DDFMStateSpaceParams',
|
|
113
108
|
# Model-specific results (from results.py)
|
|
114
109
|
'DFMResult', 'DDFMResult',
|
|
115
110
|
# Utilities
|
|
@@ -123,7 +118,6 @@ __all__ = [
|
|
|
123
118
|
'PERIODS_PER_YEAR',
|
|
124
119
|
'get_periods_per_year',
|
|
125
120
|
'compute_idio_lengths',
|
|
126
|
-
'get_tent_weights',
|
|
127
121
|
'get_agg_structure',
|
|
128
122
|
'group_by_freq',
|
|
129
123
|
# Type definitions (from types.py)
|
|
@@ -139,8 +133,6 @@ __all__ = [
|
|
|
139
133
|
'ForecastResult',
|
|
140
134
|
'CoefficientMatrix',
|
|
141
135
|
'CovarianceMatrix',
|
|
142
|
-
'IRFArray',
|
|
143
|
-
'CompanionMatrix',
|
|
144
136
|
'SeriesID',
|
|
145
137
|
'Frequency',
|
|
146
138
|
'DatasetName',
|
|
@@ -151,7 +143,6 @@ __all__ = [
|
|
|
151
143
|
'ResultDict',
|
|
152
144
|
'ForecastDict',
|
|
153
145
|
'MetricsDict',
|
|
154
|
-
'IRFResultDict',
|
|
155
146
|
'CheckpointDict',
|
|
156
147
|
'ConfigDict',
|
|
157
148
|
'Device',
|
dfm_python/config/adapter.py
CHANGED
|
@@ -118,7 +118,12 @@ def _extract_frequency_dict(
|
|
|
118
118
|
data: Dict[str, Any],
|
|
119
119
|
clock: str = DEFAULT_CLOCK_FREQUENCY
|
|
120
120
|
) -> Optional[Dict[str, str]]:
|
|
121
|
-
"""Extract frequency dict from data, handling
|
|
121
|
+
"""Extract frequency dict from data, handling multiple formats.
|
|
122
|
+
|
|
123
|
+
Supports:
|
|
124
|
+
1. Grouped format: {'w': [series1, series2, ...], 'm': [series3, ...]}
|
|
125
|
+
2. Individual format: {'series1': 'w', 'series2': 'm', ...}
|
|
126
|
+
3. Legacy format: {'series': [{'series_id': ..., 'frequency': ...}, ...]}
|
|
122
127
|
|
|
123
128
|
Parameters
|
|
124
129
|
----------
|
|
@@ -132,12 +137,63 @@ def _extract_frequency_dict(
|
|
|
132
137
|
Optional[Dict[str, str]]
|
|
133
138
|
Frequency dict mapping column names to frequencies, or None if not found
|
|
134
139
|
"""
|
|
135
|
-
if 'frequency' in data:
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
if 'frequency' not in data:
|
|
141
|
+
# Legacy: check for 'series' key
|
|
142
|
+
if 'series' in data:
|
|
143
|
+
return _convert_series_to_frequency_dict(data['series'], clock)
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
freq_data = data['frequency']
|
|
147
|
+
|
|
148
|
+
# Check if it's in grouped format: {'w': [...], 'm': [...]}
|
|
149
|
+
if _is_dict_like(freq_data):
|
|
150
|
+
from .constants import VALID_FREQUENCIES
|
|
151
|
+
|
|
152
|
+
# Check if all keys are valid frequency codes (grouped format)
|
|
153
|
+
keys_are_frequencies = all(
|
|
154
|
+
isinstance(k, str) and k in VALID_FREQUENCIES
|
|
155
|
+
for k in freq_data.keys()
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Check if values are lists (grouped format)
|
|
159
|
+
values_are_lists = all(
|
|
160
|
+
isinstance(v, (list, tuple)) for v in freq_data.values()
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if keys_are_frequencies and values_are_lists:
|
|
164
|
+
# Grouped format: convert to individual format
|
|
165
|
+
result = {}
|
|
166
|
+
for freq, series_list in freq_data.items():
|
|
167
|
+
if not isinstance(series_list, (list, tuple)):
|
|
168
|
+
_raise_config_error(
|
|
169
|
+
f"Frequency '{freq}' must map to a list of series names, got {type(series_list)}"
|
|
170
|
+
)
|
|
171
|
+
for series_name in series_list:
|
|
172
|
+
if not isinstance(series_name, str):
|
|
173
|
+
_raise_config_error(
|
|
174
|
+
f"Series names must be strings, got {type(series_name)} in frequency '{freq}'"
|
|
175
|
+
)
|
|
176
|
+
if series_name in result:
|
|
177
|
+
_raise_config_error(
|
|
178
|
+
f"Series '{series_name}' appears in multiple frequency groups"
|
|
179
|
+
)
|
|
180
|
+
result[series_name] = freq
|
|
181
|
+
return result
|
|
182
|
+
else:
|
|
183
|
+
# Individual format: already in correct format, validate structure
|
|
184
|
+
result = {}
|
|
185
|
+
for series_name, freq in freq_data.items():
|
|
186
|
+
if not isinstance(series_name, str):
|
|
187
|
+
_raise_config_error(
|
|
188
|
+
f"Frequency dict keys must be strings (series names), got {type(series_name)}"
|
|
189
|
+
)
|
|
190
|
+
if not isinstance(freq, str):
|
|
191
|
+
_raise_config_error(
|
|
192
|
+
f"Frequency dict values must be strings (frequency codes), got {type(freq)} for series '{series_name}'"
|
|
193
|
+
)
|
|
194
|
+
result[series_name] = freq
|
|
195
|
+
return result
|
|
196
|
+
|
|
141
197
|
return None
|
|
142
198
|
|
|
143
199
|
|
|
@@ -496,6 +552,11 @@ def detect_config_type(data: Dict[str, Any]) -> str:
|
|
|
496
552
|
if model_type == MODEL_TYPE_DFM:
|
|
497
553
|
return MODEL_TYPE_DFM
|
|
498
554
|
|
|
555
|
+
# Priority check: blocks parameter indicates DFM (highest priority)
|
|
556
|
+
# DFM is the only model type that uses blocks structure
|
|
557
|
+
if 'blocks' in data and data.get('blocks'):
|
|
558
|
+
return MODEL_TYPE_DFM
|
|
559
|
+
|
|
499
560
|
# Check for model-specific parameters
|
|
500
561
|
keys = set(data.keys())
|
|
501
562
|
if keys & _KDFM_PARAMS:
|
dfm_python/config/constants.py
CHANGED
|
@@ -23,7 +23,7 @@ DEFAULT_MIN_DELTA = 1e-6 # Minimum change for improvement
|
|
|
23
23
|
# ============================================================================
|
|
24
24
|
|
|
25
25
|
# Minimum eigenvalues and variances
|
|
26
|
-
MIN_EIGENVALUE = 1e-
|
|
26
|
+
MIN_EIGENVALUE = 1e-6 # Minimum eigenvalue for positive definite matrices (increased from 1e-8 for better conditioning)
|
|
27
27
|
MIN_DIAGONAL_VARIANCE = 1e-8 # Minimum variance for diagonal elements
|
|
28
28
|
MIN_OBSERVATION_NOISE = 1e-4 # Minimum observation noise for measurement error (used in EM updates)
|
|
29
29
|
DEFAULT_DDFM_OBSERVATION_NOISE = 1e-15 # Default observation noise for DDFM state-space (matches original TensorFlow)
|
|
@@ -33,7 +33,7 @@ MIN_STD_FOR_SCALE_CHECK = 1e-10 # Minimum standard deviation for scale ratio co
|
|
|
33
33
|
DEFAULT_VARIANCE_FALLBACK = 1.0 # Default variance fallback value for numerical stability
|
|
34
34
|
|
|
35
35
|
# Maximum eigenvalues
|
|
36
|
-
MAX_EIGENVALUE =
|
|
36
|
+
MAX_EIGENVALUE = 1e3 # Maximum eigenvalue cap (reduced to 1e3 to improve condition number: 1e3/1e-6 = 1e9)
|
|
37
37
|
|
|
38
38
|
# Eigenvalue stability thresholds
|
|
39
39
|
DEFAULT_EIGENVALUE_MAX_MAGNITUDE = 1.0 # Default maximum eigenvalue magnitude for stability checks
|
|
@@ -217,6 +217,7 @@ DEFAULT_START_DATE = datetime(2000, 1, 1)
|
|
|
217
217
|
# Default window size for DDFM
|
|
218
218
|
DEFAULT_WINDOW_SIZE = 100
|
|
219
219
|
DEFAULT_TENT_KERNEL_SIZE = 5 # Default tent kernel size for slower-frequency series aggregation
|
|
220
|
+
DEFAULT_TENT_KERNEL_REGULARIZATION_MULTIPLIER = 100.0 # Regularization multiplier for tent kernel series (handles ill-conditioning)
|
|
220
221
|
# Note: DEFAULT_BATCH_SIZE is defined above (line 88) as 32 for general neural networks
|
|
221
222
|
# Use DEFAULT_DDFM_WINDOW_SIZE (100) for DDFM-specific window size (time-step batch size)
|
|
222
223
|
|
|
@@ -284,7 +285,7 @@ DEFAULT_PROGRESS_LOG_INTERVAL = 5 # Default progress logging interval for EM al
|
|
|
284
285
|
# Precision Defaults
|
|
285
286
|
# ============================================================================
|
|
286
287
|
|
|
287
|
-
DEFAULT_DTYPE = np.
|
|
288
|
+
DEFAULT_DTYPE = np.float64 # Default numpy dtype for arrays (float64 for better numerical stability)
|
|
288
289
|
|
|
289
290
|
# PyTorch dtype (matches DEFAULT_DTYPE)
|
|
290
291
|
try:
|
|
@@ -386,22 +387,6 @@ FREQUENCY_HIERARCHY: Dict[str, int] = {
|
|
|
386
387
|
# For frequency gaps larger than this, the missing data approach is used instead
|
|
387
388
|
MAX_TENT_SIZE: int = 12
|
|
388
389
|
|
|
389
|
-
# Deterministic tent weights lookup for supported frequency pairs
|
|
390
|
-
# Format: (slower_freq, faster_freq) -> tent_weights_array
|
|
391
|
-
# These weights define how slower-frequency series aggregate clock-frequency factors
|
|
392
|
-
TENT_WEIGHTS_LOOKUP: Dict[Tuple[str, str], np.ndarray] = {
|
|
393
|
-
('q', 'm'): np.array([1, 2, 3, 2, 1]), # 5 periods: quarterly -> monthly
|
|
394
|
-
('sa', 'm'): np.array([1, 2, 3, 4, 3, 2, 1]), # 7 periods: semi-annual -> monthly
|
|
395
|
-
('a', 'm'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: annual -> monthly
|
|
396
|
-
('m', 'w'): np.array([1, 2, 3, 2, 1]), # 5 periods: monthly -> weekly
|
|
397
|
-
('q', 'w'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: quarterly -> weekly
|
|
398
|
-
('sa', 'w'): np.array([1, 2, 3, 4, 3, 2, 1]), # 7 periods: semi-annual -> weekly
|
|
399
|
-
('a', 'w'): np.array([1, 2, 3, 4, 5, 4, 3, 2, 1]), # 9 periods: annual -> weekly
|
|
400
|
-
('sa', 'q'): np.array([1, 2, 1]), # 3 periods: semi-annual -> quarterly
|
|
401
|
-
('a', 'q'): np.array([1, 2, 3, 2, 1]), # 5 periods: annual -> quarterly
|
|
402
|
-
('a', 'sa'): np.array([1, 2, 1]), # 3 periods: annual -> semi-annual
|
|
403
|
-
}
|
|
404
|
-
|
|
405
390
|
# ============================================================================
|
|
406
391
|
# Export all constants
|
|
407
392
|
# ============================================================================
|
|
@@ -518,7 +503,6 @@ __all__ = [
|
|
|
518
503
|
'MIN_VARIABLES',
|
|
519
504
|
'MIN_SHAPE_FOR_AR2',
|
|
520
505
|
'MIN_DDFM_TIME_STEPS',
|
|
521
|
-
'MIN_SHAPE_FOR_AR2',
|
|
522
506
|
'MIN_DDFM_DATASET_SIZE_WARNING',
|
|
523
507
|
'MIN_ITER_FOR_DELTA_COMPUTATION',
|
|
524
508
|
'DEFAULT_MIN_ITER_FOR_CONVERGENCE_CHECK',
|
|
@@ -546,6 +530,7 @@ __all__ = [
|
|
|
546
530
|
'DEFAULT_LOG_INTERVAL',
|
|
547
531
|
'DEFAULT_PROGRESS_LOG_INTERVAL',
|
|
548
532
|
'DEFAULT_TENT_KERNEL_SIZE',
|
|
533
|
+
'DEFAULT_TENT_KERNEL_REGULARIZATION_MULTIPLIER',
|
|
549
534
|
'MAX_ERROR_ITEMS',
|
|
550
535
|
# IRF
|
|
551
536
|
'DEFAULT_IRF_HORIZON',
|
|
@@ -581,6 +566,5 @@ __all__ = [
|
|
|
581
566
|
# Frequency hierarchy and tent kernels
|
|
582
567
|
'FREQUENCY_HIERARCHY',
|
|
583
568
|
'MAX_TENT_SIZE',
|
|
584
|
-
'TENT_WEIGHTS_LOOKUP',
|
|
585
569
|
]
|
|
586
570
|
|
|
@@ -9,15 +9,11 @@ Note: Series are specified via frequency dict mapping column names to frequencie
|
|
|
9
9
|
|
|
10
10
|
from .model import BaseModelConfig, DFMConfig, DDFMConfig
|
|
11
11
|
from .results import BaseResult, DFMResult, DDFMResult
|
|
12
|
-
from .params import
|
|
13
|
-
|
|
14
|
-
# Backward compatibility alias
|
|
15
|
-
FitParams = DFMFitParams
|
|
12
|
+
from .params import DFMStateSpaceParams, DDFMStateSpaceParams
|
|
16
13
|
|
|
17
14
|
__all__ = [
|
|
18
15
|
'BaseModelConfig', 'DFMConfig', 'DDFMConfig',
|
|
19
16
|
'BaseResult', 'DFMResult', 'DDFMResult',
|
|
20
|
-
'
|
|
21
|
-
'FitParams', # Backward compatibility alias
|
|
17
|
+
'DFMStateSpaceParams', 'DDFMStateSpaceParams',
|
|
22
18
|
]
|
|
23
19
|
|
|
@@ -5,8 +5,8 @@ parameters in EM algorithm, replacing long conditional checks with a single
|
|
|
5
5
|
optional parameter.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import Optional, Dict
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Optional, Dict, List
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
12
|
|
|
@@ -26,7 +26,10 @@ class BlockStructure:
|
|
|
26
26
|
p : int
|
|
27
27
|
VAR lag order
|
|
28
28
|
p_plus_one : int
|
|
29
|
-
|
|
29
|
+
State dimension per factor. This is equal to max_lag_size = max(p + 1, tent_kernel_size).
|
|
30
|
+
The name "p_plus_one" is used for backward compatibility with the EM algorithm interface.
|
|
31
|
+
In the model code, this is called "max_lag_size" to more clearly indicate it accounts
|
|
32
|
+
for tent kernel size when tent_kernel_size > p + 1.
|
|
30
33
|
n_clock_freq : int
|
|
31
34
|
Number of clock-frequency series
|
|
32
35
|
idio_indicator : np.ndarray
|
|
@@ -39,6 +42,30 @@ class BlockStructure:
|
|
|
39
42
|
Number of slower-frequency series
|
|
40
43
|
tent_weights_dict : dict, optional
|
|
41
44
|
Dictionary mapping frequency pairs to tent weights
|
|
45
|
+
|
|
46
|
+
Cached indices (computed once, reused across EM iterations):
|
|
47
|
+
_cached_unique_blocks : List[np.ndarray], optional
|
|
48
|
+
Unique block patterns (cached)
|
|
49
|
+
_cached_unique_indices : List[int], optional
|
|
50
|
+
Indices of unique block patterns (cached)
|
|
51
|
+
_cached_bl_idxM : List[np.ndarray], optional
|
|
52
|
+
Clock-frequency factor loadings indices per unique block pattern (cached)
|
|
53
|
+
_cached_bl_idxQ : List[np.ndarray], optional
|
|
54
|
+
Slower-frequency factor loadings indices per unique block pattern (cached)
|
|
55
|
+
_cached_R_con : np.ndarray, optional
|
|
56
|
+
Block diagonal constraint matrix (cached)
|
|
57
|
+
_cached_q_con : np.ndarray, optional
|
|
58
|
+
Constraint vector (cached)
|
|
59
|
+
_cached_total_factor_dim : int, optional
|
|
60
|
+
Total factor state dimension (cached)
|
|
61
|
+
_cached_idio_indicator_M : np.ndarray, optional
|
|
62
|
+
Clock-frequency idiosyncratic component indicator (cached)
|
|
63
|
+
_cached_n_idio_M : int, optional
|
|
64
|
+
Number of clock-frequency idiosyncratic components (cached)
|
|
65
|
+
_cached_c_idio_indicator : np.ndarray, optional
|
|
66
|
+
Cumulative sum of idiosyncratic component indicator (cached)
|
|
67
|
+
_cached_rp1 : int, optional
|
|
68
|
+
Start of idiosyncratic components in state space (cached)
|
|
42
69
|
"""
|
|
43
70
|
blocks: np.ndarray
|
|
44
71
|
r: np.ndarray
|
|
@@ -51,6 +78,20 @@ class BlockStructure:
|
|
|
51
78
|
n_slower_freq: Optional[int] = None
|
|
52
79
|
tent_weights_dict: Optional[Dict[str, np.ndarray]] = None
|
|
53
80
|
|
|
81
|
+
# Cached indices (computed once, reused across EM iterations)
|
|
82
|
+
_cached_unique_blocks: Optional[List[np.ndarray]] = field(default=None, init=False, repr=False)
|
|
83
|
+
_cached_unique_indices: Optional[List[int]] = field(default=None, init=False, repr=False)
|
|
84
|
+
_cached_bl_idxM: Optional[List[np.ndarray]] = field(default=None, init=False, repr=False)
|
|
85
|
+
_cached_bl_idxQ: Optional[List[np.ndarray]] = field(default=None, init=False, repr=False)
|
|
86
|
+
_cached_R_con: Optional[np.ndarray] = field(default=None, init=False, repr=False)
|
|
87
|
+
_cached_q_con: Optional[np.ndarray] = field(default=None, init=False, repr=False)
|
|
88
|
+
_cached_total_factor_dim: Optional[int] = field(default=None, init=False, repr=False)
|
|
89
|
+
_cached_idio_indicator_M: Optional[np.ndarray] = field(default=None, init=False, repr=False)
|
|
90
|
+
_cached_n_idio_M: Optional[int] = field(default=None, init=False, repr=False)
|
|
91
|
+
_cached_c_idio_indicator: Optional[np.ndarray] = field(default=None, init=False, repr=False)
|
|
92
|
+
_cached_rp1: Optional[int] = field(default=None, init=False, repr=False)
|
|
93
|
+
_cached_N: Optional[int] = field(default=None, init=False, repr=False) # N value used for caching
|
|
94
|
+
|
|
54
95
|
def is_valid(self) -> bool:
|
|
55
96
|
"""Check if block structure is valid (all required fields are not None)."""
|
|
56
97
|
return (
|
|
@@ -61,4 +102,23 @@ class BlockStructure:
|
|
|
61
102
|
and self.n_clock_freq is not None
|
|
62
103
|
and self.idio_indicator is not None
|
|
63
104
|
)
|
|
105
|
+
|
|
106
|
+
def has_cached_indices(self) -> bool:
|
|
107
|
+
"""Check if block structure indices are cached."""
|
|
108
|
+
return self._cached_unique_blocks is not None
|
|
109
|
+
|
|
110
|
+
def clear_cache(self) -> None:
|
|
111
|
+
"""Clear all cached indices to ensure clean state for new training runs."""
|
|
112
|
+
self._cached_unique_blocks = None
|
|
113
|
+
self._cached_unique_indices = None
|
|
114
|
+
self._cached_bl_idxM = None
|
|
115
|
+
self._cached_bl_idxQ = None
|
|
116
|
+
self._cached_R_con = None
|
|
117
|
+
self._cached_q_con = None
|
|
118
|
+
self._cached_total_factor_dim = None
|
|
119
|
+
self._cached_idio_indicator_M = None
|
|
120
|
+
self._cached_n_idio_M = None
|
|
121
|
+
self._cached_c_idio_indicator = None
|
|
122
|
+
self._cached_rp1 = None
|
|
123
|
+
self._cached_N = None
|
|
64
124
|
|
|
@@ -91,7 +91,9 @@ class BaseModelConfig:
|
|
|
91
91
|
- Data preprocessing (missing data handling)
|
|
92
92
|
|
|
93
93
|
Series Configuration:
|
|
94
|
-
- Provide `frequency` dict
|
|
94
|
+
- Provide `frequency` dict in one of two formats:
|
|
95
|
+
1. Grouped format: {'w': [series1, series2, ...], 'm': [series3, ...]} (recommended for large configs)
|
|
96
|
+
2. Individual format: {'series1': 'w', 'series2': 'm', ...} (backward compatible)
|
|
95
97
|
- If `frequency` is None, all columns will use `clock` frequency
|
|
96
98
|
- If a column is missing from `frequency` dict, it will use `clock` frequency
|
|
97
99
|
- When data is loaded, missing columns in `frequency` dict are automatically added with `clock` frequency
|
|
@@ -103,7 +105,14 @@ class BaseModelConfig:
|
|
|
103
105
|
|
|
104
106
|
Examples
|
|
105
107
|
--------
|
|
106
|
-
>>> # With
|
|
108
|
+
>>> # With grouped frequency mapping (recommended for large configs)
|
|
109
|
+
>>> config = DFMConfig(
|
|
110
|
+
... frequency={'q': ['gdp'], 'm': ['unemployment', 'interest_rate']},
|
|
111
|
+
... clock='m',
|
|
112
|
+
... blocks={...}
|
|
113
|
+
... )
|
|
114
|
+
>>>
|
|
115
|
+
>>> # With individual frequency mapping (backward compatible)
|
|
107
116
|
>>> config = DFMConfig(
|
|
108
117
|
... frequency={'gdp': 'q', 'unemployment': 'm', 'interest_rate': 'm'},
|
|
109
118
|
... clock='m',
|
|
@@ -277,12 +286,19 @@ class DFMConfig(BaseModelConfig):
|
|
|
277
286
|
# Damped Updates: If provided (not None), automatically enables damping and always warns
|
|
278
287
|
damping_factor: Optional[float] = None # Damping factor (0.8 = 80% new, 20% old). If None, no damping. If provided, damping enabled and warnings always shown.
|
|
279
288
|
|
|
280
|
-
# Idiosyncratic Component Parameters
|
|
289
|
+
# Idiosyncratic Component Parameters
|
|
281
290
|
idio_rho0: float = DEFAULT_IDIO_RHO0 # Initial AR coefficient for idiosyncratic components (default: 0.1)
|
|
282
291
|
idio_min_var: float = MIN_DIAGONAL_VARIANCE # Minimum variance for idiosyncratic innovation covariance (defaults to MIN_DIAGONAL_VARIANCE)
|
|
283
|
-
# Note: augment_idio and augment_idio_slow are auto-detected
|
|
292
|
+
# Note: augment_idio and augment_idio_slow are INTERNALLY auto-detected (not configurable)
|
|
284
293
|
# - If all series use clock frequency: augment_idio=False, augment_idio_slow=False
|
|
285
|
-
# - If mixed frequencies detected: augment_idio=True, augment_idio_slow=True (
|
|
294
|
+
# - If mixed frequencies detected: augment_idio=True, augment_idio_slow=True (required for tent kernel)
|
|
295
|
+
# These are properties, not config fields - automatically determined from frequency configuration
|
|
296
|
+
|
|
297
|
+
# Kalman Filter Type
|
|
298
|
+
use_cholesky_filter: bool = False # Use CholeskyKalmanFilter for better numerical stability (default: False)
|
|
299
|
+
|
|
300
|
+
# Tent Kernel Weights (for mixed-frequency aggregation)
|
|
301
|
+
tent_weights: Optional[Union[Dict[str, List[float]], Dict[str, np.ndarray]]] = None # Required for mixed-frequency data: tent weights for frequency pairs. Format: {'freq_pair': [weights]} or {'freq': [weights]}. Example: {'m:w': [1, 2, 1]} or {'m': [1, 2, 1]}. Must be specified in config for all slower-frequency pairs.
|
|
286
302
|
|
|
287
303
|
def __post_init__(self):
|
|
288
304
|
"""Validate blocks structure and derive block properties."""
|
|
@@ -437,6 +453,8 @@ class DFMConfig(BaseModelConfig):
|
|
|
437
453
|
'data_clip': data.get('data_clip', None),
|
|
438
454
|
'regularization': data.get('regularization', None),
|
|
439
455
|
'damping_factor': data.get('damping_factor', None),
|
|
456
|
+
'use_cholesky_filter': data.get('use_cholesky_filter', False),
|
|
457
|
+
'tent_weights': data.get('tent_weights', None),
|
|
440
458
|
}
|
|
441
459
|
|
|
442
460
|
result = base_params.copy()
|
|
@@ -515,6 +533,11 @@ class DDFMConfig(BaseModelConfig):
|
|
|
515
533
|
seed: Optional[int] = None # Random seed for reproducibility
|
|
516
534
|
lags_input: int = 0 # Number of lags of inputs on encoder (default 0, matching original TensorFlow DDFM)
|
|
517
535
|
|
|
536
|
+
# Interpolation parameters (for handling missing values)
|
|
537
|
+
interpolation_method: str = 'linear' # Interpolation method: 'linear', 'spline', 'cubic', etc. (default: 'linear' for stability)
|
|
538
|
+
interpolation_limit: Optional[int] = 10 # Maximum consecutive NaNs to interpolate (default: 10, prevents extreme extrapolation)
|
|
539
|
+
interpolation_limit_direction: str = 'both' # Direction to fill: 'forward', 'backward', or 'both' (default: 'both')
|
|
540
|
+
|
|
518
541
|
|
|
519
542
|
# ========================================================================
|
|
520
543
|
# Factory Methods
|
|
@@ -541,6 +564,9 @@ class DDFMConfig(BaseModelConfig):
|
|
|
541
564
|
'disp': DEFAULT_DISP,
|
|
542
565
|
'seed': None,
|
|
543
566
|
'lags_input': 0, # Number of lags (default 0, matching original TensorFlow)
|
|
567
|
+
'interpolation_method': 'linear', # Interpolation method (default: 'linear' for stability)
|
|
568
|
+
'interpolation_limit': 10, # Maximum consecutive NaNs to interpolate (default: 10)
|
|
569
|
+
'interpolation_limit_direction': 'both', # Direction to fill (default: 'both')
|
|
544
570
|
})
|
|
545
571
|
# Map 'epochs' from config to 'n_mc_samples' for clarity (backward compatibility)
|
|
546
572
|
# Always remove 'epochs' if present (even if n_mc_samples is also present)
|