dfm-python 0.5.59__py3-none-any.whl → 0.5.60__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 CHANGED
@@ -32,7 +32,9 @@ Example:
32
32
  >>> df_processed = df[[col for col in df.columns if col != 'date']]
33
33
  >>>
34
34
  >>> # Step 2: Create Dataset
35
- >>> dataset = DFMDataset(config_path='config/default.yaml', data=df_processed)
35
+ >>> from dfm_python.config import DFMConfig
36
+ >>> config = DFMConfig.from_yaml('config/default.yaml')
37
+ >>> dataset = DFMDataset(config=config, data=df_processed)
36
38
  >>>
37
39
  >>> # Step 3: Create model and load config
38
40
  >>> model = DFM()
@@ -61,7 +63,7 @@ Note: DFMConfig uses frequency dict to specify series (column names -> frequenci
61
63
  For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
62
64
  """
63
65
 
64
- __version__ = "0.5.56"
66
+ __version__ = "0.5.60"
65
67
 
66
68
  # ============================================================================
67
69
  # PUBLIC API DEFINITION
@@ -102,7 +104,10 @@ from .models.base import BaseFactorModel
102
104
  from .models.dfm import DFM
103
105
 
104
106
  # DDFM high-level API (PyTorch is mandatory)
105
- from .models.ddfm import DDFM
107
+ try:
108
+ from .models.ddfm.ddfm import DDFM
109
+ except ImportError:
110
+ DDFM = None
106
111
 
107
112
  __all__ = [
108
113
  # Core classes
@@ -81,12 +81,7 @@ from .types import (
81
81
  )
82
82
  # Import lazily to avoid circular dependencies
83
83
  try:
84
- from ..numeric.builder import compute_idio_lengths
85
- except ImportError:
86
- compute_idio_lengths = None
87
-
88
- try:
89
- from ..numeric.tent import get_agg_structure, group_by_freq
84
+ from ..models.dfm.tent import get_agg_structure, group_by_freq
90
85
  except ImportError:
91
86
  get_agg_structure = None
92
87
  group_by_freq = None
@@ -100,7 +95,7 @@ def get_periods_per_year(frequency: str) -> int:
100
95
  __all__ = [
101
96
  # Base classes
102
97
  'BaseModelConfig', 'BaseResult',
103
- # 'DEFAULT_BLOCK_NAME', # Removed to avoid circular import - import directly from functional.dfm_block
98
+ # 'DEFAULT_BLOCK_NAME', # Removed to avoid circular import
104
99
  # Model-specific configs (from schema.py)
105
100
  'DFMConfig', 'DDFMConfig',
106
101
  # State-space parameters
@@ -117,7 +112,6 @@ __all__ = [
117
112
  'FREQUENCY_HIERARCHY',
118
113
  'PERIODS_PER_YEAR',
119
114
  'get_periods_per_year',
120
- 'compute_idio_lengths',
121
115
  'get_agg_structure',
122
116
  'group_by_freq',
123
117
  # Type definitions (from types.py)
@@ -303,7 +303,6 @@ class YamlSource:
303
303
  _EXCLUDED_KEYS = {
304
304
  'defaults', '_target_', '_recursive_', '_convert_',
305
305
  'series', 'blocks', 'data', 'output', 'description', 'name', 'target', 'model',
306
- 'nan_method', 'nan_k' # Internal constants, not user-configurable
307
306
  }
308
307
  main_settings = {k: v for k, v in cfg_dict.items() if k not in _EXCLUDED_KEYS}
309
308
 
@@ -65,8 +65,6 @@ from ..constants import (
65
65
  MIN_EIGENVALUE,
66
66
  MAX_EIGENVALUE,
67
67
  MIN_DIAGONAL_VARIANCE,
68
- DEFAULT_NAN_METHOD,
69
- DEFAULT_NAN_K,
70
68
  DEFAULT_CLOCK_FREQUENCY,
71
69
  DEFAULT_KDFM_AR_ORDER,
72
70
  DEFAULT_KDFM_MA_ORDER,
@@ -138,7 +136,6 @@ class BaseModelConfig:
138
136
  # ========================================================================
139
137
  clock: str = 'm' # Required: Base frequency for latent factors (global clock): 'd', 'w', 'm', 'q', 'sa', 'a' (defaults to 'm' for monthly)
140
138
  target_scaler: Optional[ScalerType] = None # Fitted sklearn scaler instance (StandardScaler, RobustScaler, etc.) for target series only. Must be a fitted scaler object (call .fit() on target data first). Pass scaler object directly, not string. Feature series are assumed to be manually preprocessed. If None, target series are assumed to be already in the desired scale.
141
- # Note: nan_method and nan_k are internal constants (DEFAULT_NAN_METHOD, DEFAULT_NAN_K) used during initialization only
142
139
 
143
140
  def __post_init__(self):
144
141
  """Validate basic model structure.
@@ -294,9 +291,6 @@ class DFMConfig(BaseModelConfig):
294
291
  # - If mixed frequencies detected: augment_idio=True, augment_idio_slow=True (required for tent kernel)
295
292
  # These are properties, not config fields - automatically determined from frequency configuration
296
293
 
297
- # Kalman Filter Type
298
- use_cholesky_filter: bool = False # Use CholeskyKalmanFilter for better numerical stability (default: False)
299
-
300
294
  # Tent Kernel Weights (for mixed-frequency aggregation)
301
295
  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.
302
296
 
@@ -379,7 +373,11 @@ class DFMConfig(BaseModelConfig):
379
373
  object.__setattr__(self, '_augment_idio_slow', is_mixed_freq)
380
374
 
381
375
  def to_em_config(self) -> 'EMConfig':
382
- """Create EMConfig from DFMConfig consolidated parameters."""
376
+ """Create EMConfig from DFMConfig consolidated parameters.
377
+
378
+ Automatically passes all numerical stability parameters (regularization, ar_clip,
379
+ damping_factor, data_clip) from DFMConfig to EMConfig for use in EM algorithm.
380
+ """
383
381
  from ...functional.em import EMConfig
384
382
  from ..constants import DEFAULT_REGULARIZATION, VAR_STABILITY_THRESHOLD
385
383
 
@@ -397,6 +395,10 @@ class DFMConfig(BaseModelConfig):
397
395
  regularization=reg_scale,
398
396
  min_norm=min_eigenval,
399
397
  max_eigenval=max_eigenval,
398
+ # Pass numerical stability parameters from DFMConfig
399
+ ar_clip=self.ar_clip,
400
+ damping_factor=self.damping_factor,
401
+ data_clip=self.data_clip,
400
402
  # Other parameters use defaults from EMConfig
401
403
  )
