//  [python]
def _encoder(self, item_history_embedding, cate_history_embedding, position_embedding, domain):
        with tf.variable_scope('Encoder%s'%domain):

            X = tf.concat(
                [item_history_embedding, cate_history_embedding], 2
            )
            X = X + position_embedding
            local_PE_emb = X
            mask = self.iterator.mask

            self.float_mask = tf.cast(mask, tf.float32)
            self.real_sequence_length = tf.reduce_sum(mask, 1)
            # compute recent for matching
            self.recent_k = 1
            real_mask = tf.cast(mask, tf.float32)
            self.position = tf.math.cumsum(real_mask, axis=1, reverse=True)
            self.recent_mask = tf.logical_and(self.position >= 1, self.position <= self.recent_k)
            self.real_recent_mask = tf.where(self.recent_mask, tf.ones_like(self.recent_mask, dtype=tf.float32), tf.zeros_like(self.recent_mask, dtype=tf.float32))
            self.recent_embedding_mean = tf.reduce_sum(X*tf.expand_dims(self.real_recent_mask, -1), 1)/tf.reduce_sum(self.real_recent_mask, 1, keepdims=True)

            self.max_n_nodes = int(X.get_shape()[1])
            
            with tf.variable_scope('interest_graph'):

                S = []
    
                for i in range(self.metric_heads):
                    # weighted cosine similarity
                    self.weighted_tensor = tf.layers.dense(tf.ones([1, 1]), X.shape.as_list()[-1], use_bias=False)
                    X_fts = X * tf.expand_dims(self.weighted_tensor, 0)
                    X_fts = tf.nn.l2_normalize(X_fts,dim=2)
                    S_one = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L


                    S += [S_one]
                S = tf.reduce_mean(tf.stack(S, 0), 0)
                S = S * tf.expand_dims(self.float_mask, -1) * tf.expand_dims(self.float_mask, -2)


                # mask invalid nodes


                ## Graph sparsification via seted sparseness
                S_flatten = tf.reshape(S, [tf.shape(S)[0],-1])
                
                sorted_S_flatten = tf.sort(S_flatten, direction='DESCENDING', axis=-1) # B*L -> B*L
                    
                # relative ranking strategy of the entire graph
                num_edges = tf.cast(tf.count_nonzero(S, [1,2]), tf.float32) # B

                to_keep_edge = tf.cast(tf.math.ceil(num_edges * self.relative_threshold), tf.int32)

                threshold_score = tf.gather_nd(sorted_S_flatten, tf.expand_dims(tf.cast(to_keep_edge, tf.int32), -1), batch_dims=1) # indices[:-1]=(B) + data[indices[-1]=() --> (B)
                A = tf.cast(tf.greater(S, tf.expand_dims(tf.expand_dims(threshold_score, -1), -1)), tf.float32)

            with tf.variable_scope('interest_fusion_extraction'):
                for l in range(self.pool_layers):
                    reuse = False if l==0 else True

                    X, A, graph_readout, alphas, tmp_mask, auxiliary_loss = self._interest_fusion_extraction_new(X, A, mask, layer=l, reuse=reuse)

                    #  X = self.normalize(X)
            mask = tmp_mask
            real_mask = tf.cast(mask, tf.float32)
            # print("before X", X.shape.as_list())

            with tf.variable_scope('encoder'):

                self.reduced_sequence_length = tf.reduce_sum(mask, 1) # B

                output_seq_state, final_state = dynamic_rnn_dien(
                    VecAttGRUCell(self.hparams.hidden_size),
                    inputs=X,
                    att_scores = tf.expand_dims(alphas, -1),
                    sequence_length=self.reduced_sequence_length,
                    dtype=tf.float32,
                    scope="gru"
                )



        return output_seq_state, local_PE_emb, final_state,  graph_readout, real_mask, auxiliary_loss