def adj_sum_graph(graphs, weight_name):
    r"""Create a weighted graph whose adjacency matrix is the sum of the
    adjacency matrices of the given graphs, whose rows represent source nodes
    and columns represent destination nodes.

    All the graphs must be simple graphs, and must have only one edge type.
    They also must have the same metagraph, i.e. have the same source node type
    and the same destination node type.  Moreover, the number of nodes for every
    graph must also be the same.

    The metagraph of the returned graph will be the same as the input graphs.

    Unlike ``scipy``, if an edge in the result graph has zero weight, it will
    not be removed from the graph.

    Notes
    -----
    This function works on both CPU and GPU.  For GPU, the number of nodes and
    edges must be less than the maximum of ``int32`` (i.e. ``2 ** 31 - 1``) due
    to restriction of cuSPARSE.

    The edge weights returned by this function is differentiable w.r.t. the
    input edge weights.

    If the graph format is restricted, both graphs must have CSR available.

    Parameters
    ----------
    graphs : list[DGLGraph]
        The list of graphs.  Must have at least one element.
    weight_name : str
        The feature name of edge weight of both graphs.

        The corresponding edge feature must be scalar.

    Returns
    -------
    DGLGraph
        The new graph.  The edge weight of the returned graph will have the
        same feature name as :attr:`weight_name`.

    Examples
    --------
    The following shows weighted adjacency matrix summation between two
    bipartite graphs.  You can also perform this between homogeneous graphs.

    >>> A = dgl.heterograph(
    ...     {('A', 'AB', 'B'): ([2, 2, 0, 2, 0, 1], [2, 1, 0, 0, 2, 2])},
    ...     num_nodes_dict={'A': 3, 'B': 4})
    >>> B = dgl.heterograph(
    ...     {('A', 'AB', 'B'): ([1, 2, 0, 2, 1, 0], [0, 3, 2, 1, 3, 3])},
    ...     num_nodes_dict={'A': 3, 'B': 4})
    >>> A.edata['w'] = torch.randn(6).requires_grad_()
    >>> B.edata['w'] = torch.randn(6).requires_grad_()

    If your graph is a multigraph, call :func:`dgl.to_simple`
    to convert it into a simple graph first.

    >>> A = dgl.to_simple(A)
    >>> B = dgl.to_simple(B)

    Initialize learnable edge weights.

    >>> A.edata['w'] = torch.randn(6).requires_grad_()
    >>> B.edata['w'] = torch.randn(6).requires_grad_()

    Take the sum.

    >>> C = dgl.adj_sum_graph([A, B], 'w')
    >>> C.edges()
    (tensor([0, 0, 0, 1, 1, 1, 2, 2, 2, 2]),
     tensor([0, 2, 3, 2, 0, 3, 0, 1, 2, 3]))

    Note that this function is differentiable:

    >>> C.edata['w'].sum().backward()
    >>> A.edata['w'].grad
    tensor([1., 1., 1., 1., 1., 1.])

    >>> B.edata['w'].grad
    tensor([1., 1., 1., 1., 1., 1.])
    """
    if len(graphs) == 0:
        raise ValueError("The list of graphs must not be empty.")

    if graphs[0].device != F.cpu():
        if not all(_unitgraph_less_than_int32(A) for A in graphs):
            raise ValueError(
                "For GPU graphs the number of nodes and edges must be less than 2 ** 31 - 1."
            )
    metagraph = graphs[0]._graph.metagraph
    num_nodes = utils.toindex(
        [
            graphs[0]._graph.num_nodes(i)
            for i in range(graphs[0]._graph.number_of_ntypes())
        ]
    )
    weights = [A.edata[weight_name] for A in graphs]
    gidxs = [A._graph for A in graphs]
    C_gidx, C_weights = F.csrsum(gidxs, weights)
    C_gidx = create_heterograph_from_relations(metagraph, [C_gidx], num_nodes)

    C = DGLGraph(C_gidx, graphs[0].ntypes, graphs[0].etypes)
    C.edata[weight_name] = C_weights
    return C
Create a weighted graph whose adjacency matrix is the sum of the
    adjacency matrices of the given graphs, whose rows represent source nodes
    and columns represent destination nodes.

    All the graphs must be simple graphs, and must have only one edge type.
    They also must have the same metagraph, i.e. have the same source node type
    and the same destination node type.  Moreover, the number of nodes for every
    graph must also be the same.

    The metagraph of the returned graph will be the same as the input graphs.

    Unlike ``scipy``, if an edge in the result graph has zero weight, it will
    not be removed from the graph.

    Notes
    -----
    This function works on both CPU and GPU.  For GPU, the number of nodes and
    edges must be less than the maximum of ``int32`` (i.e. ``2 ** 31 - 1``) due
    to restriction of cuSPARSE.

    The edge weights returned by this function is differentiable w.r.t. the
    input edge weights.

    If the graph format is restricted, both graphs must have CSR available.

    Parameters
    ----------
    graphs : list[DGLGraph]
        The list of graphs.  Must have at least one element.
    weight_name : str
        The feature name of edge weight of both graphs.

        The corresponding edge feature must be scalar.

    Returns
    -------
    DGLGraph
        The new graph.  The edge weight of the returned graph will have the
        same feature name as :attr:`weight_name`.

    Examples
    --------
    The following shows weighted adjacency matrix summation between two
    bipartite graphs.  You can also perform this between homogeneous graphs.

    >>> A = dgl.heterograph(
    ...     {('A', 'AB', 'B'): ([2, 2, 0, 2, 0, 1], [2, 1, 0, 0, 2, 2])},
    ...     num_nodes_dict={'A': 3, 'B': 4})
    >>> B = dgl.heterograph(
    ...     {('A', 'AB', 'B'): ([1, 2, 0, 2, 1, 0], [0, 3, 2, 1, 3, 3])},
    ...     num_nodes_dict={'A': 3, 'B': 4})
    >>> A.edata['w'] = torch.randn(6).requires_grad_()
    >>> B.edata['w'] = torch.randn(6).requires_grad_()

    If your graph is a multigraph, call :func:`dgl.to_simple`
    to convert it into a simple graph first.

    >>> A = dgl.to_simple(A)
    >>> B = dgl.to_simple(B)

    Initialize learnable edge weights.

    >>> A.edata['w'] = torch.randn(6).requires_grad_()
    >>> B.edata['w'] = torch.randn(6).requires_grad_()

    Take the sum.

    >>> C = dgl.adj_sum_graph([A, B], 'w')
    >>> C.edges()
    (tensor([0, 0, 0, 1, 1, 1, 2, 2, 2, 2]),
     tensor([0, 2, 3, 2, 0, 3, 0, 1, 2, 3]))

    Note that this function is differentiable:

    >>> C.edata['w'].sum().backward()
    >>> A.edata['w'].grad
    tensor([1., 1., 1., 1., 1., 1.])

    >>> B.edata['w'].grad
    tensor([1., 1., 1., 1., 1., 1.])
    
python/dgl/transforms/functional.py