<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def get_edge_list(self):
 r"""Return an edge list, an alternative representation of the graph. Each edge :math:`e_k = (v_i, v_j) \in \mathcal{E}` from :math:`v_i` to :math:`v_j` is associated with the weight :math:`W[i, j]`. For each edge :math:`e_k`, the method returns :math:`(i, j, W[i, j])` as `(sources[k], targets[k], weights[k])`, with :math:`i \in [0, |\mathcal{V}|-1], j \in [0, |\mathcal{V}|-1], k \in [0, |\mathcal{E}|-1]`. Returns ------- sources : vector of int Source node indices. targets : vector of int Target node indices. weights : vector of float Edge weights. Notes ----- The weighted adjacency matrix is the canonical form used in this package to represent a graph as it is the easiest to work with when considering spectral methods. Edge orientation (i.e., which node is the source or the target) is arbitrary for undirected graphs. The implementation uses the upper triangular part of the adjacency matrix, hence :math:`i \leq j \ \forall k`. Examples -------- Edge list of a directed graph. ([0, 1, 1], [1, 0, 2], [3, 3, 4]) Edge list of an undirected graph. ([0, 1], [1, 2], [3, 4]) """


        if self.is_directed():
            W = self.W.tocoo()
        else:
            W = sparse.triu(self.W, format='coo')

        sources = W.row
        targets = W.col
        weights = W.data

        assert self.n_edges == sources.size == targets.size == weights.size
        return sources, targets, weights