@@ -27,7 +27,6 @@ AUTHORS:
2727# (at your option) any later version.
2828# http://www.gnu.org/licenses/
2929# ****************************************************************************
30- import numpy
3130
3231from 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+
6477cdef 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 \l og( 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 \t imes \{ 0, \l dots, log( Lnr) \} `. The integers in the second factor of
141+ `V \t imes \{ 0, \l dots, \l og( 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
199226cdef 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