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

            1) learnable graph: metric learning
            2) graph coarsening: propogation and pooling 
            3) reduced sequence: position flatten
        """
        hparams = self.hparams
        X = tf.concat(
            [self.item_history_embedding, self.cate_history_embedding], 2
        )
        self.mask = self.iterator.mask
        self.real_mask = tf.cast(self.mask, tf.float32)
        self.sequence_length = tf.reduce_sum(self.mask, 1)
        self.max_n_nodes = int(X.get_shape()[1])

        with tf.name_scope('learnable_graph'):
            # weighted cosine similarity
            weighted_tensor = tf.layers.dense(tf.ones([1, 1]), hparams.hidden_size, use_bias=False)
            X_fts = X * tf.expand_dims(weighted_tensor, 0)
            X_fts = tf.nn.l2_normalize(X_fts,dim=2)
            A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
            A = tf.nn.softmax(A, axis=-1)
            A = A * tf.expand_dims(self.real_mask, -1) * tf.expand_dims(self.real_mask, -2)

            # conctruct graph according to the threshold
            A_flatten = tf.reshape(A, [tf.shape(A)[0],-1])
            if 'kwai' in socket.gethostname():
                sorted_A_flatten = tf.contrib.framework.sort(A_flatten, direction='DESCENDING', axis=-1) # B*L -> B*L
            else:
                sorted_A_flatten = tf.sort(A_flatten, direction='DESCENDING', axis=-1) # B*L -> B*L
            num_edges = tf.cast(self.sequence_length * self.sequence_length, tf.float32)
            to_keep_edge = tf.cast(tf.math.ceil(num_edges * self.threshold), tf.int32)
            threshold_score = tf.gather_nd(sorted_A_flatten, tf.expand_dims(tf.cast(to_keep_edge, tf.int32), -1), batch_dims=1) 
            A = tf.cast(tf.greater_equal(A, tf.expand_dims(tf.expand_dims(threshold_score, -1), -1)), tf.float32)

        with tf.name_scope('graph_coarsening'):
            readout = []
            for l in range(self.pool_layers):
                reuse = False if l==0 else True
                X, A = self._graph_coarsening(X, A, layer=l, reuse=reuse)
                readout += [X]

        with tf.name_scope('flatten_sequence'):
            # flatten graph to sequence according to the initial position
            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
            self.mask = sorted_mask
            self.real_mask = tf.cast(self.mask, tf.float32)
            self.sequence_length = tf.reduce_sum(self.mask, 1) # B

            # split useless sequence tail per batch 
            self.to_max_length = tf.range(tf.reduce_max(self.sequence_length)) # l
            X = tf.gather(X, self.to_max_length, axis=1) # B*L*F -> B*l*F
            self.mask = tf.gather(self.mask, self.to_max_length, axis=1) # B*L -> B*l
            self.real_mask = tf.cast(self.mask, tf.float32)
            self.sequence_length = tf.reduce_sum(self.mask, 1) # B

            # prediction
            attention_output, alphas = self._attention_fcn(self.target_item_embedding, X, 'output', False, return_alpha=True)
            att_fea = tf.reduce_sum(attention_output, 1)
            _, final_state = dynamic_rnn_dien(
                VecAttGRUCell(hparams.hidden_size),
                inputs=X,
                att_scores = tf.expand_dims(alphas, -1),
                sequence_length=self.sequence_length,
                dtype=tf.float32,
                scope="gru"
            )
            tf.summary.histogram('GRU_Final_State', final_state)

        model_output = tf.concat([self.target_item_embedding, final_state, att_fea, self.target_item_embedding*att_fea], 1)
        tf.summary.histogram("model_output", model_output)
        return model_output