Skip to content

Commit 6c348ba

Browse files
author
Release Manager
committed
gh-39217: improve graph traversal methods Similarly to #39216, we avoid the conversion to `short_digraph` when the input graph is an instance of `StaticSparseBackend`. Furthermore, we use the new `PairingHeap_of_n_integers` data structure (#39046) instead of a `priority_queue` to emulate a max-heap. This is slightly faster this way and cleaner. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39217 Reported by: David Coudert Reviewer(s): Travis Scrimshaw
2 parents 3aaabb5 + 438865c commit 6c348ba

File tree

1 file changed

+113
-46
lines changed

1 file changed

+113
-46
lines changed

src/sage/graphs/traversals.pyx

Lines changed: 113 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,17 @@ from collections import deque
6363

6464
from libc.string cimport memset
6565
from libc.stdint cimport uint32_t
66-
from libcpp.queue cimport priority_queue
67-
from libcpp.pair cimport pair
6866
from libcpp.vector cimport vector
6967
from cysignals.signals cimport sig_on, sig_off
7068
from memory_allocator cimport MemoryAllocator
7169

70+
from sage.data_structures.pairing_heap cimport PairingHeap_of_n_integers
71+
from sage.graphs.base.c_graph cimport CGraph, CGraphBackend
72+
from sage.graphs.base.static_sparse_backend cimport StaticSparseCGraph
73+
from sage.graphs.base.static_sparse_backend cimport StaticSparseBackend
7274
from sage.graphs.base.static_sparse_graph cimport init_short_digraph
7375
from sage.graphs.base.static_sparse_graph cimport free_short_digraph
7476
from sage.graphs.base.static_sparse_graph cimport out_degree
75-
from sage.graphs.base.c_graph cimport CGraph, CGraphBackend
7677
from sage.graphs.graph_decompositions.slice_decomposition cimport \
7778
extended_lex_BFS
7879

