Source code for cann.data.datasets

from __future__ import annotations

from dataclasses import dataclass
from typing import Dict

import numpy as np
import torch
from torch.utils.data import Dataset

from cann.config.model_config import ModelConfig


[docs] @dataclass class ParamSurfaceDataset(Dataset): """ Synthetic dataset for surrogate learning/inference. Each sample is a tuple (θ_i, x_i, Q*(x_i; θ_i), w) where: - θ_i: Model parameters (e.g., rho, kappa) as a numpy array of shape (n,). - x_i: Market input features (e.g., moneyness, time to maturity) as a numpy array of shape (m,). - Q*(x_i; θ_i): Observed market value (e.g., implied volatility or option price) as a float. - w: Optional weight for the sample as a float. This dataclass stores the arrays directly as fields (``parameters``, ``market_inputs``, and optional ``observed``/``weights``) and prepares torch tensors in ``__post_init__`` for efficient training. """ parameters: np.ndarray market_inputs: np.ndarray model_cfg: ModelConfig observed: np.ndarray | None = None weights: np.ndarray | None = None @property def num_parameters(self) -> int: """Number of model parameters (n).""" return self.parameters.shape[1] @property def num_samples(self) -> int: """Number of samples (N).""" return self.parameters.shape[0] def __post_init__(self) -> None: # Validate and reshape numpy arrays self.parameters = np.asarray(self.parameters, dtype=np.float32) self.market_inputs = np.asarray(self.market_inputs, dtype=np.float32) num_samples = self.parameters.shape[0] if self.market_inputs.shape[0] != num_samples: raise ValueError( f"Number of samples in market_inputs ({self.market_inputs.shape[0]}) does not match parameters ({num_samples})" ) # 2. Pre-convert to PyTorch Tensors for fast access self._params_t = torch.from_numpy(self.parameters) self._market_t = torch.from_numpy(self.market_inputs) self._observed_t = None if self.observed is not None: self.observed = np.asarray(self.observed, dtype=np.float32) if self.observed.shape[0] != num_samples: raise ValueError( f"Number of samples in observed ({self.observed.shape[0]}) does not match parameters ({num_samples})" ) self._observed_t = torch.from_numpy(self.observed.reshape(-1, 1)) self._weights_t = None if self.weights is not None: self.weights = np.asarray(self.weights, dtype=np.float32).reshape(-1) if self.weights.shape[0] != num_samples: raise ValueError( f"Number of samples in weights ({self.weights.shape[0]}) does not match parameters ({num_samples})" ) self._weights_t = torch.from_numpy(self.weights.reshape(-1, 1)) def __len__(self) -> int: return self.parameters.shape[0] def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]: """ Retrieve a single sample from the dataset. Returns a dictionary with keys: - "params": Model parameters tensor of shape (n,). - "market": Market input features tensor of shape (m,). - "observed": Observed target tensor of shape (1,) if available. - "weights": Sample weight tensor of shape (1,) if available. Args: idx (int): Index of the sample to retrieve. Returns: Dict[str, torch.Tensor]: Sample dictionary containing at least ``params`` and ``market``. """ sample = { "params": self._params_t[idx], "market": self._market_t[idx], } if self._observed_t is not None: sample["observed"] = self._observed_t[idx] if self._weights_t is not None: sample["weights"] = self._weights_t[idx] return sample