Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
r""" Static sparse graph backend
This module implement a immutable sparse graph backend using the data structure from :mod:`sage.graphs.base.static_sparse_graph`. It supports both directed and undirected graphs, as well as vertex/edge labels, loops and multiple edges. As it uses a very compact C structure it should be very small in memory.
As it is a sparse data structure, you can expect it to be very efficient when you need to list the graph's edge, or those incident to a vertex, but an adjacency test can be much longer than in a dense data structure (i.e. like in :mod:`sage.graphs.base.static_dense_graph`)
For an overview of graph data structures in sage, see :mod:`~sage.graphs.base.overview`.
Two classes -----------
This module implements two classes
* :class:`StaticSparseCGraph` extends :class:`~sage.graphs.base.c_graph.CGraph` and is a Cython class that manages the definition/deallocation of the ``short_digraph`` structure. It does not know anything about labels on vertices.
* :class:`StaticSparseBackend` extends :class:`~sage.graphs.base.c_graph.CGraphBackend` and is a Python class that does know about vertex labels and contains an instance of :class:`StaticSparseCGraph` as an internal variable. The input/output of its methods are labeled vertices, which it translates to integer id before forwarding them to the :class:`StaticSparseCGraph` instance.
Classes and methods ------------------- """ from __future__ import print_function
from cysignals.memory cimport check_calloc, sig_free
from sage.graphs.base.static_sparse_graph cimport (init_short_digraph, init_reverse, out_degree, has_edge, free_short_digraph, edge_label) from .c_graph cimport CGraphBackend from sage.data_structures.bitset cimport FrozenBitset from libc.stdint cimport uint32_t include 'sage/data_structures/bitset.pxi'
cdef class StaticSparseCGraph(CGraph): """ :mod:`CGraph <sage.graphs.base.c_graph>` class based on the sparse graph data structure :mod:`static sparse graphs <sage.graphs.base.static_sparse_graph>`. """
def __cinit__(self, G): r""" Cython constructor
INPUT:
- ``G`` -- a :class:`Graph` object.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph())
Check that the digraph methods are working (see :trac:`20253`)::
sage: G = DiGraph([(0,1),(1,0)]) sage: G2 = G.copy(immutable=True) sage: G2.is_strongly_connected() True """ cdef int i, j, tmp
# Store the number of loops for undirected graphs else: except MemoryError: free_short_digraph(self.g) raise
# Defining the meaningless set of 'active' vertices. Because of CGraph. # As well as num_verts and num_edges
def __dealloc__(self): r""" Freeing the memory
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) """
cpdef bint has_vertex(self, int n) except -1: r""" Tests if a vertex belongs to the graph
INPUT:
- ``n`` -- an integer
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.has_vertex(1) True sage: g.has_vertex(10) False """
cdef int add_vertex_unsafe(self, int k) except -1:
cdef int del_vertex_unsafe(self, int v) except -1:
def add_vertex(self, int k): r""" Adds a vertex to the graph. No way.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.add_vertex(45) Traceback (most recent call last): ... ValueError: Thou shalt not add a vertex to an immutable graph
"""
cpdef del_vertex(self, int k): r""" Removes a vertex from the graph. No way.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.del_vertex(45) Traceback (most recent call last): ... ValueError: Thou shalt not remove a vertex from an immutable graph
"""
cpdef list verts(self): r""" Returns the list of vertices
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.verts() [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] """
cdef int has_arc_unsafe(self, int u, int v) except -1:
cpdef bint has_arc(self, int u, int v) except -1: r""" Tests if uv is an edge of the graph
INPUT:
- ``u,v`` -- integers
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.has_arc(0,1) True sage: g.has_arc(0,7) False """
cdef int out_neighbors_unsafe(self, int u, int *neighbors, int size) except -2: cdef int degree = self.g.neighbors[u+1] - self.g.neighbors[u] cdef int i for i in range(min(degree,size)): neighbors[i] = self.g.neighbors[u][i] return -1 if size < degree else degree
cdef int in_neighbors_unsafe(self, int u, int *neighbors, int size) except -2: if not self._directed: return self.out_neighbors_unsafe(u,neighbors,size)
cdef int degree = self.g_rev.neighbors[u+1] - self.g_rev.neighbors[u] cdef int i for i in range(min(degree,size)): neighbors[i] = self.g_rev.neighbors[u][i] return -1 if size < degree else degree
cpdef list out_neighbors(self, int u): r""" List the out-neighbors of a vertex
INPUT:
- ``u`` -- a vertex
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.out_neighbors(0) [1, 4, 5] sage: g.out_neighbors(10) Traceback (most recent call last): ... LookupError: The vertex does not belong to the graph """
cdef int i
cpdef list in_neighbors(self, int u): r""" Returns the in-neighbors of a vertex
INPUT:
- ``u`` -- a vertex
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.in_neighbors(0) [1, 4, 5] sage: g.in_neighbors(10) Traceback (most recent call last): ... LookupError: The vertex does not belong to the graph """
raise LookupError("The vertex does not belong to the graph")
cdef int i
cpdef int out_degree(self, int u) except -1: r""" Returns the out-degree of a vertex
INPUT:
- ``u`` -- a vertex
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.out_degree(0) 3 sage: g.out_degree(10) Traceback (most recent call last): ... LookupError: The vertex does not belong to the graph """
cpdef int in_degree(self, int u) except -1: r""" Returns the in-degree of a vertex
INPUT:
- ``u`` -- a vertex
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseCGraph sage: g = StaticSparseCGraph(graphs.PetersenGraph()) sage: g.in_degree(0) 3 sage: g.in_degree(10) Traceback (most recent call last): ... LookupError: The vertex does not belong to the graph """
else:
cdef class StaticSparseBackend(CGraphBackend):
def __init__(self, G, loops = False, multiedges=False): """ A graph :mod:`backend <sage.graphs.base.graph_backends>` for static sparse graphs.
EXAMPLES::
sage: D = sage.graphs.base.sparse_graph.SparseGraphBackend(9) sage: D.add_edge(0,1,None,False) sage: list(D.iterator_edges(range(9), True)) [(0, 1, None)]
::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: list(g.iterator_edges([0],1)) [(0, 1, None), (0, 4, None), (0, 5, None)]
::
sage: g=DiGraph(digraphs.DeBruijn(4,3),data_structure="static_sparse") sage: gi=DiGraph(g,data_structure="static_sparse") sage: gi.edges()[0] ('000', '000', '0') sage: gi.edges_incident('111') [('111', '110', '0'), ('111', '111', '1'), ('111', '112', '2'), ('111', '113', '3')] sage: sorted(g.edges()) == sorted(gi.edges()) True
::
sage: g = graphs.PetersenGraph() sage: gi=Graph(g,data_structure="static_sparse") sage: g == gi True sage: sorted(g.edges()) == sorted(gi.edges()) True
::
sage: gi = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, data_structure="static_sparse") sage: (0,4,2) in gi.edges() True sage: gi.has_edge(0,4) True
::
sage: G = Graph({1:{2:28, 6:10}, 2:{3:16, 7:14}, 3:{4:12}, 4:{5:22, 7:18}, 5:{6:25, 7:24}}) sage: GI = Graph({1:{2:28, 6:10}, 2:{3:16, 7:14}, 3:{4:12}, 4:{5:22, 7:18}, 5:{6:25, 7:24}}, data_structure="static_sparse") sage: G == GI True
::
sage: G = graphs.OddGraph(4) sage: d = G.diameter() sage: H = G.distance_graph(list(range(d+1))) sage: HI = Graph(H,data_structure="static_sparse") sage: HI.size() == len(HI.edges()) True
::
sage: g = Graph({1:{1:[1,2,3]}}, data_structure="static_sparse") sage: g.size() 3 sage: g.order() 1 sage: g.vertices() [1] sage: g.edges() [(1, 1, 1), (1, 1, 2), (1, 1, 3)]
:trac:`15810` is fixed::
sage: DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}, immutable=True).is_directed_acyclic() True """
# Does it allow loops/multiedges ?
# Dictionary translating a vertex int to a label, and the other way around.
# Needed by CGraph. The first one is just an alias, and the second is # useless : accessing _vertex_to_labels (which is a list) is faster than # vertex_labels (which is a dictionary)
def has_vertex(self, v): r""" Tests if the vertex belongs to the graph
INPUT:
- ``v`` -- a vertex (or not?)
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.has_vertex(0) True sage: g.has_vertex("Hey") False """
def relabel(self, perm, directed): r""" Relabel the graphs' vertices. No way.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.relabel([],True) Traceback (most recent call last): ... ValueError: Thou shalt not relabel an immutable graph
"""
def get_edge_label(self, object u, object v): """ Returns the edge label for ``(u,v)``.
INPUT:
- ``u,v`` -- two vertices
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: print(g.get_edge_label(0,1)) None sage: print(g.get_edge_label(0,"Hey")) Traceback (most recent call last): ... LookupError: One of the two vertices does not belong to the graph sage: print(g.get_edge_label(0,7)) Traceback (most recent call last): ... LookupError: The edge does not exist
::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(digraphs.DeBruijn(3,2)) sage: g.has_edge('00','01','1') True sage: g.has_edge('00','01','0') False """
cdef list l
# At this level, edge points toward a edge from u to v in the graph, but # not necessarily to the leftmost edge. Hence, we first decrease edge to # make it point toward the leftmost such edge, then build the list of # all labels.
else:
def has_edge(self, object u, object v, object l): """ Returns whether this graph has edge ``(u,v)`` with label ``l``.
If ``l`` is ``None``, return whether this graph has an edge ``(u,v)`` with any label.
INPUT:
- ``u,v`` -- two vertices
- ``l`` -- a label
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.has_edge(0,1,'e') False sage: g.has_edge(0,4,None) True """ except KeyError: raise LookupError("One of the two vertices does not belong to the graph")
# At this level, edge points toward a edge from u to v in the graph, but # not necessarily toward the right label. As there may be many uv edges # with different labels, we first make edge point toward the leftmost uv # edge, then scan them all to find the right label.
def iterator_in_edges(self, object vertices, bint labels): """ Iterate over the incoming edges incident to a sequence of vertices.
INPUT:
- ``vertices`` -- a list of vertices
- ``labels`` -- whether to return labels too
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: list(g.iterator_in_edges([0],False)) [(0, 1), (0, 4), (0, 5)] sage: list(g.iterator_in_edges([0],True)) [(0, 1, None), (0, 4, None), (0, 5, None)]
::
sage: DiGraph(digraphs.Path(5),immutable=False).incoming_edges([2]) [(1, 2, None)] sage: DiGraph(digraphs.Path(5),immutable=True).incoming_edges([2]) [(1, 2, None)] """
except KeyError: raise LookupError("One of the vertices does not belong to the graph")
cdef int i,j vi, else: yield self._vertex_to_labels[cg.g_rev.neighbors[i][j]], vi
def iterator_out_edges(self, object vertices, bint labels): """ Iterate over the outbound edges incident to a sequence of vertices.
INPUT:
- ``vertices`` -- a list of vertices
- ``labels`` -- whether to return labels too
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: list(g.iterator_out_edges([0], False)) [(0, 1), (0, 4), (0, 5)] sage: list(g.iterator_out_edges([0],True)) [(0, 1, None), (0, 4, None), (0, 5, None)]
""" except KeyError: raise LookupError("One of the vertices does not belong to the graph")
cdef int i,j else:
def iterator_verts(self, vertices): r""" Returns an iterator over the vertices
INPUT:
- ``vertices`` -- a list of objects. The method will only return the elements of the graph which are contained in ``vertices``. It's not very efficient. If ``vertices`` is equal to ``None``, all the vertices are returned.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: list(g.iterator_verts(None)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] sage: list(g.iterator_verts([1,"Hey","I am a french fry"])) [1] """ else:
def num_verts(self): r""" Returns the number of vertices
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.num_verts() 10 """
def allows_loops(self, value=None): r""" Returns whether the graph allows loops
INPUT:
- ``value`` -- only useful for compatibility with other graph backends, where this method can be used to define this boolean. This method raises an exception if ``value`` is not equal to ``None``.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.allows_loops() False sage: g = StaticSparseBackend(graphs.PetersenGraph(), loops=True) sage: g.allows_loops() True """ else: raise ValueError("The graph is immutable. You cannot change it in any way !")
def multiple_edges(self, value=None): r""" Returns whether the graph allows multiple edges
INPUT:
- ``value`` -- only useful for compatibility with other graph backends, where this method can be used to define this boolean. This method raises an exception if ``value`` is not equal to ``None``.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.multiple_edges() False sage: g = StaticSparseBackend(graphs.PetersenGraph(), multiedges=True) sage: g.multiple_edges() True """ else: raise ValueError("The graph is immutable. You cannot change it in any way !")
def num_edges(self,directed): r""" Returns the number of edges
INPUT:
- ``directed`` (boolean) -- whether to consider the graph as directed or not.
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: g.num_edges(False) 15
Testing the exception::
sage: g = StaticSparseBackend(digraphs.Circuit(4)) sage: g.num_edges(False) Traceback (most recent call last): ... NotImplementedError: Sorry, I have no idea what is expected in this situation. I don't think that it is well-defined either, especially for multigraphs.
:trac:`15491`::
sage: g=digraphs.RandomDirectedGNP(10,.3) sage: gi=DiGraph(g,data_structure="static_sparse") sage: gi.size() == len(gi.edges()) True """
# Returns the real number of directed arcs else: # Returns twice the number of edges, minus the number of # loops. This is actually equal to the index of # cg.g.neighbors[cg.g.n] in the array `cg.g.edges` return int(cg.g.neighbors[cg.g.n]-cg.g.edges) else: "in this situation. I don't think " "that it is well-defined either, " "especially for multigraphs.") else: # Returns the number of edges
def iterator_edges(self, vertices, bint labels): r""" Returns an iterator over the graph's edges.
INPUT:
- ``vertices`` -- only returns the edges incident to at least one vertex of ``vertices``.
- ``labels`` -- whether to return edge labels too
TESTS::
sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend sage: g = StaticSparseBackend(graphs.PetersenGraph()) sage: list(g.iterator_edges(g.iterator_verts(None), False)) [(0, 1), (0, 4), (0, 5), (1, 2), (1, 6), (2, 3), (2, 7), (3, 4), (3, 8), (4, 9), (5, 7), (5, 8), (6, 8), (6, 9), (7, 9)]
:trac:`15665`::
sage: Graph(immutable=True).edges() [] """ cdef FrozenBitset b_vertices
raise RuntimeError("This is not meant for directed graphs.")
except KeyError: raise LookupError("One of the vertices does not belong to the graph")
cdef int i,j,tmp
else:
def degree(self, v, directed): r""" Returns the degree of a vertex
INPUT:
- ``v`` -- a vertex
- ``directed`` -- boolean; whether to take into account the orientation of this graph in counting the degree of ``v``.
EXAMPLES::
sage: g = Graph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.degree(0) 3
:trac:`17225` about the degree of a vertex with a loop::
sage: Graph({0:[0]},immutable=True).degree(0) 2 sage: Graph({0:[0],1:[0,1,1,1]},immutable=True).degree(1) 7 """ except KeyError: raise LookupError("The vertex does not belong to the graph")
else: return 2*cg.out_degree(v) else: raise NotImplementedError("Sorry, I have no idea what is expected " "in this situation. I don't think " "that it is well-defined either, " "especially for multigraphs.") else:
def in_degree(self, v): r""" Returns the in-degree of a vertex
INPUT:
- ``v`` -- a vertex
EXAMPLES::
sage: g = DiGraph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.in_degree(0) 3 """ except KeyError: raise LookupError("The vertex does not belong to the graph")
else: return cg.out_degree(v)
def out_degree(self, v): r""" Returns the out-degree of a vertex
INPUT:
- ``v`` -- a vertex
EXAMPLES::
sage: g = DiGraph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.out_degree(0) 3 """ except KeyError: raise LookupError("The vertex does not belong to the graph")
def iterator_nbrs(self, v): r""" Returns the neighbors of a vertex
INPUT:
- ``v`` -- a vertex
EXAMPLES::
sage: g = Graph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.neighbors(0) [1, 4, 5] """ except KeyError: raise LookupError("The vertex does not belong to the graph")
cdef int i
def iterator_out_nbrs(self, v): r""" Returns the out-neighbors of a vertex
INPUT:
- ``v`` -- a vertex
EXAMPLES::
sage: g = DiGraph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.neighbors_out(0) [1, 4, 5] """ except KeyError: raise LookupError("The vertex does not belong to the graph")
cdef int i
def iterator_in_nbrs(self, v): r""" Returns the out-neighbors of a vertex
INPUT:
- ``v`` -- a vertex
EXAMPLES::
sage: g = DiGraph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.neighbors_in(0) [1, 4, 5] """ except KeyError: raise LookupError("The vertex does not belong to the graph")
cdef short_digraph g
else: for i in range(out_degree(cg.g,v)): yield self._vertex_to_labels[cg.g.neighbors[v][i]]
def add_vertex(self,v): r""" Addition of vertices is not available on an immutable graph.
EXAMPLES::
sage: g = DiGraph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.add_vertex(1) Traceback (most recent call last): ... ValueError: Thou shalt not add a vertex to an immutable graph sage: g.add_vertices([1,2,3]) Traceback (most recent call last): ... ValueError: Thou shalt not add a vertex to an immutable graph """
def del_vertex(self,v): r""" Removal of vertices is not available on an immutable graph.
EXAMPLES::
sage: g = DiGraph(graphs.PetersenGraph(), data_structure="static_sparse") sage: g.delete_vertex(1) Traceback (most recent call last): ... ValueError: Thou shalt not remove a vertex from an immutable graph sage: g.delete_vertices([1,2,3]) Traceback (most recent call last): ... ValueError: Thou shalt not remove a vertex from an immutable graph """
def _run_it_on_static_instead(f): r""" A decorator function to force the (Di)Graph functions to compute from a static sparse graph3
This decorator can be used on methods from (Di)Graph. When it is applied, the method that was meant to compute something on a graph first converts this graph to a static sparse graph, then does what it had to do on this new graph. Of course, it makes no sense to decorate Graph.add_vertex with it as such a method will never work on an immutable graph. But it can help find new bugs, from time to time.
EXAMPLES::
sage: from sage.graphs.base.static_sparse_backend import _run_it_on_static_instead sage: @_run_it_on_static_instead ....: def new_graph_method(g): ....: print("My backend is of type {}".format(type(g._backend))) sage: Graph.new_graph_method = new_graph_method sage: g = Graph(5) sage: print("My backend is of type {}".format(type(g._backend))) My backend is of type <type 'sage.graphs.base.sparse_graph.SparseGraphBackend'> sage: g.new_graph_method() My backend is of type <type 'sage.graphs.base.static_sparse_backend.StaticSparseBackend'> """ else:
|