(self, pub_obses, range_idxs)
1. do list -> padded
        2. feed through pre-processing fc layers
        3. PackedSequence (sort, pack)
        4. rnn
        5. unpack (unpack re-sort)
        6. cut output to only last entry in sequence

        Args:
            pub_obses (list):                 list of np arrays of shape [np.arr([history_len, n_features]), ...)
            range_idxs (LongTensor):        range_idxs (one for each pub_obs) tensor([2, 421, 58, 912, ...])
1. do list -> padded
        2. feed through pre-processing fc layers
        3. PackedSequence (sort, pack)
        4. rnn
        5. unpack (unpack re-sort)
        6. cut output to only last entry in sequence
1 . do list - > padded 2 . feed through pre - processing fc layers 3 . PackedSequence ( sort pack ) 4 . rnn 5 . unpack ( unpack re - sort ) 6 . cut output to only last entry in sequence
def forward(self, pub_obses, range_idxs):
        """
        1. do list -> padded
        2. feed through pre-processing fc layers
        3. PackedSequence (sort, pack)
        4. rnn
        5. unpack (unpack re-sort)
        6. cut output to only last entry in sequence

        Args:
            pub_obses (list):                 list of np arrays of shape [np.arr([history_len, n_features]), ...)
            range_idxs (LongTensor):        range_idxs (one for each pub_obs) tensor([2, 421, 58, 912, ...])
        """

        # ____________________________________________ Packed Sequence _____________________________________________
        bs = len(pub_obses)

        # this is just for one history step! has shape [bs, n_priv_features] now
        priv_obs = self.lut_range_idx_2_priv_o[range_idxs]

        if bs > 1:

            seq_lens = torch.tensor([sample.shape[0] for sample in pub_obses], device=self.device, dtype=torch.int32)
            max_len = seq_lens.max().item()

            _pub_obs = pub_obses
            pub_obses = torch.zeros((max_len, bs, self.pub_obs_size), dtype=torch.float32, device=self.device)
            for i, pub in zip(range(bs), _pub_obs):
                pub_obses[:seq_lens[i], i] = torch.from_numpy(pub).to(self.device)

            # extend private obs to whole time series. Technically goes over the seq_len (to max len)
            priv_obs = priv_obs.unsqueeze(0).repeat(max_len, 1, 1)

            if self.args.use_pre_layers:
                # slice and feed through the fc layers. the result is a tensor: [max_len, bs, some_n_of_units]
                y = self._feed_through_pre_layers(pub_o=pub_obses, priv_o=priv_obs)
            else:
                # concat pub and priv obs
                y = torch.cat((priv_obs, pub_obses), dim=-1)

            # sort descending for packing
            seq_lens, idx_shifts = torch.sort(seq_lens, descending=True)
            y = y[:, idx_shifts, :]

            # pack
            y = torch.nn.utils.rnn.pack_padded_sequence(y, lengths=seq_lens, batch_first=False)

            # Feed through RNN
            y, _ = self.rnn(y)

            # Unpack Sequence
            y, seq_lens = nn.utils.rnn.pad_packed_sequence(y, batch_first=False)

            if self.args.sum_step_outputs:
                y = y.sum(0) * (1.0 / seq_lens.float()).unsqueeze(-1)
            else:  # use last element of each sequence in the batch, because we only need the last action
                y = y[seq_lens - 1, torch.arange(bs, device=self.device, dtype=torch.long), :].squeeze(dim=0)

            # sort the samples back to their original order
            idx_unsort_obs_t = torch.arange(bs, device=self.device, dtype=torch.long)
            idx_unsort_obs_t.scatter_(src=idx_unsort_obs_t.clone(), dim=0, index=idx_shifts)

            return y[idx_unsort_obs_t]

        else:
            seq_len = pub_obses[0].shape[0]
            pub_obses = torch.from_numpy(pub_obses[0]).to(self.device).view(seq_len, bs, self.pub_obs_size)
            priv_obs = priv_obs.unsqueeze(0).expand(seq_len, bs, self.priv_obs_size)

            if self.args.use_pre_layers:
                y = self._feed_through_pre_layers(pub_o=pub_obses, priv_o=priv_obs)
            else:
                # concat pub and priv obs
                y = torch.cat((priv_obs, pub_obses), dim=-1)

            y, _ = self.rnn(y)

            if self.args.sum_step_outputs:
                return y.sum(0) * (1.0 / seq_len)
            else:  # use last element of each sequence in the batch, because we only need the last action
                return y[seq_len - 1].view(bs, -1)
