@@ -2098,6 +2098,122 @@ def predict(
20982098
20992099 return fcsts
21002100
2101+ def simulate (
2102+ self ,
2103+ dataset ,
2104+ n_paths = 500 ,
2105+ random_seed = None ,
2106+ quantiles = None ,
2107+ method = "gaussian_copula" ,
2108+ ** data_module_kwargs ,
2109+ ):
2110+ """Simulate sample paths with temporal correlation.
2111+
2112+ Calls ``predict()`` to obtain quantile forecasts, then draws correlated
2113+ sample paths using the specified simulation method.
2114+
2115+ Works with any loss that supports quantile output (``DistributionLoss``,
2116+ ``MQLoss``, etc.).
2117+
2118+ Args:
2119+ dataset (TimeSeriesDataset): NeuralForecast's ``TimeSeriesDataset``.
2120+ n_paths (int): Number of sample paths to generate.
2121+ random_seed (int, optional): Random seed for reproducibility.
2122+ quantiles (list of float, optional): Quantile grid for marginals.
2123+ Only used for ``DistributionLoss`` and mixture losses.
2124+ Defaults to ``[0.01, 0.02, ..., 0.99]``.
2125+ For ``MQLoss``/``HuberMQLoss``, the model's trained quantiles
2126+ are used automatically.
2127+ method (str): Simulation method. Default: ``"gaussian_copula"``.
2128+ **data_module_kwargs: Extra arguments for ``TimeSeriesDataModule``.
2129+
2130+ Returns:
2131+ samples (np.ndarray): Array of shape ``[n_series, n_paths, H]``.
2132+ """
2133+ from neuralforecast .utils import (
2134+ DEFAULT_QUANTILE_GRID ,
2135+ VALID_SIMULATION_METHODS ,
2136+ )
2137+
2138+ if method not in VALID_SIMULATION_METHODS :
2139+ raise ValueError (
2140+ f"Unknown simulation method '{ method } '. "
2141+ f"Valid methods: { sorted (VALID_SIMULATION_METHODS )} "
2142+ )
2143+ if quantiles is None :
2144+ quantiles = DEFAULT_QUANTILE_GRID
2145+
2146+ # Determine quantile grid based on loss type
2147+ if self .loss .is_distribution_output :
2148+ # DistributionLoss/mixture: can produce arbitrary quantiles
2149+ predict_quantiles = quantiles
2150+ quantile_positions = np .array (quantiles )
2151+ elif isinstance (self .loss , MULTIQUANTILE_LOSSES ):
2152+ # MQLoss/HuberMQLoss: uses its trained quantiles
2153+ predict_quantiles = None # use model's built-in quantiles
2154+ quantile_positions = self .loss .quantiles .cpu ().numpy ()
2155+ elif isinstance (self .loss , (losses .IQLoss , losses .HuberIQLoss )):
2156+ # IQLoss: one forward pass per quantile, then take quantile over results
2157+ predict_quantiles = None # handled below
2158+ quantile_positions = np .array (quantiles )
2159+ else :
2160+ raise ValueError (
2161+ f"Simulation requires a loss with quantile output "
2162+ f"(DistributionLoss, MQLoss, IQLoss, etc.). "
2163+ f"Model uses { type (self .loss ).__name__ } ."
2164+ )
2165+
2166+ # Get quantile forecasts via existing predict infrastructure
2167+ if isinstance (self .loss , (losses .IQLoss , losses .HuberIQLoss )):
2168+ # IQLoss: multiple forward passes, one per quantile in a grid
2169+ iq_grid = [0.01 , 0.05 , 0.1 , 0.2 , 0.3 , 0.5 , 0.7 , 0.8 , 0.9 , 0.95 , 0.99 ]
2170+ iq_fcsts = []
2171+ for q in iq_grid :
2172+ f = self .predict (
2173+ dataset = dataset ,
2174+ random_seed = random_seed ,
2175+ quantiles = [q ],
2176+ ** data_module_kwargs ,
2177+ )
2178+ iq_fcsts .append (f )
2179+ # Each f is (n_series * H, 1); stack along last axis
2180+ iq_all = np .concatenate (iq_fcsts , axis = - 1 ) # (n_series * H, len(iq_grid))
2181+ # Interpolate to the requested quantile positions
2182+ fcsts = np .quantile (iq_all , quantiles , axis = - 1 ).T # (n_series * H, n_quantiles)
2183+ n_quantiles = len (quantile_positions )
2184+ quantile_fcsts = fcsts [:, :n_quantiles ]
2185+ else :
2186+ fcsts = self .predict (
2187+ dataset = dataset ,
2188+ random_seed = random_seed ,
2189+ quantiles = predict_quantiles ,
2190+ ** data_module_kwargs ,
2191+ )
2192+ # fcsts: flattened array, shape (n_series * H, n_outputs)
2193+ n_quantiles = len (quantile_positions )
2194+ if self .loss .is_distribution_output :
2195+ # col 0 = mean, cols 1..Q = quantiles
2196+ quantile_fcsts = fcsts [:, 1 : 1 + n_quantiles ]
2197+ else :
2198+ # MQLoss: all columns are quantiles
2199+ quantile_fcsts = fcsts [:, :n_quantiles ]
2200+
2201+ h = self .horizon_backup
2202+ n_series = quantile_fcsts .shape [0 ] // h
2203+ quantile_values = quantile_fcsts .reshape (n_series , h , n_quantiles )
2204+
2205+ # Generate sample paths using shared helper
2206+ from neuralforecast .utils import sample_from_quantiles
2207+
2208+ return sample_from_quantiles (
2209+ quantile_positions = quantile_positions ,
2210+ quantile_values = quantile_values ,
2211+ dataset = dataset ,
2212+ n_paths = n_paths ,
2213+ seed = random_seed ,
2214+ method = method ,
2215+ ) # (n_series, n_paths, H)
2216+
21012217 def decompose (
21022218 self ,
21032219 dataset ,
0 commit comments