Skip to content
This repository was archived by the owner on Feb 1, 2023. It is now read-only.

Commit dffbb69

Browse files
committed
28531: fix bliss encoding of sage graphs
1 parent 0f20b37 commit dffbb69

File tree

1 file changed

+148
-85
lines changed

1 file changed

+148
-85
lines changed

src/sage/graphs/bliss.pyx

Lines changed: 148 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ AUTHORS:
2727
# (at your option) any later version.
2828
# http://www.gnu.org/licenses/
2929
# ****************************************************************************
30-
import numpy
3130

3231
from libc.limits cimport LONG_MAX
3332

@@ -61,6 +60,20 @@ cdef extern from "bliss/graph.hh" namespace "bliss":
6160
unsigned int get_hash()
6261

6362

63+
cdef int encoding_numbits(int n):
64+
r"""
65+
Return the number of bits needed to encode the ``n`` numbers from ``1`` to ``n``. In
66+
other words, the last bit set in ``n``.
67+
"""
68+
if n <= 0:
69+
return 0
70+
cdef int i = 0
71+
while n:
72+
n >>= 1
73+
i += 1
74+
return i
75+
76+
6477
cdef void add_gen(void *user_param, unsigned int n, const unsigned int *aut):
6578
r"""
6679
Function called each time a new generator of the automorphism group is
@@ -79,33 +92,30 @@ cdef void add_gen(void *user_param, unsigned int n, const unsigned int *aut):
7992
8093
- ``aut`` -- ``int *``; an automorphism of the graph
8194
"""
95+
cdef int N
8296
cdef int tmp = 0
83-
cdef int marker = 0
8497
cdef int cur = 0
8598
cdef list perm = []
8699
cdef bint* done = <bint*> check_calloc(n, sizeof(bint))
87100
cdef int i
88-
for i in range(n):
89-
done[i] = False
90101

91-
gens, int_to_vertex = <object>user_param
102+
gens, int_to_vertex, N = <object>user_param
103+
104+
while cur < N:
105+
if not done[cur]:
106+
tmp = cur
107+
cycle = [int_to_vertex[cur]]
108+
done[cur] = True
92109

93-
while True:
94-
while cur < n and done[cur]:
95-
cur += 1
96-
if cur == n:
97-
break
110+
while aut[tmp] != cur:
111+
tmp = aut[tmp]
112+
done[tmp] = True
113+
cycle.append(int_to_vertex[tmp])
98114

99-
marker = tmp = cur
100-
cycle = [int_to_vertex[cur]]
101-
done[cur] = True
115+
perm.append(tuple(cycle))
102116

103-
while aut[tmp] != marker:
104-
tmp = aut[tmp]
105-
done[tmp] = True
106-
cycle.append(int_to_vertex[tmp])
117+
cur += 1
107118

108-
perm.append(tuple(cycle))
109119
gens.append(perm)
110120

