dfm-python 0.3.1__py3-none-any.whl → 0.4.0__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.
Files changed (67) hide show
  1. dfm_python/__init__.py +103 -60
  2. dfm_python/config/__init__.py +36 -6
  3. dfm_python/config/params.py +202 -0
  4. dfm_python/{core → config}/results.py +96 -71
  5. dfm_python/config/schema.py +260 -331
  6. dfm_python/{core → config}/structure.py +1 -1
  7. dfm_python/config/utils.py +93 -0
  8. dfm_python/encoder/__init__.py +33 -0
  9. dfm_python/encoder/base.py +0 -0
  10. dfm_python/encoder/pca.py +157 -0
  11. dfm_python/encoder/vae.py +291 -0
  12. dfm_python/lightning/__init__.py +48 -0
  13. dfm_python/lightning/callbacks.py +0 -0
  14. dfm_python/lightning/data_module.py +364 -0
  15. dfm_python/lightning/ddfm_module.py +553 -0
  16. dfm_python/lightning/dfm_module.py +466 -0
  17. dfm_python/lightning/utils.py +0 -0
  18. dfm_python/logger/__init__.py +51 -0
  19. dfm_python/logger/inference_logger.py +270 -0
  20. dfm_python/logger/logger.py +110 -0
  21. dfm_python/logger/train_logger.py +309 -0
  22. dfm_python/models/__init__.py +21 -4
  23. dfm_python/models/base.py +24 -28
  24. dfm_python/models/ddfm.py +862 -720
  25. dfm_python/models/dfm.py +698 -101
  26. dfm_python/models/estimation.py +352 -0
  27. dfm_python/nowcast/__init__.py +28 -0
  28. dfm_python/nowcast/dataview.py +143 -0
  29. dfm_python/nowcast/helpers.py +554 -0
  30. dfm_python/{nowcasting → nowcast}/nowcast.py +57 -536
  31. dfm_python/nowcast/utils.py +290 -0
  32. dfm_python/ssm/__init__.py +47 -0
  33. dfm_python/ssm/em.py +488 -0
  34. dfm_python/ssm/kalman.py +604 -0
  35. dfm_python/ssm/utils.py +379 -0
  36. dfm_python/trainer/__init__.py +20 -0
  37. dfm_python/trainer/ddfm.py +196 -0
  38. dfm_python/trainer/dfm.py +153 -0
  39. dfm_python/transformations/__init__.py +35 -0
  40. dfm_python/transformations/sktime.py +40 -0
  41. dfm_python/transformations/transformers.py +320 -0
  42. dfm_python/transformations/utils.py +339 -0
  43. dfm_python/utils/__init__.py +189 -26
  44. dfm_python/utils/data.py +706 -0
  45. dfm_python/{core → utils}/diagnostics.py +11 -87
  46. dfm_python/utils/helpers.py +533 -0
  47. dfm_python/utils/statespace.py +1326 -0
  48. dfm_python/{core → utils}/time.py +373 -0
  49. {dfm_python-0.3.1.dist-info → dfm_python-0.4.0.dist-info}/METADATA +11 -9
  50. dfm_python-0.4.0.dist-info/RECORD +53 -0
  51. dfm_python/api/__init__.py +0 -44
  52. dfm_python/api/model_api.py +0 -1148
  53. dfm_python/api/session_api.py +0 -402
  54. dfm_python/core/__init__.py +0 -139
  55. dfm_python/core/em.py +0 -1385
  56. dfm_python/core/estimation.py +0 -553
  57. dfm_python/core/helpers.py +0 -1338
  58. dfm_python/core/state_space.py +0 -993
  59. dfm_python/dataloader/__init__.py +0 -30
  60. dfm_python/dataloader/loader.py +0 -763
  61. dfm_python/dataloader/preprocess.py +0 -11
  62. dfm_python/nowcasting/__init__.py +0 -13
  63. dfm_python/utils/convert.py +0 -342
  64. dfm_python/utils/loss.py +0 -184
  65. dfm_python-0.3.1.dist-info/RECORD +0 -32
  66. {dfm_python-0.3.1.dist-info → dfm_python-0.4.0.dist-info}/WHEEL +0 -0
  67. {dfm_python-0.3.1.dist-info → dfm_python-0.4.0.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py CHANGED
@@ -26,23 +26,39 @@ Key Features:
26
26
 
27
27
  Example (High-level API - Recommended):
28
28
  >>> import dfm_python as dfm
29
+ >>> from dfm_python.lightning import DFMDataModule
30
+ >>> from sktime.transformations.compose import ColumnTransformer
31
+ >>>
29
32
  >>> # Linear DFM
30
33
  >>> model = dfm.DFM()
31
34
  >>> model.load_config('config/default.yaml')
32
- >>> model.load_data('data/sample_data.csv')
33
- >>> model.train(max_iter=100)
35
+ >>>
36
+ >>> # Create transformer (user must provide)
37
+ >>> transformer = ColumnTransformer([...]) # User-defined sktime transformer
38
+ >>>
39
+ >>> # Create DataModule
40
+ >>> data_module = DFMDataModule(config=model.config, transformer=transformer, data_path='data/sample_data.csv')
41
+ >>> data_module.setup()
42
+ >>>
43
+ >>> # Train
44
+ >>> model.train(data_module, max_iter=100)
34
45
  >>> Xf, Zf = model.predict(horizon=6)
35
46
  >>>
36
47
  >>> # Or use DDFM (separate class)
37
48
  >>> ddfm_model = dfm.DDFM(encoder_layers=[64, 32], num_factors=2)
38
49
  >>> ddfm_model.load_config('config/default.yaml')
39
- >>> ddfm_model.load_data('data/sample_data.csv')
40
- >>> ddfm_model.train(epochs=100)
50
+ >>>
51
+ >>> # Create DataModule for DDFM
52
+ >>> data_module_ddfm = DFMDataModule(config=ddfm_model.config, transformer=transformer, data_path='data/sample_data.csv')
53
+ >>> data_module_ddfm.setup()
54
+ >>>
55
+ >>> # Train
56
+ >>> ddfm_model.train(data_module_ddfm, epochs=100)
41
57
  >>> Xf, Zf = ddfm_model.predict(horizon=6)
42
58
 
43
59
  Example (Low-level API - For advanced usage):
44
60
  >>> from dfm_python import DFM, DFMConfig, SeriesConfig
45
- >>> from dfm_python.dataloader import load_data # Preferred import
61
+ >>> from dfm_python.transformations.utils import read_data # Preferred import
46
62
  >>> # Option 1: Load from YAML
47
63
  >>> config = load_config('config.yaml')
48
64
  >>> # Option 2: Create directly
@@ -50,15 +66,25 @@ Example (Low-level API - For advanced usage):
50
66
  ... series=[SeriesConfig(frequency='m', transformation='lin', blocks=[1], series_id='series1')],
51
67
  ... block_names=['Global']
52
68
  ... )
