Skip to content

Commit

Permalink
Trac #33573: extend method cores to accept graphs with multiple edges
Browse files Browse the repository at this point in the history
We add an implementation for graphs with multiple edges.

URL: https://trac.sagemath.org/33573
Reported by: dcoudert
Ticket author(s): David Coudert
Reviewer(s): Travis Scrimshaw
  • Loading branch information
Release Manager committed Mar 31, 2022
2 parents 9543d07 + c3ef95c commit a79b1b8
Showing 1 changed file with 104 additions and 40 deletions.
144 changes: 104 additions & 40 deletions src/sage/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -7233,8 +7233,9 @@ def cores(self, k=None, with_labels=False):
.. SEEALSO::
* Graph cores is also a notion related to graph homomorphisms. For
this second meaning, see :meth:`Graph.has_homomorphism_to`.
* Graph cores is also a notion related to graph homomorphisms. For
this second meaning, see :meth:`Graph.has_homomorphism_to`.
* :wikipedia:`Degeneracy_(graph_theory)`
EXAMPLES::
Expand All @@ -7257,47 +7258,110 @@ def cores(self, k=None, with_labels=False):
sage: ordering, core = g.cores(2)
sage: len(core) == 0
True
Checking the cores of a bull graph::
sage: G = graphs.BullGraph()
sage: G.cores(with_labels=True)
{0: 2, 1: 2, 2: 2, 3: 1, 4: 1}
sage: G.cores(k=2)
([3, 4], [0, 1, 2])
Graphs with multiple edges::
sage: G.allow_multiple_edges(True)
sage: G.add_edges(G.edges())
sage: G.cores(with_labels=True)
{0: 4, 1: 4, 2: 4, 3: 2, 4: 2}
sage: G.cores(k=4)
([3, 4], [0, 1, 2])
"""
self._scream_if_not_simple()
# compute the degrees of each vertex
self._scream_if_not_simple(allow_multiple_edges=True)
if k is not None and k < 0:
raise ValueError("parameter k must be a non negative integer")
if not self or not self.size():
if k is not None:
return ([], list(self)) if not k else (list(self), [])
if with_labels:
return {u: 0 for u in self}
return [0]*self.order()

# Compute the degrees of each vertex and set up initial guesses for core
degrees = self.degree(labels=True)

# Sort vertices by degree. Store in a list and keep track of where a
# specific degree starts (effectively, the list is sorted by bins).
verts = sorted(degrees.keys(), key=lambda x: degrees[x])
bin_boundaries = [0]
curr_degree = 0
for i,v in enumerate(verts):
if degrees[v] > curr_degree:
bin_boundaries.extend([i] * (degrees[v] - curr_degree))
curr_degree = degrees[v]
vert_pos = {v: pos for pos,v in enumerate(verts)}
# Set up initial guesses for core and lists of neighbors.
core = degrees
nbrs = {v: set(self.neighbors(v)) for v in self}
# form vertex core building up from smallest
for v in verts:

# If all the vertices have a degree larger than k, we can return our
# answer if k is not None
if k is not None and core[v] >= k:
return verts[:vert_pos[v]], verts[vert_pos[v]:]

for u in nbrs[v]:
if core[u] > core[v]:
nbrs[u].remove(v)

# Cleverly move u to the end of the next smallest bin (i.e.,
# subtract one from the degree of u). We do this by swapping
# u with the first vertex in the bin that contains u, then
# incrementing the bin boundary for the bin that contains u.
pos = vert_pos[u]
bin_start = bin_boundaries[core[u]]
vert_pos[u] = bin_start
vert_pos[verts[bin_start]] = pos
verts[bin_start],verts[pos] = verts[pos],verts[bin_start]
bin_boundaries[core[u]] += 1
core[u] -= 1

if self.has_multiple_edges():
# We partition the vertex set into buckets of vertices by degree
nbuckets = max(degrees.values()) + 1
bucket = [set() for _ in range(nbuckets)]
for u, d in degrees.items():
bucket[d].add(u)

verts = []
for d in range(nbuckets):
if not bucket[d]:
continue

if k is not None and d >= k:
# all vertices have degree >= k. We can return the solution
from itertools import chain
return verts, list(chain(*bucket))

while bucket[d]:
# Remove a vertex from the bucket and add it to the ordering
u = bucket[d].pop()
verts.append(u)

# Update the degree and core number of unordered neighbors.
# The core number of an unordered neighbor v cannot be less
# than the core number of an ordered vertex u.
for x, y in self.edge_iterator(vertices=[u], labels=False, sort_vertices=False):
v = y if x is u else x
if core[v] > core[u]:
dv = core[v]
bucket[dv].discard(v)
bucket[dv - 1].add(v)
core[v] -= 1

else:
# Sort vertices by degree. Store in a list and keep track of where a
# specific degree starts (effectively, the list is sorted by bins).
verts = sorted(degrees.keys(), key=lambda x: degrees[x])
if k is not None and k > degrees[verts[-1]]:
return verts, []
bin_boundaries = [0]
curr_degree = 0
for i,v in enumerate(verts):
if degrees[v] > curr_degree:
bin_boundaries.extend([i] * (degrees[v] - curr_degree))
curr_degree = degrees[v]
vert_pos = {v: pos for pos,v in enumerate(verts)}
# Lists of neighbors.
nbrs = {v: set(self.neighbors(v)) for v in self}
# form vertex core building up from smallest
for v in verts:

# If all the vertices have a degree larger than k, we can return
# our answer if k is not None
if k is not None and core[v] >= k:
return verts[:vert_pos[v]], verts[vert_pos[v]:]

for u in nbrs[v]:
if core[u] > core[v]:
nbrs[u].remove(v)

# Cleverly move u to the end of the next smallest bin
# (i.e., subtract one from the degree of u). We do this
# by swapping u with the first vertex in the bin that
# contains u, then incrementing the bin boundary for the
# bin that contains u.
pos = vert_pos[u]
bin_start = bin_boundaries[core[u]]
vert_pos[u] = bin_start
vert_pos[verts[bin_start]] = pos
verts[bin_start],verts[pos] = verts[pos],verts[bin_start]
bin_boundaries[core[u]] += 1
core[u] -= 1

if k is not None:
return verts, []
Expand Down

0 comments on commit a79b1b8

Please sign in to comment.