Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/matchpoly.pyx
8815 views
1
"""
2
Matching Polynomial Routine
3
4
This module contains the following methods:
5
6
.. csv-table::
7
:class: contentstable
8
:widths: 30, 70
9
:delim: |
10
11
:meth:`matching_polynomial` | Computes the matching polynomial of a given graph
12
:meth:`complete_poly` | Compute the matching polynomial of the complete graph on `n` vertices.
13
14
AUTHORS:
15
16
- Robert Miller, Tom Boothby - original implementation
17
18
REFERENCE:
19
20
.. [Godsil93] Chris Godsil (1993) Algebraic Combinatorics.
21
22
23
Methods
24
-------
25
"""
26
27
#*****************************************************************************
28
# Copyright (C) 2010 Robert Miller
29
#
30
# Distributed under the terms of the GNU General Public License (GPL)
31
# http://www.gnu.org/licenses/
32
#*****************************************************************************
33
34
from sage.rings.polynomial.polynomial_ring import polygen
35
from sage.rings.integer_ring import ZZ
36
from sage.rings.integer cimport Integer
37
from sage.misc.misc import prod
38
include 'sage/ext/interrupt.pxi'
39
include 'sage/ext/cdefs.pxi'
40
include 'sage/ext/stdsage.pxi'
41
include 'sage/libs/flint/fmpz.pxi'
42
include 'sage/libs/flint/fmpz_poly.pxi'
43
44
x = polygen(ZZ, 'x')
45
46
47
def matching_polynomial(G, complement=True, name=None):
48
"""
49
Computes the matching polynomial of the graph `G`.
50
51
If `p(G, k)` denotes the number of `k`-matchings (matchings with `k` edges)
52
in `G`, then the matching polynomial is defined as [Godsil93]_:
53
54
.. MATH::
55
56
\mu(x)=\sum_{k \geq 0} (-1)^k p(G,k) x^{n-2k}
57
58
INPUT:
59
60
- ``complement`` - (default: ``True``) whether to use Godsil's duality
61
theorem to compute the matching polynomial from that of the graphs
62
complement (see ALGORITHM).
63
64
- ``name`` - optional string for the variable name in the polynomial
65
66
.. NOTE::
67
68
The ``complement`` option uses matching polynomials of complete graphs,
69
which are cached. So if you are crazy enough to try computing the
70
matching polynomial on a graph with millions of vertices, you might not
71
want to use this option, since it will end up caching millions of
72
polynomials of degree in the millions.
73
74
ALGORITHM:
75
76
The algorithm used is a recursive one, based on the following observation
77
[Godsil93]_:
78
79
- If `e` is an edge of `G`, `G'` is the result of deleting the edge `e`, and
80
`G''` is the result of deleting each vertex in `e`, then the matching
81
polynomial of `G` is equal to that of `G'` minus that of `G''`.
82
83
(the algorithm actually computes the *signless* matching polynomial, for
84
which the recursion is the same when one replaces the substraction by an
85
addition. It is then converted into the matching polynomial and returned)
86
87
Depending on the value of ``complement``, Godsil's duality theorem
88
[Godsil93]_ can also be used to compute `\mu(x)` :
89
90
.. MATH::
91
92
\mu(\overline{G}, x) = \sum_{k \geq 0} p(G,k) \mu( K_{n-2k}, x)
93
94
95
Where `\overline{G}` is the complement of `G`, and `K_n` the complete graph
96
on `n` vertices.
97
98
EXAMPLES::
99
100
sage: g = graphs.PetersenGraph()
101
sage: g.matching_polynomial()
102
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
103
sage: g.matching_polynomial(complement=False)
104
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
105
sage: g.matching_polynomial(name='tom')
106
tom^10 - 15*tom^8 + 75*tom^6 - 145*tom^4 + 90*tom^2 - 6
107
sage: g = Graph()
108
sage: L = [graphs.RandomGNP(8, .3) for i in range(1, 6)]
109
sage: prod([h.matching_polynomial() for h in L]) == sum(L, g).matching_polynomial() # long time (up to 10s on sage.math, 2011)
110
True
111
112
::
113
114
sage: for i in range(1, 12): # long time (10s on sage.math, 2011)
115
....: for t in graphs.trees(i):
116
....: if t.matching_polynomial() != t.characteristic_polynomial():
117
....: raise RuntimeError('bug for a tree A of size {0}'.format(i))
118
....: c = t.complement()
119
....: if c.matching_polynomial(complement=False) != c.matching_polynomial():
120
....: raise RuntimeError('bug for a tree B of size {0}'.format(i))
121
122
::
123
124
sage: from sage.graphs.matchpoly import matching_polynomial
125
sage: matching_polynomial(graphs.CompleteGraph(0))
126
1
127
sage: matching_polynomial(graphs.CompleteGraph(1))
128
x
129
sage: matching_polynomial(graphs.CompleteGraph(2))
130
x^2 - 1
131
sage: matching_polynomial(graphs.CompleteGraph(3))
132
x^3 - 3*x
133
sage: matching_polynomial(graphs.CompleteGraph(4))
134
x^4 - 6*x^2 + 3
135
sage: matching_polynomial(graphs.CompleteGraph(5))
136
x^5 - 10*x^3 + 15*x
137
sage: matching_polynomial(graphs.CompleteGraph(6))
138
x^6 - 15*x^4 + 45*x^2 - 15
139
sage: matching_polynomial(graphs.CompleteGraph(7))
140
x^7 - 21*x^5 + 105*x^3 - 105*x
141
sage: matching_polynomial(graphs.CompleteGraph(8))
142
x^8 - 28*x^6 + 210*x^4 - 420*x^2 + 105
143
sage: matching_polynomial(graphs.CompleteGraph(9))
144
x^9 - 36*x^7 + 378*x^5 - 1260*x^3 + 945*x
145
sage: matching_polynomial(graphs.CompleteGraph(10))
146
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
147
sage: matching_polynomial(graphs.CompleteGraph(11))
148
x^11 - 55*x^9 + 990*x^7 - 6930*x^5 + 17325*x^3 - 10395*x
149
sage: matching_polynomial(graphs.CompleteGraph(12))
150
x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395
151
sage: matching_polynomial(graphs.CompleteGraph(13))
152
x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x
153
154
::
155
156
sage: G = Graph({0:[1,2], 1:[2]})
157
sage: matching_polynomial(G)
158
x^3 - 3*x
159
sage: G = Graph({0:[1,2]})
160
sage: matching_polynomial(G)
161
x^3 - 2*x
162
sage: G = Graph({0:[1], 2:[]})
163
sage: matching_polynomial(G)
164
x^3 - x
165
sage: G = Graph({0:[], 1:[], 2:[]})
166
sage: matching_polynomial(G)
167
x^3
168
169
::
170
171
sage: matching_polynomial(graphs.CompleteGraph(0), complement=False)
172
1
173
sage: matching_polynomial(graphs.CompleteGraph(1), complement=False)
174
x
175
sage: matching_polynomial(graphs.CompleteGraph(2), complement=False)
176
x^2 - 1
177
sage: matching_polynomial(graphs.CompleteGraph(3), complement=False)
178
x^3 - 3*x
179
sage: matching_polynomial(graphs.CompleteGraph(4), complement=False)
180
x^4 - 6*x^2 + 3
181
sage: matching_polynomial(graphs.CompleteGraph(5), complement=False)
182
x^5 - 10*x^3 + 15*x
183
sage: matching_polynomial(graphs.CompleteGraph(6), complement=False)
184
x^6 - 15*x^4 + 45*x^2 - 15
185
sage: matching_polynomial(graphs.CompleteGraph(7), complement=False)
186
x^7 - 21*x^5 + 105*x^3 - 105*x
187
sage: matching_polynomial(graphs.CompleteGraph(8), complement=False)
188
x^8 - 28*x^6 + 210*x^4 - 420*x^2 + 105
189
sage: matching_polynomial(graphs.CompleteGraph(9), complement=False)
190
x^9 - 36*x^7 + 378*x^5 - 1260*x^3 + 945*x
191
sage: matching_polynomial(graphs.CompleteGraph(10), complement=False)
192
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
193
sage: matching_polynomial(graphs.CompleteGraph(11), complement=False)
194
x^11 - 55*x^9 + 990*x^7 - 6930*x^5 + 17325*x^3 - 10395*x
195
sage: matching_polynomial(graphs.CompleteGraph(12), complement=False)
196
x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395
197
sage: matching_polynomial(graphs.CompleteGraph(13), complement=False)
198
x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x
199
"""
200
201
cdef int nverts, nedges, i, j, v, cur
202
cdef int *edges1, *edges2, *edges_mem, **edges
203
cdef fmpz_poly_t pol
204
205
if G.has_multiple_edges():
206
raise NotImplementedError
207
208
nverts = G.num_verts()
209
210
# Using Godsil's duality theorem when the graph is dense
211
212
if complement and G.density() > 0.5: # this cutoff could probably be tuned
213
f_comp = matching_polynomial(G.complement()).list()
214
f = x.parent().zero()
215
for i from 0 <= i <= nverts / 2: # implicit floor
216
j = nverts - 2 * i
217
f += complete_poly(j) * f_comp[j] * (-1)**i
218
return f
219
220
nedges = G.num_edges()
221
222
# Relabelling the vertices of the graph as [0...n-1] so that they are sorted
223
# in increasing order of degree
224
225
L = []
226
for v, d in G.degree_iterator(labels=True):
227
L.append((d, v))
228
L.sort()
229
d = {}
230
for i from 0 <= i < nverts:
231
d[L[i][1]] = i
232
G = G.relabel(d, inplace=False)
233
G.allow_loops(False)
234
235
# Initialization of pol, edges* variables.
236
237
# The edges_mem table is of size (2 * nedges * nedges), and is to be read as
238
# nedges blocks of size (2 * nedges). These blocks of size (2 * nedges) are
239
# themselves to be read as two blocks of length nedges, addressed as edges1
240
# and edges2
241
242
# Only the first block of size (2*nedges) is here filled. The function
243
# delete_and_add will need the rest of the memory.
244
245
fmpz_poly_init(pol) # sets to zero
246
edges_mem = <int *> sage_malloc(2 * nedges * nedges * sizeof(int))
247
edges = <int **> sage_malloc(2 * nedges * sizeof(int *))
248
if edges_mem is NULL or edges is NULL:
249
if edges_mem is not NULL:
250
sage_free(edges_mem)
251
if edges is not NULL:
252
sage_free(edges)
253
raise MemoryError("Error allocating memory for matchpoly.")
254
255
for i from 0 <= i < 2 * nedges:
256
edges[i] = edges_mem + i * nedges
257
258
edges1 = edges[0]
259
edges2 = edges[1]
260
261
cur = 0
262
for i, j in sorted(map(sorted, G.edges(labels=False))):
263
edges1[cur] = i
264
edges2[cur] = j
265
cur += 1
266
267
# Computing the signless matching polynomial
268
269
sig_on()
270
delete_and_add(edges, nverts, nedges, nverts, 0, pol)
271
sig_off()
272
273
# Building the actual matching polynomial
274
275
coeffs_ZZ = []
276
cdef Integer c_ZZ
277
for i from 0 <= i <= nverts:
278
c_ZZ = Integer(0)
279
fmpz_poly_get_coeff_mpz(c_ZZ.value, pol, i)
280
coeffs_ZZ.append(c_ZZ * (-1)**((nverts - i) / 2))
281
282
f = x.parent()(coeffs_ZZ)
283
sage_free(edges_mem)
284
sage_free(edges)
285
fmpz_poly_clear(pol)
286
if name is not None:
287
return f.change_variable_name(name)
288
return f
289
290
# The following is a cache of complete graph matching polynomials.
291
292
cdef list complete_matching_polys = [x.parent().one(), x]
293
294
295
def complete_poly(n):
296
"""
297
Compute the matching polynomial of the complete graph on `n` vertices.
298
299
INPUT:
300
301
- ``n`` -- order of the complete graph
302
303
.. TODO::
304
305
This code could probably be made more efficient by using FLINT
306
polynomials and being written in Cython, using an array of
307
fmpz_poly_t pointers or something... Right now just about the
308
whole complement optimization is written in Python, and could
309
be easily sped up.
310
311
EXAMPLES::
312
313
sage: from sage.graphs.matchpoly import complete_poly
314
sage: f = complete_poly(10)
315
sage: f
316
x^10 - 45*x^8 + 630*x^6 - 3150*x^4 + 4725*x^2 - 945
317
sage: f = complete_poly(20)
318
sage: f[8]
319
1309458150
320
sage: f = complete_poly(1000)
321
sage: len(str(f))
322
406824
323
324
TESTS:
325
326
Checking the numerical results up to 20::
327
328
sage: from sage.functions.orthogonal_polys import hermite
329
sage: p = lambda n: 2^(-n/2)*hermite(n, x/sqrt(2))
330
sage: all(p(i) == complete_poly(i) for i in range(2, 20))
331
True
332
"""
333
# global complete_matching_polys # if we do eventually make it a C array...
334
cdef int lcmp = len(complete_matching_polys)
335
336
if n < lcmp:
337
return complete_matching_polys[n]
338
lcmp -= 2
339
a = complete_matching_polys[lcmp]
340
b = complete_matching_polys[lcmp + 1]
341
while lcmp + 2 <= n:
342
a, b, lcmp = b, x * b - (lcmp + 1) * a, lcmp + 1
343
complete_matching_polys.append(b)
344
return b
345
346
cdef void delete_and_add(int **edges, int nverts, int nedges, int totverts, int depth, fmpz_poly_t pol):
347
"""
348
Add matching polynomial to pol via recursion.
349
350
NOTE : at the end of this function, pol represents the *SIGNLESS*
351
matching polynomial.
352
"""
353
cdef int i, j, k, edge1, edge2, new_edge1, new_edge2, new_nedges
354
cdef int *edges1, *edges2, *new_edges1, *new_edges2
355
cdef fmpz * coeff
356
357
if nverts == 3:
358
coeff = fmpz_poly_get_coeff_ptr(pol, 3)
359
if coeff is NULL:
360
fmpz_poly_set_coeff_ui(pol, 3, 1)
361
else:
362
fmpz_add_ui(coeff, coeff, 1)
363
coeff = fmpz_poly_get_coeff_ptr(pol, 1)
364
if coeff is NULL:
365
fmpz_poly_set_coeff_ui(pol, 1, nedges)
366
else:
367
fmpz_add_ui(coeff, coeff, nedges)
368
return
369
370
if nedges == 0:
371
coeff = fmpz_poly_get_coeff_ptr(pol, nverts)
372
if coeff is NULL:
373
fmpz_poly_set_coeff_ui(pol, nverts, 1)
374
else:
375
fmpz_add_ui(coeff, coeff, 1)
376
return
377
378
edges1 = edges[2 * depth]
379
edges2 = edges[2 * depth + 1]
380
new_edges1 = edges[2 * depth + 2]
381
new_edges2 = edges[2 * depth + 3]
382
383
nedges -= 1
384
385
# The last edge is (edge1, edge2)
386
edge1 = edges1[nedges]
387
edge2 = edges2[nedges]
388
new_nedges = 0
389
390
# The new edges are all the edges that are not incident with (edge1, edge2)
391
392
for i from 0 <= i < nedges:
393
if edge1 == edges1[i]:
394
break # since the rest of the edges are incident to edge1
395
# (the edges
396
# are sorted by increasing order of their first component)
397
398
if edge1 != edges2[i] and edge2 != edges2[i]:
399
# since edge2 > edge1 > edges1[i], only need to check edges2[i]
400
new_edges1[new_nedges] = edges1[i]
401
new_edges2[new_nedges] = edges2[i]
402
new_nedges += 1
403
404
delete_and_add(edges, nverts - 2, new_nedges, totverts, depth + 1, pol)
405
delete_and_add(edges, nverts, nedges, totverts, depth, pol)
406
407