53
- >>> X, Time, _ = load_data('data.csv', config) # or use database adapter
69
+ >>> from dfm_python.lightning import DFMDataModule
70
+ >>> from sktime.transformations.compose import ColumnTransformer
71
+ >>>
72
+ >>> # Create transformer (user must provide)
73
+ >>> transformer = ColumnTransformer([...]) # User-defined
74
+ >>>
75
+ >>> # Create DataModule
76
+ >>> data_module = DFMDataModule(config=config, transformer=transformer, data_path='data.csv')
77
+ >>> data_module.setup()
78
+ >>>
79
+ >>> # Fit model
54
80
  >>> model = DFM()
55
- >>> result = model.fit(X, config)
81
+ >>> result = model.fit(data_module, config)
56
82
  >>> factors = result.Z # Extract estimated factors
57
83
 
58
84
  For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
59
85
  """
60
86
 
61
- __version__ = "0.3.1"
87
+ __version__ = "0.4.0"
62
88
 
63
89
  # ============================================================================
64
90
  # PUBLIC API DEFINITION
@@ -84,45 +110,61 @@ from .config import (
84
110
  )
85
111
 
86
112
  # Data utilities
87
- from .dataloader import transform_data
113
+ # Note: transform_data and DFMScaler have been removed - users must provide their own sktime transformers to DFMDataModule
88
114
 
89
115
  # Results
90
- from .core import DFMResult
116
+ from .config.results import DFMResult, DDFMResult, BaseResult
91
117
 
92
- # Core utilities (from core/ subpackage)
93
- from .core import calculate_rmse, diagnose_series, print_series_diagnosis
94
- from .core.state_space import run_kf, skf, fis, miss_data
95
- # DFMCore is now an alias for DFMLinear (backward compatibility)
118
+ # Utilities (from utils/ subpackage)
119
+ from .utils.diagnostics import diagnose_series, print_series_diagnosis
120
+ from .utils.time import calculate_rmse
121
+ # DFMCore is an alias for DFMLinear (backward compatibility)
96
122
  from .models.dfm import DFMLinear
97
123
  DFMCore = DFMLinear
98
124
 
99
- # Nowcasting (already in nowcasting/ subpackage)
100
- from .nowcasting import (
125
+ # PyTorch Lightning modules (recommended)
126
+ try:
127
+ from .lightning import (
128
+ DFMLightningModule,
129
+ DDFMLightningModule,
130
+ DFMDataModule,
131
+ KalmanFilter, # New module class
132
+ EMAlgorithm, # New module class
133
+ )
134
+ _has_lightning = True
135
+ except ImportError:
136
+ _has_lightning = False
137
+ DFMLightningModule = None # type: ignore
138
+ DDFMLightningModule = None # type: ignore
139
+ DFMDataModule = None # type: ignore
140
+ KalmanFilter = None # type: ignore
141
+ EMAlgorithm = None # type: ignore
142
+
143
+ # Nowcasting (from nowcast/ subpackage)
144
+ from .nowcast import (
101
145
  Nowcast,
102
- para_const,
103
146
  NowcastResult,
147
+ )
148
+ from .nowcast.helpers import (
149
+ para_const,
104
150
  NewsDecompResult,
105
151
  BacktestResult,
106
152
  )
107
153
 
108
- # High-level API (from api/ subpackage)
109
- from .api import (
110
- DFM, _dfm_instance, from_yaml, from_spec, from_spec_df, from_dict,
111
- load_config, load_data, load_pickle, train, predict, plot, reset, create_model
112
- )
113
-
114
154
  # Model implementations
115
155
  from .models.base import BaseFactorModel
116
- from .models.dfm import DFMLinear
156
+ from .models.dfm import DFMLinear, DFM
157
+ from .models.dfm import (
158
+ from_yaml, from_spec, from_spec_df, from_dict,
159
+ load_config, load_pickle, train, predict, plot, reset, create_model
160
+ )
161
+ # Note: load_data has been removed - use DFMDataModule instead
117
162
 
118
163
  # DDFM high-level API and low-level model (both optional, requires PyTorch)
119
164
  try:
120
- from .api import DDFM as DDFMAPI, _ddfm_instance
121
- from .api import load_config_ddfm, load_data_ddfm, train_ddfm, predict_ddfm, plot_ddfm, reset_ddfm
122
- from .models.ddfm import DDFM as DDFMModel
165
+ from .models.ddfm import DDFM, DDFMModel
166
+ from .models.ddfm import load_config_ddfm, load_data_ddfm, train_ddfm, predict_ddfm, plot_ddfm, reset_ddfm
123
167
  _has_ddfm = True
124
- # Export high-level API as DDFM
125
- DDFM = DDFMAPI
126
168
  except ImportError:
127
169
  _has_ddfm = False
128
170
  DDFM = None # type: ignore
@@ -133,30 +175,7 @@ except ImportError:
133
175
  predict_ddfm = None # type: ignore
134
176
  plot_ddfm = None # type: ignore
135
177
  reset_ddfm = None # type: ignore
136
-
137
- # Expose properties as module-level attributes
138
- # Use property-like access via functions or direct attribute access
139
- # Since 'config' conflicts with the config module, we'll use a different approach
140
- # Users can access: dfm.get_config(), dfm.get_data(), dfm.get_result()
141
- def get_config():
142
- """Get current configuration."""
143
- return _dfm_instance.config
144
-
145
- def get_data():
146
- """Get current data matrix."""
147
- return _dfm_instance.data
148
-
149
- def get_time():
150
- """Get current time index."""
151
- return _dfm_instance.time
152
-
153
- def get_result():
154
- """Get training result."""
155
- return _dfm_instance.result
156
-
157
- def get_original_data():
158
- """Get original (untransformed) data matrix."""
159
- return _dfm_instance.original_data
178
+ # DDFM module-level functions removed - create DDFM() instance directly
160
179
 
161
180
  __all__ = [
162
181
  # Core classes
@@ -172,22 +191,46 @@ __all__ = [
172
191
  'MergedConfigSource', 'make_config_source',
173
192
  # High-level API (module-level - recommended)
174
193
  'load_config',
175
- 'load_data', 'load_pickle', 'train', 'predict', 'plot', 'reset', 'create_model',
176
- 'get_config', 'get_data', 'get_time', 'get_result', 'get_original_data',
194
+ 'load_pickle', 'train', 'predict', 'plot', 'reset', 'create_model',
177
195
  # Convenience constructors (cleaner API)
178
196
  'from_yaml', 'from_spec', 'from_spec_df', 'from_dict',
179
197
  # Low-level API (functional interface - advanced usage)
180
- 'transform_data',
181
- 'DFMResult', 'calculate_rmse', 'diagnose_series', 'print_series_diagnosis',
182
- 'run_kf', 'skf', 'fis', 'miss_data',
183
- 'para_const', # Internal utility, kept for backward compatibility
198
+ 'BaseResult', 'DFMResult', 'DDFMResult', 'calculate_rmse', 'diagnose_series', 'print_series_diagnosis',
199
+ 'para_const', # Internal utility for nowcasting
184
200
  ]
185
201
 
186
202
  # Add DDFM high-level API and convenience functions if available
187
203
  if _has_ddfm:
188
204
  __all__.extend([
189
205
  'DDFM', # High-level API class
190
- 'load_config_ddfm', 'load_data_ddfm', 'train_ddfm',
206
+ 'DDFMModel', # Low-level implementation
207
+ 'load_config_ddfm', 'train_ddfm',
191
208
  'predict_ddfm', 'plot_ddfm', 'reset_ddfm'
209
+ # Note: load_data_ddfm has been removed - use DFMDataModule instead
210
+ ])
211
+
212
+ # Add Lightning modules if available
213
+ if _has_lightning:
214
+ __all__.extend([
215
+ 'DFMLightningModule',
216
+ 'DDFMLightningModule',
217
+ 'DFMDataModule',
218
+ 'KalmanFilter', # New module class
219
+ 'EMAlgorithm', # New module class
220
+ ])
221
+
222
+ # Add Trainer classes if available
223
+ try:
224
+ from .trainer import DFMTrainer, DDFMTrainer
225
+ _has_trainers = True
226
+ except ImportError:
227
+ _has_trainers = False
228
+ DFMTrainer = None # type: ignore
229
+ DDFMTrainer = None # type: ignore
230
+
231
+ if _has_trainers:
232
+ __all__.extend([
233
+ 'DFMTrainer',
234
+ 'DDFMTrainer',
192
235
  ])
193
236
 
@@ -6,10 +6,12 @@ This subpackage provides:
6
6
  """
