class_docstr: Base Multivariate
  
  Base class for all multivariate models. The forecasts for all time-series are produced simultaneously
  within each window, which are randomly sampled during training.
  
  This class implements the basic functionality for all windows-based models, including:
  - PyTorch Lightning's methods training_step, validation_step, predict_step.<br>
  - fit and predict methods used by NeuralForecast.core class.<br>
  - sampling and wrangling methods to generate multivariate windows.
class_docstr_tok: ["Base","Multivariate","Base","class","for","all","multivariate","models",".","The","forecasts","for","all","time-series","are","produced","simultaneously","within","each","window",",","which","are","randomly","sampled","during","training",".","This","class","implements","the","basic","functionality","for","all","windows-based","models",",","including",":","-","PyTorch","Lightning","'s","methods","training_step",",","validation_step",",","predict_step.","<","br",">","-","fit","and","predict","methods","used","by","NeuralForecast.core","class.","<","br",">","-","sampling","and","wrangling","methods","to","generate","multivariate","windows","."]
code: def _create_windows(self, batch, step):
      # Parse common data
      window_size = self.input_size + self.h
      temporal_cols = batch["temporal_cols"]
      temporal = batch["temporal"]
  
      if step == "train":
          if self.val_size + self.test_size > 0:
              cutoff = -self.val_size - self.test_size
              temporal = temporal[:, :, :cutoff]
  
          temporal = self.padder(temporal)
          windows = temporal.unfold(
              dimension=-1, size=window_size, step=self.step_size
          )
          # [n_series, C, Ws, L+H] 0, 1, 2, 3
  
          # Sample and Available conditions
          available_idx = temporal_cols.get_loc("available_mask")
          sample_condition = windows[:, available_idx, :, -self.h :]
          sample_condition = torch.sum(
              sample_condition, axis=2
          )  # Sum over time
          sample_condition = torch.sum(
              sample_condition, axis=0
          )  # Sum over time-series
          available_condition = windows[:, available_idx, :, : -self.h]
          available_condition = torch.sum(
              available_condition, axis=2
          )  # Sum over time
          available_condition = torch.sum(
              available_condition, axis=0
          )  # Sum over time-series
          final_condition = (sample_condition > 0) & (
              available_condition > 0
          )  # Of shape [Ws]
          windows = windows[:, :, final_condition, :]
  
          # Get Static data
          static = batch.get("static", None)
          static_cols = batch.get("static_cols", None)
  
          # Protection of empty windows
          if final_condition.sum() == 0:
              raise Exception("No windows available for training")
  
          # Sample windows
          n_windows = len(windows)
          if self.batch_size is not None:
              w_idxs = np.random.choice(
                  n_windows,
                  size=self.batch_size,
                  replace=(n_windows < self.batch_size),
              )
              windows = windows[:, :, w_idxs, :]
  
          windows = windows.permute(
              2, 1, 3, 0
          )  # [Ws, C, L+H, n_series]
  
          windows_batch = dict(
              temporal=windows,
              temporal_cols=temporal_cols,
              static=static,
              static_cols=static_cols,
          )
  
          return windows_batch
  
      elif step in ["predict", "val"]:
          if step == "predict":
              predict_step_size = self.predict_step_size
              cutoff = -self.input_size - self.test_size
              temporal = batch["temporal"][:, :, cutoff:]
  
          elif step == "val":
              predict_step_size = self.step_size
              cutoff = -self.input_size - self.val_size - self.test_size
              if self.test_size > 0:
                  temporal = batch["temporal"][
                      :, :, cutoff : -self.test_size
                  ]
              else:
                  temporal = batch["temporal"][:, :, cutoff:]
  
          if (
              (step == "predict")
              and (self.test_size == 0)
              and (len(self.futr_exog_list) == 0)
          ):
              temporal = self.padder(temporal)
  
          windows = temporal.unfold(
              dimension=-1, size=window_size, step=predict_step_size
          )
          # [n_series, C, Ws, L+H] -> [Ws, C, L+H, n_series]
          windows = windows.permute(2, 1, 3, 0)
  
          # Get Static data
          static = batch.get("static", None)
          static_cols = batch.get("static_cols", None)
  
          windows_batch = dict(
              temporal=windows,
              temporal_cols=temporal_cols,
              static=static,
              static_cols=static_cols,
          )
  
          return windows_batch
      else:
          raise ValueError(f"Unknown step {step}")
  
imports: import random
  import warnings
  import numpy
  import torch
  import torch.nn
  import pytorch_lightning
  from pytorch_lightning.callbacks import TQDMProgressBar
  from pytorch_lightning.callbacks.early_stopping import EarlyStopping
  from ._scalers import TemporalNorm
  from ..tsdataset import TimeSeriesDataModule
  from ..utils import get_indexer_raise_missing
label_desc: Use image node_id 5 for calling the BaseMultivariate obj's underlying member method code with example usage: obj._create_windows(batch, step) and returns: windows_batch, windows_batch