402
404
 
@@ -453,7 +455,6 @@ class DFMConfig(BaseModelConfig):
453
455
  'data_clip': data.get('data_clip', None),
454
456
  'regularization': data.get('regularization', None),
455
457
  'damping_factor': data.get('damping_factor', None),
456
- 'use_cholesky_filter': data.get('use_cholesky_filter', False),
457
458
  'tent_weights': data.get('tent_weights', None),
458
459
  }
459
460
 
@@ -8,97 +8,12 @@ import numpy as np
8
8
  from typing import Optional, Any
9
9
 
10
10
 
11
- @dataclass
12
- class DFMStateSpaceParams:
13
- """State-space model parameters for DFM (fitted during model training).
14
-
15
- These parameters are computed during DFM.fit() and represent the fitted
16
- state-space model structure. Contains only the state-space parameters,
17
- not training metadata (loglik, num_iter, converged) which are stored
18
- separately in checkpoint dicts.
19
-
20
- Parameters
21
- ----------
22
- A : np.ndarray
23
- Transition matrix (m x m) - VAR dynamics for factors
24
- C : np.ndarray
25
- Observation matrix (N x m) - factor loadings
26
- Q : np.ndarray
27
- Process noise covariance (m x m) - innovation covariance
28
- R : np.ndarray
29
- Observation noise covariance (N x N) - typically diagonal
30
- Z_0 : np.ndarray
31
- Initial state mean (m,) - initial factor values
32
- V_0 : np.ndarray
33
- Initial state covariance (m x m) - initial uncertainty
34
- """
35
- A: np.ndarray # Transition matrix (m x m)
36
- C: np.ndarray # Observation matrix (N x m)
37
- Q: np.ndarray # Process noise covariance (m x m)
38
- R: np.ndarray # Observation noise covariance (N x N)
39
- Z_0: np.ndarray # Initial state mean (m,)
40
- V_0: np.ndarray # Initial state covariance (m x m)
41
-
42
- @classmethod
43
- def from_model(cls, model: Any) -> 'DFMStateSpaceParams':
44
- """Create DFMStateSpaceParams from DFM model instance.
45
-
46
- Parameters
47
- ----------
48
- model : DFM
49
- DFM model instance with fitted parameters
50
-
51
- Returns
52
- -------
53
- DFMStateSpaceParams
54
- State-space parameters dataclass
55
- """
56
- # Get parameters from model (stored after fit)
57
- # First check if training_state is already DFMStateSpaceParams (new format)
58
- if hasattr(model, 'training_state') and model.training_state is not None:
59
- if isinstance(model.training_state, cls):
60
- # Already a DFMStateSpaceParams instance
61
- return model.training_state
62
- # Backward compatibility: If old format training_state (has A, C, Q, R, Z_0, V_0 attributes)
63
- if hasattr(model.training_state, 'A'):
64
- return cls(
65
- A=model.training_state.A,
66
- C=model.training_state.C,
67
- Q=model.training_state.Q,
68
- R=model.training_state.R,
69
- Z_0=model.training_state.Z_0,
70
- V_0=model.training_state.V_0
71
- )
72
-
73
- # Get from model attributes (after _update_parameters is called)
74
- return cls(
75
- A=getattr(model, 'A', None),
76
- C=getattr(model, 'C', None),
77
- Q=getattr(model, 'Q', None),
78
- R=getattr(model, 'R', None),
79
- Z_0=getattr(model, 'Z_0', None),
80
- V_0=getattr(model, 'V_0', None)
81
- )
82
-
83
- def apply_to_model(self, model: Any) -> None:
84
- """Apply state-space parameters to DFM model instance.
85
-
86
- Parameters
87
- ----------
88
- model : DFM
89
- DFM model instance to update
90
- """
91
- model._update_parameters(
92
- self.A, self.C, self.Q, self.R, self.Z_0, self.V_0
93
- )
94
-
95
-
96
11
  @dataclass
