//  [python]
def _build_seq_graph(self):
        """ SURGE Model: 

            1) Interest graph: Graph construction based on metric learning
            2) Interest fusion and extraction : Graph convolution and graph pooling 
            3) Prediction: Flatten pooled graph to reduced sequence
        """
        X = tf.concat(
            [self.item_history_embedding, self.cate_history_embedding], 2
        )
        X = X + self.position_embedding
        self.mask = self.iterator.mask
        self.float_mask = tf.cast(self.mask, tf.float32)
        self.real_sequence_length = tf.reduce_sum(self.mask, 1)
        # compute recent for matching
        self.recent_k = 1
        #  self.recent_k = 5
        self.real_mask = tf.cast(self.mask, tf.float32)
        self.position = tf.math.cumsum(self.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])

        # change to his mean
        #  self.real_mask = tf.cast(self.mask, tf.float32)
        #  self.recent_embedding_mean = tf.reduce_sum(X*tf.expand_dims(self.real_mask, -1), 1)/tf.reduce_sum(self.real_mask, 1, keepdims=True)

        with tf.name_scope('interest_graph'):
            ## Node similarity metric learning 

            # raw graph
#              A = tf.matmul(X, tf.transpose(X, (0,2,1))) # B*L*L
            #  #  A = tf.linalg.band_part(tf.ones_like(A),0,1) # B*L*L
#              A = tf.linalg.band_part(tf.ones_like(A),0,3) # B*L*L

            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)
                # import pdb
                # pdb.set_trace()
                X_fts = X * tf.expand_dims(self.weighted_tensor, 0)
                #  X_fts = X
                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_one = A + S_one

                #  min-max normalization for mask
                #  S_min = tf.reduce_min(S_one, -1, keepdims=True)
                #  S_max = tf.reduce_max(S_one, -1, keepdims=True)
                #  S_one = (S_one - S_min) / (S_max - S_min)

                # Euclidean distance
#                  p1 = tf.reduce_sum(tf.square(X), -1, keepdims=True)
                #  dist = tf.sqrt(tf.add(p1, tf.transpose(p1, (0,2,1))) - 2 * tf.matmul(X, tf.transpose(X, (0,2,1))))
                #  # 高斯核将距离转换为相似度，之后元素为0-1
#                  S_one = tf.exp(-1*dist)

                # Maha distance
#                  M_w = tf.get_variable(
                #      name="maha_weight",
                #      shape=[X.shape.as_list()[-1], X.shape.as_list()[-1]],
                #      initializer=self.initializer,
                #  )
                #  #  X_fts = tf.tensordot(X, M_w, [[2], [0]])
                #  # 根据转置矩阵乘法展开，要求M_w左乘X
                #  X_fts = tf.tensordot(X, M_w, [[-1], [-1]])
                #  p1 = tf.reduce_sum(tf.square(X_fts), -1, keepdims=True)
                #  dist = tf.sqrt(tf.add(p1, tf.transpose(p1, (0,2,1))) - 2 * tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))))
                #  # 高斯核将距离转换为相似度，之后元素为0-1
#                  S_one = tf.exp(-1*dist)

                #  S_one = A + S_one

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


            ## Graph sparsification via seted sparseness
            S_flatten = tf.reshape(S, [tf.shape(S)[0],-1])
            if 'kwai' in socket.gethostname():
                sorted_S_flatten = tf.contrib.framework.sort(S_flatten, direction='DESCENDING', axis=-1) # B*L -> B*L
            else:
                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)
            if 'kwai' in socket.gethostname():
                threshold_index = tf.stack([tf.range(tf.shape(X)[0]), tf.cast(to_keep_edge, tf.int32)], 1) # B*2
                threshold_score = tf.gather_nd(sorted_S_flatten, threshold_index) # indices[:-1]=(B) + data[indices[-1]=() --> (B)
            else:
                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)

            #  A = S



        with tf.name_scope('interest_fusion_extraction'):
            for l in range(self.pool_layers):
                reuse = False if l==0 else True
                #  X, A, graph_readout, alphas = self._interest_fusion_extraction(X, A, layer=l, reuse=reuse)
                X, A, graph_readout, alphas = self._interest_fusion_extraction_new(X, A, layer=l, reuse=reuse)
                #  X = self.normalize(X)


        with tf.name_scope('prediction'):
