Path: blob/master/src/sage/graphs/generic_graph_pyx.pyx
8815 views
"""1GenericGraph Cython functions23AUTHORS:4-- Robert L. Miller (2007-02-13): initial version5-- Robert W. Bradshaw (2007-03-31): fast spring layout algorithms6-- Nathann Cohen : exhaustive search7"""89#*****************************************************************************10# Copyright (C) 2007 Robert L. Miller <[email protected]>11# 2007 Robert W. Bradshaw <[email protected]>12#13# Distributed under the terms of the GNU General Public License (GPL)14# http://www.gnu.org/licenses/15#*****************************************************************************1617include "sage/ext/interrupt.pxi"18include 'sage/ext/cdefs.pxi'19include 'sage/ext/stdsage.pxi'2021# import from Python standard library22from sage.misc.prandom import random2324# import from third-party library25from sage.graphs.base.sparse_graph cimport SparseGraph262728cdef class GenericGraph_pyx(SageObject):29pass3031def spring_layout_fast_split(G, **options):32"""33Graphs each component of G separately, placing them adjacent to34each other. This is done because on a disconnected graph, the35spring layout will push components further and further from each36other without bound, resulting in very tight clumps for each37component.3839NOTE:40If the axis are scaled to fit the plot in a square, the41horizontal distance may end up being "squished" due to42the several adjacent components.4344EXAMPLES:4546sage: G = graphs.DodecahedralGraph()47sage: for i in range(10): G.add_cycle(range(100*i, 100*i+3))48sage: from sage.graphs.generic_graph_pyx import spring_layout_fast_split49sage: spring_layout_fast_split(G)50{0: [0.452..., 0.247...], ..., 502: [25.7..., 0.505...]}5152AUTHOR:53Robert Bradshaw54"""55Gs = G.connected_components_subgraphs()56pos = {}57left = 058buffer = 1/sqrt(len(G))59for g in Gs:60cur_pos = spring_layout_fast(g, **options)61xmin = min([x[0] for x in cur_pos.values()])62xmax = max([x[0] for x in cur_pos.values()])63if len(g) > 1:64buffer = (xmax - xmin)/sqrt(len(g))65for v, loc in cur_pos.iteritems():66loc[0] += left - xmin + buffer67pos[v] = loc68left += xmax - xmin + buffer69return pos7071def spring_layout_fast(G, iterations=50, int dim=2, vpos=None, bint rescale=True, bint height=False, by_component = False, **options):72"""73Spring force model layout7475This function primarily acts as a wrapper around run_spring,76converting to and from raw c types.7778This kind of speed cannot be achieved by naive Cythonification of the79function alone, especially if we require a function call (let alone80an object creation) every time we want to add a pair of doubles.8182INPUT:8384- ``by_component`` - a boolean8586EXAMPLES::8788sage: G = graphs.DodecahedralGraph()89sage: for i in range(10): G.add_cycle(range(100*i, 100*i+3))90sage: from sage.graphs.generic_graph_pyx import spring_layout_fast91sage: spring_layout_fast(G)92{0: [-0.0733..., 0.157...], ..., 502: [-0.551..., 0.682...]}9394With ``split=True``, each component of G is layed out separately,95placing them adjacent to each other. This is done because on a96disconnected graph, the spring layout will push components further97and further from each other without bound, resulting in very tight98clumps for each component.99100NOTE:101102If the axis are scaled to fit the plot in a square, the103horizontal distance may end up being "squished" due to104the several adjacent components.105106sage: G = graphs.DodecahedralGraph()107sage: for i in range(10): G.add_cycle(range(100*i, 100*i+3))108sage: from sage.graphs.generic_graph_pyx import spring_layout_fast109sage: spring_layout_fast(G, by_component = True)110{0: [2.12..., -0.321...], ..., 502: [26.0..., -0.812...]}111"""112113if by_component:114return spring_layout_fast_split(G, iterations=iterations, dim = dim,115vpos = vpos, rescale = rescale, height = height,116**options)117118G = G.to_undirected()119vlist = G.vertices() # this defines a consistent order120121cdef int i, j, x122cdef int n = G.order()123if n == 0:124return {}125126cdef double* pos = <double*>sage_malloc(n * dim * sizeof(double))127if pos is NULL:128raise MemoryError, "error allocating scratch space for spring layout"129130# convert or create the starting positions as a flat list of doubles131if vpos is None: # set the initial positions randomly in 1x1 box132for i from 0 <= i < n*dim:133pos[i] = random()134else:135for i from 0 <= i < n:136loc = vpos[vlist[i]]137for x from 0 <= x <dim:138pos[i*dim + x] = loc[x]139140141# here we construct a lexicographically ordered list of all edges142# where elist[2*i], elist[2*i+1] represents the i-th edge143cdef int* elist = <int*>sage_malloc( (2 * len(G.edges()) + 2) * sizeof(int) )144if elist is NULL:145sage_free(pos)146raise MemoryError, "error allocating scratch space for spring layout"147148cdef int cur_edge = 0149150for i from 0 <= i < n:151for j from i < j < n:152if G.has_edge(vlist[i], vlist[j]):153elist[cur_edge] = i154elist[cur_edge+1] = j155cur_edge += 2156157# finish the list with -1, -1 which never gets matched158# but does get compared against when looking for the "next" edge159elist[cur_edge] = -1160elist[cur_edge+1] = -1161162run_spring(iterations, dim, pos, elist, n, height)163164# recenter165cdef double* cen166cdef double r, r2, max_r2 = 0167if rescale:168cen = <double *>sage_malloc(sizeof(double) * dim)169if cen is NULL:170sage_free(elist)171sage_free(pos)172raise MemoryError, "error allocating scratch space for spring layout"173for x from 0 <= x < dim: cen[x] = 0174for i from 0 <= i < n:175for x from 0 <= x < dim:176cen[x] += pos[i*dim + x]177for x from 0 <= x < dim: cen[x] /= n178for i from 0 <= i < n:179r2 = 0180for x from 0 <= x < dim:181pos[i*dim + x] -= cen[x]182r2 += pos[i*dim + x] * pos[i*dim + x]183if r2 > max_r2:184max_r2 = r2185r = 1 if max_r2 == 0 else sqrt(max_r2)186for i from 0 <= i < n:187for x from 0 <= x < dim:188pos[i*dim + x] /= r189sage_free(cen)190191# put the data back into a position dictionary192vpos = {}193for i from 0 <= i < n:194vpos[vlist[i]] = [pos[i*dim+x] for x from 0 <= x < dim]195196sage_free(pos)197sage_free(elist)198199return vpos200201202cdef run_spring(int iterations, int dim, double* pos, int* edges, int n, bint height):203"""204Find a locally optimal layout for this graph, according to the205constraints that neighboring nodes want to be a fixed distance206from each other, and non-neighboring nodes always repel.207208This is not a true physical model of mutually-repulsive particles209with springs, rather it is more a model of such things traveling,210without any inertia, through an (ever thickening) fluid.211212TODO: The inertial model could be incorporated (with F=ma)213TODO: Are the hard-coded constants here optimal?214215INPUT:216iterations -- number of steps to take217dim -- number of dimensions of freedom218pos -- already initialized initial positions219Each vertex is stored as [dim] consecutive doubles.220These doubles are then placed consecutively in the array.221For example, if dim=3, we would have222pos = [x_1, y_1, z_1, x_2, y_2, z_2, ... , x_n, y_n, z_n]223edges -- List of edges, sorted lexicographically by the first224(smallest) vertex, terminated by -1, -1.225The first two entries represent the first edge, and so on.226n -- number of vertices in the graph227height -- if True, do not update the last coordinate ever228229OUTPUT:230Modifies contents of pos.231232AUTHOR:233Robert Bradshaw234"""235236cdef int cur_iter, cur_edge237cdef int i, j, x238239# k -- the equilibrium distance between two adjacent nodes240cdef double t = 1, dt = t/(1e-20 + iterations), k = sqrt(1.0/n)241cdef double square_dist, force, scale242cdef double* disp_i243cdef double* disp_j244cdef double* delta245246cdef double* disp = <double*>sage_malloc((n+1) * dim * sizeof(double))247if disp is NULL:248raise MemoryError, "error allocating scratch space for spring layout"249delta = &disp[n*dim]250251if height:252update_dim = dim-1253else:254update_dim = dim255256sig_on()257258for cur_iter from 0 <= cur_iter < iterations:259cur_edge = 1 # offset by one for fast checking against 2nd element first260# zero out the disp vectors261memset(disp, 0, n * dim * sizeof(double))262for i from 0 <= i < n:263disp_i = disp + (i*dim)264for j from i < j < n:265disp_j = disp + (j*dim)266267for x from 0 <= x < dim:268delta[x] = pos[i*dim+x] - pos[j*dim+x]269270square_dist = delta[0] * delta[0]271for x from 1 <= x < dim:272square_dist += delta[x] * delta[x]273274if square_dist < 0.01:275square_dist = 0.01276277# they repel according to the (capped) inverse square law278force = k*k/square_dist279280# and if they are neighbors, attract according Hooke's law281if edges[cur_edge] == j and edges[cur_edge-1] == i:282force -= sqrt(square_dist)/k283cur_edge += 2284285# add this factor into each of the involved points286for x from 0 <= x < dim:287disp_i[x] += delta[x] * force288disp_j[x] -= delta[x] * force289290# now update the positions291for i from 0 <= i < n:292disp_i = disp + (i*dim)293294square_dist = disp_i[0] * disp_i[0]295for x from 1 <= x < dim:296square_dist += disp_i[x] * disp_i[x]297298scale = t / (1 if square_dist < 0.01 else sqrt(square_dist))299300for x from 0 <= x < update_dim:301pos[i*dim+x] += disp_i[x] * scale302303t -= dt304305sig_off()306307sage_free(disp)308309def binary(n, length=None):310"""311A quick python int to binary string conversion.312313EXAMPLE:314sage: sage.graphs.generic_graph_pyx.binary(389)315'110000101'316sage: Integer(389).binary()317'110000101'318sage: sage.graphs.generic_graph_pyx.binary(2007)319'11111010111'320"""321cdef mpz_t i322mpz_init(i)323mpz_set_ui(i,n)324cdef char* s=mpz_get_str(NULL, 2, i)325t=str(s)326sage_free(s)327mpz_clear(i)328return t329330def R(x):331"""332A helper function for the graph6 format. Described in [McK]333334EXAMPLE:335sage: from sage.graphs.generic_graph_pyx import R336sage: R('110111010110110010111000001100000001000000001')337'vUqwK@?G'338339REFERENCES:340McKay, Brendan. 'Description of graph6 and sparse6 encodings.'341http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)342"""343# pad on the right to make a multiple of 6344x += '0' * ( (6 - (len(x) % 6)) % 6)345346# split into groups of 6, and convert numbers to decimal, adding 63347six_bits = ''348cdef int i349for i from 0 <= i < len(x)/6:350six_bits += chr( int( x[6*i:6*(i+1)], 2) + 63 )351return six_bits352353def N(n):354"""355A helper function for the graph6 format. Described in [McK]356357EXAMPLE:358sage: from sage.graphs.generic_graph_pyx import N359sage: N(13)360'L'361sage: N(136)362'~?AG'363364REFERENCES:365McKay, Brendan. 'Description of graph6 and sparse6 encodings.'366http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)367"""368if n < 63:369return chr(n + 63)370else:371# get 18-bit rep of n372n = binary(n)373n = '0'*(18-len(n)) + n374return chr(126) + R(n)375376def N_inverse(s):377"""378A helper function for the graph6 format. Described in [McK]379380EXAMPLE:381sage: from sage.graphs.generic_graph_pyx import N_inverse382sage: N_inverse('~??~?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?')383(63, '?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?')384sage: N_inverse('_???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????')385(32, '???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????')386387REFERENCES:388McKay, Brendan. 'Description of graph6 and sparse6 encodings.'389http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)390"""391if s[0] == chr(126): # first four bytes are N392a = binary(ord(s[1]) - 63).zfill(6)393b = binary(ord(s[2]) - 63).zfill(6)394c = binary(ord(s[3]) - 63).zfill(6)395n = int(a + b + c,2)396s = s[4:]397else: # only first byte is N398o = ord(s[0])399if o > 126 or o < 63:400raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))401n = o - 63402s = s[1:]403return n, s404405def R_inverse(s, n):406"""407A helper function for the graph6 format. Described in [McK]408409REFERENCES:410McKay, Brendan. 'Description of graph6 and sparse6 encodings.'411http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)412413EXAMPLE:414sage: from sage.graphs.generic_graph_pyx import R_inverse415sage: R_inverse('?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?', 63)416'0000000000000000000000000000001000000000010000000001000010000000000000000000110000000000000000010100000010000000000001000000000010000000000...10000000000000000000000000000000010000000001011011000000100000000001001110000000000000000000000000001000010010000001100000001000000001000000000100000000'417sage: R_inverse('???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????', 32)418'0000000000000000000001000000000000010000100000100000001000000000000000100000000100000...010000000000000100010000001000000000000000000000000000001010000000001011000000000000010010000000000000010000000000100000000001000001000000000000000001000000000000000000000000000000000000'419420"""421l = []422cdef int i423for i from 0 <= i < len(s):424o = ord(s[i])425if o > 126 or o < 63:426raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))427a = binary(o-63)428l.append( '0'*(6-len(a)) + a )429m = "".join(l)430return m431432def D_inverse(s, n):433"""434A helper function for the dig6 format.435436EXAMPLE:437sage: from sage.graphs.generic_graph_pyx import D_inverse438sage: D_inverse('?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?', 63)439'0000000000000000000000000000001000000000010000000001000010000000000000000000110000000000000000010100000010000000000001000000000010000000000...10000000000000000000000000000000010000000001011011000000100000000001001110000000000000000000000000001000010010000001100000001000000001000000000100000000'440sage: D_inverse('???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????', 32)441'0000000000000000000001000000000000010000100000100000001000000000000000100000000100000...010000000000000100010000001000000000000000000000000000001010000000001011000000000000010010000000000000010000000000100000000001000001000000000000000001000000000000000000000000000000000000'442443"""444l = []445cdef int i446for i from 0 <= i < len(s):447o = ord(s[i])448if o > 126 or o < 63:449raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))450a = binary(o-63)451l.append( '0'*(6-len(a)) + a )452m = "".join(l)453return m[:n*n]454455# Exhaustive search in graphs456457cdef class SubgraphSearch:458r"""459This class implements methods to exhaustively search for labelled460copies of a graph `H` in a larger graph `G`.461462It is possible to look for induced subgraphs instead, and to463iterate or count the number of their occurrences.464465ALGORITHM:466467The algorithm is a brute-force search. Let `V(H) =468\{h_1,\dots,h_k\}`. It first tries to find in `G` a possible469representant of `h_1`, then a representant of `h_2` compatible470with `h_1`, then a representant of `h_3` compatible with the first471two, etc.472473This way, most of the time we need to test far less than `k!474\binom{|V(G)|}{k}` subsets, and hope this brute-force technique475can sometimes be useful.476"""477def __init__(self, G, H, induced = False):478r"""479Constructor480481This constructor only checks there is no inconsistency in the482input : `G` and `H` are both graphs or both digraphs and that `H`483has order at least 2.484485EXAMPLE::486487sage: g = graphs.PetersenGraph()488sage: g.subgraph_search(graphs.CycleGraph(5))489Subgraph of (Petersen graph): Graph on 5 vertices490491TESTS:492493Test proper initialization and deallocation, see :trac:`14067`.494We intentionally only create the class without doing any495computations with it::496497sage: from sage.graphs.generic_graph_pyx import SubgraphSearch498sage: SubgraphSearch(Graph(5), Graph(1))499Traceback (most recent call last):500...501ValueError: Searched graph should have at least 2 vertices.502sage: SubgraphSearch(Graph(5), Graph(2))503<sage.graphs.generic_graph_pyx.SubgraphSearch ...>504"""505if H.order() <= 1:506raise ValueError("Searched graph should have at least 2 vertices.")507508if sum([G.is_directed(), H.is_directed()]) == 1:509raise ValueError("One graph can not be directed while the other is not.")510511G._scream_if_not_simple(allow_loops=True)512H._scream_if_not_simple(allow_loops=True)513514self._initialization()515516def __iter__(self):517r"""518Returns an iterator over all the labeleld subgraphs of `G`519isomorphic to `H`.520521EXAMPLE:522523Iterating through all the `P_3` of `P_5`::524525sage: from sage.graphs.generic_graph_pyx import SubgraphSearch526sage: g = graphs.PathGraph(5)527sage: h = graphs.PathGraph(3)528sage: S = SubgraphSearch(g, h)529sage: for p in S:530... print p531[0, 1, 2]532[1, 2, 3]533[2, 1, 0]534[2, 3, 4]535[3, 2, 1]536[4, 3, 2]537"""538self._initialization()539return self540541def cardinality(self):542r"""543Returns the number of labelled subgraphs of `G` isomorphic to544`H`.545546.. NOTE::547548This method counts the subgraphs by enumerating them all !549Hence it probably is not a good idea to count their number550before enumerating them :-)551552EXAMPLE:553554Counting the number of labelled `P_3` in `P_5`::555556sage: from sage.graphs.generic_graph_pyx import SubgraphSearch557sage: g = graphs.PathGraph(5)558sage: h = graphs.PathGraph(3)559sage: S = SubgraphSearch(g, h)560sage: S.cardinality()5616562"""563if self.nh > self.ng:564return 0565566self._initialization()567cdef int i568569i=0570for _ in self:571i+=1572573return i574575def _initialization(self):576r"""577Initialization of the variables.578579Once the memory allocation is done in :meth:`__cinit__`,580several variables need to be set to a default value. As this581operation needs to be performed before any call to582:meth:`__iter__` or to :meth:`cardinality`, it is cleaner to583create a dedicated method.584585EXAMPLE:586587Finding two times the first occurrence through the588re-initialization of the instance ::589590sage: from sage.graphs.generic_graph_pyx import SubgraphSearch591sage: g = graphs.PathGraph(5)592sage: h = graphs.PathGraph(3)593sage: S = SubgraphSearch(g, h)594sage: S.__next__()595[0, 1, 2]596sage: S._initialization()597sage: S.__next__()598[0, 1, 2]599"""600601memset(self.busy, 0, self.ng * sizeof(int))602# 0 is the first vertex we use, so it is at first busy603self.busy[0] = 1604# stack -- list of the vertices which are part of the partial copy of H605# in G.606#607# stack[i] -- the integer corresponding to the vertex of G representing608# the i-th vertex of H.609#610# stack[i] = -1 means that i is not represented611# ... yet!612613self.stack[0] = 0614self.stack[1] = -1615616# Number of representants we have already found. Set to 1 as vertex 0617# is already part of the partial copy of H in G.618self.active = 1619620def __cinit__(self, G, H, induced = False):621r"""622Cython constructor623624This method initializes all the C values.625626EXAMPLE::627628sage: g = graphs.PetersenGraph()629sage: g.subgraph_search(graphs.CycleGraph(5))630Subgraph of (Petersen graph): Graph on 5 vertices631"""632633# Storing the number of vertices634self.ng = G.order()635self.nh = H.order()636637# Storing the list of vertices638self.g_vertices = G.vertices()639640# Are the graphs directed (in __init__(), we check641# whether both are of the same type)642self.directed = G.is_directed()643644cdef int i, j, k645646self.tmp_array = <int *>sage_malloc(self.ng * sizeof(int))647if self.tmp_array == NULL:648raise MemoryError()649650# Should we look for induced subgraphs ?651if induced:652self.is_admissible = vectors_equal653else:654self.is_admissible = vectors_inferior655656# static copies of the two graphs for more efficient operations657self.g = DenseGraph(self.ng)658self.h = DenseGraph(self.nh)659660# copying the adjacency relations in both G and H661i = 0662for row in G.adjacency_matrix():663j = 0664for k in row:665if k:666self.g.add_arc(i, j)667j += 1668i += 1669i = 0670for row in H.adjacency_matrix():671j = 0672for k in row:673if k:674self.h.add_arc(i, j)675j += 1676i += 1677678# A vertex is said to be busy if it is already part of the partial copy679# of H in G.680self.busy = <int *>sage_malloc(self.ng * sizeof(int))681self.stack = <int *>sage_malloc(self.nh * sizeof(int))682683# vertices is equal to range(nh), as an int *variable684self.vertices = <int *>sage_malloc(self.nh * sizeof(int))685for 0 <= i < self.nh:686self.vertices[i] = i687688# line_h_out[i] represents the adjacency sequence of vertex i689# in h relative to vertices 0, 1, ..., i-1690self.line_h_out = <int **>sage_malloc(self.nh * sizeof(int *))691for 0 <= i < self.nh:692self.line_h_out[i] = <int *> sage_malloc(self.nh * sizeof(int *))693if self.line_h_out[i] is NULL:694raise MemoryError()695self.h.adjacency_sequence_out(i, self.vertices, i, self.line_h_out[i])696697# Similarly in the opposite direction (only useful if the698# graphs are directed)699if self.directed:700self.line_h_in = <int **>sage_malloc(self.nh * sizeof(int *))701for 0 <= i < self.nh:702self.line_h_in[i] = <int *> sage_malloc(self.nh * sizeof(int *))703if self.line_h_in[i] is NULL:704raise MemoryError()705706self.h.adjacency_sequence_in(i, self.vertices, i, self.line_h_in[i])707708def __next__(self):709r"""710Returns the next isomorphic subgraph if any, and raises a711``StopIteration`` otherwise.712713EXAMPLE::714715sage: from sage.graphs.generic_graph_pyx import SubgraphSearch716sage: g = graphs.PathGraph(5)717sage: h = graphs.PathGraph(3)718sage: S = SubgraphSearch(g, h)719sage: S.__next__()720[0, 1, 2]721"""722sig_on()723cdef bint is_admissible724cdef int * tmp_array = self.tmp_array725726# as long as there is a non-void partial copy of H in G727while self.active >= 0:728# If we are here and found nothing yet, we try the next possible729# vertex as a representant of the active i-th vertex of H.730self.i = self.stack[self.active] + 1731# Looking for a vertex that is not busy and compatible with the732# partial copy we have of H.733while self.i < self.ng:734if self.busy[self.i]:735self.i += 1736else:737# Testing whether the vertex we picked is a738# correct extension by checking the edges from the739# vertices already selected to self.i satisfy the740# constraints741self.g.adjacency_sequence_out(self.active, self.stack, self.i, tmp_array)742is_admissible = self.is_admissible(self.active, tmp_array, self.line_h_out[self.active])743744# If G and H are digraphs, we also need to ensure745# the edges going in the opposite direction746# satisfy the constraints747if is_admissible and self.directed:748self.g.adjacency_sequence_in(self.active, self.stack, self.i, tmp_array)749is_admissible = is_admissible and self.is_admissible(self.active, tmp_array, self.line_h_in[self.active])750751if is_admissible:752break753else:754self.i += 1755756# If we have found a good representant of H's i-th vertex in G757if self.i < self.ng:758759# updating the last vertex of the stack760if self.stack[self.active] != -1:761self.busy[self.stack[self.active]] = 0762self.stack[self.active] = self.i763764# We have found our copy !!!765if self.active == self.nh-1:766sig_off()767return [self.g_vertices[self.stack[l]] for l in xrange(self.nh)]768769# We are still missing several vertices ...770else:771self.busy[self.stack[self.active]] = 1772self.active += 1773774# we begin the search of the next vertex at 0775self.stack[self.active] = -1776777# If we found no representant for the i-th vertex, it778# means that we cannot extend the current copy of H so we779# update the status of stack[active] and prepare to change780# the previous vertex.781782else:783if self.stack[self.active] != -1:784self.busy[self.stack[self.active]] = 0785self.stack[self.active] = -1786self.active -= 1787788sig_off()789raise StopIteration790791def __dealloc__(self):792r"""793Freeing the allocated memory.794"""795796# Free the memory797sage_free(self.busy)798sage_free(self.stack)799sage_free(self.vertices)800for 0 <= i < self.nh:801sage_free(self.line_h_out[i])802sage_free(self.line_h_out)803804if self.directed:805for 0 <= i < self.nh:806sage_free(self.line_h_in[i])807sage_free(self.line_h_in)808809if self.tmp_array != NULL:810sage_free(self.tmp_array)811812cdef inline bint vectors_equal(int n, int *a, int *b):813r"""814Tests whether the two given vectors are equal. Two integer vectors815`a = (a_1, a_2, \dots, a_n)` and `b = (b_1, b_2, \dots, b_n)` are equal816iff `a_i = b_i` for all `i = 1, 2, \dots, n`. See the function817``_test_vectors_equal_inferior()`` for unit tests.818819INPUT:820821- ``n`` -- positive integer; length of the vectors.822823- ``a``, ``b`` -- two vectors of integers.824825OUTPUT:826827- ``True`` if ``a`` and ``b`` are the same vector; ``False`` otherwise.828"""829cdef int i = 0830for 0 <= i < n:831if a[i] != b[i]:832return False833return True834835cdef inline bint vectors_inferior(int n, int *a, int *b):836r"""837Tests whether the second vector of integers is inferior to the first. Let838`u = (u_1, u_2, \dots, u_k)` and `v = (v_1, v_2, \dots, v_k)` be two839integer vectors of equal length. Then `u` is said to be less than840(or inferior to) `v` if `u_i \leq v_i` for all `i = 1, 2, \dots, k`. See841the function ``_test_vectors_equal_inferior()`` for unit tests. Given two842equal integer vectors `u` and `v`, `u` is inferior to `v` and vice versa.843We could also define two vectors `a` and `b` to be equal if `a` is844inferior to `b` and `b` is inferior to `a`.845846INPUT:847848- ``n`` -- positive integer; length of the vectors.849850- ``a``, ``b`` -- two vectors of integers.851852OUTPUT:853854- ``True`` if ``b`` is inferior to (or less than) ``a``; ``False``855otherwise.856"""857cdef int i = 0858for 0 <= i < n:859if a[i] < b[i]:860return False861return True862863##############################864# Further tests. Unit tests for methods, functions, classes defined with cdef.865##############################866867def _test_vectors_equal_inferior():868"""869Unit testing the function ``vectors_equal()``. No output means that no870errors were found in the random tests.871872TESTS::873874sage: from sage.graphs.generic_graph_pyx import _test_vectors_equal_inferior875sage: _test_vectors_equal_inferior()876"""877from sage.misc.prandom import randint878n = randint(500, 10**3)879cdef int *u = <int *>sage_malloc(n * sizeof(int))880cdef int *v = <int *>sage_malloc(n * sizeof(int))881cdef int i882# equal vectors: u = v883for 0 <= i < n:884u[i] = randint(-10**6, 10**6)885v[i] = u[i]886try:887assert vectors_equal(n, u, v)888assert vectors_equal(n, v, u)889# Since u and v are equal vectors, then u is inferior to v and v is890# inferior to u. One could also define u and v as being equal if891# u is inferior to v and vice versa.892assert vectors_inferior(n, u, v)893assert vectors_inferior(n, v, u)894except AssertionError:895sage_free(u)896sage_free(v)897raise AssertionError("Vectors u and v should be equal.")898# Different vectors: u != v because we have u_j > v_j for some j. Thus,899# u_i = v_i for 0 <= i < j and u_j > v_j. For j < k < n - 2, we could have:900# (1) u_k = v_k,901# (2) u_k < v_k, or902# (3) u_k > v_k.903# And finally, u_{n-1} < v_{n-1}.904cdef int j = randint(1, n//2)905cdef int k906for 0 <= i < j:907u[i] = randint(-10**6, 10**6)908v[i] = u[i]909u[j] = randint(-10**6, 10**6)910v[j] = u[j] - randint(1, 10**6)911for j < k < n:912u[k] = randint(-10**6, 10**6)913v[k] = randint(-10**6, 10**6)914u[n - 1] = v[n - 1] - randint(1, 10**6)915try:916assert not vectors_equal(n, u, v)917assert not vectors_equal(n, v, u)918# u is not inferior to v because at least u_j > v_j919assert u[j] > v[j]920assert not vectors_inferior(n, v, u)921# v is not inferior to u because at least v_{n-1} > u_{n-1}922assert v[n - 1] > u[n - 1]923assert not vectors_inferior(n, u, v)924except AssertionError:925sage_free(u)926sage_free(v)927raise AssertionError("".join([928"Vectors u and v should not be equal. ",929"u should not be inferior to v, and vice versa."]))930# Different vectors: u != v because we have u_j < v_j for some j. Thus,931# u_i = v_i for 0 <= i < j and u_j < v_j. For j < k < n - 2, we could have:932# (1) u_k = v_k,933# (2) u_k < v_k, or934# (3) u_k > v_k.935# And finally, u_{n-1} > v_{n-1}.936j = randint(1, n//2)937for 0 <= i < j:938u[i] = randint(-10**6, 10**6)939v[i] = u[i]940u[j] = randint(-10**6, 10**6)941v[j] = u[j] + randint(1, 10**6)942for j < k < n:943u[k] = randint(-10**6, 10**6)944v[k] = randint(-10**6, 10**6)945u[n - 1] = v[n - 1] + randint(1, 10**6)946try:947assert not vectors_equal(n, u, v)948assert not vectors_equal(n, v, u)949# u is not inferior to v because at least u_{n-1} > v_{n-1}950assert u[n - 1] > v[n - 1]951assert not vectors_inferior(n, v, u)952# v is not inferior to u because at least u_j < v_j953assert u[j] < v[j]954assert not vectors_inferior(n, u, v)955except AssertionError:956sage_free(u)957sage_free(v)958raise AssertionError("".join([959"Vectors u and v should not be equal. ",960"u should not be inferior to v, and vice versa."]))961# different vectors u != v962# What's the probability of two random vectors being equal?963for 0 <= i < n:964u[i] = randint(-10**6, 10**6)965v[i] = randint(-10**6, 10**6)966try:967assert not vectors_equal(n, u, v)968assert not vectors_equal(n, v, u)969except AssertionError:970sage_free(u)971sage_free(v)972raise AssertionError("Vectors u and v should not be equal.")973# u is inferior to v, but v is not inferior to u974for 0 <= i < n:975v[i] = randint(-10**6, 10**6)976u[i] = randint(-10**6, 10**6)977while u[i] > v[i]:978u[i] = randint(-10**6, 10**6)979try:980assert not vectors_equal(n, u, v)981assert not vectors_equal(n, v, u)982assert vectors_inferior(n, v, u)983assert not vectors_inferior(n, u, v)984except AssertionError:985raise AssertionError(986"u should be inferior to v, but v is not inferior to u.")987finally:988sage_free(u)989sage_free(v)990991cpdef tuple find_hamiltonian( G, long max_iter=100000, long reset_bound=30000, long backtrack_bound=1000, find_path=False ):992r"""993Randomized backtracking for finding hamiltonian cycles and paths.994995ALGORITHM:996997A path ``P`` is maintained during the execution of the algorithm. Initially998the path will contain an edge of the graph. Every 10 iterations the path999is reversed. Every ``reset_bound`` iterations the path will be cleared1000and the procedure is restarted. Every ``backtrack_bound`` steps we discard1001the last five vertices and continue with the procedure. The total number1002of steps in the algorithm is controlled by ``max_iter``. If a hamiltonian1003cycle or hamiltonian path is found it is returned. If the number of steps reaches1004``max_iter`` then a longest path is returned. See OUTPUT for more details.100510061007INPUT:10081009- ``G`` - Graph.10101011- ``max_iter`` - Maximum number of iterations.10121013- ``reset_bound`` - Number of iterations before restarting the1014procedure.10151016- ``backtrack_bound`` - Number of iterations to elapse before1017discarding the last 5 vertices of the path.10181019- ``find_path`` - If set to ``True``, will search a hamiltonian1020path. If ``False``, will search for a hamiltonian1021cycle. Default value is ``False``.10221023OUTPUT:10241025A pair ``(B,P)``, where ``B`` is a Boolean and ``P`` is a list of vertices.10261027* If ``B`` is ``True`` and ``find_path`` is ``False``, ``P``1028represents a hamiltonian cycle.10291030* If ``B`` is ``True`` and ``find_path`` is ``True``, ``P``1031represents a hamiltonian path.10321033* If ``B`` is false, then ``P`` represents the longest path1034found during the execution of the algorithm.10351036.. WARNING::10371038May loop endlessly when run on a graph with vertices of degree10391.10401041EXAMPLES:10421043First we try the algorithm in the Dodecahedral graph, which is1044hamiltonian, so we are able to find a hamiltonian cycle and a1045hamiltonian path ::10461047sage: from sage.graphs.generic_graph_pyx import find_hamiltonian as fh1048sage: G=graphs.DodecahedralGraph()1049sage: fh(G)1050(True, [9, 10, 0, 19, 3, 2, 1, 8, 7, 6, 5, 4, 17, 18, 11, 12, 16, 15, 14, 13])1051sage: fh(G,find_path=True)1052(True, [8, 9, 10, 11, 18, 17, 4, 3, 19, 0, 1, 2, 6, 7, 14, 13, 12, 16, 15, 5])10531054Another test, now in the Moebius-Kantor graph which is also1055hamiltonian, as in our previous example, we are able to find a1056hamiltonian cycle and path ::10571058sage: G=graphs.MoebiusKantorGraph()1059sage: fh(G)1060(True, [5, 4, 3, 2, 10, 15, 12, 9, 1, 0, 7, 6, 14, 11, 8, 13])1061sage: fh(G,find_path=True)1062(True, [4, 5, 6, 7, 15, 12, 9, 1, 0, 8, 13, 10, 2, 3, 11, 14])10631064Now, we try the algorithm on a non hamiltonian graph, the Petersen1065graph. This graph is known to be hypohamiltonian, so a1066hamiltonian path can be found ::10671068sage: G=graphs.PetersenGraph()1069sage: fh(G)1070(False, [7, 9, 4, 3, 2, 1, 0, 5, 8, 6])1071sage: fh(G,find_path=True)1072(True, [3, 8, 6, 1, 2, 7, 9, 4, 0, 5])10731074We now show the algorithm working on another known hypohamiltonian1075graph, the generalized Petersen graph with parameters 11 and 2 ::10761077sage: G=graphs.GeneralizedPetersenGraph(11,2)1078sage: fh(G)1079(False, [13, 11, 0, 10, 9, 20, 18, 16, 14, 3, 2, 1, 12, 21, 19, 8, 7, 6, 17, 15, 4, 5])1080sage: fh(G,find_path=True)1081(True, [7, 18, 20, 9, 8, 19, 17, 6, 5, 16, 14, 3, 4, 15, 13, 11, 0, 10, 21, 12, 1, 2])10821083Finally, an example on a graph which does not have a hamiltonian1084path ::10851086sage: G=graphs.HyperStarGraph(5,2)1087sage: fh(G,find_path=False)1088(False, ['00011', '10001', '01001', '11000', '01010', '10010', '00110', '10100', '01100'])1089sage: fh(G,find_path=True)1090(False, ['00101', '10001', '01001', '11000', '01010', '10010', '00110', '10100', '01100'])1091"""10921093from sage.misc.prandom import randint1094cdef int n = G.order()1095cdef int m = G.num_edges()10961097#Initialize the path.1098cdef int *path = <int *>sage_malloc(n * sizeof(int))1099memset(path, -1, n * sizeof(int))11001101#Initialize the membership array1102cdef bint *member = <bint *>sage_malloc(n * sizeof(int))1103memset(member, 0, n * sizeof(int))11041105# static copy of the graph for more efficient operations1106cdef SparseGraph g = SparseGraph(n)1107# copying the adjacency relations in G1108cdef int i1109cdef int j1110i = 01111for row in G.adjacency_matrix():1112j = 01113for k in row:1114if k:1115g.add_arc(i, j)1116j += 11117i += 11118# Cache copy of the vertices1119cdef list vertices = g.verts()11201121# A list to store the available vertices at each step1122cdef list available_vertices=[]11231124#We now work towards picking a random edge1125# First we pick a random vertex u1126cdef int x = randint( 0, n-1 )1127cdef int u = vertices[x]1128# Then we pick at random a neighbor of u1129x = randint( 0, len(g.out_neighbors( u ))-1 )1130cdef int v = g.out_neighbors( u )[x]1131# This will be the first edge in the path1132cdef int length=21133path[ 0 ] = u1134path[ 1 ] = v1135member[ u ] = True1136member[ v ] = True11371138#Initialize all the variables neccesary to start iterating1139cdef bint done = False1140cdef long counter = 01141cdef long bigcount = 01142cdef int longest = length11431144#Initialize a path to contain the longest path1145cdef int *longest_path = <int *>sage_malloc(n * sizeof(int))1146memset(longest_path, -1, n * sizeof(int))1147i = 01148for 0 <= i < length:1149longest_path[ i ] = path[ i ]11501151#Initialize a temporary path for flipping1152cdef int *temp_path = <int *>sage_malloc(n * sizeof(int))1153memset(temp_path, -1, n * sizeof(int))11541155cdef bint longer = False1156cdef bint good = True11571158while not done:1159counter = counter + 11160if counter%10 == 0:1161#Reverse the path11621163i=01164for 0<= i < length/2:1165t=path[ i ]1166path[ i ] = path[ length - i - 1]1167path[ length -i -1 ] = t11681169if counter > reset_bound:1170bigcount = bigcount + 11171counter = 111721173#Time to reset the procedure1174for 0 <= i < n:1175member[ i ]=False1176# First we pick a random vertex u1177x = randint( 0, n-1 )1178u = vertices[x]1179# Then we pick at random a neighbor of u1180degree = len(g.out_neighbors( u ))1181x = randint( 0, degree-1 )1182v = g.out_neighbors( u )[x]1183# This will be the first edge in the path1184length=21185path[ 0 ] = u1186path[ 1 ] = v1187member[ u ] = True1188member[ v ] = True11891190if counter%backtrack_bound == 0:1191for 0 <= i < 5:1192member[ path[length - i - 1] ] = False1193length = length - 51194longer = False11951196available_vertices = []1197for u in g.out_neighbors( path[ length-1 ] ):1198if not member[ u ]:1199available_vertices.append( u )12001201n_available=len( available_vertices )1202if n_available > 0:1203longer = True1204x=randint( 0, n_available-1 )1205path[ length ] = available_vertices[ x ]1206length = length + 11207member [ available_vertices[ x ] ] = True12081209if not longer and length > longest:12101211for 0 <= i < length:1212longest_path[ i ] = path[ i ]12131214longest = length1215if not longer:12161217memset(temp_path, -1, n * sizeof(int))1218degree = len(g.out_neighbors( path[ length-1 ] ))1219while True:1220x = randint( 0, degree-1 )1221u = g.out_neighbors(path[length - 1])[ x ]1222if u != path[length - 2]:1223break12241225flag = False1226i=01227j=01228for 0 <= i < length:1229if i > length-j-1:1230break1231if flag:1232t=path[ i ]1233path[ i ] = path[ length - j - 1]1234path[ length - j - 1 ] = t1235j=j+11236if path[ i ] == u:1237flag = True1238if length == n:1239if find_path:1240done=True1241else:1242done = g.has_arc( path[n-1], path[0] )12431244if bigcount*reset_bound > max_iter:1245verts=G.vertices()1246output=[ verts[ longest_path[i] ] for i from 0<= i < longest ]1247sage_free( member )1248sage_free( path )1249sage_free( longest_path )1250sage_free( temp_path )1251return (False, output)1252# #1253# # Output test1254# #12551256# Test adjacencies1257for 0 <=i < n-1:1258u = path[i]1259v = path[i + 1]1260#Graph is simple, so both arcs are present1261if not g.has_arc( u, v ):1262good = False1263break1264if good == False:1265raise RuntimeError( 'Vertices %d and %d are consecutive in the cycle but are not ajacent.'%(u,v) )1266if not find_path and not g.has_arc( path[0], path[n-1] ):1267raise RuntimeError( 'Vertices %d and %d are not ajacent.'%(path[0],path[n-1]) )1268for 0 <= u < n:1269member[ u ]=False12701271for 0 <= u < n:1272if member[ u ]:1273good = False1274break1275member[ u ] = True1276if good == False:1277raise RuntimeError( 'Vertex %d appears twice in the cycle.'%(u) )1278verts=G.vertices()1279output=[ verts[path[i]] for i from 0<= i < length ]1280sage_free( member )1281sage_free( path )1282sage_free( longest_path )1283sage_free( temp_path )12841285return (True,output)1286128712881289