def fused_csc_sampling_graph(
    csc_indptr: torch.Tensor,
    indices: torch.Tensor,
    node_type_offset: Optional[torch.tensor] = None,
    type_per_edge: Optional[torch.tensor] = None,
    node_type_to_id: Optional[Dict[str, int]] = None,
    edge_type_to_id: Optional[Dict[str, int]] = None,
    node_attributes: Optional[Dict[str, torch.tensor]] = None,
    edge_attributes: Optional[Dict[str, torch.tensor]] = None,
) -> FusedCSCSamplingGraph:
    """Create a FusedCSCSamplingGraph object from a CSC representation.

    Parameters
    ----------
    csc_indptr : torch.Tensor
        Pointer to the start of each row in the `indices`. An integer tensor
        with shape `(total_num_nodes+1,)`.
    indices : torch.Tensor
        Column indices of the non-zero elements in the CSC graph. An integer
        tensor with shape `(total_num_edges,)`.
    node_type_offset : Optional[torch.tensor], optional
        Offset of node types in the graph, by default None.
    type_per_edge : Optional[torch.tensor], optional
        Type ids of each edge in the graph, by default None. If provided, it is
        required that the edge types in each vertex neighborhood are in sorted
        order. To be more precise, For each i in [0, csc_indptr.size(0) - 1),
        `type_per_edge[indptr[i]: indptr[i + 1]]` is expected to be
        monotonically nondecreasing.
    node_type_to_id : Optional[Dict[str, int]], optional
        Map node types to ids, by default None.
    edge_type_to_id : Optional[Dict[str, int]], optional
        Map edge types to ids, by default None.
    node_attributes: Optional[Dict[str, torch.tensor]], optional
        Node attributes of the graph, by default None.
    edge_attributes: Optional[Dict[str, torch.tensor]], optional
        Edge attributes of the graph, by default None.

    Returns
    -------
    FusedCSCSamplingGraph
        The created FusedCSCSamplingGraph object.

    Examples
    --------
    >>> ntypes = {'n1': 0, 'n2': 1, 'n3': 2}
    >>> etypes = {'n1:e1:n2': 0, 'n1:e2:n3': 1}
    >>> csc_indptr = torch.tensor([0, 2, 5, 7, 8])
    >>> indices = torch.tensor([1, 3, 0, 1, 2, 0, 3, 2])
    >>> node_type_offset = torch.tensor([0, 1, 2, 4])
    >>> type_per_edge = torch.tensor([0, 1, 0, 1, 1, 0, 0, 0])
    >>> graph = graphbolt.fused_csc_sampling_graph(csc_indptr, indices,
    ...         node_type_offset=node_type_offset,
    ...         type_per_edge=type_per_edge,
    ...         node_type_to_id=ntypes, edge_type_to_id=etypes,
    ...         node_attributes=None, edge_attributes=None,)
    >>> print(graph)
    FusedCSCSamplingGraph(csc_indptr=tensor([0, 2, 5, 7, 8]),
                          indices=tensor([1, 3, 0, 1, 2, 0, 3, 2]),
                          total_num_nodes=4, num_edges={'n1:e1:n2': 5, 'n1:e2:n3': 3},
                          node_type_offset=tensor([0, 1, 2, 4]),
                          type_per_edge=tensor([0, 1, 0, 1, 1, 0, 0, 0]),
                          node_type_to_id={'n1': 0, 'n2': 1, 'n3': 2},
                          edge_type_to_id={'n1:e1:n2': 0, 'n1:e2:n3': 1},)
    """
    if node_type_to_id is not None and edge_type_to_id is not None:
        node_types = list(node_type_to_id.keys())
        edge_types = list(edge_type_to_id.keys())
        node_type_ids = list(node_type_to_id.values())
        edge_type_ids = list(edge_type_to_id.values())

        # Validate node_type_to_id.
        assert all(
            isinstance(x, str) for x in node_types
        ), "Node type name should be string."
        assert all(
            isinstance(x, int) for x in node_type_ids
        ), "Node type id should be int."
        assert len(node_type_ids) == len(
            set(node_type_ids)
        ), "Multiple node types shoud not be mapped to a same id."
        # Validate edge_type_to_id.
        for edge_type in edge_types:
            src, edge, dst = etype_str_to_tuple(edge_type)
            assert isinstance(edge, str), "Edge type name should be string."
            assert (
                src in node_types
            ), f"Unrecognized node type {src} in edge type {edge_type}"
            assert (
                dst in node_types
            ), f"Unrecognized node type {dst} in edge type {edge_type}"
        assert all(
            isinstance(x, int) for x in edge_type_ids
        ), "Edge type id should be int."
        assert len(edge_type_ids) == len(
            set(edge_type_ids)
        ), "Multiple edge types shoud not be mapped to a same id."

        if node_type_offset is not None:
            assert len(node_type_to_id) + 1 == node_type_offset.size(
                0
            ), "node_type_offset length should be |ntypes| + 1."
    return FusedCSCSamplingGraph(
        torch.ops.graphbolt.fused_csc_sampling_graph(
            csc_indptr,
            indices,
            node_type_offset,
            type_per_edge,
            node_type_to_id,
            edge_type_to_id,
            node_attributes,
            edge_attributes,
        ),
    )
