from typing import Dict import torch from torch.nn.functional import pad class ConcatLeftAlign: def __init__( self, action_target_dim: int | None = None, state_target_dim: int | None = None ): self.action_target_dim = action_target_dim self.state_target_dim = state_target_dim def set_shape_meta(self, shape_meta): self.action_meta = shape_meta["action"] self.state_meta = shape_meta["state"] def forward(self, batch): if "action" in batch: batch["action"] = self._concat(batch["action"], self.action_meta) batch["action"], batch["action_dim_is_pad"] = self._pad(batch["action"], self.action_target_dim) batch["state"] = self._concat(batch["state"], self.state_meta) batch["state"], batch["state_dim_is_pad"] = self._pad(batch["state"], self.state_target_dim) return batch def backward(self, batch): if self.state_target_dim is not None: assert batch["state"].shape[-1] == self.state_target_dim batch["state"] = self._crop(batch["state"], self.state_meta) batch["state"] = self._split(batch["state"], self.state_meta) if self.action_target_dim is not None: assert batch["action"].shape[-1] == self.action_target_dim batch["action"] = self._crop(batch["action"], self.action_meta) batch["action"] = self._split(batch["action"], self.action_meta) return batch @staticmethod def _pad(x: torch.Tensor, dim: int): if dim is None: dim = x.shape[-1] assert x.ndim == 2 and x.shape[-1] <= dim pad_dim = dim - x.shape[-1] x_padded = pad(x, (0, pad_dim)) mask = torch.zeros_like(x[0]).bool() mask = pad(mask, (0, pad_dim), value=True) return x_padded, mask @staticmethod def _crop(x: torch.Tensor, meta: int): assert x.ndim == 3 dim = sum([m["shape"] for m in meta]) x = x[:, :, :dim] return x @staticmethod def _concat(x: Dict[str, torch.Tensor], meta: Dict[str, Dict]): x = torch.cat([x[m["key"]] for m in meta], dim=-1) assert x.ndim == 2 return x @staticmethod def _split(x: torch.Tensor, meta: Dict[str, Dict]): assert x.ndim == 3 y = {} idx = 0 for m in meta: key, dim = m["key"], m["shape"] y[key] = x[:, :, idx: idx + dim] idx += dim return y