97
12
  class DFMModelState:
98
- """DFM model structure and mixed-frequency parameters.
13
+ """DFM model state including structure, mixed-frequency parameters, and state-space parameters.
99
14
 
100
- Consolidates model structure and mixed-frequency state for checkpointing.
101
- This allows save/load methods to be simplified by using a single dataclass.
15
+ Consolidates all DFM model state for checkpointing: model structure, mixed-frequency
16
+ configuration, and fitted state-space parameters.
102
17
 
103
18
  Attributes
104
19
  ----------
@@ -128,6 +43,18 @@ class DFMModelState:
128
43
  Indicator for idiosyncratic components
129
44
  max_lag_size : int, optional
130
45
  Maximum lag size for state dimension
46
+ A : np.ndarray, optional
47
+ Transition matrix (m x m) - VAR dynamics for factors
48
+ C : np.ndarray, optional
49
+ Observation matrix (N x m) - factor loadings
50
+ Q : np.ndarray, optional
51
+ Process noise covariance (m x m) - innovation covariance
52
+ R : np.ndarray, optional
53
+ Observation noise covariance (N x N) - typically diagonal
54
+ Z_0 : np.ndarray, optional
55
+ Initial state mean (m,) - initial factor values
56
+ V_0 : np.ndarray, optional
57
+ Initial state covariance (m x m) - initial uncertainty
131
58
  """
132
59
  num_factors: int
133
60
  r: np.ndarray
@@ -142,6 +69,13 @@ class DFMModelState:
142
69
  frequencies: Optional[np.ndarray] = None
143
70
  idio_indicator: Optional[np.ndarray] = None
144
71
  max_lag_size: Optional[int] = None
72
+ # State-space parameters (fitted during training)
73
+ A: Optional[np.ndarray] = None
74
+ C: Optional[np.ndarray] = None
75
+ Q: Optional[np.ndarray] = None
76
+ R: Optional[np.ndarray] = None
77
+ Z_0: Optional[np.ndarray] = None
78
+ V_0: Optional[np.ndarray] = None
145
79
 
146
80
  @classmethod
147
81
  def from_model(cls, model: Any) -> 'DFMModelState':
@@ -157,6 +91,27 @@ class DFMModelState:
157
91
  DFMModelState
158
92
  Model state dataclass
159
93
  """