Create a FusedCSCSamplingGraph object from a CSC representation.

    Parameters
    ----------
    csc_indptr : torch.Tensor
        Pointer to the start of each row in the `indices`. An integer tensor
        with shape `(total_num_nodes+1,)`.
    indices : torch.Tensor
        Column indices of the non-zero elements in the CSC graph. An integer
        tensor with shape `(total_num_edges,)`.
    node_type_offset : Optional[torch.tensor], optional
        Offset of node types in the graph, by default None.
    type_per_edge : Optional[torch.tensor], optional
        Type ids of each edge in the graph, by default None. If provided, it is
        required that the edge types in each vertex neighborhood are in sorted
        order. To be more precise, For each i in [0, csc_indptr.size(0) - 1),
        `type_per_edge[indptr[i]: indptr[i + 1]]` is expected to be
        monotonically nondecreasing.
    node_type_to_id : Optional[Dict[str, int]], optional
        Map node types to ids, by default None.
    edge_type_to_id : Optional[Dict[str, int]], optional
        Map edge types to ids, by default None.
    node_attributes: Optional[Dict[str, torch.tensor]], optional
        Node attributes of the graph, by default None.
    edge_attributes: Optional[Dict[str, torch.tensor]], optional
        Edge attributes of the graph, by default None.

    Returns
    -------
    FusedCSCSamplingGraph
        The created FusedCSCSamplingGraph object.

    Examples
    --------
    >>> ntypes = {'n1': 0, 'n2': 1, 'n3': 2}
    >>> etypes = {'n1:e1:n2': 0, 'n1:e2:n3': 1}
    >>> csc_indptr = torch.tensor([0, 2, 5, 7, 8])
    >>> indices = torch.tensor([1, 3, 0, 1, 2, 0, 3, 2])
    >>> node_type_offset = torch.tensor([0, 1, 2, 4])
    >>> type_per_edge = torch.tensor([0, 1, 0, 1, 1, 0, 0, 0])
    >>> graph = graphbolt.fused_csc_sampling_graph(csc_indptr, indices,
    ...         node_type_offset=node_type_offset,
    ...         type_per_edge=type_per_edge,
    ...         node_type_to_id=ntypes, edge_type_to_id=etypes,
    ...         node_attributes=None, edge_attributes=None,)
    >>> print(graph)
    FusedCSCSamplingGraph(csc_indptr=tensor([0, 2, 5, 7, 8]),
                          indices=tensor([1, 3, 0, 1, 2, 0, 3, 2]),
                          total_num_nodes=4, num_edges={'n1:e1:n2': 5, 'n1:e2:n3': 3},
                          node_type_offset=tensor([0, 1, 2, 4]),
                          type_per_edge=tensor([0, 1, 0, 1, 1, 0, 0, 0]),
                          node_type_to_id={'n1': 0, 'n2': 1, 'n3': 2},
                          edge_type_to_id={'n1:e1:n2': 0, 'n1:e2:n3': 1},)
    
python/dgl/graphbolt/impl/fused_csc_sampling_graph.py