Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parameter sort_neighbors to method init_short_digraph #37662

Merged
merged 9 commits into from
Jun 9, 2024
3 changes: 2 additions & 1 deletion src/sage/graphs/asteroidal_triples.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ def is_asteroidal_triple_free(G, certificate=False):
# module sage.graphs.base.static_sparse_graph
cdef list int_to_vertex = list(G)
cdef short_digraph sd
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex,
sort_neighbors=False)

cdef bitset_t seen
bitset_init(seen, n)
Expand Down
7 changes: 6 additions & 1 deletion src/sage/graphs/base/static_sparse_graph.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ cdef extern from "stdlib.h":
void *bsearch(const_void *key, const_void *base, size_t nmemb,
size_t size, int(*compar)(const_void *, const_void *)) nogil

cdef extern from "search.h":
void *lfind(const_void *key, const_void *base, size_t *nmemb,
size_t size, int(*compar)(const_void *, const_void *)) nogil

ctypedef struct short_digraph_s:
uint32_t * edges
uint32_t ** neighbors
PyObject * edge_labels
int m
int n
bint sorted_neighbors

ctypedef short_digraph_s short_digraph[1]

cdef int init_short_digraph(short_digraph g, G, edge_labelled=?, vertex_list=?) except -1
cdef int init_short_digraph(short_digraph g, G, edge_labelled=?, vertex_list=?, sort_neighbors=?) except -1
cdef void free_short_digraph(short_digraph g) noexcept
cdef int init_reverse(short_digraph dst, short_digraph src) except -1
cdef int out_degree(short_digraph g, int u) noexcept
Expand Down
126 changes: 86 additions & 40 deletions src/sage/graphs/base/static_sparse_graph.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Technical details
-----------------

* When creating a ``short_digraph`` from a ``Graph`` or ``DiGraph`` named ``G``,
the `i^{\text{th}}` vertex corresponds *by default* to ``G.vertices(sort=True)[i]``.
the `i^{\text{th}}` vertex corresponds *by default* to ``list(G)[i]``.
Using optional parameter ``vertex_list``, you can specify the order of the
vertices. Then `i^{\text{th}}` vertex will corresponds to ``vertex_list[i]``.

Expand Down Expand Up @@ -117,7 +117,7 @@ Cython functions
:widths: 30, 70
:delim: |

``init_short_digraph(short_digraph g, G)`` | Initialize ``short_digraph g`` from a Sage (Di)Graph.
``init_short_digraph(short_digraph g, G, edge_labelled, vertex_list, sort_neighbors)`` | Initialize ``short_digraph g`` from a Sage (Di)Graph.
``int n_edges(short_digraph g)`` | Return the number of edges in ``g``
``int out_degree(short_digraph g, int i)`` | Return the out-degree of vertex `i` in ``g``
``has_edge(short_digraph g, int u, int v)`` | Test the existence of an edge.
Expand Down Expand Up @@ -205,23 +205,39 @@ cdef extern from "fenv.h":
int fesetround (int)


cdef int init_short_digraph(short_digraph g, G, edge_labelled=False, vertex_list=None) except -1:
cdef int init_short_digraph(short_digraph g, G, edge_labelled=False,
vertex_list=None, sort_neighbors=True) except -1:
r"""
Initialize ``short_digraph g`` from a Sage (Di)Graph.

If ``G`` is a ``Graph`` object (and not a ``DiGraph``), an edge between two
vertices `u` and `v` is replaced by two arcs in both directions.
INPUT:

- ``g`` -- a short_digraph

- ``G`` -- a ``Graph`` or a ``DiGraph``. If ``G`` is a ``Graph`` object,
then any edge between two vertices `u` and `v` is replaced by two arcs in
both directions.

- ``edge_labelled`` -- boolean (default: ``False``); whether to store the
label of edges or not

- ``vertex_list`` -- list (default: ``None``); list of all vertices of ``G``
in some order. When given, it is used to map the vertices of the graph to
consecutive integers. Otherwise, the result of ``list(G)`` is used
instead.

- ``sort_neighbors`` -- boolean (default: ``True``); whether to ensure that
the vertices in the list of neighbors of a vertex are sorted by increasing
vertex labels. This choice may have a non-negligeable impact on the time
complexity of some methods. More precisely:

The optional argument ``vertex_list`` is assumed to be a list of all
vertices of the graph ``G`` in some order.
**Beware that no checks are made that this input is correct**.
- When set to ``True``, the time complexity for initializing ``g`` is in
`O(m + n\log{m})` and deciding if ``g`` has edge `(u, v)` can be done in
time `O(\log{m})` using binary search.

If ``vertex_list`` is given, it will be used to map vertices of
the graph to consecutive integers. Otherwise, the result of
``G.vertices(sort=True)`` will be used instead. Because this only
works if the vertices can be sorted, using ``vertex_list`` is
useful when working with possibly non-sortable objects in Python
3.
- When set to ``False``, the time complexity for initializing ``g`` is
reduced to `O(n + m)` but the time complexity for deciding if ``g`` has
edge `(u, v)` increases to `O(m)`.
"""
g.edge_labels = NULL