#              # flatten pooled graph to reduced sequence
#              output_shape = self.mask.get_shape()
#              if 'kwai' in socket.gethostname():
#                  sorted_mask_index = tf.contrib.framework.argsort(self.mask, direction='DESCENDING', stable=True, axis=-1) # B*L -> B*L
#                  sorted_mask = tf.contrib.framework.sort(self.mask, direction='DESCENDING', axis=-1) # B*L -> B*L
#              else:
#                  sorted_mask_index = tf.argsort(self.mask, direction='DESCENDING', stable=True, axis=-1) # B*L -> B*L
#                  sorted_mask = tf.sort(self.mask, direction='DESCENDING', axis=-1) # B*L -> B*L
#              sorted_mask.set_shape(output_shape)
#              sorted_mask_index.set_shape(output_shape)
#              X = tf.batch_gather(X, sorted_mask_index) # B*L*F  < B*L = B*L*F
#              # change alphas
#              #  alphas = tf.batch_gather(alphas, sorted_mask_index) # B*L  < B*L = B*L
#              self.mask = sorted_mask
#              self.reduced_sequence_length = tf.reduce_sum(self.mask, 1) # B
#  #
#              # cut useless sequence tail per batch
#              self.to_max_length = tf.range(tf.reduce_max(self.reduced_sequence_length)) # l
#              X = tf.gather(X, self.to_max_length, axis=1) # B*L*F -> B*l*F
#              # change alphas
#              alphas = tf.gather(alphas, self.to_max_length, axis=1) # B*L -> B*l
#              self.mask = tf.gather(self.mask, self.to_max_length, axis=1) # B*L -> B*l
#              self.float_mask = tf.cast(self.mask, tf.float32)
#              self.reduced_sequence_length = tf.reduce_sum(self.mask, 1) # B
#
            # rm sort
            self.reduced_sequence_length = tf.reduce_sum(self.mask, 1) # B

            # fix length, k = 10
            #  self.reduced_sequence_length = tf.fill([tf.shape(self.mask)[0]],10)

            # use cluster score as attention weights in AUGRU 
            if not self.remove_target: 
                #  if not self.recent_target:
                    #  _, alphas = self._attention_fcn(self.target_item_embedding, X, 'AGRU', False, return_alpha=True)
                #  else:
                    #  _, alphas = self._attention_fcn(self.recent_embedding_mean, X, 'AGRU', False, return_alpha=True)
                _, 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"
                )

                #  _, final_state = dynamic_rnn(
                    #  GRUCell(self.hparams.hidden_size),
                    #  inputs=X,
                    #  sequence_length=self.reduced_sequence_length,
                    #  dtype=tf.float32,
                    #  scope="gru",
                #  )
#

                #  self.mask = tf.ones([tf.shape(self.mask)[0], 10])
                #  attention_output, alphas = self._attention_fcn(self.target_item_embedding, X, 'Att-last', False, return_alpha=True)
                #  final_state = tf.reduce_sum(attention_output, 1)

                #  final_state = tf.reduce_mean(X, 1)

            else:
                _, final_state = dynamic_rnn(
                    GRUCell(self.hparams.hidden_size),
                    inputs=X,
                    sequence_length=self.reduced_sequence_length,
                    dtype=tf.float32,
                    scope="gru",
                )

            #  graph_readout = tf.reduce_sum(X*tf.expand_dims(alphas,-1)*tf.expand_dims(self.float_mask, -1), 1)

            #  #  self.recent_k = 5
            #  self.recent_k = 1
            #  self.real_mask = tf.cast(self.mask, tf.float32)
            #  self.position = tf.math.cumsum(self.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)

            if not self.remove_target: 
                if not self.recent_target:
                    model_output = tf.concat([final_state, graph_readout, self.target_item_embedding, graph_readout*self.target_item_embedding], 1)
                    #  model_output = tf.concat([final_state, graph_readout, self.target_item_embedding], 1)
                else:
                    #  model_output = tf.concat([final_state, graph_readout, self.target_item_embedding, graph_readout*self.recent_embedding_mean], 1)
                    #  model_output = tf.concat([final_state, graph_readout, self.target_item_embedding, self.recent_embedding_mean ,graph_readout*self.recent_embedding_mean], 1)
                    model_output = tf.concat([final_state, self.target_item_embedding, self.recent_embedding_mean], 1)
                    #  model_output = tf.concat([final_state, self.target_item_embedding], 1)
            else:
                #  model_output = tf.concat([final_state, graph_readout, self.target_item_embedding, graph_readout], 1)
                #  model_output = tf.concat([final_state, graph_readout, self.target_item_embedding], 1)
                model_output = tf.concat([final_state, self.target_item_embedding], 1)


        return model_output