@@ -753,8 +754,7 @@ def lex_M(self, triangulation=False, labels=False, initial_vertex=None, algorith
753754
- ``labels`` -- boolean (default: ``False``); whether to return the labels
754755
assigned to each vertex
755756
756-
- ``initial_vertex`` -- (default: ``None``) the first vertex to
757-
consider
757+
- ``initial_vertex`` -- (default: ``None``); the first vertex to consider
758758
759759
- ``algorithm`` -- string (default: ``None``); one of the following
760760
algorithms:
@@ -820,6 +820,18 @@ def lex_M(self, triangulation=False, labels=False, initial_vertex=None, algorith
820820
sage: g.lex_M()
821821
[6, 4, 5, 3, 2, 1]
822822
823+
The ordering depends on the initial vertex::
824+
825+
sage: G = graphs.HouseGraph()
826+
sage: G.lex_M(algorithm='lex_M_slow', initial_vertex=0)
827+
[4, 3, 2, 1, 0]
828+
sage: G.lex_M(algorithm='lex_M_slow', initial_vertex=2)
829+
[1, 4, 3, 0, 2]
830+
sage: G.lex_M(algorithm='lex_M_fast', initial_vertex=0)
831+
[4, 3, 2, 1, 0]
832+
sage: G.lex_M(algorithm='lex_M_fast', initial_vertex=2)
833+
[1, 4, 3, 0, 2]
834+
823835
TESTS:
824836
825837
``'lex_M_fast'`` cannot return labels::
@@ -1127,6 +1139,18 @@ def lex_M_fast(G, triangulation=False, initial_vertex=None):
11271139
Traceback (most recent call last):
11281140
...
11291141
ValueError: 'foo' is not a graph vertex
1142+
1143+
Immutable graphs::
1144+
1145+
sage: from sage.graphs.traversals import lex_M_fast
1146+
sage: G = graphs.RandomGNP(10, .7)
1147+
sage: G._backend
1148+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
1149+
sage: H = Graph(G, immutable=True)
1150+
sage: H._backend
1151+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
1152+
sage: lex_M_fast(G) == lex_M_fast(H)
1153+
True
11301154
"""
11311155
if initial_vertex is not None and initial_vertex not in G:
11321156
raise ValueError("'{}' is not a graph vertex".format(initial_vertex))
@@ -1136,23 +1160,31 @@ def lex_M_fast(G, triangulation=False, initial_vertex=None):
11361160

11371161
# ==> Initialization
11381162

1139-
cdef list int_to_v = list(G)
11401163
cdef int i, j, k, v, w, z
11411164

1142-
if initial_vertex is not None:
1143-
# We put the initial vertex at first place in the ordering
1144-
i = int_to_v.index(initial_vertex)
1145-
int_to_v[0], int_to_v[i] = int_to_v[i], int_to_v[0]
1146-
1165+
cdef list int_to_v
1166+
cdef StaticSparseCGraph cg
11471167
cdef short_digraph sd
1148-
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v)
1168+
if isinstance(G, StaticSparseBackend):
1169+
cg = <StaticSparseCGraph> G._cg
1170+
sd = <short_digraph> cg.g
1171+
int_to_v = cg._vertex_to_labels
1172+
else:
1173+
int_to_v = list(G)
1174+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v)
1175+
11491176
cdef uint32_t* p_tmp
11501177
cdef uint32_t* p_end
11511178

11521179
cdef int n = G.order()
11531180

11541181
cdef list unnumbered_vertices = list(range(n))
11551182

1183+
if initial_vertex is not None:
1184+
# We put the initial vertex at the first place
1185+
i = int_to_v.index(initial_vertex)
1186+
unnumbered_vertices[0], unnumbered_vertices[i] = unnumbered_vertices[i], unnumbered_vertices[0]
1187+
11561188
cdef MemoryAllocator mem = MemoryAllocator()
11571189
cdef int* label = <int*>mem.allocarray(n, sizeof(int))
11581190
cdef int* alpha = <int*>mem.allocarray(n, sizeof(int))
@@ -1237,7 +1269,8 @@ def lex_M_fast(G, triangulation=False, initial_vertex=None):
12371269
k += 2
12381270
label[w] = k
12391271

1240-
free_short_digraph(sd)
1272+
if not isinstance(G, StaticSparseBackend):
1273+
free_short_digraph(sd)
12411274

12421275
cdef list ordering = [int_to_v[alpha[i]] for i in range(n)]
12431276

@@ -1354,9 +1387,9 @@ def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None
13541387
sage: G.maximum_cardinality_search(initial_vertex=0)
13551388
[3, 2, 1, 0]
13561389
sage: G.maximum_cardinality_search(initial_vertex=1)
1357-
[0, 3, 2, 1]
1390+
[3, 2, 0, 1]
13581391
sage: G.maximum_cardinality_search(initial_vertex=2)
1359-
[0, 1, 3, 2]
1392+
[0, 3, 1, 2]
13601393
sage: G.maximum_cardinality_search(initial_vertex=3)
13611394
[0, 1, 2, 3]
13621395
sage: G.maximum_cardinality_search(initial_vertex=3, reverse=True)
@@ -1388,6 +1421,17 @@ def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None
13881421
Traceback (most recent call last):
13891422
...
13901423
ValueError: vertex (17) is not a vertex of the graph
1424+
1425+
Immutable graphs;:
1426+
1427+
sage: G = graphs.RandomGNP(10, .7)
1428+
sage: G._backend
1429+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
1430+
sage: H = Graph(G, immutable=True)
1431+
sage: H._backend
1432+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
1433+
sage: G.maximum_cardinality_search() == H.maximum_cardinality_search()
1434+
True
13911435
"""
13921436
if tree:
13931437
from sage.graphs.digraph import DiGraph
@@ -1398,17 +1442,27 @@ def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None
13981442
if N == 1:
13991443
return (list(G), DiGraph(G)) if tree else list(G)
14001444

1401-
cdef list int_to_vertex = list(G)
1445+
cdef list int_to_vertex
1446+
cdef StaticSparseCGraph cg
1447+
cdef short_digraph sd
1448+
if isinstance(G, StaticSparseBackend):
1449+
cg = <StaticSparseCGraph> G._cg
1450+
sd = <short_digraph> cg.g
1451+
int_to_vertex = cg._vertex_to_labels
1452+
else:
1453+
int_to_vertex = list(G)
1454+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
14021455

14031456
if initial_vertex is None:
14041457
initial_vertex = 0
14051458
elif initial_vertex in G:
1406-
initial_vertex = int_to_vertex.index(initial_vertex)
1459+
if isinstance(G, StaticSparseBackend):
1460+
initial_vertex = cg._vertex_to_int[initial_vertex]
1461+
else:
1462+
initial_vertex = int_to_vertex.index(initial_vertex)
14071463
else:
14081464
raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex))
14091465

1410-
cdef short_digraph sd
1411-
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
14121466
cdef uint32_t** p_vertices = sd.neighbors
14131467
cdef uint32_t* p_tmp
14141468
cdef uint32_t* p_end
@@ -1420,27 +1474,18 @@ def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None
14201474

14211475
cdef int i, u, v
14221476
for i in range(N):
1423-
weight[i] = 0
1424-
seen[i] = False
14251477
pred[i] = i
14261478