94
+ # Get state-space parameters from training_state if available
95
+ A = C = Q = R = Z_0 = V_0 = None
96
+ if hasattr(model, 'training_state') and model.training_state is not None:
97
+ # Handle both old DFMStateSpaceParams format and new DFMModelState format
98
+ ts = model.training_state
99
+ if hasattr(ts, 'A'):
100
+ A = ts.A
101
+ C = ts.C
102
+ Q = ts.Q
103
+ R = ts.R
104
+ Z_0 = ts.Z_0
105
+ V_0 = ts.V_0
106
+ else:
107
+ # Fallback: get from model attributes directly
108
+ A = getattr(model, 'A', None)
109
+ C = getattr(model, 'C', None)
110
+ Q = getattr(model, 'Q', None)
111
+ R = getattr(model, 'R', None)
112
+ Z_0 = getattr(model, 'Z_0', None)
113
+ V_0 = getattr(model, 'V_0', None)
114
+
160
115
  return cls(
161
116
  num_factors=model.num_factors,
162
117
  r=model.r,
@@ -170,7 +125,13 @@ class DFMModelState:
170
125
  tent_weights_dict=getattr(model, '_tent_weights_dict', None),
171
126
  frequencies=getattr(model, '_frequencies', None),
172
127
  idio_indicator=getattr(model, '_idio_indicator', None),
173
- max_lag_size=getattr(model, '_max_lag_size', None)
128
+ max_lag_size=getattr(model, '_max_lag_size', None),
129
+ A=A,
130
+ C=C,
131
+ Q=Q,
132
+ R=R,
133
+ Z_0=Z_0,
134
+ V_0=V_0
174
135
  )
175
136
 
176
137
  def apply_to_model(self, model: Any) -> None:
@@ -194,6 +155,12 @@ class DFMModelState:
194
155
  model._frequencies = self.frequencies
195
156
  model._idio_indicator = self.idio_indicator
196
157
  model._max_lag_size = self.max_lag_size
158
+
159
+ # Apply state-space parameters if available
160
+ if self.A is not None and self.C is not None and self.Q is not None:
161
+ model._update_parameters(
162
+ self.A, self.C, self.Q, self.R, self.Z_0, self.V_0
163
+ )
197
164
 
198
165
  @dataclass
199
166
  class DDFMTrainingState:
@@ -277,3 +244,6 @@ class DDFMStateSpaceParams:
277
244
  H: np.ndarray # Observation matrix (N x m) - theta in paper
278
245
  R: np.ndarray # Observation noise covariance (N x N)
279
246
 
247
+ # Backward compatibility alias
248
+ DFMStateSpaceParams = DFMModelState
249
+
@@ -29,21 +29,29 @@ class DDFMDataset(Dataset):
29
29
  Input data. Target series will be scaled if scaler provided.
30
30
  time_idx : str
31
31
  Time index column name.
32
+ covariates : List[str], optional
33
+ Series to exclude from targets (used for factor extraction but not forecasted).
34
+ If None, all series are targets (default).
32
35
  target_series : List[str], optional
33
- Target series column names to scale. If None or empty, all columns will be used as targets.
34
- target_scaler : StandardScaler | RobustScaler | MinMaxScaler, optional
36
+ Target series column names (deprecated, use covariates instead).
37
+ If None or empty, all columns will be used as targets.
38
+ scaler : StandardScaler | RobustScaler | MinMaxScaler, optional
35
39
  Scaler instance to scale target series. If None, no scaling.
36
40
  feature_scaler : StandardScaler | RobustScaler | MinMaxScaler, optional
37
41
  Scaler instance to scale feature series. If None, no scaling.
