Binary Paint Shop Problem

Overview

The Binary Paint Shop Problem involves a sequence of cars, each appearing exactly twice. Each car must be painted red in one occurrence and blue in the other. The goal is to minimize the number of color changes when processing the sequence in order.

This example demonstrates:

  • Formulating the paint shop problem
  • Converting it to a tensor network
  • Finding optimal coloring sequences
  • Visualizing solutions

We'll use a character sequence where each character represents a car.

using GenericTensorNetworks, Graphs, GenericTensorNetworks.ProblemReductions

Define our sequence (each character appears exactly twice)

sequence = collect("iadgbeadfcchghebif")
18-element Vector{Char}:
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
 'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
 'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
 'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)
 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)

Problem Visualization

We can represent this problem as a graph:

  • Vertices are positions in the sequence
  • Blue edges connect the same car's two occurrences
  • Black edges connect adjacent positions in the sequence
rot(a, b, θ) = cos(θ)*a + sin(θ)*b, cos(θ)*b - sin(θ)*a
locations = [rot(0.0, 100.0, -0.25π - 1.5*π*(i-0.5)/length(sequence)) for i=1:length(sequence)]
graph = path_graph(length(sequence))
for i=1:length(sequence)
    j = findlast(==(sequence[i]), sequence)
    i != j && add_edge!(graph, i, j)
end
show_graph(graph, locations; texts=string.(sequence), format=:svg, edge_colors=
    [sequence[e.src] == sequence[e.dst] ? "blue" : "black" for e in edges(graph)])
Example block output

Note: Vertices connected by blue edges must have different colors, and our goal becomes a min-cut problem on the black edges.

Tensor Network Formulation

Define the binary paint shop problem:

pshop = PaintShop(sequence)
PaintShop{Char}(['i', 'a', 'd', 'g', 'b', 'e', 'a', 'd', 'f', 'c', 'c', 'h', 'g', 'h', 'e', 'b', 'i', 'f'], Bool[1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0])

The objective is to minimize color changes:

objectives(pshop)
17-element Vector{ProblemReductions.LocalSolutionSize{Bool}}:
 LocalSolutionSize{Bool} on [1, 2]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [2, 3]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [3, 4]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [4, 5]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [5, 6]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [6, 2]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ true  │
│    [1, 0]     │ false │
│    [0, 1]     │ false │
│    [1, 1]     │ true  │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [2, 3]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [3, 7]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ true  │
│    [1, 0]     │ false │
│    [0, 1]     │ false │
│    [1, 1]     │ true  │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [7, 8]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [8, 8]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ true  │
│    [1, 0]     │ false │
│    [0, 1]     │ false │
│    [1, 1]     │ true  │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [8, 9]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ true  │
│    [1, 0]     │ false │
│    [0, 1]     │ false │
│    [1, 1]     │ true  │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [9, 4]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ true  │
│    [1, 0]     │ false │
│    [0, 1]     │ false │
│    [1, 1]     │ true  │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [4, 9]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [9, 6]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [6, 5]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [5, 1]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

 LocalSolutionSize{Bool} on [1, 7]
┌───────────────┬───────┐
│ Configuration │ Size  │
├───────────────┼───────┤
│    [0, 0]     │ false │
│    [1, 0]     │ true  │
│    [0, 1]     │ true  │
│    [1, 1]     │ false │
└───────────────┴───────┘

Convert to tensor network representation:

problem = GenericTensorNetwork(pshop)
GenericTensorNetwork{PaintShop{Char}, OMEinsum.DynamicNestedEinsum{Int64}, Int64}
- open vertices: Int64[]
- fixed vertices: Dict{Int64, Int64}()
- contraction time = 2^7.524, space = 2^4.0, read-write = 2^8.234

Mathematical Background

For each car $c_i$, we assign a boolean variable $s_{c_i} \in \{0,1\}$, where:

  • $0$ means the first appearance is colored red
  • $1$ means the first appearance is colored blue

For adjacent positions $(i,i+1)$, we define edge tensors:

  1. If both cars are at their first or both at their second appearance:

    \[B^{\text{parallel}} = \begin{pmatrix} x & 1 \\ 1 & x \end{pmatrix}\]

    (Cars tend to have the same configuration to avoid color changes)

  2. Otherwise (one first, one second appearance):

    \[B^{\text{anti-parallel}} = \begin{pmatrix} 1 & x \\ x & 1 \end{pmatrix}\]

    (Cars tend to have different configurations to avoid color changes)

Solution Analysis

1. Paint Shop Polynomial

The paint shop polynomial $P(G,x) = \sum_i p_i x^i$ counts colorings by number of color changes, where $p_i$ is the number of colorings with $(2m-1-i)$ color changes

paint_polynomial = solve(problem, GraphPolynomial())[]
2∙x4 + 28∙x5 + 32∙x6 + 80∙x7 + 60∙x8 + 88∙x9 + 104∙x10 + 48∙x11 + 50∙x12 + 12∙x13 + 8∙x14

2. Optimal Coloring Configurations

Find all optimal coloring configurations:

best_configs = solve(problem, ConfigsMin())[]
(4.0, {000000110, 111111001})ₜ

Note: We get two identical bitstrings corresponding to different vertex configurations due to bit-flip symmetry (we can start with either red or blue)

3. Solution Visualization

Convert the optimal configuration to a coloring sequence:

painting1 = ProblemReductions.paint_shop_coloring_from_config(pshop, read_config(best_configs)[1])
18-element Vector{Bool}:
 0
 0
 0
 0
 0
 0
 1
 1
 1
 1
 0
 0
 1
 1
 1
 1
 1
 0

Visualize the optimal coloring:

show_graph(graph, locations; format=:svg, texts=string.(sequence),
    edge_colors=[sequence[e.src] == sequence[e.dst] ? "blue" : "black" for e in edges(graph)],
    vertex_colors=[isone(c) ? "red" : "black" for c in painting1],
    config=GraphDisplayConfig(;vertex_text_color="white"))
Example block output

4. Verification

Verify the solution by counting the number of color switches:

num_paint_shop_color_switch(sequence, painting1)
4

More APIs

The Independent Set Problem chapter has more examples on how to use the APIs.


This page was generated using Literate.jl.