Expand All @@ -243,7 +259,7 @@ cdef int init_short_digraph(short_digraph g, G, edge_labelled=False, vertex_list
raise ValueError("The source graph must be either a DiGraph or a Graph object !")

cdef int i, j, v_id
cdef list vertices = vertex_list if vertex_list is not None else G.vertices(sort=True)
cdef list vertices = vertex_list if vertex_list is not None else list(G)
cdef dict v_to_id = {v: i for i, v in enumerate(vertices)}
cdef list neighbor_label
cdef list edge_labels
Expand All @@ -257,6 +273,7 @@ cdef int init_short_digraph(short_digraph g, G, edge_labelled=False, vertex_list
# Initializing the value of neighbors
g.neighbors[0] = g.edges
cdef CGraph cg = <CGraph> G._backend
g.sorted_neighbors = sort_neighbors

if not G.has_loops():
# Normal case
Expand All @@ -273,7 +290,7 @@ cdef int init_short_digraph(short_digraph g, G, edge_labelled=False, vertex_list
g.neighbors[i] = g.neighbors[i - 1] + <int> len(G.edges_incident(vertices[i - 1]))

if not edge_labelled:
for u, v in G.edge_iterator(labels=False):
for u, v in G.edge_iterator(labels=False, sort_vertices=False):
i = v_to_id[u]
j = v_to_id[v]

Expand All @@ -290,22 +307,33 @@ cdef int init_short_digraph(short_digraph g, G, edge_labelled=False, vertex_list

g.neighbors[0] = g.edges

# Sorting the neighbors
for i in range(g.n):
qsort(g.neighbors[i], g.neighbors[i + 1] - g.neighbors[i], sizeof(int), compare_uint32_p)
if sort_neighbors:
# Sorting the neighbors
for i in range(g.n):
qsort(g.neighbors[i], g.neighbors[i + 1] - g.neighbors[i], sizeof(int), compare_uint32_p)

else:
from operator import itemgetter
edge_labels = [None] * n_edges
for v in G:
neighbor_label = [(v_to_id[uu], l) if uu != v else (v_to_id[u], l)
for u, uu, l in G.edges_incident(v)]
neighbor_label.sort(key=itemgetter(0))
v_id = v_to_id[v]

for i, (j, label) in enumerate(neighbor_label):
g.neighbors[v_id][i] = j
edge_labels[(g.neighbors[v_id] + i) - g.edges] = label
if sort_neighbors:
for v in G:
neighbor_label = [(v_to_id[uu], l) if uu != v else (v_to_id[u], l)
for u, uu, l in G.edges_incident(v)]
neighbor_label.sort(key=itemgetter(0))
v_id = v_to_id[v]

for i, (j, label) in enumerate(neighbor_label):
g.neighbors[v_id][i] = j
edge_labels[(g.neighbors[v_id] + i) - g.edges] = label
else:
for v in G:
v_id = v_to_id[v]
for i, (u, uu, label) in enumerate(G.edges_incident(v)):
if v == uu:
g.neighbors[v_id][i] = v_to_id[u]
else:
g.neighbors[v_id][i] = v_to_id[uu]
edge_labels[(g.neighbors[v_id] + i) - g.edges] = label

g.edge_labels = <PyObject *> <void *> edge_labels
cpython.Py_XINCREF(g.edge_labels)
Expand Down Expand Up @@ -336,6 +364,7 @@ cdef int init_empty_copy(short_digraph dst, short_digraph src) except -1:
"""
dst.n = src.n
dst.m = src.m
dst.sorted_neighbors = src.sorted_neighbors
dst.edge_labels = NULL
cdef list edge_labels

Expand All @@ -361,7 +390,7 @@ cdef int init_reverse(short_digraph dst, short_digraph src) except -1:
if not dst.n:
return 0

# 1/3
# 1/4
#
# In a first pass, we count the in-degrees of each vertex and store it in a
# vector. With this information, we can initialize dst.neighbors to its
Expand All @@ -377,7 +406,7 @@ cdef int init_reverse(short_digraph dst, short_digraph src) except -1:
dst.neighbors[i] = dst.neighbors[i - 1] + in_degree[i - 1]
sig_free(in_degree)

# 2/3
# 2/4
#
# Second pass : we list the edges again, and add them in dst.edges. Doing
# so, we will change the value of dst.neighbors, but that is not so bad as
Expand All @@ -392,20 +421,28 @@ cdef int init_reverse(short_digraph dst, short_digraph src) except -1:

dst.neighbors[v] += 1

# 3/3
# 3/4
#
# Final step : set the correct values of dst.neighbors again. It is easy, as
# Third step : set the correct values of dst.neighbors again. It is easy, as
# the correct value of dst.neighbors[i] is actually dst.neighbors[i-1]
for i in range(src.n - 1, 0, -1):
dst.neighbors[i] = dst.neighbors[i - 1]
dst.neighbors[0] = dst.edges

# 4/4
#
# Final step : if the neighbors of src are assumed to be sorted by
# increasing labels, we do the same for dst.
if src.sorted_neighbors:
for i in range(dst.n):
qsort(dst.neighbors[i], dst.neighbors[i + 1] - dst.neighbors[i], sizeof(int), compare_uint32_p)

return 0


cdef int compare_uint32_p(const_void *a, const_void *b) noexcept:
"""
Comparison function needed for ``bsearch``.
Comparison function needed for ``bsearch`` and ``lfind``.
"""
return (<uint32_t *> a)[0] - (<uint32_t *> b)[0]

Expand All @@ -414,9 +451,19 @@ cdef inline uint32_t * has_edge(short_digraph g, int u, int v) noexcept:
r"""
Test the existence of an edge.

Assumes that the neighbors of each vertex are sorted.
Return a pointer to ``v`` in the list of neighbors of ``u`` if found and
``NULL`` otherwise.
"""
return <uint32_t *> bsearch(&v, g.neighbors[u], g.neighbors[u + 1] - g.neighbors[u], sizeof(uint32_t), compare_uint32_p)
if g.sorted_neighbors:
# The neighbors of u are sorted by increasing label. We can use binary
# search to decide if g has edge (u, v)
return <uint32_t *> bsearch(&v, g.neighbors[u], g.neighbors[u + 1] - g.neighbors[u],
sizeof(uint32_t), compare_uint32_p)

# Otherwise, we use the linear time lfind method
cdef size_t nelem = g.neighbors[u + 1] - g.neighbors[u]
return <uint32_t *> lfind(&v, g.neighbors[u], &nelem,
sizeof(uint32_t), compare_uint32_p)


cdef inline object edge_label(short_digraph g, uint32_t * edge):
Expand All @@ -425,8 +472,7 @@ cdef inline object edge_label(short_digraph g, uint32_t * edge):
"""
if not g.edge_labels:
return None
else:
return (<list> g.edge_labels)[edge - g.edges]
return (<list> g.edge_labels)[edge - g.edges]


cdef uint32_t simple_BFS(short_digraph g,
Expand Down Expand Up @@ -751,7 +797,7 @@ def tarjan_strongly_connected_components(G):
cdef MemoryAllocator mem = MemoryAllocator()
cdef list int_to_vertex = list(G)
cdef short_digraph g
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex, sort_neighbors=False)
cdef int * scc = <int*> mem.malloc(g.n * sizeof(int))
sig_on()
cdef int nscc = tarjan_strongly_connected_components_C(g, scc)
Expand Down Expand Up @@ -868,7 +914,7 @@ def strongly_connected_components_digraph(G):
cdef MemoryAllocator mem = MemoryAllocator()
cdef list int_to_vertex = list(G)
cdef short_digraph g, scc_g
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex, sort_neighbors=False)
cdef int * scc = <int*> mem.malloc(g.n * sizeof(int))
cdef int i, j, nscc
cdef list edges = []
Expand Down Expand Up @@ -933,7 +979,7 @@ def triangles_count(G):
# g is a copy of G. If G is internally a static sparse graph, we use it.
cdef list int_to_vertex = list(G)
cdef short_digraph g
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex, sort_neighbors=True)

cdef uint64_t * count = <uint64_t *> check_calloc(G.order(), sizeof(uint64_t))

Expand Down
6 changes: 3 additions & 3 deletions src/sage/graphs/centrality.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ cdef dict centrality_betweenness_C(G, numerical_type _, bint normalize=True):
mpq_init(mpq_tmp)

try:
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex, sort_neighbors=False)
init_reverse(bfs_dag, g)

queue = <uint32_t*> check_allocarray(n, sizeof(uint32_t))
Expand Down Expand Up @@ -691,7 +691,7 @@ def centrality_closeness_top_k(G, int k=1, int verbose=0):
# calling out_neighbors. This data structure is well documented in the
# module sage.graphs.base.static_sparse_graph
cdef list V = list(G)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=V)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=V, sort_neighbors=False)
cdef int n = sd.n
cdef int* reachL = <int*> mem.malloc(n * sizeof(int))
cdef int* reachU
Expand Down Expand Up @@ -944,7 +944,7 @@ def centrality_closeness_random_k(G, int k=1):
# Copying the whole graph as a static_sparse_graph for fast shortest
# paths computation in unweighted graph. This data structure is well
# documented in module sage.graphs.base.static_sparse_graph
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex, sort_neighbors=False)
distance = <uint32_t*> mem.malloc(n * sizeof(uint32_t))
waiting_list = <uint32_t*> mem.malloc(n * sizeof(uint32_t))
bitset_init(seen, n)
Expand Down
2 changes: 1 addition & 1 deletion src/sage/graphs/convexity_properties.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ def is_geodetic(G):
# Copy the graph as a short digraph
cdef int n = G.order()
cdef short_digraph sd
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G), sort_neighbors=False)