def forward ( self , pub_obses , range_idxs ) : # ____________________________________________ Packed Sequence _____________________________________________ bs = len ( pub_obses ) # this is just for one history step! has shape [bs, n_priv_features] now priv_obs = self . lut_range_idx_2_priv_o [ range_idxs ] if bs > 1 : seq_lens = torch . tensor ( [ sample . shape [ 0 ] for sample in pub_obses ] , device = self . device , dtype = torch . int32 ) max_len = seq_lens . max ( ) . item ( ) _pub_obs = pub_obses pub_obses = torch . zeros ( ( max_len , bs , self . pub_obs_size ) , dtype = torch . float32 , device = self . device ) for i , pub in zip ( range ( bs ) , _pub_obs ) : pub_obses [ : seq_lens [ i ] , i ] = torch . from_numpy ( pub ) . to ( self . device ) # extend private obs to whole time series. Technically goes over the seq_len (to max len) priv_obs = priv_obs . unsqueeze ( 0 ) . repeat ( max_len , 1 , 1 ) if self . args . use_pre_layers : # slice and feed through the fc layers. the result is a tensor: [max_len, bs, some_n_of_units] y = self . _feed_through_pre_layers ( pub_o = pub_obses , priv_o = priv_obs ) else : # concat pub and priv obs y = torch . cat ( ( priv_obs , pub_obses ) , dim = - 1 ) # sort descending for packing seq_lens , idx_shifts = torch . sort ( seq_lens , descending = True ) y = y [ : , idx_shifts , : ] # pack y = torch . nn . utils . rnn . pack_padded_sequence ( y , lengths = seq_lens , batch_first = False ) # Feed through RNN y , _ = self . rnn ( y ) # Unpack Sequence y , seq_lens = nn . utils . rnn . pad_packed_sequence ( y , batch_first = False ) if self . args . sum_step_outputs : y = y . sum ( 0 ) * ( 1.0 / seq_lens . float ( ) ) . unsqueeze ( - 1 ) else : # use last element of each sequence in the batch, because we only need the last action y = y [ seq_lens - 1 , torch . arange ( bs , device = self . device , dtype = torch . long ) , : ] . squeeze ( dim = 0 ) # sort the samples back to their original order idx_unsort_obs_t = torch . arange ( bs , device = self . device , dtype = torch . long ) idx_unsort_obs_t . scatter_ ( src = idx_unsort_obs_t . clone ( ) , dim = 0 , index = idx_shifts ) return y [ idx_unsort_obs_t ] else : seq_len = pub_obses [ 0 ] . shape [ 0 ] pub_obses = torch . from_numpy ( pub_obses [ 0 ] ) . to ( self . device ) . view ( seq_len , bs , self . pub_obs_size ) priv_obs = priv_obs . unsqueeze ( 0 ) . expand ( seq_len , bs , self . priv_obs_size ) if self . args . use_pre_layers : y = self . _feed_through_pre_layers ( pub_o = pub_obses , priv_o = priv_obs ) else : # concat pub and priv obs y = torch . cat ( ( priv_obs , pub_obses ) , dim = - 1 ) y , _ = self . rnn ( y ) if self . args . sum_step_outputs : return y . sum ( 0 ) * ( 1.0 / seq_len ) else : # use last element of each sequence in the batch, because we only need the last action return y [ seq_len - 1 ] . view ( bs , - 1 )