42
+ target_scaler : StandardScaler | RobustScaler | MinMaxScaler, optional
43
+ Deprecated: use scaler instead.
38
44
  """
39
45
 
40
46
  def __init__(
41
47
  self,
42
48
  data: Union[pd.DataFrame, PolarsDataFrame],
43
49
  time_idx: str,
44
- target_series: Optional[List[str]] = None,
45
- target_scaler: Optional[Union[StandardScaler, RobustScaler, MinMaxScaler]] = None,
50
+ covariates: Optional[List[str]] = None,
51
+ target_series: Optional[List[str]] = None, # Deprecated: use covariates instead
52
+ scaler: Optional[Union[StandardScaler, RobustScaler, MinMaxScaler]] = None,
46
53
  feature_scaler: Optional[Union[StandardScaler, RobustScaler, MinMaxScaler]] = None,
54
+ target_scaler: Optional[Union[StandardScaler, RobustScaler, MinMaxScaler]] = None, # Deprecated: use scaler instead
47
55
  ):
48
56
  if _has_polars and isinstance(data, pl.DataFrame):
49
57
  data = data.to_pandas()
@@ -59,24 +67,65 @@ class DDFMDataset(Dataset):
59
67
  else:
60
68
  self.time_index = data.index
61
69
 
62
- # If target_series is None or empty, use all columns as targets
63
- if target_series is None or len(target_series) == 0:
64
- target_series_list = list(data.columns)
65
- else:
66
- target_series_list = list(target_series)
67
- missing_cols = [col for col in target_series_list if col not in data.columns]
70
+ # Get all available columns (excluding time_idx if it's a column)
71
+ all_columns = [col for col in data.columns if col != time_idx] if time_idx in data.columns else list(data.columns)
72
+
73
+ # Handle backward compatibility: if target_series is provided, compute covariates from it
74
+ if target_series is not None and len(target_series) > 0:
75
+ import warnings
76
+ warnings.warn(
77
+ "target_series parameter is deprecated. Use covariates instead. "
78
+ "If you specify target_series, it will be converted to covariates automatically.",
79
+ DeprecationWarning,
80
+ stacklevel=2
81
+ )
82
+ # Compute covariates = all_columns - target_series
83
+ valid_targets = [t for t in target_series if t in all_columns]
84
+ missing_cols = [col for col in target_series if col not in all_columns]
68
85
  if missing_cols:
69
86
  raise ValueError(
70
87
  f"target_series columns {missing_cols} not found in data. "
71
- f"Available columns: {list(data.columns)}"
88
+ f"Available columns: {all_columns}"
72
89
  )
90
+ # Compute covariates from target_series
91
+ covariates = [col for col in all_columns if col not in valid_targets]
92
+ else:
93
+ covariates = covariates if covariates is not None else []
94
+
95
+ # Compute target_series from covariates
96
+ # If covariates is empty, all series are targets
97
+ if covariates:
98
+ # Filter covariates to only those that exist in data
99
+ valid_covariates = [c for c in covariates if c in all_columns]
100
+ missing_covariates = [c for c in covariates if c not in all_columns]
101
+ if missing_covariates:
102
+ import warnings
103
+ warnings.warn(
104
+ f"Some covariates not found in data: {missing_covariates}. "
105
+ f"Available columns: {all_columns}",
106
+ UserWarning
107
+ )
108
+ # target_series = all_columns - covariates
109
+ target_series_list = [col for col in all_columns if col not in valid_covariates]
110
+ self.covariates = valid_covariates
111
+ else:
112
+ # No covariates: all series are targets
113
+ target_series_list = all_columns
114
+ self.covariates = []
115
+
116
+ # Store computed target_series for backward compatibility
73
117
  self.target_series = target_series_list
74
118
  self.data_original = data.copy()
75
- self.target_scaler = target_scaler
119
+ # Use scaler parameter, fallback to target_scaler for backward compatibility
120
+ self.scaler = scaler if scaler is not None else target_scaler
76
121
  self.feature_scaler = feature_scaler
77
122
 
78
- y = data[target_series_list]
79
- X = data.drop(columns=target_series_list)
123
+ # Exclude time_idx from data when splitting X and y
124
+ # time_idx is metadata, not a data column
125
+ data_for_split = data.drop(columns=[time_idx]) if time_idx in data.columns else data
126
+
127
+ y = data_for_split[target_series_list]
128
+ X = data_for_split.drop(columns=target_series_list)
80
129
 
81
130
  # Optionally scale feature series to avoid scale dominance in encoder input
82
131
  if feature_scaler is not None and not X.empty:
@@ -99,9 +148,9 @@ class DDFMDataset(Dataset):
99
148
  self.feature_scaler.fit(X.values)
100
149
 
101
150
  # Check if data is already standardized (mean≈0, std≈1) to avoid double scaling
102
- # If data is already scaled, we still need target_scaler for inverse transformation
151
+ # If data is already scaled, we still need scaler for inverse transformation
103
152
  # but we should NOT scale again
104
- if target_scaler is not None:
153
+ if self.scaler is not None:
105
154
  # Check if target data appears already standardized
106
155
  y_mean = y.mean().abs().max()
107
156
  y_std = (y.std() - 1.0).abs().max()
@@ -109,10 +158,10 @@ class DDFMDataset(Dataset):
109
158
 
110
159
  # Check if scaler is already fitted
111
160
  scaler_is_fitted = (
112
- hasattr(target_scaler, 'mean_') and
113
- target_scaler.mean_ is not None and
114
- hasattr(target_scaler, 'scale_') and
115
- target_scaler.scale_ is not None
161
+ hasattr(self.scaler, 'mean_') and
162
+ self.scaler.mean_ is not None and
163
+ hasattr(self.scaler, 'scale_') and
164
+ self.scaler.scale_ is not None
116
165
  )
117
166
 
118
167
  if is_already_scaled:
@@ -130,7 +179,7 @@ class DDFMDataset(Dataset):
130
179
  "Ensure scaler is fitted on original unscaled data before passing to DDFMDataset.",
131
180
  UserWarning
132
181
  )
133
- self.target_scaler.fit(y.values)
182
+ self.scaler.fit(y.values)
134
183
  # Don't transform - data is already scaled
135
184
  else:
136
185
  # Data is not standardized - apply scaling
@@ -138,8 +187,8 @@ class DDFMDataset(Dataset):
138
187
  if scaler_is_fitted:
139
188
  # Check if scaler was fitted on data with similar scale
140
189
  # If mean/scale are very different, warn user but keep scaler.
141
- scaler_mean = np.abs(target_scaler.mean_)
142
- scaler_scale = target_scaler.scale_
190
+ scaler_mean = np.abs(self.scaler.mean_)
191
+ scaler_scale = self.scaler.scale_
143
192
  data_mean = np.abs(y.mean().values)
144
193
  data_std = y.std().values
145
194
 
@@ -157,9 +206,9 @@ class DDFMDataset(Dataset):
157
206
  )
158
207
  else:
159
208
  # Scaler not fitted - fit on current data
160
- self.target_scaler.fit(y.values)
209
+ self.scaler.fit(y.values)
161
210
 
162
- y_scaled = self.target_scaler.transform(y.values)
211
+ y_scaled = self.scaler.transform(y.values)
163
212
  y = pd.DataFrame(y_scaled, index=y.index, columns=y.columns)
164
213
 
165
214
  self.data = pd.concat([X, y], axis=1)
@@ -168,6 +217,11 @@ class DDFMDataset(Dataset):
168
217
  self.missing_y = y.isna().values
169
218
  self.observed_y = ~self.missing_y
170
219
 
220
+ @property
221
+ def target_scaler(self):
222
+ """Backward compatibility: returns scaler."""
223
+ return self.scaler
224
+
171
225
  @property
172
226
  def target_nan_ratio(self) -> float:
173
227
  """Target interpolate ratio."""
@@ -249,13 +303,13 @@ class DDFMDataset(Dataset):
249
303
  Returns
250
304
  -------
251
305
  DDFMDataset
252
- New dataset with same time_idx, target_series, target_scaler, feature_scaler.
306
+ New dataset with same time_idx, covariates, scaler, feature_scaler.
253
307
  """