# Allocate some data structures
cdef MemoryAllocator mem = MemoryAllocator()
Expand Down
18 changes: 12 additions & 6 deletions src/sage/graphs/distances_all_pairs.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@ cdef inline all_pairs_shortest_path_BFS(gg,
# Copying the whole graph to obtain the list of neighbors quicker than by
# calling out_neighbors
cdef short_digraph sd
init_short_digraph(sd, gg, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(sd, gg, edge_labelled=False, vertex_list=int_to_vertex,
sort_neighbors=False)

c_all_pairs_shortest_path_BFS(sd, predecessors, distances, eccentricity)

Expand Down Expand Up @@ -1058,7 +1059,8 @@ def eccentricity(G, algorithm="standard", vertex_list=None):
ecc = c_eccentricity(G, vertex_list=int_to_vertex)

else:
init_short_digraph(sd, G, edge_labelled=False, vertex_list=vertex_list)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=vertex_list,
sort_neighbors=False)

if algorithm == "DHV":
ecc = c_eccentricity_DHV(sd)
Expand Down Expand Up @@ -1833,7 +1835,8 @@ def diameter(G, algorithm=None, source=None):
# module sage.graphs.base.static_sparse_graph
cdef list int_to_vertex = list(G)
cdef short_digraph sd
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex,
sort_neighbors=False)
cdef short_digraph rev_sd # to store copy of sd with edges reversed

# and we map the source to an int in [0,n-1]
Expand Down Expand Up @@ -1935,7 +1938,8 @@ def radius_DHV(G):

cdef list int_to_vertex = list(G)
cdef short_digraph sd
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex,
sort_neighbors=False)

cdef uint32_t source, ecc_source
cdef uint32_t antipode, ecc_antipode
Expand Down Expand Up @@ -2052,7 +2056,8 @@ def wiener_index(G):
# calling out_neighbors. This data structure is well documented in the
# module sage.graphs.base.static_sparse_graph
cdef short_digraph sd
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G),
sort_neighbors=False)

# allocated some data structures
cdef bitset_t seen
Expand Down Expand Up @@ -2364,12 +2369,13 @@ def szeged_index(G, algorithm=None):
return 0

cdef short_digraph sd
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
cdef uint64_t s

if algorithm is "low":
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G), sort_neighbors=True)
s = c_szeged_index_low_memory(sd)
else:
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G), sort_neighbors=False)
s = c_szeged_index_high_memory(sd)

free_short_digraph(sd)
Expand Down
Loading
Loading