1427-
# We emulate a heap with decrease key operation using a priority queue.
1428-
# A vertex can be inserted multiple times (up to its degree), but only the
1429-
# first extraction (with maximum weight) matters. The size of the queue will
1430-
# never exceed O(m).
1431-
cdef priority_queue[pair[int, int]] pq
1432-
pq.push((0, initial_vertex))
1479+
# We emulate a max-heap data structure using a min-heap with negative values
1480+
cdef PairingHeap_of_n_integers P = PairingHeap_of_n_integers(N)
1481+
P.push(initial_vertex, 0)
14331482

14341483
# The ordering alpha is feed in reversed order and revert afterword
14351484
cdef list alpha = []
14361485

1437-
while not pq.empty():
1438-
_, u = pq.top()
1439-
pq.pop()
1440-
if seen[u]:
1441-
# We use a lazy decrease key mode, so u can be several times in pq
1442-
continue
1443-
1486+
while P:
1487+
u = P.top_item()
1488+
P.pop()
14441489
alpha.append(int_to_vertex[u])
14451490
seen[u] = True
14461491

@@ -1450,12 +1495,13 @@ def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None
14501495
v = p_tmp[0]
14511496
if not seen[v]:
14521497
weight[v] += 1
1453-
pq.push((weight[v], v))
1498+
P.decrease(v, -weight[v])
14541499
if pred[v] == v:
14551500
pred[v] = u
14561501
p_tmp += 1
14571502

1458-
free_short_digraph(sd)
1503+
if not isinstance(G, StaticSparseBackend):
1504+
free_short_digraph(sd)
14591505

14601506
if len(alpha) < N:
14611507
raise ValueError("the input graph is not connected")
@@ -1762,16 +1808,18 @@ def maximum_cardinality_search_M(G, initial_vertex=None):
17621808
Traceback (most recent call last):
17631809
...
17641810
ValueError: vertex (17) is not a vertex of the graph
1765-
"""
1766-
cdef list int_to_vertex = list(G)
17671811
1768-
if initial_vertex is None:
1769-
initial_vertex = 0
1770-
elif initial_vertex in G:
1771-
initial_vertex = int_to_vertex.index(initial_vertex)
1772-
else:
1773-
raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex))
1812+
Immutable graphs::
17741813
1814+
sage: G = graphs.RandomGNP(10, .7)
1815+
sage: G._backend
1816+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
1817+
sage: H = Graph(G, immutable=True)
1818+
sage: H._backend
1819+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
1820+
sage: G.maximum_cardinality_search_M() == H.maximum_cardinality_search_M()
1821+
True
1822+
"""
17751823
cdef int N = G.order()
17761824
if not N:
17771825
return ([], [], [])
@@ -1781,8 +1829,26 @@ def maximum_cardinality_search_M(G, initial_vertex=None):
17811829
# Copying the whole graph to obtain the list of neighbors quicker than by
17821830
# calling out_neighbors. This data structure is well documented in the
17831831
# module sage.graphs.base.static_sparse_graph
1832+
cdef list int_to_vertex
1833+
cdef StaticSparseCGraph cg
17841834
cdef short_digraph sd
1785-
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
1835+
if isinstance(G, StaticSparseBackend):
1836+
cg = <StaticSparseCGraph> G._cg
1837+
sd = <short_digraph> cg.g
1838+
int_to_vertex = cg._vertex_to_labels
1839+
else:
1840+
int_to_vertex = list(G)
1841+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
1842+
1843+
if initial_vertex is None:
1844+
initial_vertex = 0
1845+
elif initial_vertex in G:
1846+
if isinstance(G, StaticSparseBackend):
1847+
initial_vertex = cg._vertex_to_int[initial_vertex]
1848+
else:
1849+
initial_vertex = int_to_vertex.index(initial_vertex)
1850+
else:
1851+
raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex))
17861852

17871853
cdef MemoryAllocator mem = MemoryAllocator()
17881854
cdef int* alpha = <int*>mem.calloc(N, sizeof(int))
@@ -1794,7 +1860,8 @@ def maximum_cardinality_search_M(G, initial_vertex=None):
17941860
maximum_cardinality_search_M_short_digraph(sd, initial_vertex, alpha, alpha_inv, F, X)
17951861
sig_off()
17961862

1797-
free_short_digraph(sd)
1863+
if not isinstance(G, StaticSparseBackend):
1864+
free_short_digraph(sd)
17981865

17991866
cdef int u, v
18001867
return ([int_to_vertex[alpha[u]] for u in range(N)],

0 commit comments

Comments
 (0)