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