7
7
 
8
8
  from .schema import (
9
- DFMConfig, SeriesConfig, BlockConfig, Params,
9
+ BaseModelConfig, DFMConfig, DDFMConfig, SeriesConfig, BlockConfig,
10
10
  DEFAULT_GLOBAL_BLOCK_NAME,
11
- validate_frequency, validate_transformation,
12
11
  )
12
+ from .params import Params, FitParams
13
+ from .results import BaseResult, DFMResult, DDFMResult, DFMParams
14
+ from .utils import validate_frequency, validate_transformation
13
15
  from .io import (
14
16
  ConfigSource,
15
17
  YamlSource,
@@ -18,18 +20,46 @@ from .io import (
18
20
  MergedConfigSource,
19
21
  make_config_source,
20
22
  from_spec,
21
- _load_config_from_dataframe,
22
- _write_series_blocks_yaml,
23
+ # Note: Private functions (_load_config_from_dataframe, _write_series_blocks_yaml)
24
+ # are available from io module but not exported as public API
25
+ )
26
+ from .structure import (
27
+ FREQUENCY_HIERARCHY,
28
+ PERIODS_PER_YEAR,
29
+ get_periods_per_year,
30
+ compute_idio_chain_lengths,
31
+ get_tent_weights_for_pair,
32
+ generate_tent_weights,
33
+ generate_R_mat,
34
+ get_aggregation_structure,
35
+ group_series_by_frequency,
23
36
  )
24
37
 
25
38
  __all__ = [
26
39
  # Schema
27
- 'DFMConfig', 'SeriesConfig', 'BlockConfig', 'Params',
40
+ 'BaseModelConfig', 'DFMConfig', 'DDFMConfig', 'SeriesConfig', 'BlockConfig',
28
41
  'DEFAULT_GLOBAL_BLOCK_NAME',
42
+ # Parameters
43
+ 'Params', 'FitParams',
44
+ # Results
45
+ 'BaseResult', 'DFMResult', 'DDFMResult', 'DFMParams',
46
+ # Utilities
29
47
  'validate_frequency', 'validate_transformation',
30
48
  # IO
31
49
  'ConfigSource', 'YamlSource', 'DictSource',
32
50
  'HydraSource', 'MergedConfigSource', 'make_config_source',
33
- 'from_spec', '_load_config_from_dataframe', '_write_series_blocks_yaml',
51
+ 'from_spec',
52
+ # Structure utilities
53
+ 'FREQUENCY_HIERARCHY',
54
+ 'PERIODS_PER_YEAR',
55
+ 'get_periods_per_year',
56
+ 'compute_idio_chain_lengths',
57
+ 'get_tent_weights_for_pair',
58
+ 'generate_tent_weights',
59
+ 'generate_R_mat',
60
+ 'get_aggregation_structure',
61
+ 'group_series_by_frequency',
62
+ # Note: Private functions (_load_config_from_dataframe, _write_series_blocks_yaml)
63
+ # are available from io module but not exported as public API
34
64
  ]
35
65
 
@@ -0,0 +1,202 @@
1
+ """Parameter dataclasses for DFM models.
2
+
3
+ This module contains parameter configuration classes:
4
+ - Params: Global estimation parameters with defaults
5
+ - FitParams: Optional parameter overrides for model fitting
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Optional
10
+ from .utils import validate_frequency
11
+
12
+
13
+ @dataclass
14
+ class Params:
15
+ """Global estimation parameters for DFM (main settings only).
16
+
17
+ This dataclass contains only the global knobs that control the EM algorithm
18
+ and numerical stability. It does NOT include series or block definitions.
19
+ Use this with spec CSV files to provide main settings while the spec CSV
20
+ defines all series and their block memberships.
21
+
22
+ All parameters have defaults matching the standard YAML configuration.
23
+
24
+ Attributes
25
+ ----------
26
+ ar_lag : int
27
+ AR lag for factor transition equation (typically 1)
28
+ threshold : float
29
+ EM convergence threshold (default: 1e-5)
30
+ max_iter : int
31
+ Maximum EM iterations (default: 5000)
32
+ nan_method : int
33
+ Missing data handling method (1-5, default: 2 = spline interpolation).
34
+ This preprocessing step is followed by Kalman Filter-based missing data
35
+ handling during DFM estimation, following standard practice in state-space
36
+ models (Mariano & Murasawa 2003, Harvey 1989).
37
+ nan_k : int
38
+ Spline parameter for NaN interpolation (default: 3 for cubic spline)
39
+ clock : str
40
+ Base frequency for all latent factors (default: 'm' for monthly)
41
+ clip_ar_coefficients : bool
42
+ Enable AR coefficient clipping for stationarity (default: True)
43
+ ar_clip_min : float
44
+ Minimum AR coefficient (default: -0.99)
45
+ ar_clip_max : float
46
+ Maximum AR coefficient (default: 0.99)
47
+ warn_on_ar_clip : bool
48
+ Warn when AR coefficients are clipped (default: True)
49
+ clip_data_values : bool
50
+ Enable clipping of extreme data values (default: True)
51
+ data_clip_threshold : float
52
+ Clip values beyond this many standard deviations (default: 100.0)
53
+ warn_on_data_clip : bool
54
+ Warn when data values are clipped (default: True)
55
+ use_regularization : bool
56
+ Enable regularization for numerical stability (default: True)
57
+ regularization_scale : float
58
+ Scale factor for ridge regularization (default: 1e-5)
59
+ min_eigenvalue : float
60
+ Minimum eigenvalue for positive definite matrices (default: 1e-8)
61
+ max_eigenvalue : float
62
+ Maximum eigenvalue cap (default: 1e6)
63
+ warn_on_regularization : bool
64
+ Warn when regularization is applied (default: True)
65
+ use_damped_updates : bool
66
+ Enable damped updates when likelihood decreases (default: True)
67
+ damping_factor : float
68
+ Damping factor: 0.8 = 80% new, 20% old (default: 0.8)
69
+ warn_on_damped_update : bool
70
+ Warn when damped updates are used (default: True)
71
+ augment_idio : bool
72
+ Enable state augmentation with idiosyncratic components (default: True)
73
+ augment_idio_slow : bool
74
+ Enable tent-length chains for slower-frequency series (default: True)
75
+ idio_rho0 : float
76
+ Initial AR coefficient for idiosyncratic components (default: 0.1)
77
+ idio_min_var : float
78
+ Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
79
+
80
+ Examples
81
+ --------
82
+ >>> from dfm_python.config import Params
83
+ >>> params = Params(max_iter=100, threshold=1e-4)
84
+ >>> dfm.from_spec('data/spec.csv', params=params)
85
+ """
86
+ # Estimation parameters
87
+ ar_lag: int = 1
88
+ threshold: float = 1e-5
89
+ max_iter: int = 5000
90
+ nan_method: int = 2
91
+ nan_k: int = 3
92
+ clock: str = 'm'
93
+
94
+ # Numerical stability - AR clipping
95
+ clip_ar_coefficients: bool = True
96
+ ar_clip_min: float = -0.99
97
+ ar_clip_max: float = 0.99
98
+ warn_on_ar_clip: bool = True
99
+
100
+ # Numerical stability - Data clipping
101
+ clip_data_values: bool = True
102
+ data_clip_threshold: float = 100.0
103
+ warn_on_data_clip: bool = True
104
+
105
+ # Numerical stability - Regularization
106
+ use_regularization: bool = True
107
+ regularization_scale: float = 1e-5
108
+ min_eigenvalue: float = 1e-8
109
+ max_eigenvalue: float = 1e6
110
+ warn_on_regularization: bool = True
111
+
112
+ # Numerical stability - Damped updates
113
+ use_damped_updates: bool = True
114
+ damping_factor: float = 0.8
115
+ warn_on_damped_update: bool = True
116
+
117
+ # Idiosyncratic component augmentation
118
+ augment_idio: bool = True # Enable state augmentation with idiosyncratic components (default: True)
119
+ augment_idio_slow: bool = True # Enable tent-length chains for slower-frequency series (default: True)
120
+ idio_rho0: float = 0.1 # Initial AR coefficient for idiosyncratic components (default: 0.1)
121
+ idio_min_var: float = 1e-8 # Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
122
+
123
+ def __post_init__(self):
124
+ """Validate parameters."""
125
+ self.clock = validate_frequency(self.clock)
126
+ if self.threshold <= 0:
127
+ raise ValueError(f"threshold must be positive, got {self.threshold}")
128
+ if self.max_iter < 1:
129
+ raise ValueError(f"max_iter must be at least 1, got {self.max_iter}")
130
+ if self.ar_clip_min >= self.ar_clip_max:
131
+ raise ValueError(f"ar_clip_min ({self.ar_clip_min}) must be < ar_clip_max ({self.ar_clip_max})")
132
+ if not (-1 < self.ar_clip_min < 1):
133
+ raise ValueError(f"ar_clip_min must be in (-1, 1), got {self.ar_clip_min}")
134
+ if not (-1 < self.ar_clip_max < 1):
135
+ raise ValueError(f"ar_clip_max must be in (-1, 1), got {self.ar_clip_max}")
136
+
137
+
138
+ @dataclass
139
+ class FitParams:
140
+ """Parameters for fitting DFM models.
141
+
142
+ This dataclass groups all optional parameters that can override
143
+ config values during model fitting. This reduces method signature
144
+ complexity and improves code readability.
145
+
146
+ All parameters are optional. If None, the corresponding value
147
+ from DFMConfig will be used during parameter resolution.
148
+ """
149
+ # Convergence parameters
150
+ threshold: Optional[float] = None
151
+ max_iter: Optional[int] = None
152
+
153
+ # Model structure
154
+ ar_lag: Optional[int] = None
155
+ num_factors: Optional[int] = None
156
+
157
+ # Missing data handling
158
+ nan_method: Optional[int] = None
159
+ nan_k: Optional[int] = None
160
+
161
+ # AR coefficient clipping
162
+ clip_ar_coefficients: Optional[bool] = None
163
+ ar_clip_min: Optional[float] = None
164
+ ar_clip_max: Optional[float] = None
165
+
166
+ # Data clipping
167
+ clip_data_values: Optional[bool] = None
168
+ data_clip_threshold: Optional[float] = None
169
+
170
+ # Regularization
171
+ use_regularization: Optional[bool] = None
172
+ regularization_scale: Optional[float] = None
173
+ min_eigenvalue: Optional[float] = None
174
+ max_eigenvalue: Optional[float] = None
175
+
176
+ # Damping
177
+ use_damped_updates: Optional[bool] = None
178
+ damping_factor: Optional[float] = None
179
+
180
+ def to_dict(self) -> dict:
181
+ """Convert to dictionary, excluding None values."""
182
+ return {k: v for k, v in self.__dict__.items() if v is not None}
183
+
184
+ @classmethod
185
+ def from_kwargs(cls, **kwargs) -> 'FitParams':
186
+ """Create FitParams from keyword arguments.
187
+
188
+ Filters kwargs to only include valid parameter names,
189
+ ignoring any extra arguments.
190
+ """
191
+ valid_params = {
192
+ 'threshold', 'max_iter', 'ar_lag', 'num_factors',
193
+ 'nan_method', 'nan_k',
194
+ 'clip_ar_coefficients', 'ar_clip_min', 'ar_clip_max',
195
+ 'clip_data_values', 'data_clip_threshold',
196
+ 'use_regularization', 'regularization_scale',
197
+ 'min_eigenvalue', 'max_eigenvalue',
198
+ 'use_damped_updates', 'damping_factor'
199
+ }
200
+ filtered = {k: v for k, v in kwargs.items() if k in valid_params}
201
+ return cls(**filtered)
202
+