Graph Edge Coloring Problem
Given an undirected graph $G=(V,E)$, the graph edge coloring problem aims to assign a color to each edge so that no two edges of the same color share a common endpoint.
More specifically, for a set $C$ of colors, the goal is to find an assignment $\sigma:E\rightarrow C$ such that for any two distinct edges $e$ and $e’$ incident to the same node, we have $\sigma(e)\neq \sigma(e’)$. Equivalently, for every node $u\in V$ and any two distinct neighbors $v\neq v’$ with $(u,v)\in E$ and $(u,v’)\in E$ the following must hold:
\[\sigma((u,v))\neq \sigma((u,v')).\]The graph edge coloring problem can be formulated easily as a QUBO expression. Let $V=\lbrace 0,1,\ldots,n−1\rbrace$ and $C=\lbrace 0,1,\ldots,m−1\rbrace$. We assign unique IDs $0,1,\ldots,s−1$ to the $s=|E|$ edges, and let $(u_i,v_i)$ denote the $i$-th edge.
We introduce an $s\times m$ matrix $X=(x_{i,j})$ of binary variables, where $x_{i,j}=1$ if and only if edge $(u_i,v_i)$ is assigned color $j$.
One-hot constraint
Since exactly one color must be assigned to each edge, each row of $X$ must be one-hot:
\[\begin{aligned} \text{onehot}&= \sum_{i=0}^{s-1}\bigr(\sum_{j=0}^{m-1}x_{i,j}==1\bigl)\\ &=\sum_{i=0}^{s-1}\bigr(1-\sum_{j=0}^{m-1}x_{i,j}\bigl)^2 \end{aligned}\]Incident edges must differ
For each node, any pair of distinct incident edges must not be assigned the same color. This can be penalized as follows:
\[\begin{aligned} \text{different}&= \sum_{u\in V}\sum_{\substack{i<k\\ i,k\in I(u)}}x_i\cdot x_k\\ &=\sum_{u\in V}\sum_{\substack{i<k\\ i,k\in I(u)}}\sum_{j=0}^{m-1}x_{i,j}x_{k,j} \end{aligned}\]where $I(u)\subseteq \lbrace 0,1,\ldots,s−1\rbrace$ denotes the set of edge IDs incident to node $u$.
QUBO objective
By combining these expressions, we obtain the QUBO objective function:
\[\begin{aligned} f &= \text{onehot}+\text{different} \end{aligned}\]This objective attains the minimum value 0 if and only if a valid edge coloring of the graph exists.
PyQBPP formulation
It is known that the edge chromatic number of a simple graph is either $\Delta$ or $\Delta+1$, where $\Delta$ is the maximum degree of the graph. The following PyQBPP program attempts to find an edge coloring of a graph with $n$ nodes and $s$ edges using $m=\Delta$ colors:
import pyqbpp as qbpp
n = 16
edges = [
(0, 1), (0, 2), (0, 4), (1, 3), (1, 4), (1, 7), (2, 5),
(2, 6), (3, 7), (3, 13), (3, 15), (4, 6), (4, 7), (4, 14),
(5, 8), (6, 8), (6, 14), (7, 14), (7, 15), (8, 9), (8, 12),
(9, 10), (9, 11), (9, 12), (10, 11),(10, 12),(10, 13),(10, 14),
(10, 15),(11, 13),(12, 14),(13, 15),(14, 15)]
adj = [[] for _ in range(n)]
for i, (u, v) in enumerate(edges):
adj[u].append(i)
adj[v].append(i)
max_degree = max(len(neighbors) for neighbors in adj)
m = max_degree
s = len(edges)
x = qbpp.var("x", shape=(s, m))
onehot = qbpp.sum(qbpp.vector_sum(x) == 1)
different = 0
for i in range(n):
for u in adj[i]:
for v in adj[i]:
if u < v:
different += qbpp.sum(x[u] * x[v])
f = onehot + different
f.simplify_as_binary()
solver = qbpp.EasySolver(f)
sol = solver.search(target_energy=0)
print(f"colors = {m}")
print(f"onehot = {sol(onehot)}")
print(f"different = {sol(different)}")
# Extract the color assigned to each edge (returns -1 if the row is not one-hot)
edge_color = [next((j for j in range(m) if sol(x[i][j]) == 1), -1)
for i in range(s)]
for i in range(s):
print(f"Edge {edges[i]}: color {edge_color[i]}")
In this program, we first build the incidence list adj, where adj[i] stores the indices of edges incident to node i. We then compute the maximum degree $\Delta$ and set m=$\Delta$. Next, we define an s$\times$m matrix x of binary variables, where x[i][j]=1 means that edge i is assigned color j. We construct the expressions onehot, different, and f as follows:
onehotenforces that each edge is assigned exactly one color, usingqbpp.vector_sum(x) == 1on each row.differentpenalizes pairs of edges that share an endpoint and are assigned the same color, usingx[u] * x[v]to form the elementwise product of the two rows andqbpp.sum()to sum across themcolor columns.f = onehot + differentis the QUBO objective, which attains the minimum value 0 if and only if a validm-edge-coloring is found.
We solve the resulting QUBO using the Easy Solver with target_energy=0, and store the solution in sol. We then print the values of onehot and different evaluated at sol.
We also compute edge_color, which stores the color assigned to each edge, by scanning each row of sol(x) and recording the position of the 1. If a row is not a valid one-hot vector, -1 is recorded instead.
This program produces the following output:
colors = 6
onehot = 0
different = 0
Therefore, a valid edge coloring using m = 6 colors is found.
Visualization using matplotlib
The following code visualizes the Edge Coloring solution. Each edge is drawn in the color assigned by the solver, cycling through a fixed palette:
import matplotlib.pyplot as plt
import networkx as nx
G = nx.Graph()
G.add_nodes_from(range(n))
G.add_edges_from(edges)
pos = nx.spring_layout(G, seed=42)
palette = ["#e74c3c", "#3498db", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c",
"#e67e22", "#2c3e50"]
edge_color_idx = [next((k for k in range(m) if sol(x[i][k]) == 1), 0)
for i in range(len(edges))]
edge_colors = [palette[c % len(palette)] for c in edge_color_idx]
nx.draw(G, pos, with_labels=True, node_color="#d5dbdb", node_size=400,
font_size=9, edge_color=edge_colors, width=2.5)
plt.title(f"Edge Coloring ({m} colors)")
plt.savefig("edge_color.png", dpi=150, bbox_inches="tight")
plt.show()
Each edge is colored according to its assigned color, so that no two edges sharing an endpoint have the same color.