254
308
  return cls(
255
309
  data=new_data,
256
310
  time_idx=dataset.time_idx,
257
- target_series=dataset.target_series,
258
- target_scaler=dataset.target_scaler,
311
+ covariates=getattr(dataset, 'covariates', None) or [],
312
+ scaler=getattr(dataset, 'scaler', None),
259
313
  feature_scaler=getattr(dataset, 'feature_scaler', None)
260
314
  )
261
315
 
@@ -404,8 +458,9 @@ class DDFMDataset(Dataset):
404
458
  class AutoencoderDataset:
405
459
  """Container for autoencoder training data with corrupted inputs and clean targets.
406
460
 
407
- Stores pre-loaded tensors for efficient direct slicing (no DataLoader needed).
408
- All tensors are expected to be on the correct device.
461
+ Stores pre-loaded tensors for efficient direct slicing. All tensors are expected
462
+ to be on the correct device. Use direct tensor slicing (e.g., `dataset.full_input[i:j]`)
463
+ rather than indexing.
409
464
 
410
465
  Parameters
411
466
  ----------
@@ -440,20 +495,3 @@ class AutoencoderDataset:
440
495
  def __len__(self) -> int:
441
496
  """Return number of time steps."""
442
497
  return self.y_corrupted.shape[0]
443
-
444
- def __getitem__(self, idx: int) -> Tuple[torch.Tensor, torch.Tensor]:
445
- """Get item at index idx.
446
-
447
- Parameters
448
- ----------
449
- idx : int
450
- Index of time step
451
-
452
- Returns
453
- -------
454
- full_input : torch.Tensor
455
- Full input at time step idx (shape: (N_input,))
456
- y_clean : torch.Tensor
457
- Clean target at time step idx (shape: (N,))
458
- """
459
- return self._full_input[idx], self.y_clean[idx]