Set Covering Problem
Overview
The Set Covering Problem is a fundamental optimization challenge: given a collection of sets, find the minimum number of sets needed to cover all elements. This NP-hard problem appears in many real-world applications including facility location, scheduling, and resource allocation.
This example demonstrates:
- Formulating a set covering problem
- Converting it to a tensor network
- Finding minimum set covers
- Analyzing the solution space
We'll use the camera location and stadium area example from the Cornell University Computational Optimization Open Textbook.
using GenericTensorNetworks, Graphs
Define our sets (each representing a camera's coverage area)
sets = [[1,3,4,6,7], [4,7,8,12], [2,5,9,11,13],
[1,2,14,15], [3,6,10,12,14], [8,14,15],
[1,2,6,11], [1,2,4,6,8,12]]
8-element Vector{Vector{Int64}}:
[1, 3, 4, 6, 7]
[4, 7, 8, 12]
[2, 5, 9, 11, 13]
[1, 2, 14, 15]
[3, 6, 10, 12, 14]
[8, 14, 15]
[1, 2, 6, 11]
[1, 2, 4, 6, 8, 12]
Tensor Network Formulation
Define the set covering problem:
setcover = SetCovering(sets)
SetCovering{Int64, Int64, UnitWeight}([1, 3, 4, 6, 7, 8, 12, 2, 5, 9, 11, 13, 14, 15, 10], [[1, 3, 4, 6, 7], [4, 7, 8, 12], [2, 5, 9, 11, 13], [1, 2, 14, 15], [3, 6, 10, 12, 14], [8, 14, 15], [1, 2, 6, 11], [1, 2, 4, 6, 8, 12]], [1, 1, 1, 1, 1, 1, 1, 1])
The problem consists of:
- Coverage constraints: Every element must be covered by at least one set
- Optimization objective: Minimize the number of sets used
constraints(setcover)
15-element Vector{ProblemReductions.LocalConstraint}:
LocalConstraint on [1, 4, 7, 8]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0, 0] │ false │
│ [1, 0, 0, 0] │ true │
│ [0, 1, 0, 0] │ true │
│ [1, 1, 0, 0] │ true │
│ [0, 0, 1, 0] │ true │
│ [1, 0, 1, 0] │ true │
│ [0, 1, 1, 0] │ true │
│ [1, 1, 1, 0] │ true │
│ [0, 0, 0, 1] │ true │
│ [1, 0, 0, 1] │ true │
│ [0, 1, 0, 1] │ true │
│ [1, 1, 0, 1] │ true │
│ [0, 0, 1, 1] │ true │
│ [1, 0, 1, 1] │ true │
│ [0, 1, 1, 1] │ true │
│ [1, 1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [1, 5]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0] │ false │
│ [1, 0] │ true │
│ [0, 1] │ true │
│ [1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [1, 2, 8]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0] │ false │
│ [1, 0, 0] │ true │
│ [0, 1, 0] │ true │
│ [1, 1, 0] │ true │
│ [0, 0, 1] │ true │
│ [1, 0, 1] │ true │
│ [0, 1, 1] │ true │
│ [1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [1, 5, 7, 8]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0, 0] │ false │
│ [1, 0, 0, 0] │ true │
│ [0, 1, 0, 0] │ true │
│ [1, 1, 0, 0] │ true │
│ [0, 0, 1, 0] │ true │
│ [1, 0, 1, 0] │ true │
│ [0, 1, 1, 0] │ true │
│ [1, 1, 1, 0] │ true │
│ [0, 0, 0, 1] │ true │
│ [1, 0, 0, 1] │ true │
│ [0, 1, 0, 1] │ true │
│ [1, 1, 0, 1] │ true │
│ [0, 0, 1, 1] │ true │
│ [1, 0, 1, 1] │ true │
│ [0, 1, 1, 1] │ true │
│ [1, 1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [1, 2]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0] │ false │
│ [1, 0] │ true │
│ [0, 1] │ true │
│ [1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [2, 6, 8]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0] │ false │
│ [1, 0, 0] │ true │
│ [0, 1, 0] │ true │
│ [1, 1, 0] │ true │
│ [0, 0, 1] │ true │
│ [1, 0, 1] │ true │
│ [0, 1, 1] │ true │
│ [1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [2, 5, 8]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0] │ false │
│ [1, 0, 0] │ true │
│ [0, 1, 0] │ true │
│ [1, 1, 0] │ true │
│ [0, 0, 1] │ true │
│ [1, 0, 1] │ true │
│ [0, 1, 1] │ true │
│ [1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [3, 4, 7, 8]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0, 0] │ false │
│ [1, 0, 0, 0] │ true │
│ [0, 1, 0, 0] │ true │
│ [1, 1, 0, 0] │ true │
│ [0, 0, 1, 0] │ true │
│ [1, 0, 1, 0] │ true │
│ [0, 1, 1, 0] │ true │
│ [1, 1, 1, 0] │ true │
│ [0, 0, 0, 1] │ true │
│ [1, 0, 0, 1] │ true │
│ [0, 1, 0, 1] │ true │
│ [1, 1, 0, 1] │ true │
│ [0, 0, 1, 1] │ true │
│ [1, 0, 1, 1] │ true │
│ [0, 1, 1, 1] │ true │
│ [1, 1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [3]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0] │ false │
│ [1] │ true │
└───────────────┴───────┘
LocalConstraint on [3]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0] │ false │
│ [1] │ true │
└───────────────┴───────┘
LocalConstraint on [3, 7]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0] │ false │
│ [1, 0] │ true │
│ [0, 1] │ true │
│ [1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [3]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0] │ false │
│ [1] │ true │
└───────────────┴───────┘
LocalConstraint on [4, 5, 6]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0, 0] │ false │
│ [1, 0, 0] │ true │
│ [0, 1, 0] │ true │
│ [1, 1, 0] │ true │
│ [0, 0, 1] │ true │
│ [1, 0, 1] │ true │
│ [0, 1, 1] │ true │
│ [1, 1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [4, 6]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0, 0] │ false │
│ [1, 0] │ true │
│ [0, 1] │ true │
│ [1, 1] │ true │
└───────────────┴───────┘
LocalConstraint on [5]
┌───────────────┬───────┐
│ Configuration │ Valid │
├───────────────┼───────┤
│ [0] │ false │
│ [1] │ true │
└───────────────┴───────┘
objectives(setcover)
8-element Vector{ProblemReductions.LocalSolutionSize{Int64}}:
LocalSolutionSize{Int64} on [1]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [2]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [3]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [4]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [5]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [6]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [7]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [8]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0] │ 0 │
│ [1] │ 1 │
└───────────────┴──────┘
Convert to tensor network representation:
problem = GenericTensorNetwork(setcover)
GenericTensorNetwork{SetCovering{Int64, Int64, UnitWeight}, OMEinsum.DynamicNestedEinsum{Int64}, Int64}
- open vertices: Int64[]
- fixed vertices: Dict{Int64, Int64}()
- contraction time = 2^8.426, space = 2^4.0, read-write = 2^9.377
Mathematical Background
For each set $s$ with weight $w_s$, we assign a boolean variable $v_s ∈ \{0,1\}$, indicating whether the set is included in the solution.
The network uses two types of tensors:
Set Tensors: For each set $s$:
\[W(x_s, w_s) = \begin{pmatrix} 1 \\ x_s^{w_s} \end{pmatrix}\]
Element Constraint Tensors: For each element a and its containing sets N(a):
\[B_{s_1,...,s_{|N(a)|}} = \begin{cases} 0 & \text{if all } s_i = 0 \text{ (element not covered - invalid)} \\ 1 & \text{otherwise (element is covered - valid)} \end{cases}\]
Check the contraction complexity:
contraction_complexity(problem)
Time complexity: 2^8.426264754702098
Space complexity: 2^4.0
Read-write complexity: 2^9.377210530388552
Solution Analysis
1. Set Covering Polynomial
The polynomial $P(S,x) = \sum_i c_i x^i$ counts set covers by size, where $c_i$ is the number of valid covers using $i$ sets
covering_polynomial = solve(problem, GraphPolynomial())[]
2∙x4 + 11∙x5 + 13∙x6 + 6∙x7 + x82. Minimum Set Cover Size
Find the minimum number of sets needed:
min_cover_size = solve(problem, SizeMin())[]
4.0ₜ
Count minimum set covers:
counting_minimum_setcovering = solve(problem, CountingMin())[]
(4.0, 2.0)ₜ
3. Minimum Set Cover Configurations
Enumerate all minimum set covers:
min_configs = read_config(solve(problem, ConfigsMin())[])
2-element Vector{StaticBitVector{8, 1}}:
01111000
10101100
The two optimal solutions are $\{z_1, z_3, z_5, z_6\}$ and $\{z_2, z_3, z_4, z_5\}$, where $z_i$ represents the $i$-th set in our original list.
Verify solutions are valid:
all(c->is_set_covering(problem.problem, c), min_configs)
true
Note: For finding just one minimum set cover, use the SingleConfigMin property
More APIs
The Independent Set Problem chapter has more examples on how to use the APIs.
This page was generated using Literate.jl.