111121
sig_free(done)
@@ -125,6 +135,20 @@ cdef Graph *bliss_graph_from_labelled_edges(int Vnr, int Lnr, Vout, Vin, labels,
125135
\log(Lnr)` many vertices as described in Sec. 14 of the `nauty reference
126136
manual <http://pallini.di.uniroma1.it/Guide.html>`_.
127137
138+
More precisely, let `V` the vertices of the original graph. We
139+
construct the new graph on the vertex set
140+
`V \times \{0, \ldots, log(Lnr)\}`. The integers in the second factor of
141+
`V \times \{0, \ldots, \log(Lnr)` encode the coloring of the edges. Then
142+
143+
- for each vertex `v` in `G` and each `i` in `0`, ..., `log(Lnr)-1`, we
144+
add an edge from `(v, i)` to `(v, i+1)`
145+
146+
- for each edge `e` from `u` to `v` with label `lab` in `G` we add edges
147+
from `(u, i)` to `(v, i)` for each `i` so that the `i`-th bit of `lab+1`
148+
is set (recall that the labels range from `0` to `Lnr-1` and hence the
149+
binary encoding of `1` to `Lnr` is so that at least one bit is set and
150+
their binary encoding has length at most `log(Lnr)`)
151+
128152
.. WARNING::
129153
130154
the input is not checked for correctness, any wrong input will result in
@@ -144,56 +168,59 @@ cdef Graph *bliss_graph_from_labelled_edges(int Vnr, int Lnr, Vout, Vin, labels,
144168
145169
- ``labels`` -- ``list``; the list of edge labels
146170
147-
- ``partition`` -- a partition of the vertex set
171+
- ``partition`` -- an ordered partition of the vertex set
148172
"""
149-
cdef Py_ssize_t i, j
150-
cdef int logLnr = 0
151-
cdef str binrep
152-
153-
cdef Graph *g
154-
cdef int x,y, lab
173+
cdef Graph * g
174+
cdef int i, j, x, y, lab, Pnr, Enr
175+
cdef int logLnr = 1
155176

156-
if Lnr == 1:
177+
if Lnr <= 1:
157178
g = new Graph(Vnr)
158-
if not g:
159-
raise MemoryError("allocation failed")
160179
else:
161-
logLnr = len(numpy.binary_repr(Lnr))
180+
logLnr = encoding_numbits(Lnr)
162181
g = new Graph(Vnr * logLnr)
163-
if not g:
164-
raise MemoryError("allocation failed")
165-
for j in range(1, logLnr):
166-
for i in range((j - 1) * Vnr, j * Vnr):
167-
g.add_edge(i, i + Vnr)
182+
if not g:
183+
raise MemoryError("allocation failed")
168184

169-
cdef int Enr = len(Vout)
185+
Enr = len(Vout)
170186

171-
for i in range(Enr):
172-
x = Vout[i]
173-
y = Vin[i]
174-
if Lnr == 1:
175-
lab = 0
176-
else:
177-
lab = labels[i]
187+
if Lnr <= 1:
188+
for i in range(Enr):
189+
x = Vout[i]
190+
y = Vin[i]
191+
g.add_edge(x, y)
178192

179-
if lab:
180-
lab += 1
181-
for j in range(logLnr - 1, -1, -1):
182-
if lab & (1 << j):
193+
else:
194+
# arrows going up in layers
195+
for i in range(Vnr * (logLnr - 1)):
196+
g.add_edge(i, i + Vnr)
197+
198+
# arrows inside layers shadowing the original graph
199+
for i in range(Enr):
200+
x = Vout[i]
201+
y = Vin[i]
202+
lab = labels[i] + 1
203+
204+
j = 0
205+
while lab:
206+
if lab & 1:
183207
g.add_edge(j * Vnr + x, j * Vnr + y)
184-
else:
185-
g.add_edge(x, y)
208+
j += 1
209+
lab >>= 1
186210

187-
if not bool(partition):
188-
partition = [list(range(Vnr))]
189-
cdef int Pnr = len(partition)
190-
for i in range(Pnr):
191-
for v in partition[i]:
192-
if Lnr == 1:
193-
g.change_color(v, i)
194-
else:
211+
# vertex partition gives colors
212+
if partition:
213+
Pnr = len(partition)
214+
for i in range(len(partition)):
215+
for v in partition[i]:
195216
for j in range(logLnr):
196217
g.change_color(j * Vnr + v, j * Pnr + i)
218+
else:
219+
Pnr = 1
220+
for j in range(logLnr):
221+
for v in range(Vnr):
222+
g.change_color(j * Vnr + v, j)
223+
197224
return g
198225

199226
cdef Digraph *bliss_digraph_from_labelled_edges(int Vnr, int Lnr, Vout, Vin, labels, partition):
@@ -227,7 +254,6 @@ cdef Digraph *bliss_digraph_from_labelled_edges(int Vnr, int Lnr, Vout, Vin, lab
227254
"""
228255
cdef Py_ssize_t i, j
229256
cdef int logLnr = 0
230-
cdef str binrep
231257

232258
cdef Digraph *g
233259
cdef int x, y, lab
@@ -237,7 +263,7 @@ cdef Digraph *bliss_digraph_from_labelled_edges(int Vnr, int Lnr, Vout, Vin, lab
237263
if not g:
238264
raise MemoryError("allocation failed")
239265
else:
240-
logLnr = len(numpy.binary_repr(Lnr))
266+
logLnr = encoding_numbits(Lnr)
241267
g = new Digraph(Vnr * logLnr)
242268
if not g:
243269
raise MemoryError("allocation failed")
@@ -376,7 +402,9 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True
376402
canonical graph of ``G`` or its set of edges
377403
378404
- ``use_edge_labels`` -- boolean (default: ``True``); whether to consider
379-
edge labels
405+
edge labels. The edge labels are assumed to be hashable and sortable. If
406+
this is not the case (ie a ``TypeError`` is raised), the algorithm will
407+
consider the string representations of the labels instead of the labels.
380408
381409
- ``certificate`` -- boolean (default: ``False``); when set to ``True``,
382410
returns the labeling of G into a canonical graph
@@ -428,16 +456,41 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True
428456
429457
Check that parameter ``use_edge_labels`` can be used (:trac:`27571`)::
430458
431-
sage: g = Graph({1: {2: 'a'}}) # optional - bliss
459+
sage: g = Graph({1: {2: 'a'}})
432460
sage: canonical_form(g, use_edge_labels=True) # optional - bliss
433461
[(1, 0, 'a')]
434462
sage: canonical_form(g, use_edge_labels=False) # optional - bliss
435463
[(1, 0, None)]
464+
465+
Check that :trac:`28531` is fixed::
466+
467+
sage: from itertools import product, permutations
468+
sage: edges_list = [[(0,1), (1,2)],
469+
....: [(0,1),(1,2),(2,3)],
470+
....: [(0,1),(1,2),(2,3),(3,0)]]
471+
sage: for edges in edges_list: # optional - bliss
472+
....: for labels in product([0,1], repeat=len(edges)):
473+
....: g = Graph([(u,v,l) for ((u,v),l) in zip(edges, labels)])
474+
....: gcan = canonical_form(g, use_edge_labels=True)
475+
....: for p in permutations(range(g.num_verts())):
476+
....: h = Graph([(p[u], p[v], lab) for u,v,lab in g.edges()])
477+
....: hcan = canonical_form(h, use_edge_labels=True)
478+
....: if gcan != hcan: print(edges, labels, p)
479+
480+
Check that it works with non hashable non sortable edge labels (relying
481+
on string representations of the labels)::
482+
483+
sage: g1 = Graph([(0, 1, matrix(ZZ, 2)), (0, 2, RDF.pi()), (1, 2, 'a')])
484+
sage: g2 = Graph([(1, 2, matrix(ZZ, 2)), (2, 0, RDF.pi()), (0, 1, 'a')])
485+
sage: g1can = canonical_form(g1, use_edge_labels=True) # optional - bliss
486+
sage: g2can = canonical_form(g2, use_edge_labels=True) # optional - bliss
487+
sage: g1can == g2can # optional - bliss
488+
True
436489
"""
437490
# We need this to convert the numbers from <unsigned int> to <long>.
438491
# This assertion should be true simply for memory reasons.
439492
cdef unsigned long Vnr = G.order()
440-
assert Vnr <= <unsigned long>LONG_MAX
493+
assert Vnr <= <unsigned long> LONG_MAX
441494

442495
cdef bint directed = G.is_directed()
443496

@@ -448,34 +501,50 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True
448501

449502
cdef list int2vert
450503
cdef dict vert2int
504+
cdef dict lab_to_index
451505
cdef list edge_labels = [] if use_edge_labels else [None]
452-
cdef int Lnr = 0 if use_edge_labels else 1
506+
cdef int Lnr = 1
453507

454-
if bool(partition):
508+
if partition:
455509
from itertools import chain
456510
int2vert = list(chain(*partition))
457511
else:
458512
int2vert = list(G)
459513
vert2int = {v: i for i, v in enumerate(int2vert)}
460-
if bool(partition):
514+
if partition:
461515
partition = [[vert2int[i] for i in part] for part in partition]
462516

463517
# Create 3 lists to represent edges
464518
# - Vout[i] : source of the ith edge
465519
# - Vin[i] : destination of the ith edge
466520
# - labels[i] : label of the ith edge if use_edge_labels is True
467521
# On the way, assign a unique integer to each distinct label
468-
for x,y,lab in G.edge_iterator(labels=True):
469-
Vout.append(vert2int[x])
470-
Vin.append(vert2int[y])
471-
if use_edge_labels:
472-
try:
473-
labInd = edge_labels.index(lab)
474-
except ValueError:
475-
labInd = Lnr
476-
Lnr += 1
477-
edge_labels.append(lab)
478-
labels.append(labInd)
522+
if use_edge_labels:
523+
try:
524+
edge_labels = sorted(set(G.edge_labels()))
525+
except TypeError:
526+
# NOTE: use edge labels might not be hashable or sortable...
527+
# rely loosely on string representation
528+
edge_labels = sorted(set(map(str, G.edge_labels())))
529+
lab_to_index = {lab: i for i,lab in enumerate(edge_labels)}
530+
for x,y,lab in G.edge_iterator(labels=True):
531+
Vout.append(vert2int[x])
532+
Vin.append(vert2int[y])
533+
labels.append(lab_to_index[str(lab)])
534+
535+
else:
536+
lab_to_index = {lab:i for i,lab in enumerate(edge_labels)}
537+
for x,y,lab in G.edge_iterator(labels=True):
538+
Vout.append(vert2int[x])
539+
Vin.append(vert2int[y])
540+
labels.append(lab_to_index[lab])
541+
542+
Lnr = len(lab_to_index)
543+
544+
else:
545+
for x,y,lab in G.edge_iterator(labels=True):
546+
Vout.append(vert2int[x])
547+
Vin.append(vert2int[y])
479548

480549
new_edges, relabel = canonical_form_from_edge_list(Vnr, Vout, Vin, Lnr, labels, partition, directed, certificate=True)
481550

@@ -538,14 +607,8 @@ cdef automorphism_group_gens_from_edge_list(int Vnr, Vout, Vin, int Lnr=1, label
538607
if not int2vert:
539608
int2vert = list(range(Vnr))
540609

541-
# the following is needed because the internal graph has size Vnr*logLnr for
542-
# labelled graphs
543-
if Lnr != 1:
544-
logLnr = len(numpy.binary_repr(Lnr))
545-
int2vert.extend([None] * (Vnr * (logLnr - 1)))
546-
547610
cdef list gens = []
548-
cdef tuple data = (gens, int2vert)
611+
cdef tuple data = (gens, int2vert, Vnr)
549612

550613
if directed:
551614
d = bliss_digraph_from_labelled_edges(Vnr, Lnr, Vout, Vin, labels, partition)
@@ -577,7 +640,7 @@ cpdef automorphism_group(G, partition=None, use_edge_labels=True):
577640
of ``G`` into color classes. Defaults to ``None``, which is equivalent to
578641
a partition of size 1.
579642
580-
- ``use_edge_labels`` -- boolean (default: ``False``); whether to consider edge
643+
- ``use_edge_labels`` -- boolean (default: ``True``); whether to consider edge
581644
labels
582645
583646
EXAMPLES::
@@ -710,13 +773,13 @@ cpdef automorphism_group(G, partition=None, use_edge_labels=True):
710773
cdef list edge_labels = []
711774
cdef int Lnr = 0 if use_edge_labels else 1
712775

713-
if bool(partition):
776+
if partition:
714777
from itertools import chain
715778
int2vert = list(chain(*partition))
716779
else:
717780
int2vert = list(G)
718781
vert2int = {v: i for i, v in enumerate(int2vert)}
719-
if bool(partition):
782+
if partition:
720783
partition = [[vert2int[i] for i in part] for part in partition]
721784

722785
# Create 3 lists to represent edges

0 commit comments

Comments
 (0)