The Godsil–Gutman identity #
The sum over all ±1 signings of the edges of G of the characteristic polynomials of the signed
adjacency matrices is #signings • μ(G): the expected characteristic polynomial of a random signing
is the matching polynomial.
The proof expands each determinant over permutations and exchanges the two sums. For a fixed
permutation the sum over signings factors over edges, and an edge contributes 0 unless it is used
an even number of times. The surviving permutations are the involutions whose 2-cycles are edges
(SimpleGraph.IsEdgeInvolution); they correspond bijectively to the matchings of G, with sign
(-1)^k for a k-edge matching.
Main results #
SimpleGraph.godsil_gutman: the Godsil–Gutman identity.SimpleGraph.innerSum_eq_ite: the sum over signings for a fixed permutation.SimpleGraph.permOfMatching,SimpleGraph.matchingOfPerm: the bijection between matchings and edge involutions.SimpleGraph.signAvg_ne_zero_iff: the average of a product of edge signs is nonzero iff every exponent is even.
References #
- C. D. Godsil, I. Gutman, On the theory of the matching polynomial, J. Graph Theory 5 (1981) 137–144.
- A. W. Marcus, D. A. Spielman, N. Srivastava, Interlacing families I: Bipartite Ramanujan graphs of all degrees, Ann. of Math. 182 (2015) 307–325.
The signing matrix is symmetric — both the support (G.Adj) and the sign (Sym2 is
unordered) are symmetric. Hence A_cfg is Hermitian over ℝ, so it has real eigenvalues (the input
the spectral/Ramanujan side needs).
The diagonal of the signing matrix is zero (no self-loops).
Steps 1–2: expand charpoly over permutations and read off the entries #
Step 2 (fixed point). On the diagonal the char-matrix entry of the signing matrix is just X
(the signing matrix has zero diagonal). So a fixed point of σ contributes a factor X.
Step 2 (off-diagonal). Off the diagonal the char-matrix entry is −C of the signed adjacency
entry. So a point in σ.support contributes −C (A_cfg)_{σ i, i}.
Step 1. The characteristic polynomial of the signing matrix, expanded over permutations via
Matrix.det_apply (charpoly = (charmatrix).det). The summand factorizes (step 2) into
X^{#fixed} · ∏_{support} (−C …).
Step 3: split each permutation's product into X^{#fixed} · ∏_{support}(−C …) #
Step 2/3 (per factor). Each factor of the permutation product is X on a fixed point and
−C (A_cfg)_{σ i, i} on the support.
Step 3 (product split). The permutation product factorizes into X over the fixed points
times −C(A_cfg)_{σ i, i} over the support: ∏_i = X^{#fixed} · ∏_{i ∈ supp σ} (−C …).
Step 4 (first cut): only permutations whose support pairs are all edges survive #
Step 4 (non-edge kills it). If some support pair (σ i, i) is NOT an edge of G, the support
product is 0. So in ∑_σ, only permutations whose every support pair (σ j, j) is a G-edge can
contribute — the first restriction toward "only edge-involutions (matchings) survive".
Step 5 (sign): an involution with k transpositions has sign (−1)^k #
Step 5 (the sign). For an involution σ (σ² = 1), sign σ = (−1)^{#support / 2}. Since an
edge-involution with k 2-cycles has #support = 2k, this is the (−1)^k weight that matches the
matching polynomial's sign — obtained WITHOUT any cycleType multiset bookkeeping
(sign_of_pow_two_eq_one + card_fixedPoints).
Assembly: swap the sums and isolate the inner config-sum S(σ) #
Assembly step. Combining the permutation expansion (step 1) with the product split (step 3) and
swapping ∑_cfg/∑_σ, the summed characteristic polynomial is a sum over permutations of
sign σ • (X^{#fixed} · S(σ)), where S(σ) = ∑_cfg ∏_{support} (−C …) is the inner config-sum to be
analyzed next (it vanishes unless σ is an edge-involution).
Analysis of S(σ): vanishes unless every support pair is an edge #
S(σ) analysis, first cut. If σ moves some vertex i to a non-neighbour ((σ i, i) not an
edge), then the inner config-sum S(σ) is 0: every single configuration's support product already
vanishes (prod_support_eq_zero_of_not_adj), so the sum over configurations is 0. Concept: a signed
walk that steps along a non-edge carries weight 0, regardless of the signing.
S(σ) reduces to a real-valued sum. Since C (the constant-polynomial embedding ℝ →+* ℝ[X])
is a ring hom, the whole ℝ[X]-valued inner sum S(σ) is C of a purely real quantity
T'(σ) = ∑_cfg ∏_{support} (−A_cfg)_{σi,i}. This is the key reduction: the hard combinatorics (edge
factorization, odd-multiplicity-vanishes) now happens in ℝ, where Finset.prod_univ_sum applies
cleanly — no ℝ[X] machinery in the way. Concept: the polynomial structure was inert; the content is
the real expectation of signed-edge-weight products.
Engine of the edge factorization: the per-edge ±1 power-sum (odd ↦ 0, even ↦ 2) #
When T'(σ) is factorized over edges via Finset.prod_univ_sum, each edge e contributes the Bool-sum
∑_{b} (signOf b)^{mult(e)}, where mult(e) is how many support vertices map to e. These two lemmas
give its value: 0 if the edge's multiplicity is odd, 2 if even. An odd multiplicity anywhere zeroes
the whole product — this is precisely "every edge must be used an even number of times ⟺ σ² = 1".
Even multiplicity ⟹ the per-edge ±1 power-sum is 2 (1^k + (−1)^k = 2 for even k). The
surviving edges each contribute a factor 2, giving the overall 2^{#edges} normalization.
Fiberwise edge-factorization of the inner config-sum T'(σ) #
The structural heart of step 3. Group the support product by the edge s(σ i, i) that each support
vertex sits on (Finset.prod_fiberwise), then push ∑_cfg through ∏_e (Fintype.prod_sum): the
inner real config-sum T'(σ) becomes a product over edges of a per-edge Bool-sum. This is what
lets the "odd-multiplicity edge ⟹ 0" engine (sum_signOf_pow_eq_zero_of_odd) act edge by edge — each
factor is a ∑_b over a fiber whose size is exactly that edge's multiplicity.
Per-edge collapse: each surviving edge contributes ∑_b (−signOf b)^{mult} #
The −signOf power-sum, odd case — the engine lemma in the sign that actually appears in
innerSum_factor_edges. Same value as sum_signOf_pow_eq_zero_of_odd: the extra negation is absorbed
by b ↦ ¬b (−(if true … )=−1, −(if false …)=1).
Fiber collapse. On an edge e all of whose support-fiber pairs (σ i, i) are genuine
G-edges, the inner fiber product collapses to a single power: every factor equals −signOf b, so the
product is (−signOf b)^{mult}, where mult is the fiber cardinality — exactly the multiplicity of
e in σ's support walk. Feeding this into sum_negSignOf_pow_eq_* is what makes an odd-multiplicity
edge zero the whole term.
The hard characterization: an odd edge-multiplicity exists ⟺ σ is not an involution #
mult_σ(s(a,b)) = [σ a = b] + [σ b = a] ∈ {0,1,2}. It is odd (=1) exactly when σ sends a ↦ b
but not b ↦ a, i.e. a lies on a cycle of length ≥ 3. Hence some pair has odd multiplicity ⟺
σ² ≠ 1. This lemma is the permutation-theoretic key step of the ⟹ direction: it produces the witnessing
ordered edge a ↦ b ↦ (≠a).
The witness edge carries multiplicity 1. For the ordered edge a ↦ b with σ b ≠ a (the
output of exists_of_sq_ne_one), the support-fiber over the unordered pair s(a, b) is exactly
{a}: only a satisfies s(σ i, i) = s(a, b) (since b would need σ b = a). So this edge's
multiplicity is 1 — odd — which is what makes its per-edge Bool-sum vanish.
The hard direction's payoff: non-involutions contribute 0. If σ² ≠ 1, the inner config-sum
T'(σ) vanishes. Proof: the witness edge s(a, c) (with σ a = c, σ c ≠ a) has fiber {a}
(fiber_witness), so its per-edge factor is the single Bool-sum ∑_b −(if Adj then signOf b else 0),
which is 0 (an odd, =1, multiplicity); one zero factor zeroes the whole edge-product
(innerSum_factor_edges). This is precisely why only involutions (matchings) survive in ∑_σ.
The surviving value: edge-involutions give product 1 for every signing #
Edge-involutions give product 1 for every signing. If σ² = 1 and every support pair is a
G-edge, then for every configuration cfg the support product is 1. Pair each support vertex
a with σ a (an involution on the support, Finset.prod_involution): the two factors of a 2-cycle
are the same ±1 (by symmetry of the signing matrix), so their product is (±1)² = 1. Hence
T'(σ) = ∑_cfg 1 = #configs for an edge-involution — the value that, weighted by sign = (−1)^k and
X^{n−2k}, reassembles the matching polynomial.
The surviving value. For an edge-involution (σ² = 1, every support pair a G-edge) the inner
config-sum T'(σ) equals #configs: the integrand is 1 for every signing (prod_eq_one_of_involution),
so the sum is just the number of configurations. This is the constant that, summed against
sign(σ)·X^{#fix(σ)} over the matchings, yields #configs • matchingPoly.
Assembly: the per-permutation trichotomy and the edge-involution predicate #
Non-edge support ⟹ T'(σ) = 0 (real level). If some support pair (σ i, i) is not a G-edge,
every configuration's support product already contains a 0 factor, so T'(σ) = 0. Completes the
trichotomy with innerSum_factor_zero_of_sq_ne_one (non-involution) and
innerSum_eq_card_of_involution (edge-involution): only edge-involutions survive.
σ is an edge-involution: an involution (σ² = 1) all of whose 2-cycles are G-edges. These
are exactly the permutations that survive ∑_cfg charpoly, and they biject with the matchings of G
(a k-transposition edge-involution ↔ a k-edge matching).
Instances For
Equations
The trichotomy, collapsed. T'(σ) is #configs on edge-involutions and 0 on everything
else. This is the per-permutation input to the final assembly: ∑_cfg charpoly = ∑_σ sign·X^{#fix}·C(T'(σ))
then collapses to a sum over edge-involutions only.
The bijection, inverse map: a matching M induces an involution σ_M #
The hard half of the matching ↔ edge-involution bijection. Given a matching M (pairwise
vertex-disjoint edges), the map partner M sends each matched vertex to the other endpoint of its
(unique) edge and fixes everything else; it is an involution, hence a permutation permOfMatching.
Sym2.Mem.other' depends only on the element and the edge, not on the membership proof: equal
edges give equal partners.
In a matching, the edge containing a given vertex is unique.
The partner of v under matching M: the other endpoint of v's edge if matched, else v.
Equations
- SimpleGraph.partner M v = if h : ∃ e ∈ M, v ∈ e then Sym2.Mem.other' ⋯ else v
Instances For
Value of partner at a matched vertex: the other endpoint of its edge (well-defined by
uniqueness of the matching edge).
partner M is an involution: matched vertices swap within their edge (Sym2.other_invol'),
unmatched vertices are fixed.
The involution permutation induced by a matching.
Equations
Instances For
permOfMatching is an involution.
When a matching's edges are edges of G, the induced involution is an edge-involution: every
moved vertex is sent along a G-edge (the matching edge it lies on).
The bijection, forward map: an edge-involution σ induces a matching M(σ) #
For an involution, a 2-cycle's edge is determined by any of its vertices: if v lies on the
edge s(a, σ a), then that edge is s(v, σ v). (Either v = a, or v = σ a and then σ v = a.)
The matching induced by an edge-involution: the set of its 2-cycle edges {s(a, σ a) : a ∈ supp}.
Equations
- SimpleGraph.matchingOfPerm σ = Finset.image (fun (a : V) => s(a, σ a)) σ.support
Instances For
M(σ)'s edges are edges of G (each 2-cycle pair is a G-edge).
M(σ) is a matching: its edges are pairwise vertex-disjoint, because for an involution any vertex
determines its 2-cycle edge (edge_eq_of_mem).
The bijection is mutually inverse #
Left inverse: recovering the edge-involution from its matching gives σ back. For each v:
if v is moved, its M(σ)-edge is s(v, σ v) so its partner is σ v; if v is fixed, it is
unmatched, so its partner is v = σ v.
other' of a vertex on a non-diagonal edge differs from that vertex.
Right inverse: the matching of the involution of a matching M (with M ⊆ G.edgeFinset) is
M. ⊆: each 2-cycle edge s(a, partner a) reconstructs the matching edge through a. ⊇: each
e ∈ M is a G-edge (non-diagonal), so any vertex a ∈ e is moved (its partner is the other,
distinct, endpoint), and s(a, partner a) = e.
Card relation: support.card = 2 · |matching| #
Each edge of M(σ) has exactly two support preimages under a ↦ s(a, σ a): the two
endpoints {b, σ b} of its 2-cycle.
The 2-to-1 count. For an edge-involution, the support has twice as many vertices as M(σ)
has edges: supp.card = 2 · |M(σ)|. (Each matching edge is covered by its two endpoints.)
Final assembly #
Step 1: collapse to a sum over edge-involutions. Combining the permutation expansion
(sum_charpoly_eq), the C-reduction (innerSum_eq_C) and the trichotomy (innerSum_eq_ite),
∑_cfg charpoly becomes a sum over the edge-involutions only, each contributing
sign(σ)·X^{#fix}·C(#configs).
Step A: per-term rewrite. For an edge-involution σ, its contribution sign(σ)·X^{#fix}
equals C((−1)^{|M(σ)|})·X^{n−2|M(σ)|} — the term of the matching polynomial indexed by the matching
M(σ). Uses sign_involution (=(−1)^{supp/2}), support_card_eq (supp=2|M|) and #fix = n−supp.
A matching of an edge-involution has at most n/2 edges (its 2|M| matched vertices fit in V).
M(σ) is a |M(σ)|-edge matching of G.
Step B: reindex the edge-involution sum to a sum over all matchings of G, via the bijection
σ ↦ M(σ) / M ↦ σ_M.
Step C: group by edge-count. The sum over all matchings, grouped by cardinality k, gives the
matching polynomial (each size-k class contributes m_k copies of C((−1)^k)·X^{n−2k}).
The Godsil–Gutman identity (C2, complete) #
Godsil–Gutman (Component C2). The sum over all ±1 edge-signings of the characteristic
polynomial of the signed adjacency matrix equals #configs • matchingPoly G. Equivalently, the
expected characteristic polynomial of a uniformly random signing is the matching polynomial. This is
the bridge from the spectral side to the combinatorial side of the Marcus–Spielman–Srivastava program.
Assembles: charpoly_sum_eq_filter (only edge-involutions survive) ∘ edgeInv_term (each contributes a
matching-polynomial term) ∘ sum_filter_eq_sum_matchings (bijection) ∘ sum_matchings_eq_matchingPoly
(group by edge-count).
The parity-closed-walk kernel (moment-level companion of Godsil–Gutman) #
The algebraic heart of the identity P_d = 2^{−|E|} ∑_π trace(A_π^d) (Chen–van Dam–Bu 2023): a walk's
signed weight, averaged over all ±1 signings, vanishes unless the walk is parity-closed (every
edge used an even number of times). It is exactly our sign-killing engine sum_signOf_pow_* summed over
edges via Fintype.prod_sum. Here t e is the edge-multiplicity of a walk; the product over signings
of signOf(cfg e)^{t e} is 2^{#pairs} if all multiplicities are even, else 0.
The unifying principle: averaging over signings is the ℤ/2 parity projection #
Both formalized results in this file are instances of one fact — averaging over all ±1 signings
projects onto the parity-even part. Concretely (sum_signOf_prod_pow), ∑_cfg ∏_e signOf(cfg e)^{t e}
is 2^{#pairs} when every multiplicity t e is even, and 0 otherwise. The "survives ⟺ even" gate
(signAvg_ne_zero_iff) is the common heart of the two levels:
- Godsil–Gutman (
godsil_gutman, characteristic-polynomial level): a permutationσcontributes to∑_cfg charpoly(A_cfg)exactly when its support multiplicities are all even — i.e.σ² = 1, an involution, i.e. a matching. (Elementary-symmetric functions of the eigenvalues.) - Parity-closed walks (Chen–van Dam–Bu 2023, moment level): a closed walk contributes to
∑_cfg trace(A_cfg^d)exactly when every edge is used an even number of times — a parity-closed walk. (Power sums of the eigenvalues.)
Elementary-symmetric (charpoly) and power-sum (moments) are two readings of the same ℤ/2-Fourier
projection ∑_{s ∈ {±1}} s^k = 2·[k \text{ even}].
The parity gate. A sign-monomial survives the signing-average iff its multiplicity vector is
even — the single principle underlying both godsil_gutman (where "even everywhere" = σ²=1, a
matching) and the parity-closed-walk identity (where it = the walk is parity-closed).